Skip to content

Commit 50281e7

Browse files
committed
refactor(verification): organization and typing improvements
1 parent 769d255 commit 50281e7

12 files changed

+207
-233
lines changed

hathor/manager.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -929,9 +929,10 @@ def push_tx(self, tx: Transaction, allow_non_standard_script: bool = False,
929929
raise NonStandardTxError('Transaction is non standard.')
930930

931931
# Validate tx.
932-
success, message = self.verification_service.validate_vertex_error(tx)
933-
if not success:
934-
raise InvalidNewTransaction(message)
932+
try:
933+
self.verification_service.verify(tx)
934+
except TxValidationError as e:
935+
raise InvalidNewTransaction(str(e))
935936

936937
self.propagate_tx(tx, fails_silently=False)
937938

hathor/transaction/base_transaction.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def __init__(self,
139139
nonce: int = 0,
140140
timestamp: Optional[int] = None,
141141
signal_bits: int = 0,
142-
version: int = TxVersion.REGULAR_BLOCK,
142+
version: TxVersion = TxVersion.REGULAR_BLOCK,
143143
weight: float = 0,
144144
inputs: Optional[list['TxInput']] = None,
145145
outputs: Optional[list['TxOutput']] = None,

hathor/transaction/block.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def __init__(self,
4747
nonce: int = 0,
4848
timestamp: Optional[int] = None,
4949
signal_bits: int = 0,
50-
version: int = TxVersion.REGULAR_BLOCK,
50+
version: TxVersion = TxVersion.REGULAR_BLOCK,
5151
weight: float = 0,
5252
outputs: Optional[list[TxOutput]] = None,
5353
parents: Optional[list[bytes]] = None,

hathor/transaction/merge_mined_block.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def __init__(self,
2828
nonce: int = 0,
2929
timestamp: Optional[int] = None,
3030
signal_bits: int = 0,
31-
version: int = TxVersion.MERGE_MINED_BLOCK,
31+
version: TxVersion = TxVersion.MERGE_MINED_BLOCK,
3232
weight: float = 0,
3333
outputs: Optional[list[TxOutput]] = None,
3434
parents: Optional[list[bytes]] = None,

hathor/transaction/resources/create_tx.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def render_POST(self, request):
8888
# conservative estimate of the input data size to estimate a valid weight
8989
tx_input.data = b'\0' * 107
9090
tx.weight = self.manager.daa.minimum_tx_weight(fake_signed_tx)
91-
self.manager.verification_service.verifiers.tx.verify_unsigned_skip_pow(tx)
91+
self._verify_unsigned_skip_pow(tx)
9292

9393
if tx.is_double_spending():
9494
raise InvalidNewTransaction('At least one of your inputs has already been spent.')
@@ -104,6 +104,19 @@ def render_POST(self, request):
104104
'data': data,
105105
})
106106

107+
def _verify_unsigned_skip_pow(self, tx: Transaction) -> None:
108+
""" Same as .verify but skipping pow and signature verification."""
109+
assert type(tx) is Transaction
110+
verifier = self.manager.verification_service.verifiers.tx
111+
verifier.verify_number_of_inputs(tx)
112+
verifier.verify_number_of_outputs(tx)
113+
verifier.verify_outputs(tx)
114+
verifier.verify_sigops_output(tx)
115+
verifier.verify_sigops_input(tx)
116+
verifier.verify_inputs(tx, skip_script=True) # need to run verify_inputs first to check if all inputs exist
117+
verifier.verify_parents(tx)
118+
verifier.verify_sum(tx)
119+
107120

108121
CreateTxResource.openapi = {
109122
'/create_tx': {

hathor/transaction/token_creation_tx.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def __init__(self,
3636
nonce: int = 0,
3737
timestamp: Optional[int] = None,
3838
signal_bits: int = 0,
39-
version: int = TxVersion.TOKEN_CREATION_TRANSACTION,
39+
version: TxVersion = TxVersion.TOKEN_CREATION_TRANSACTION,
4040
weight: float = 0,
4141
inputs: Optional[list[TxInput]] = None,
4242
outputs: Optional[list[TxOutput]] = None,

hathor/transaction/transaction.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def __init__(self,
5757
nonce: int = 0,
5858
timestamp: Optional[int] = None,
5959
signal_bits: int = 0,
60-
version: int = TxVersion.REGULAR_TRANSACTION,
60+
version: TxVersion = TxVersion.REGULAR_TRANSACTION,
6161
weight: float = 0,
6262
inputs: Optional[list[TxInput]] = None,
6363
outputs: Optional[list[TxOutput]] = None,

hathor/verification/transaction_verifier.py

-11
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,6 @@ def verify(self, tx: Transaction, *, reject_locked_reward: bool = True) -> None:
7575
if reject_locked_reward:
7676
self.verify_reward_locked(tx)
7777

78-
def verify_unsigned_skip_pow(self, tx: Transaction) -> None:
79-
""" Same as .verify but skipping pow and signature verification."""
80-
self.verify_number_of_inputs(tx)
81-
self.verify_number_of_outputs(tx)
82-
self.verify_outputs(tx)
83-
self.verify_sigops_output(tx)
84-
self.verify_sigops_input(tx)
85-
self.verify_inputs(tx, skip_script=True) # need to run verify_inputs first to check if all inputs exist
86-
self.verify_parents(tx)
87-
self.verify_sum(tx)
88-
8978
def verify_parents_basic(self, tx: Transaction) -> None:
9079
"""Verify number and non-duplicity of parents."""
9180
assert tx.storage is not None

hathor/verification/verification_service.py

+20-31
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@
1414

1515
from typing import NamedTuple
1616

17+
from typing_extensions import assert_never
18+
1719
from hathor.conf.settings import HathorSettings
1820
from hathor.daa import DifficultyAdjustmentAlgorithm
1921
from hathor.feature_activation.feature_service import FeatureService
2022
from hathor.transaction import BaseTransaction, Block, MergeMinedBlock, Transaction, TxVersion
21-
from hathor.transaction.exceptions import TxValidationError
2223
from hathor.transaction.token_creation_tx import TokenCreationTransaction
2324
from hathor.transaction.validation_state import ValidationState
2425
from hathor.verification.block_verifier import BlockVerifier
@@ -107,76 +108,64 @@ def verify_basic(self, vertex: BaseTransaction, *, skip_block_weight_verificatio
107108
"""Basic verifications (the ones without access to dependencies: parents+inputs). Raises on error.
108109
109110
Used by `self.validate_basic`. Should not modify the validation state."""
111+
# We assert with type() instead of isinstance() because each subclass has a specific branch.
110112
match vertex.version:
111113
case TxVersion.REGULAR_BLOCK:
112-
assert isinstance(vertex, Block)
114+
assert type(vertex) is Block
113115
self.verifiers.block.verify_basic(
114116
vertex,
115117
skip_block_weight_verification=skip_block_weight_verification
116118
)
117119
case TxVersion.MERGE_MINED_BLOCK:
118-
assert isinstance(vertex, MergeMinedBlock)
120+
assert type(vertex) is MergeMinedBlock
119121
self.verifiers.merge_mined_block.verify_basic(
120122
vertex,
121123
skip_block_weight_verification=skip_block_weight_verification
122124
)
123125
case TxVersion.REGULAR_TRANSACTION:
124-
assert isinstance(vertex, Transaction)
126+
assert type(vertex) is Transaction
125127
self.verifiers.tx.verify_basic(vertex)
126128
case TxVersion.TOKEN_CREATION_TRANSACTION:
127-
assert isinstance(vertex, TokenCreationTransaction)
129+
assert type(vertex) is TokenCreationTransaction
128130
self.verifiers.token_creation_tx.verify_basic(vertex)
129131
case _:
130-
raise NotImplementedError
132+
assert_never(vertex.version)
131133

132134
def verify(self, vertex: BaseTransaction, *, reject_locked_reward: bool = True) -> None:
133135
"""Run all verifications. Raises on error.
134136
135137
Used by `self.validate_full`. Should not modify the validation state."""
138+
# We assert with type() instead of isinstance() because each subclass has a specific branch.
136139
match vertex.version:
137140
case TxVersion.REGULAR_BLOCK:
138-
assert isinstance(vertex, Block)
141+
assert type(vertex) is Block
139142
self.verifiers.block.verify(vertex)
140143
case TxVersion.MERGE_MINED_BLOCK:
141-
assert isinstance(vertex, MergeMinedBlock)
144+
assert type(vertex) is MergeMinedBlock
142145
self.verifiers.merge_mined_block.verify(vertex)
143146
case TxVersion.REGULAR_TRANSACTION:
144-
assert isinstance(vertex, Transaction)
147+
assert type(vertex) is Transaction
145148
self.verifiers.tx.verify(vertex, reject_locked_reward=reject_locked_reward)
146149
case TxVersion.TOKEN_CREATION_TRANSACTION:
147-
assert isinstance(vertex, TokenCreationTransaction)
150+
assert type(vertex) is TokenCreationTransaction
148151
self.verifiers.token_creation_tx.verify(vertex, reject_locked_reward=reject_locked_reward)
149152
case _:
150-
raise NotImplementedError
153+
assert_never(vertex.version)
151154

152155
def verify_without_storage(self, vertex: BaseTransaction) -> None:
156+
# We assert with type() instead of isinstance() because each subclass has a specific branch.
153157
match vertex.version:
154158
case TxVersion.REGULAR_BLOCK:
155-
assert isinstance(vertex, Block)
159+
assert type(vertex) is Block
156160
self.verifiers.block.verify_without_storage(vertex)
157161
case TxVersion.MERGE_MINED_BLOCK:
158-
assert isinstance(vertex, MergeMinedBlock)
162+
assert type(vertex) is MergeMinedBlock
159163
self.verifiers.merge_mined_block.verify_without_storage(vertex)
160164
case TxVersion.REGULAR_TRANSACTION:
161-
assert isinstance(vertex, Transaction)
165+
assert type(vertex) is Transaction
162166
self.verifiers.tx.verify_without_storage(vertex)
163167
case TxVersion.TOKEN_CREATION_TRANSACTION:
164-
assert isinstance(vertex, TokenCreationTransaction)
168+
assert type(vertex) is TokenCreationTransaction
165169
self.verifiers.token_creation_tx.verify_without_storage(vertex)
166170
case _:
167-
raise NotImplementedError
168-
169-
def validate_vertex_error(self, vertex: BaseTransaction) -> tuple[bool, str]:
170-
""" Verify if tx is valid and return success and possible error message
171-
172-
:return: Success if tx is valid and possible error message, if not
173-
:rtype: tuple[bool, str]
174-
"""
175-
success = True
176-
message = ''
177-
try:
178-
self.verify(vertex)
179-
except TxValidationError as e:
180-
success = False
181-
message = str(e)
182-
return success, message
171+
assert_never(vertex.version)

poetry.lock

+6-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ structlog-sentry = {version = "^1.4.0", optional = true}
7878
hathorlib = "0.3.0"
7979
pydantic = "~1.10.13"
8080
pyyaml = "^6.0.1"
81+
typing-extensions = "~4.8.0"
8182

8283
[tool.poetry.extras]
8384
sentry = ["sentry-sdk", "structlog-sentry"]

0 commit comments

Comments
 (0)