Skip to content

Commit 9145c97

Browse files
committed
refactor(verification): finish moving all verification methods
1 parent 422ec12 commit 9145c97

File tree

8 files changed

+201
-187
lines changed

8 files changed

+201
-187
lines changed

hathor/simulator/simulator.py

+15-9
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,14 @@
2828
from hathor.simulator.clock import HeapClock, MemoryReactorHeapClock
2929
from hathor.simulator.miner.geometric_miner import GeometricMiner
3030
from hathor.simulator.tx_generator import RandomTransactionGenerator
31+
from hathor.simulator.verification import (
32+
SimulatorBlockVerifier,
33+
SimulatorMergeMinedBlockVerifier,
34+
SimulatorTokenCreationTransactionVerifier,
35+
SimulatorTransactionVerifier,
36+
)
3137
from hathor.util import Random
38+
from hathor.verification.verification_service import VertexVerifiers
3239
from hathor.wallet import HDWallet
3340

3441
if TYPE_CHECKING:
@@ -52,25 +59,17 @@ def _apply_patches(cls):
5259
5360
Patches:
5461
55-
- disable pow verification
5662
- disable Transaction.resolve method
5763
- set DAA test-mode to DISABLED (will actually run the pow function, that won't actually verify the pow)
5864
- override AVG_TIME_BETWEEN_BLOCKS to 64
5965
"""
6066
from hathor.transaction import BaseTransaction
6167

62-
def verify_pow(self: BaseTransaction, *args: Any, **kwargs: Any) -> None:
63-
assert self.hash is not None
64-
logger.new().debug('Skipping BaseTransaction.verify_pow() for simulator')
65-
6668
def resolve(self: BaseTransaction, update_time: bool = True) -> bool:
6769
self.update_hash()
6870
logger.new().debug('Skipping BaseTransaction.resolve() for simulator')
6971
return True
7072

71-
cls._original_verify_pow = BaseTransaction.verify_pow
72-
BaseTransaction.verify_pow = verify_pow
73-
7473
cls._original_resolve = BaseTransaction.resolve
7574
BaseTransaction.resolve = resolve
7675

@@ -85,7 +84,6 @@ def _remove_patches(cls):
8584
""" Remove the patches previously applied.
8685
"""
8786
from hathor.transaction import BaseTransaction
88-
BaseTransaction.verify_pow = cls._original_verify_pow
8987
BaseTransaction.resolve = cls._original_resolve
9088

9189
from hathor import daa
@@ -170,10 +168,18 @@ def create_artifacts(self, builder: Optional[Builder] = None) -> BuildArtifacts:
170168
wallet = HDWallet(gap_limit=2)
171169
wallet._manually_initialize()
172170

171+
vertex_verifiers = VertexVerifiers(
172+
block=SimulatorBlockVerifier(settings=self.settings),
173+
merge_mined_block=SimulatorMergeMinedBlockVerifier(settings=self.settings),
174+
tx=SimulatorTransactionVerifier(settings=self.settings),
175+
token_creation_tx=SimulatorTokenCreationTransactionVerifier(settings=self.settings),
176+
)
177+
173178
artifacts = builder \
174179
.set_reactor(self._clock) \
175180
.set_rng(Random(self.rng.getrandbits(64))) \
176181
.set_wallet(wallet) \
182+
.set_vertex_verifiers(vertex_verifiers) \
177183
.build()
178184

179185
artifacts.manager.start()

hathor/simulator/verification.py

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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 typing import Optional
16+
17+
from structlog import get_logger
18+
19+
from hathor.transaction import BaseTransaction
20+
from hathor.verification.block_verifier import BlockVerifier
21+
from hathor.verification.merge_mined_block_verifier import MergeMinedBlockVerifier
22+
from hathor.verification.token_creation_transaction_verifier import TokenCreationTransactionVerifier
23+
from hathor.verification.transaction_verifier import TransactionVerifier
24+
25+
logger = get_logger()
26+
27+
28+
def verify_pow(vertex: BaseTransaction) -> None:
29+
assert vertex.hash is not None
30+
logger.new().debug('Skipping BaseTransaction.verify_pow() for simulator')
31+
32+
33+
class SimulatorBlockVerifier(BlockVerifier):
34+
@classmethod
35+
def verify_pow(cls, vertex: BaseTransaction, *, override_weight: Optional[float] = None) -> None:
36+
verify_pow(vertex)
37+
38+
39+
class SimulatorMergeMinedBlockVerifier(MergeMinedBlockVerifier):
40+
@classmethod
41+
def verify_pow(cls, vertex: BaseTransaction, *, override_weight: Optional[float] = None) -> None:
42+
verify_pow(vertex)
43+
44+
45+
class SimulatorTransactionVerifier(TransactionVerifier):
46+
@classmethod
47+
def verify_pow(cls, vertex: BaseTransaction, *, override_weight: Optional[float] = None) -> None:
48+
verify_pow(vertex)
49+
50+
51+
class SimulatorTokenCreationTransactionVerifier(TokenCreationTransactionVerifier):
52+
@classmethod
53+
def verify_pow(cls, vertex: BaseTransaction, *, override_weight: Optional[float] = None) -> None:
54+
verify_pow(vertex)

hathor/transaction/base_transaction.py

+1-152
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,7 @@
2828

2929
from hathor.checkpoint import Checkpoint
3030
from hathor.conf.get_settings import get_settings
31-
from hathor.transaction.exceptions import (
32-
DuplicatedParents,
33-
IncorrectParents,
34-
InvalidOutputScriptSize,
35-
InvalidOutputValue,
36-
InvalidToken,
37-
ParentDoesNotExist,
38-
PowError,
39-
TimestampError,
40-
TooManyOutputs,
41-
TooManySigOps,
42-
WeightError,
43-
)
31+
from hathor.transaction.exceptions import InvalidOutputValue, WeightError
4432
from hathor.transaction.transaction_metadata import TransactionMetadata
4533
from hathor.transaction.util import VerboseCallback, int_to_bytes, unpack, unpack_len
4634
from hathor.transaction.validation_state import ValidationState
@@ -70,14 +58,6 @@
7058
# Weight (d), timestamp (I), and parents len (B)
7159
_GRAPH_FORMAT_STRING = '!dIB'
7260

73-
# tx should have 2 parents, both other transactions
74-
_TX_PARENTS_TXS = 2
75-
_TX_PARENTS_BLOCKS = 0
76-
77-
# blocks have 3 parents, 2 txs and 1 block
78-
_BLOCK_PARENTS_TXS = 2
79-
_BLOCK_PARENTS_BLOCKS = 1
80-
8161
# The int value of one byte
8262
_ONE_BYTE = 0xFF
8363

@@ -540,137 +520,6 @@ def verify_checkpoint(self, checkpoints: list[Checkpoint]) -> None:
540520
To be implemented by tx/block, used by `self.validate_checkpoint`. Should not modify the validation state."""
541521
raise NotImplementedError
542522

543-
def verify_parents(self) -> None:
544-
"""All parents must exist and their timestamps must be smaller than ours.
545-
546-
Also, txs should have 2 other txs as parents, while blocks should have 2 txs + 1 block.
547-
548-
Parents must be ordered with blocks first, followed by transactions.
549-
550-
:raises TimestampError: when our timestamp is less or equal than our parent's timestamp
551-
:raises ParentDoesNotExist: when at least one of our parents does not exist
552-
:raises IncorrectParents: when tx does not confirm the correct number/type of parent txs
553-
"""
554-
from hathor.transaction.storage.exceptions import TransactionDoesNotExist
555-
556-
assert self.storage is not None
557-
558-
# check if parents are duplicated
559-
parents_set = set(self.parents)
560-
if len(self.parents) > len(parents_set):
561-
raise DuplicatedParents('Tx has duplicated parents: {}', [tx_hash.hex() for tx_hash in self.parents])
562-
563-
my_parents_txs = 0 # number of tx parents
564-
my_parents_blocks = 0 # number of block parents
565-
min_timestamp: Optional[int] = None
566-
567-
for parent_hash in self.parents:
568-
try:
569-
parent = self.storage.get_transaction(parent_hash)
570-
assert parent.hash is not None
571-
if self.timestamp <= parent.timestamp:
572-
raise TimestampError('tx={} timestamp={}, parent={} timestamp={}'.format(
573-
self.hash_hex,
574-
self.timestamp,
575-
parent.hash_hex,
576-
parent.timestamp,
577-
))
578-
579-
if parent.is_block:
580-
if self.is_block and not parent.is_genesis:
581-
if self.timestamp - parent.timestamp > self._settings.MAX_DISTANCE_BETWEEN_BLOCKS:
582-
raise TimestampError('Distance between blocks is too big'
583-
' ({} seconds)'.format(self.timestamp - parent.timestamp))
584-
if my_parents_txs > 0:
585-
raise IncorrectParents('Parents which are blocks must come before transactions')
586-
for pi_hash in parent.parents:
587-
pi = self.storage.get_transaction(parent_hash)
588-
if not pi.is_block:
589-
min_timestamp = (
590-
min(min_timestamp, pi.timestamp) if min_timestamp is not None
591-
else pi.timestamp
592-
)
593-
my_parents_blocks += 1
594-
else:
595-
if min_timestamp and parent.timestamp < min_timestamp:
596-
raise TimestampError('tx={} timestamp={}, parent={} timestamp={}, min_timestamp={}'.format(
597-
self.hash_hex,
598-
self.timestamp,
599-
parent.hash_hex,
600-
parent.timestamp,
601-
min_timestamp
602-
))
603-
my_parents_txs += 1
604-
except TransactionDoesNotExist:
605-
raise ParentDoesNotExist('tx={} parent={}'.format(self.hash_hex, parent_hash.hex()))
606-
607-
# check for correct number of parents
608-
if self.is_block:
609-
parents_txs = _BLOCK_PARENTS_TXS
610-
parents_blocks = _BLOCK_PARENTS_BLOCKS
611-
else:
612-
parents_txs = _TX_PARENTS_TXS
613-
parents_blocks = _TX_PARENTS_BLOCKS
614-
if my_parents_blocks != parents_blocks:
615-
raise IncorrectParents('wrong number of parents (block type): {}, expecting {}'.format(
616-
my_parents_blocks, parents_blocks))
617-
if my_parents_txs != parents_txs:
618-
raise IncorrectParents('wrong number of parents (tx type): {}, expecting {}'.format(
619-
my_parents_txs, parents_txs))
620-
621-
def verify_pow(self, override_weight: Optional[float] = None) -> None:
622-
"""Verify proof-of-work
623-
624-
:raises PowError: when the hash is equal or greater than the target
625-
"""
626-
assert self.hash is not None
627-
numeric_hash = int(self.hash_hex, self.HEX_BASE)
628-
minimum_target = self.get_target(override_weight)
629-
if numeric_hash >= minimum_target:
630-
raise PowError(f'Transaction has invalid data ({numeric_hash} < {minimum_target})')
631-
632-
def verify_number_of_outputs(self) -> None:
633-
"""Verify number of outputs does not exceeds the limit"""
634-
if len(self.outputs) > self._settings.MAX_NUM_OUTPUTS:
635-
raise TooManyOutputs('Maximum number of outputs exceeded')
636-
637-
def verify_sigops_output(self) -> None:
638-
""" Count sig operations on all outputs and verify that the total sum is below the limit
639-
"""
640-
from hathor.transaction.scripts import get_sigops_count
641-
n_txops = 0
642-
643-
for tx_output in self.outputs:
644-
n_txops += get_sigops_count(tx_output.script)
645-
646-
if n_txops > self._settings.MAX_TX_SIGOPS_OUTPUT:
647-
raise TooManySigOps('TX[{}]: Maximum number of sigops for all outputs exceeded ({})'.format(
648-
self.hash_hex, n_txops))
649-
650-
def verify_outputs(self) -> None:
651-
"""Verify there are no hathor authority UTXOs and outputs are all positive
652-
653-
:raises InvalidToken: when there's a hathor authority utxo
654-
:raises InvalidOutputValue: output has negative value
655-
:raises TooManyOutputs: when there are too many outputs
656-
"""
657-
self.verify_number_of_outputs()
658-
for index, output in enumerate(self.outputs):
659-
# no hathor authority UTXO
660-
if (output.get_token_index() == 0) and output.is_token_authority():
661-
raise InvalidToken('Cannot have authority UTXO for hathor tokens: {}'.format(
662-
output.to_human_readable()))
663-
664-
# output value must be positive
665-
if output.value <= 0:
666-
raise InvalidOutputValue('Output value must be a positive integer. Value: {} and index: {}'.format(
667-
output.value, index))
668-
669-
if len(output.script) > self._settings.MAX_OUTPUT_SCRIPT_SIZE:
670-
raise InvalidOutputScriptSize('size: {} and max-size: {}'.format(
671-
len(output.script), self._settings.MAX_OUTPUT_SCRIPT_SIZE
672-
))
673-
674523
def resolve(self, update_time: bool = False) -> bool:
675524
"""Run a CPU mining looking for the nonce that solves the proof-of-work
676525

hathor/transaction/block.py

+1-7
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from hathor.feature_activation.model.feature_state import FeatureState
2424
from hathor.profiler import get_cpu_profiler
2525
from hathor.transaction import BaseTransaction, TxOutput, TxVersion
26-
from hathor.transaction.exceptions import BlockWithTokensError, CheckpointError
26+
from hathor.transaction.exceptions import CheckpointError
2727
from hathor.transaction.util import VerboseCallback, int_to_bytes, unpack, unpack_len
2828
from hathor.util import not_none
2929
from hathor.utils.int import get_bit_list
@@ -328,12 +328,6 @@ def verify_checkpoint(self, checkpoints: list[Checkpoint]) -> None:
328328
# TODO: check whether self is a parent of any checkpoint-valid block, this is left for a future PR
329329
pass
330330

331-
def verify_outputs(self) -> None:
332-
super().verify_outputs()
333-
for output in self.outputs:
334-
if output.get_token_index() > 0:
335-
raise BlockWithTokensError('in output: {}'.format(output.to_human_readable()))
336-
337331
def get_base_hash(self) -> bytes:
338332
from hathor.merged_mining.bitcoin import sha256d_hash
339333
return sha256d_hash(self.get_header_without_nonce())

hathor/transaction/transaction.py

-12
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
from hathor.profiler import get_cpu_profiler
2323
from hathor.transaction import BaseTransaction, Block, TxInput, TxOutput, TxVersion
2424
from hathor.transaction.base_transaction import TX_HASH_SIZE
25-
from hathor.transaction.exceptions import InvalidToken
2625
from hathor.transaction.util import VerboseCallback, unpack, unpack_len
2726
from hathor.types import TokenUid, VertexId
2827
from hathor.util import not_none
@@ -279,17 +278,6 @@ def verify_checkpoint(self, checkpoints: list[Checkpoint]) -> None:
279278
raise InvalidNewTransaction(f'Invalid new transaction {self.hash_hex}: expected to reach a checkpoint but '
280279
'none of its children is checkpoint-valid')
281280

282-
def verify_outputs(self) -> None:
283-
"""Verify outputs reference an existing token uid in the tokens list
284-
285-
:raises InvalidToken: output references non existent token uid
286-
"""
287-
super().verify_outputs()
288-
for output in self.outputs:
289-
# check index is valid
290-
if output.get_token_index() > len(self.tokens):
291-
raise InvalidToken('token uid index not available: index {}'.format(output.get_token_index()))
292-
293281
def get_token_info_from_inputs(self) -> dict[TokenUid, TokenInfo]:
294282
"""Sum up all tokens present in the inputs and their properties (amount, can_mint, can_melt)
295283
"""

hathor/verification/block_verifier.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from hathor.transaction import BaseTransaction, Block
1818
from hathor.transaction.exceptions import (
1919
BlockWithInputs,
20+
BlockWithTokensError,
2021
InvalidBlockReward,
2122
RewardLocked,
2223
TransactionDataError,
@@ -100,7 +101,11 @@ def verify_no_inputs(block: Block) -> None:
100101
raise BlockWithInputs('number of inputs {}'.format(len(inputs)))
101102

102103
def verify_outputs(self, block: BaseTransaction) -> None:
103-
block.verify_outputs()
104+
assert isinstance(block, Block)
105+
super().verify_outputs(block)
106+
for output in block.outputs:
107+
if output.get_token_index() > 0:
108+
raise BlockWithTokensError('in output: {}'.format(output.to_human_readable()))
104109

105110
def verify_data(self, block: Block) -> None:
106111
if len(block.data) > self._settings.BLOCK_DATA_MAX_SIZE:

hathor/verification/transaction_verifier.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,12 @@ def verify_outputs(self, tx: BaseTransaction) -> None:
226226
227227
:raises InvalidToken: output references non existent token uid
228228
"""
229-
tx.verify_outputs()
229+
assert isinstance(tx, Transaction)
230+
super().verify_outputs(tx)
231+
for output in tx.outputs:
232+
# check index is valid
233+
if output.get_token_index() > len(tx.tokens):
234+
raise InvalidToken('token uid index not available: index {}'.format(output.get_token_index()))
230235

231236
def verify_authorities_and_deposit(self, token_dict: dict[TokenUid, TokenInfo]) -> None:
232237
"""Verify that the sum of outputs is equal of the sum of inputs, for each token. If sum of inputs

0 commit comments

Comments
 (0)