Skip to content

Commit 3d4fb3e

Browse files
committed
refactor(verification): implement VerificationDependencies and VerificationModel
1 parent 2cdd9b3 commit 3d4fb3e

File tree

3 files changed

+280
-5
lines changed

3 files changed

+280
-5
lines changed

hathor/verification/block_verifier.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,10 @@ def __init__(self, *, settings: HathorSettings, daa: DifficultyAdjustmentAlgorit
3838

3939
def verify_height(self, block: Block) -> None:
4040
"""Validate that the block height is enough to confirm all transactions being confirmed."""
41-
meta = block.get_metadata()
42-
assert meta.height is not None
43-
assert meta.min_height is not None
44-
if meta.height < meta.min_height:
45-
raise RewardLocked(f'Block needs {meta.min_height} height but has {meta.height}')
41+
if block.static_metadata.height < block.static_metadata.min_height:
42+
raise RewardLocked(
43+
f'Block needs {block.static_metadata.min_height} height but has {block.static_metadata.height}'
44+
)
4645

4746
def verify_weight(self, block: Block) -> None:
4847
"""Validate minimum block difficulty."""
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Copyright 2023 Hathor Labs
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from dataclasses import dataclass
16+
from typing import TYPE_CHECKING
17+
18+
from typing_extensions import Self
19+
20+
from hathor.daa import DifficultyAdjustmentAlgorithm
21+
from hathor.reward_lock import get_spent_reward_locked_info
22+
from hathor.reward_lock.reward_lock import get_minimum_best_height
23+
from hathor.transaction import Block, Vertex
24+
from hathor.transaction.transaction import RewardLockedInfo, TokenInfo, Transaction
25+
from hathor.types import TokenUid, VertexId
26+
27+
if TYPE_CHECKING:
28+
from hathor.transaction.storage import TransactionStorage
29+
30+
31+
@dataclass(frozen=True, slots=True)
32+
class VertexDependencies:
33+
"""A dataclass of dependencies necessary for vertex verification."""
34+
parents: dict[VertexId, Vertex]
35+
36+
@staticmethod
37+
def _get_parents_from_storage(vertex: Vertex, storage: 'TransactionStorage') -> dict[VertexId, Vertex]:
38+
return {vertex_id: storage.get_vertex(vertex_id) for vertex_id in vertex.parents}
39+
40+
41+
@dataclass(frozen=True, slots=True)
42+
class BasicBlockDependencies(VertexDependencies):
43+
"""A dataclass of dependencies necessary for basic block verification."""
44+
daa_deps: dict[VertexId, Block] | None
45+
46+
@classmethod
47+
def create_from_storage(
48+
cls,
49+
block: Block,
50+
*,
51+
storage: 'TransactionStorage',
52+
daa: DifficultyAdjustmentAlgorithm,
53+
skip_weight_verification: bool,
54+
) -> Self:
55+
"""Create a basic block dependencies instance using dependencies from a storage."""
56+
parents = cls._get_parents_from_storage(block, storage)
57+
daa_deps: dict[VertexId, Block] | None = None
58+
59+
if not block.is_genesis and not skip_weight_verification:
60+
daa_dep_ids = daa.get_block_dependencies(block, storage.get_parent_block)
61+
daa_deps = {vertex_id: storage.get_block(vertex_id) for vertex_id in daa_dep_ids}
62+
63+
return cls(
64+
parents=parents,
65+
daa_deps=daa_deps,
66+
)
67+
68+
def get_parent_block(self) -> Block:
69+
"""Return the parent block of the block being verified."""
70+
parent_blocks = [vertex for vertex in self.parents.values() if isinstance(vertex, Block)]
71+
assert len(parent_blocks) == 1
72+
return parent_blocks[0]
73+
74+
def get_parent_block_for_daa(self, block: Block) -> Block:
75+
"""A method for getting parent blocks during DAA-related verification."""
76+
assert self.daa_deps is not None
77+
parent_hash = block.get_block_parent_hash()
78+
return self.daa_deps[parent_hash]
79+
80+
81+
@dataclass(frozen=True, slots=True)
82+
class BlockDependencies(VertexDependencies):
83+
"""A dataclass of dependencies necessary for block verification."""
84+
85+
@classmethod
86+
def create_from_storage(cls, block: Block, *, storage: 'TransactionStorage') -> Self:
87+
"""Create a block dependencies instance using dependencies from a storage."""
88+
parents = cls._get_parents_from_storage(block, storage)
89+
return cls(parents=parents)
90+
91+
92+
@dataclass(frozen=True, slots=True)
93+
class TransactionDependencies(VertexDependencies):
94+
"""A dataclass of dependencies necessary for transaction verification."""
95+
spent_txs: dict[VertexId, Vertex]
96+
token_info: dict[TokenUid, TokenInfo]
97+
reward_locked_info: RewardLockedInfo | None
98+
best_block_height: int
99+
100+
@classmethod
101+
def create_from_storage(cls, tx: Transaction, storage: 'TransactionStorage') -> Self:
102+
"""Create a transaction dependencies instance using dependencies from a storage."""
103+
parents = cls._get_parents_from_storage(tx, storage)
104+
spent_txs = {input_tx.tx_id: storage.get_vertex(input_tx.tx_id) for input_tx in tx.inputs}
105+
token_info = tx.get_complete_token_info()
106+
reward_locked_info = get_spent_reward_locked_info(tx, storage)
107+
best_block_height = get_minimum_best_height(storage)
108+
109+
return cls(
110+
parents=parents,
111+
spent_txs=spent_txs,
112+
token_info=token_info,
113+
reward_locked_info=reward_locked_info,
114+
best_block_height=best_block_height,
115+
)
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# Copyright 2024 Hathor Labs
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from dataclasses import dataclass
16+
from typing import Generic, TypeAlias, TypeVar
17+
18+
from typing_extensions import assert_never
19+
20+
from hathor.daa import DifficultyAdjustmentAlgorithm
21+
from hathor.transaction import BaseTransaction, Block, MergeMinedBlock, TxVersion
22+
from hathor.transaction.storage import TransactionStorage
23+
from hathor.transaction.token_creation_tx import TokenCreationTransaction
24+
from hathor.transaction.transaction import Transaction
25+
from hathor.verification.verification_dependencies import (
26+
BasicBlockDependencies,
27+
BlockDependencies,
28+
TransactionDependencies,
29+
)
30+
31+
T = TypeVar('T', bound=BaseTransaction)
32+
BasicDepsT = TypeVar('BasicDepsT')
33+
DepsT = TypeVar('DepsT')
34+
35+
36+
@dataclass(frozen=True, slots=True)
37+
class _VerificationModel(Generic[T, BasicDepsT, DepsT]):
38+
"""A simple dataclass that wraps a vertex and all dependencies necessary for its verification."""
39+
vertex: T
40+
basic_deps: BasicDepsT
41+
deps: DepsT | None
42+
43+
44+
@dataclass(frozen=True, slots=True)
45+
class VerificationBlock(_VerificationModel[Block, BasicBlockDependencies, BlockDependencies]):
46+
"""A simple dataclass that wraps a Block and all dependencies necessary for its verification."""
47+
48+
49+
@dataclass(frozen=True, slots=True)
50+
class VerificationMergeMinedBlock(_VerificationModel[MergeMinedBlock, BasicBlockDependencies, BlockDependencies]):
51+
"""A simple dataclass that wraps a MergeMinedBlock and all dependencies necessary for its verification."""
52+
53+
54+
@dataclass(frozen=True, slots=True)
55+
class VerificationTransaction(_VerificationModel[Transaction, None, TransactionDependencies]):
56+
"""A simple dataclass that wraps a Transaction and all dependencies necessary for its verification."""
57+
58+
59+
@dataclass(frozen=True, slots=True)
60+
class VerificationTokenCreationTransaction(
61+
_VerificationModel[TokenCreationTransaction, None, TransactionDependencies]
62+
):
63+
"""A simple dataclass that wraps a TokenCreationTransaction and all dependencies necessary for its verification."""
64+
65+
66+
"""A type alias representing an union type for verification models for all vertex types."""
67+
VerificationModel: TypeAlias = (
68+
VerificationBlock | VerificationMergeMinedBlock | VerificationTransaction | VerificationTokenCreationTransaction
69+
)
70+
71+
72+
def get_verification_model_from_storage(
73+
vertex: BaseTransaction,
74+
storage: TransactionStorage,
75+
*,
76+
daa: DifficultyAdjustmentAlgorithm,
77+
skip_weight_verification: bool = False,
78+
only_basic: bool = False
79+
) -> VerificationModel:
80+
"""Create a verification model instance for a vertex using dependencies from a storage."""
81+
# We assert with type() instead of isinstance() because each subclass has a specific branch.
82+
match vertex.version:
83+
case TxVersion.REGULAR_BLOCK:
84+
assert type(vertex) is Block
85+
basic_deps, deps = _get_block_deps(
86+
vertex,
87+
storage=storage,
88+
daa=daa,
89+
skip_weight_verification=skip_weight_verification,
90+
only_basic=only_basic,
91+
)
92+
return VerificationBlock(
93+
vertex=vertex,
94+
basic_deps=basic_deps,
95+
deps=deps,
96+
)
97+
98+
case TxVersion.MERGE_MINED_BLOCK:
99+
assert type(vertex) is MergeMinedBlock
100+
basic_deps, deps = _get_block_deps(
101+
vertex,
102+
storage=storage,
103+
daa=daa,
104+
skip_weight_verification=skip_weight_verification,
105+
only_basic=only_basic,
106+
)
107+
return VerificationMergeMinedBlock(
108+
vertex=vertex,
109+
basic_deps=basic_deps,
110+
deps=deps,
111+
)
112+
113+
case TxVersion.REGULAR_TRANSACTION:
114+
assert type(vertex) is Transaction
115+
return VerificationTransaction(
116+
vertex=vertex,
117+
basic_deps=None,
118+
deps=_get_tx_deps(vertex, storage=storage, only_basic=only_basic),
119+
)
120+
121+
case TxVersion.TOKEN_CREATION_TRANSACTION:
122+
assert type(vertex) is TokenCreationTransaction
123+
return VerificationTokenCreationTransaction(
124+
vertex=vertex,
125+
basic_deps=None,
126+
deps=_get_tx_deps(vertex, storage=storage, only_basic=only_basic),
127+
)
128+
129+
case _:
130+
assert_never(vertex.version)
131+
132+
133+
def _get_block_deps(
134+
block: Block,
135+
*,
136+
storage: TransactionStorage,
137+
daa: DifficultyAdjustmentAlgorithm,
138+
skip_weight_verification: bool,
139+
only_basic: bool
140+
) -> tuple[BasicBlockDependencies, BlockDependencies | None]:
141+
"""Create the necessary dependencies instances for a Block, using a storage."""
142+
basic_deps = BasicBlockDependencies.create_from_storage(
143+
block,
144+
storage=storage,
145+
daa=daa,
146+
skip_weight_verification=skip_weight_verification,
147+
)
148+
deps = None
149+
if not only_basic:
150+
deps = BlockDependencies.create_from_storage(block, storage=storage)
151+
152+
return basic_deps, deps
153+
154+
155+
def _get_tx_deps(tx: Transaction, *, storage: TransactionStorage, only_basic: bool) -> TransactionDependencies | None:
156+
"""Create the necessary dependencies instances for a Transaction, using a storage."""
157+
deps = None
158+
if not only_basic:
159+
deps = TransactionDependencies.create_from_storage(tx, storage)
160+
161+
return deps

0 commit comments

Comments
 (0)