Skip to content

Commit 302f5aa

Browse files
committed
refactor(verification): move vertex verification to its own service
1 parent 4b34cb4 commit 302f5aa

30 files changed

+357
-221
lines changed

hathor/builder/builder.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
TransactionStorage,
4242
)
4343
from hathor.util import Random, Reactor, get_environment_info
44+
from hathor.verification.verification_service import VerificationService
4445
from hathor.wallet import BaseWallet, Wallet
4546

4647
logger = get_logger()
@@ -101,6 +102,8 @@ def __init__(self) -> None:
101102
self._feature_service: Optional[FeatureService] = None
102103
self._bit_signaling_service: Optional[BitSignalingService] = None
103104

105+
self._verification_service: Optional[VerificationService] = None
106+
104107
self._rocksdb_path: Optional[str] = None
105108
self._rocksdb_storage: Optional[RocksDBStorage] = None
106109
self._rocksdb_cache_capacity: Optional[int] = None
@@ -157,6 +160,7 @@ def build(self) -> BuildArtifacts:
157160
tx_storage = self._get_or_create_tx_storage(indexes)
158161
feature_service = self._get_or_create_feature_service(tx_storage)
159162
bit_signaling_service = self._get_or_create_bit_signaling_service(tx_storage)
163+
verification_service = self._get_or_create_verification_service()
160164

161165
if self._enable_address_index:
162166
indexes.enable_address_index(pubsub)
@@ -191,6 +195,7 @@ def build(self) -> BuildArtifacts:
191195
environment_info=get_environment_info(self._cmdline, peer_id.id),
192196
feature_service=feature_service,
193197
bit_signaling_service=bit_signaling_service,
198+
verification_service=verification_service,
194199
**kwargs
195200
)
196201

@@ -424,6 +429,12 @@ def _get_or_create_bit_signaling_service(self, tx_storage: TransactionStorage) -
424429

425430
return self._bit_signaling_service
426431

432+
def _get_or_create_verification_service(self) -> VerificationService:
433+
if self._verification_service is None:
434+
self._verification_service = VerificationService()
435+
436+
return self._verification_service
437+
427438
def use_memory(self) -> 'Builder':
428439
self.check_if_can_modify()
429440
self._storage_type = StorageType.MEMORY
@@ -516,6 +527,11 @@ def set_event_storage(self, event_storage: EventStorage) -> 'Builder':
516527
self._event_storage = event_storage
517528
return self
518529

530+
def set_verification_service(self, verification_service: VerificationService) -> 'Builder':
531+
self.check_if_can_modify()
532+
self._verification_service = verification_service
533+
return self
534+
519535
def set_reactor(self, reactor: Reactor) -> 'Builder':
520536
self.check_if_can_modify()
521537
self._reactor = reactor

hathor/builder/cli_builder.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from hathor.pubsub import PubSubManager
3636
from hathor.stratum import StratumFactory
3737
from hathor.util import Random, Reactor
38+
from hathor.verification.verification_service import VerificationService
3839
from hathor.wallet import BaseWallet, HDWallet, Wallet
3940

4041
logger = get_logger()
@@ -202,6 +203,8 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
202203
not_support_features=self._args.signal_not_support
203204
)
204205

206+
verification_service = VerificationService()
207+
205208
p2p_manager = ConnectionsManager(
206209
reactor,
207210
network=network,
@@ -231,7 +234,8 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
231234
full_verification=full_verification,
232235
enable_event_queue=self._args.x_enable_event_queue,
233236
feature_service=self.feature_service,
234-
bit_signaling_service=bit_signaling_service
237+
bit_signaling_service=bit_signaling_service,
238+
verification_service=verification_service,
235239
)
236240

237241
p2p_manager.set_manager(self.manager)

hathor/manager.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
from hathor.transaction.storage.tx_allow_scope import TxAllowScope
5858
from hathor.types import Address, VertexId
5959
from hathor.util import EnvironmentInfo, LogDuration, Random, Reactor, calculate_min_significant_weight, not_none
60+
from hathor.verification.verification_service import VerificationService
6061
from hathor.wallet import BaseWallet
6162

6263
settings = HathorSettings()
@@ -102,6 +103,7 @@ def __init__(self,
102103
event_manager: EventManager,
103104
feature_service: FeatureService,
104105
bit_signaling_service: BitSignalingService,
106+
verification_service: VerificationService,
105107
network: str,
106108
hostname: Optional[str] = None,
107109
wallet: Optional[BaseWallet] = None,
@@ -177,6 +179,7 @@ def __init__(self,
177179

178180
self._feature_service = feature_service
179181
self._bit_signaling_service = bit_signaling_service
182+
self.verification_service = verification_service
180183

181184
self.consensus_algorithm = consensus_algorithm
182185

@@ -453,7 +456,10 @@ def _initialize_components_full_verification(self) -> None:
453456
tx.calculate_min_height()
454457
if tx.is_genesis:
455458
assert tx.validate_checkpoint(self.checkpoints)
456-
assert tx.validate_full(skip_block_weight_verification=skip_block_weight_verification)
459+
assert self.verification_service.validate_full(
460+
tx,
461+
skip_block_weight_verification=skip_block_weight_verification
462+
)
457463
self.tx_storage.add_to_indexes(tx)
458464
with self.tx_storage.allow_only_valid_context():
459465
self.consensus_algorithm.update(tx)
@@ -464,7 +470,10 @@ def _initialize_components_full_verification(self) -> None:
464470
self.sync_v2_step_validations([tx], quiet=True)
465471
self.tx_storage.save_transaction(tx, only_metadata=True)
466472
else:
467-
assert tx.validate_basic(skip_block_weight_verification=skip_block_weight_verification)
473+
assert self.verification_service.validate_basic(
474+
tx,
475+
skip_block_weight_verification=skip_block_weight_verification
476+
)
468477
self.tx_storage.save_transaction(tx, only_metadata=True)
469478
except (InvalidNewTransaction, TxValidationError):
470479
self.log.error('unexpected error when initializing', tx=tx, exc_info=True)
@@ -919,7 +928,7 @@ def push_tx(self, tx: Transaction, allow_non_standard_script: bool = False,
919928
raise NonStandardTxError('Transaction is non standard.')
920929

921930
# Validate tx.
922-
success, message = tx.validate_tx_error()
931+
success, message = self.verification_service.validate_vertex_error(tx)
923932
if not success:
924933
raise InvalidNewTransaction(message)
925934

@@ -992,7 +1001,7 @@ def on_new_tx(self, tx: BaseTransaction, *, conn: Optional[HathorProtocol] = Non
9921001

9931002
if not metadata.validation.is_fully_connected():
9941003
try:
995-
tx.validate_full(reject_locked_reward=reject_locked_reward)
1004+
self.verification_service.validate_full(tx, reject_locked_reward=reject_locked_reward)
9961005
except HathorError as e:
9971006
if not fails_silently:
9981007
raise InvalidNewTransaction('full validation failed') from e
@@ -1014,7 +1023,11 @@ def on_new_tx(self, tx: BaseTransaction, *, conn: Optional[HathorProtocol] = Non
10141023
self.log.warn('on_new_tx(): consensus update failed', tx=tx.hash_hex, exc_info=True)
10151024
return False
10161025

1017-
assert tx.validate_full(skip_block_weight_verification=True, reject_locked_reward=reject_locked_reward)
1026+
assert self.verification_service.validate_full(
1027+
tx,
1028+
skip_block_weight_verification=True,
1029+
reject_locked_reward=reject_locked_reward
1030+
)
10181031
self.tx_storage.indexes.update(tx)
10191032
if self.tx_storage.indexes.mempool_tips:
10201033
self.tx_storage.indexes.mempool_tips.update(tx) # XXX: move to indexes.update
@@ -1068,7 +1081,7 @@ def sync_v2_step_validations(self, txs: Iterable[BaseTransaction], *, quiet: boo
10681081
try:
10691082
# XXX: `reject_locked_reward` might not apply, partial validation is only used on sync-v2
10701083
# TODO: deal with `reject_locked_reward` on sync-v2
1071-
assert tx.validate_full(reject_locked_reward=False)
1084+
assert self.verification_service.validate_full(tx, reject_locked_reward=False)
10721085
except (AssertionError, HathorError):
10731086
# TODO
10741087
raise

hathor/p2p/sync_v2/agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1154,7 +1154,7 @@ def on_new_tx(self, tx: BaseTransaction, *, quiet: bool = False, propagate_to_pe
11541154
if isinstance(tx, Block) and not tx.has_basic_block_parent():
11551155
self.log.warn('on_new_tx(): block parent needs to be at least basic-valid', tx=tx.hash_hex)
11561156
return False
1157-
if not tx.validate_basic():
1157+
if not self.manager.verification_service.validate_basic(tx):
11581158
self.log.warn('on_new_tx(): basic validation failed', tx=tx.hash_hex)
11591159
return False
11601160

hathor/transaction/base_transaction.py

Lines changed: 0 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
TimestampError,
4040
TooManyOutputs,
4141
TooManySigOps,
42-
TxValidationError,
4342
WeightError,
4443
)
4544
from hathor.transaction.transaction_metadata import TransactionMetadata
@@ -514,42 +513,6 @@ def validate_checkpoint(self, checkpoints: list[Checkpoint]) -> bool:
514513
self.set_validation(ValidationState.CHECKPOINT)
515514
return True
516515

517-
def validate_basic(self, skip_block_weight_verification: bool = False) -> bool:
518-
""" Run basic validations (all that are possible without dependencies) and update the validation state.
519-
520-
If no exception is raised, the ValidationState will end up as `BASIC` and return `True`.
521-
"""
522-
self.verify_basic(skip_block_weight_verification=skip_block_weight_verification)
523-
self.set_validation(ValidationState.BASIC)
524-
return True
525-
526-
def validate_full(self, skip_block_weight_verification: bool = False, sync_checkpoints: bool = False,
527-
reject_locked_reward: bool = True) -> bool:
528-
""" Run full validations (these need access to all dependencies) and update the validation state.
529-
530-
If no exception is raised, the ValidationState will end up as `FULL` or `CHECKPOINT_FULL` and return `True`.
531-
"""
532-
from hathor.transaction.transaction_metadata import ValidationState
533-
534-
meta = self.get_metadata()
535-
536-
# skip full validation when it is a checkpoint
537-
if meta.validation.is_checkpoint():
538-
self.set_validation(ValidationState.CHECKPOINT_FULL)
539-
return True
540-
541-
# XXX: in some cases it might be possible that this transaction is verified by a checkpoint but we went
542-
# directly into trying a full validation so we should check it here to make sure the validation states
543-
# ends up being CHECKPOINT_FULL instead of FULL
544-
if not meta.validation.is_at_least_basic():
545-
# run basic validation if we haven't already
546-
self.verify_basic(skip_block_weight_verification=skip_block_weight_verification)
547-
548-
self.verify(reject_locked_reward=reject_locked_reward)
549-
validation = ValidationState.CHECKPOINT_FULL if sync_checkpoints else ValidationState.FULL
550-
self.set_validation(validation)
551-
return True
552-
553516
def _mark_partially_validated(self) -> None:
554517
""" This function is used to add the partially-validated mark from the voided-by metadata.
555518
@@ -577,20 +540,6 @@ def verify_checkpoint(self, checkpoints: list[Checkpoint]) -> None:
577540
To be implemented by tx/block, used by `self.validate_checkpoint`. Should not modify the validation state."""
578541
raise NotImplementedError
579542

580-
@abstractmethod
581-
def verify_basic(self, skip_block_weight_verification: bool = False) -> None:
582-
"""Basic verifications (the ones without access to dependencies: parents+inputs). Raises on error.
583-
584-
To be implemented by tx/block, used by `self.validate_basic`. Should not modify the validation state."""
585-
raise NotImplementedError
586-
587-
@abstractmethod
588-
def verify(self, reject_locked_reward: bool = True) -> None:
589-
"""Run all verifications. Raises on error.
590-
591-
To be implemented by tx/block, used by `self.validate_full`. Should not modify the validation state."""
592-
raise NotImplementedError
593-
594543
def verify_parents(self) -> None:
595544
"""All parents must exist and their timestamps must be smaller than ours.
596545
@@ -1102,21 +1051,6 @@ def serialize_output(tx: BaseTransaction, tx_out: TxOutput) -> dict[str, Any]:
11021051

11031052
return ret
11041053

1105-
def validate_tx_error(self) -> tuple[bool, str]:
1106-
""" Verify if tx is valid and return success and possible error message
1107-
1108-
:return: Success if tx is valid and possible error message, if not
1109-
:rtype: tuple[bool, str]
1110-
"""
1111-
success = True
1112-
message = ''
1113-
try:
1114-
self.verify()
1115-
except TxValidationError as e:
1116-
success = False
1117-
message = str(e)
1118-
return success, message
1119-
11201054
def clone(self) -> 'BaseTransaction':
11211055
"""Return exact copy without sharing memory, including metadata if loaded.
11221056

hathor/transaction/block.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -321,12 +321,6 @@ def has_basic_block_parent(self) -> bool:
321321
return False
322322
return metadata.validation.is_at_least_basic()
323323

324-
def verify_basic(self, skip_block_weight_verification: bool = False) -> None:
325-
"""Partially run validations, the ones that need parents/inputs are skipped."""
326-
if not skip_block_weight_verification:
327-
self.verify_weight()
328-
self.verify_reward()
329-
330324
def verify_checkpoint(self, checkpoints: list[Checkpoint]) -> None:
331325
assert self.hash is not None
332326
assert self.storage is not None
@@ -396,27 +390,6 @@ def get_base_hash(self) -> bytes:
396390
from hathor.merged_mining.bitcoin import sha256d_hash
397391
return sha256d_hash(self.get_header_without_nonce())
398392

399-
@cpu.profiler(key=lambda self: 'block-verify!{}'.format(self.hash.hex()))
400-
def verify(self, reject_locked_reward: bool = True) -> None:
401-
"""
402-
(1) confirms at least two pending transactions and references last block
403-
(2) solves the pow with the correct weight (done in HathorManager)
404-
(3) creates the correct amount of tokens in the output (done in HathorManager)
405-
(4) all parents must exist and have timestamp smaller than ours
406-
(5) data field must contain at most BLOCK_DATA_MAX_SIZE bytes
407-
"""
408-
# TODO Should we validate a limit of outputs?
409-
if self.is_genesis:
410-
# TODO do genesis validation
411-
return
412-
413-
self.verify_without_storage()
414-
415-
# (1) and (4)
416-
self.verify_parents()
417-
418-
self.verify_height()
419-
420393
def get_height(self) -> int:
421394
"""Returns the block's height."""
422395
meta = self.get_metadata()

hathor/transaction/token_creation_tx.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -220,14 +220,6 @@ def to_json_extended(self) -> dict[str, Any]:
220220
json['tokens'] = []
221221
return json
222222

223-
def verify(self, reject_locked_reward: bool = True) -> None:
224-
""" Run all validations as regular transactions plus validation on token info.
225-
226-
We also overload verify_sum to make some different checks
227-
"""
228-
super().verify(reject_locked_reward=reject_locked_reward)
229-
self.verify_token_info()
230-
231223
def verify_sum(self) -> None:
232224
""" Besides all checks made on regular transactions, a few extra ones are made:
233225
- only HTR tokens on the inputs;

hathor/transaction/transaction.py

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -284,15 +284,6 @@ def to_json(self, decode_script: bool = False, include_metadata: bool = False) -
284284
json['tokens'] = [h.hex() for h in self.tokens]
285285
return json
286286

287-
def verify_basic(self, skip_block_weight_verification: bool = False) -> None:
288-
"""Partially run validations, the ones that need parents/inputs are skipped."""
289-
if self.is_genesis:
290-
# TODO do genesis validation?
291-
return
292-
self.verify_parents_basic()
293-
self.verify_weight()
294-
self.verify_without_storage()
295-
296287
def verify_checkpoint(self, checkpoints: list[Checkpoint]) -> None:
297288
assert self.storage is not None
298289
if self.is_genesis:
@@ -328,30 +319,6 @@ def verify_weight(self) -> None:
328319
raise WeightError(f'Invalid new tx {self.hash_hex}: weight ({self.weight}) is '
329320
f'greater than the maximum allowed ({max_tx_weight})')
330321

331-
@cpu.profiler(key=lambda self: 'tx-verify!{}'.format(self.hash.hex()))
332-
def verify(self, reject_locked_reward: bool = True) -> None:
333-
""" Common verification for all transactions:
334-
(i) number of inputs is at most 256
335-
(ii) number of outputs is at most 256
336-
(iii) confirms at least two pending transactions
337-
(iv) solves the pow (we verify weight is correct in HathorManager)
338-
(v) validates signature of inputs
339-
(vi) validates public key and output (of the inputs) addresses
340-
(vii) validate that both parents are valid
341-
(viii) validate input's timestamps
342-
(ix) validate inputs and outputs sum
343-
"""
344-
if self.is_genesis:
345-
# TODO do genesis validation
346-
return
347-
self.verify_without_storage()
348-
self.verify_sigops_input()
349-
self.verify_inputs() # need to run verify_inputs first to check if all inputs exist
350-
self.verify_parents()
351-
self.verify_sum()
352-
if reject_locked_reward:
353-
self.verify_reward_locked()
354-
355322
def verify_unsigned_skip_pow(self) -> None:
356323
""" Same as .verify but skipping pow and signature verification."""
357324
self.verify_number_of_inputs()

hathor/verification/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)