From 462f90f8c3ba95374b4f8ee3b73d5e21c9c1fca0 Mon Sep 17 00:00:00 2001 From: Gabriel Levcovitz Date: Fri, 29 Sep 2023 15:16:06 -0300 Subject: [PATCH] refactor(verification): create vertex verifiers --- hathor/builder/builder.py | 18 +++++- hathor/builder/cli_builder.py | 5 +- hathor/verification/block_verification.py | 47 -------------- hathor/verification/block_verifier.py | 50 +++++++++++++++ .../merge_mined_block_verifier.py | 19 ++++++ ...=> token_creation_transaction_verifier.py} | 17 ++--- .../verification/transaction_verification.py | 53 ---------------- hathor/verification/transaction_verifier.py | 56 +++++++++++++++++ hathor/verification/verification_service.py | 62 +++++++++++++++---- hathor/verification/vertex_verifier.py | 22 +++++++ 10 files changed, 227 insertions(+), 122 deletions(-) delete mode 100644 hathor/verification/block_verification.py create mode 100644 hathor/verification/block_verifier.py create mode 100644 hathor/verification/merge_mined_block_verifier.py rename hathor/verification/{token_creation_transaction_verification.py => token_creation_transaction_verifier.py} (55%) delete mode 100644 hathor/verification/transaction_verification.py create mode 100644 hathor/verification/transaction_verifier.py create mode 100644 hathor/verification/vertex_verifier.py diff --git a/hathor/builder/builder.py b/hathor/builder/builder.py index ecb2bd02c..c67d20d63 100644 --- a/hathor/builder/builder.py +++ b/hathor/builder/builder.py @@ -41,7 +41,7 @@ TransactionStorage, ) from hathor.util import Random, Reactor, get_environment_info -from hathor.verification.verification_service import VerificationService +from hathor.verification.verification_service import VerificationService, VertexVerifiers from hathor.wallet import BaseWallet, Wallet logger = get_logger() @@ -102,6 +102,7 @@ def __init__(self) -> None: self._feature_service: Optional[FeatureService] = None self._bit_signaling_service: Optional[BitSignalingService] = None + self._vertex_verifiers: Optional[VertexVerifiers] = None self._verification_service: Optional[VerificationService] = None self._rocksdb_path: Optional[str] = None @@ -432,10 +433,18 @@ def _get_or_create_bit_signaling_service(self, tx_storage: TransactionStorage) - def _get_or_create_verification_service(self) -> VerificationService: if self._verification_service is None: - self._verification_service = VerificationService() + verifiers = self._get_or_create_vertex_verifiers() + self._verification_service = VerificationService(verifiers=verifiers) return self._verification_service + def _get_or_create_vertex_verifiers(self) -> VertexVerifiers: + if self._vertex_verifiers is None: + settings = self._get_or_create_settings() + self._vertex_verifiers = VertexVerifiers.create(settings=settings) + + return self._vertex_verifiers + def use_memory(self) -> 'Builder': self.check_if_can_modify() self._storage_type = StorageType.MEMORY @@ -533,6 +542,11 @@ def set_verification_service(self, verification_service: VerificationService) -> self._verification_service = verification_service return self + def set_vertex_verifiers(self, vertex_verifiers: VertexVerifiers) -> 'Builder': + self.check_if_can_modify() + self._vertex_verifiers = vertex_verifiers + return self + def set_reactor(self, reactor: Reactor) -> 'Builder': self.check_if_can_modify() self._reactor = reactor diff --git a/hathor/builder/cli_builder.py b/hathor/builder/cli_builder.py index 7dab5bdf5..a738f74c5 100644 --- a/hathor/builder/cli_builder.py +++ b/hathor/builder/cli_builder.py @@ -35,7 +35,7 @@ from hathor.pubsub import PubSubManager from hathor.stratum import StratumFactory from hathor.util import Random, Reactor -from hathor.verification.verification_service import VerificationService +from hathor.verification.verification_service import VerificationService, VertexVerifiers from hathor.wallet import BaseWallet, HDWallet, Wallet logger = get_logger() @@ -202,7 +202,8 @@ def create_manager(self, reactor: Reactor) -> HathorManager: not_support_features=self._args.signal_not_support ) - verification_service = VerificationService() + vertex_verifiers = VertexVerifiers.create(settings=settings) + verification_service = VerificationService(verifiers=vertex_verifiers) p2p_manager = ConnectionsManager( reactor, diff --git a/hathor/verification/block_verification.py b/hathor/verification/block_verification.py deleted file mode 100644 index 3e47aa254..000000000 --- a/hathor/verification/block_verification.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2023 Hathor Labs -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from hathor.profiler import get_cpu_profiler -from hathor.transaction import Block - -cpu = get_cpu_profiler() - - -def verify_basic(block: Block, *, skip_block_weight_verification: bool = False) -> None: - """Partially run validations, the ones that need parents/inputs are skipped.""" - if not skip_block_weight_verification: - block.verify_weight() - block.verify_reward() - - -@cpu.profiler(key=lambda block: 'block-verify!{}'.format(block.hash.hex())) -def verify(block: Block) -> None: - """ - (1) confirms at least two pending transactions and references last block - (2) solves the pow with the correct weight (done in HathorManager) - (3) creates the correct amount of tokens in the output (done in HathorManager) - (4) all parents must exist and have timestamp smaller than ours - (5) data field must contain at most BLOCK_DATA_MAX_SIZE bytes - """ - # TODO Should we validate a limit of outputs? - if block.is_genesis: - # TODO do genesis validation - return - - block.verify_without_storage() - - # (1) and (4) - block.verify_parents() - - block.verify_height() diff --git a/hathor/verification/block_verifier.py b/hathor/verification/block_verifier.py new file mode 100644 index 000000000..dd8903f72 --- /dev/null +++ b/hathor/verification/block_verifier.py @@ -0,0 +1,50 @@ +# Copyright 2023 Hathor Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from hathor.profiler import get_cpu_profiler +from hathor.transaction import Block +from hathor.verification.vertex_verifier import VertexVerifier + +cpu = get_cpu_profiler() + + +class BlockVerifier(VertexVerifier): + __slots__ = () + + def verify_basic(self, block: Block, *, skip_block_weight_verification: bool = False) -> None: + """Partially run validations, the ones that need parents/inputs are skipped.""" + if not skip_block_weight_verification: + block.verify_weight() + block.verify_reward() + + @cpu.profiler(key=lambda _, block: 'block-verify!{}'.format(block.hash.hex())) + def verify(self, block: Block) -> None: + """ + (1) confirms at least two pending transactions and references last block + (2) solves the pow with the correct weight (done in HathorManager) + (3) creates the correct amount of tokens in the output (done in HathorManager) + (4) all parents must exist and have timestamp smaller than ours + (5) data field must contain at most BLOCK_DATA_MAX_SIZE bytes + """ + # TODO Should we validate a limit of outputs? + if block.is_genesis: + # TODO do genesis validation + return + + block.verify_without_storage() + + # (1) and (4) + block.verify_parents() + + block.verify_height() diff --git a/hathor/verification/merge_mined_block_verifier.py b/hathor/verification/merge_mined_block_verifier.py new file mode 100644 index 000000000..efbfc4c07 --- /dev/null +++ b/hathor/verification/merge_mined_block_verifier.py @@ -0,0 +1,19 @@ +# Copyright 2023 Hathor Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from hathor.verification.block_verifier import BlockVerifier + + +class MergeMinedBlockVerifier(BlockVerifier): + __slots__ = () diff --git a/hathor/verification/token_creation_transaction_verification.py b/hathor/verification/token_creation_transaction_verifier.py similarity index 55% rename from hathor/verification/token_creation_transaction_verification.py rename to hathor/verification/token_creation_transaction_verifier.py index b1d9622b2..fee1bec0e 100644 --- a/hathor/verification/token_creation_transaction_verification.py +++ b/hathor/verification/token_creation_transaction_verifier.py @@ -13,13 +13,16 @@ # limitations under the License. from hathor.transaction.token_creation_tx import TokenCreationTransaction -from hathor.verification import transaction_verification +from hathor.verification.transaction_verifier import TransactionVerifier -def verify(tx: TokenCreationTransaction, *, reject_locked_reward: bool = True) -> None: - """ Run all validations as regular transactions plus validation on token info. +class TokenCreationTransactionVerifier(TransactionVerifier): + __slots__ = () - We also overload verify_sum to make some different checks - """ - transaction_verification.verify(tx, reject_locked_reward=reject_locked_reward) - tx.verify_token_info() + def verify(self, tx: TokenCreationTransaction, *, reject_locked_reward: bool = True) -> None: + """ Run all validations as regular transactions plus validation on token info. + + We also overload verify_sum to make some different checks + """ + super().verify(tx, reject_locked_reward=reject_locked_reward) + tx.verify_token_info() diff --git a/hathor/verification/transaction_verification.py b/hathor/verification/transaction_verification.py deleted file mode 100644 index 02d887a10..000000000 --- a/hathor/verification/transaction_verification.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2023 Hathor Labs -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from hathor.profiler import get_cpu_profiler -from hathor.transaction import Transaction - -cpu = get_cpu_profiler() - - -def verify_basic(transaction: Transaction) -> None: - """Partially run validations, the ones that need parents/inputs are skipped.""" - if transaction.is_genesis: - # TODO do genesis validation? - return - transaction.verify_parents_basic() - transaction.verify_weight() - transaction.verify_without_storage() - - -@cpu.profiler(key=lambda tx: 'tx-verify!{}'.format(tx.hash.hex())) -def verify(tx: Transaction, *, reject_locked_reward: bool = True) -> None: - """ Common verification for all transactions: - (i) number of inputs is at most 256 - (ii) number of outputs is at most 256 - (iii) confirms at least two pending transactions - (iv) solves the pow (we verify weight is correct in HathorManager) - (v) validates signature of inputs - (vi) validates public key and output (of the inputs) addresses - (vii) validate that both parents are valid - (viii) validate input's timestamps - (ix) validate inputs and outputs sum - """ - if tx.is_genesis: - # TODO do genesis validation - return - tx.verify_without_storage() - tx.verify_sigops_input() - tx.verify_inputs() # need to run verify_inputs first to check if all inputs exist - tx.verify_parents() - tx.verify_sum() - if reject_locked_reward: - tx.verify_reward_locked() diff --git a/hathor/verification/transaction_verifier.py b/hathor/verification/transaction_verifier.py new file mode 100644 index 000000000..8c3711524 --- /dev/null +++ b/hathor/verification/transaction_verifier.py @@ -0,0 +1,56 @@ +# Copyright 2023 Hathor Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from hathor.profiler import get_cpu_profiler +from hathor.transaction import Transaction +from hathor.verification.vertex_verifier import VertexVerifier + +cpu = get_cpu_profiler() + + +class TransactionVerifier(VertexVerifier): + __slots__ = () + + def verify_basic(self, tx: Transaction) -> None: + """Partially run validations, the ones that need parents/inputs are skipped.""" + if tx.is_genesis: + # TODO do genesis validation? + return + tx.verify_parents_basic() + tx.verify_weight() + tx.verify_without_storage() + + @cpu.profiler(key=lambda _, tx: 'tx-verify!{}'.format(tx.hash.hex())) + def verify(self, tx: Transaction, *, reject_locked_reward: bool = True) -> None: + """ Common verification for all transactions: + (i) number of inputs is at most 256 + (ii) number of outputs is at most 256 + (iii) confirms at least two pending transactions + (iv) solves the pow (we verify weight is correct in HathorManager) + (v) validates signature of inputs + (vi) validates public key and output (of the inputs) addresses + (vii) validate that both parents are valid + (viii) validate input's timestamps + (ix) validate inputs and outputs sum + """ + if tx.is_genesis: + # TODO do genesis validation + return + tx.verify_without_storage() + tx.verify_sigops_input() + tx.verify_inputs() # need to run verify_inputs first to check if all inputs exist + tx.verify_parents() + tx.verify_sum() + if reject_locked_reward: + tx.verify_reward_locked() diff --git a/hathor/verification/verification_service.py b/hathor/verification/verification_service.py index 2a98cb662..ee6127234 100644 --- a/hathor/verification/verification_service.py +++ b/hathor/verification/verification_service.py @@ -12,15 +12,40 @@ # See the License for the specific language governing permissions and # limitations under the License. -from hathor.transaction import BaseTransaction, Block, Transaction, TxVersion +from typing import NamedTuple + +from hathor.conf.settings import HathorSettings +from hathor.transaction import BaseTransaction, Block, MergeMinedBlock, Transaction, TxVersion from hathor.transaction.exceptions import TxValidationError from hathor.transaction.token_creation_tx import TokenCreationTransaction from hathor.transaction.validation_state import ValidationState -from hathor.verification import block_verification, token_creation_transaction_verification, transaction_verification +from hathor.verification.block_verifier import BlockVerifier +from hathor.verification.merge_mined_block_verifier import MergeMinedBlockVerifier +from hathor.verification.token_creation_transaction_verifier import TokenCreationTransactionVerifier +from hathor.verification.transaction_verifier import TransactionVerifier + + +class VertexVerifiers(NamedTuple): + block: BlockVerifier + merge_mined_block: MergeMinedBlockVerifier + tx: TransactionVerifier + token_creation_tx: TokenCreationTransactionVerifier + + @classmethod + def create(cls, *, settings: HathorSettings) -> 'VertexVerifiers': + return VertexVerifiers( + block=BlockVerifier(settings=settings), + merge_mined_block=MergeMinedBlockVerifier(settings=settings), + tx=TransactionVerifier(settings=settings), + token_creation_tx=TokenCreationTransactionVerifier(settings=settings), + ) class VerificationService: - __slots__ = () + __slots__ = ('verifiers', ) + + def __init__(self, *, verifiers: VertexVerifiers) -> None: + self.verifiers = verifiers def validate_basic(self, vertex: BaseTransaction, *, skip_block_weight_verification: bool = False) -> bool: """ Run basic validations (all that are possible without dependencies) and update the validation state. @@ -70,12 +95,24 @@ def verify_basic(self, vertex: BaseTransaction, *, skip_block_weight_verificatio Used by `self.validate_basic`. Should not modify the validation state.""" match vertex.version: - case TxVersion.REGULAR_BLOCK | TxVersion.MERGE_MINED_BLOCK: + case TxVersion.REGULAR_BLOCK: assert isinstance(vertex, Block) - block_verification.verify_basic(vertex, skip_block_weight_verification=skip_block_weight_verification) - case TxVersion.REGULAR_TRANSACTION | TxVersion.TOKEN_CREATION_TRANSACTION: + self.verifiers.block.verify_basic( + vertex, + skip_block_weight_verification=skip_block_weight_verification + ) + case TxVersion.MERGE_MINED_BLOCK: + assert isinstance(vertex, MergeMinedBlock) + self.verifiers.merge_mined_block.verify_basic( + vertex, + skip_block_weight_verification=skip_block_weight_verification + ) + case TxVersion.REGULAR_TRANSACTION: assert isinstance(vertex, Transaction) - transaction_verification.verify_basic(vertex) + self.verifiers.tx.verify_basic(vertex) + case TxVersion.TOKEN_CREATION_TRANSACTION: + assert isinstance(vertex, TokenCreationTransaction) + self.verifiers.token_creation_tx.verify_basic(vertex) case _: raise NotImplementedError @@ -84,15 +121,18 @@ def verify(self, vertex: BaseTransaction, *, reject_locked_reward: bool = True) Used by `self.validate_full`. Should not modify the validation state.""" match vertex.version: - case TxVersion.REGULAR_BLOCK | TxVersion.MERGE_MINED_BLOCK: + case TxVersion.REGULAR_BLOCK: assert isinstance(vertex, Block) - block_verification.verify(vertex) + self.verifiers.block.verify(vertex) + case TxVersion.MERGE_MINED_BLOCK: + assert isinstance(vertex, MergeMinedBlock) + self.verifiers.merge_mined_block.verify(vertex) case TxVersion.REGULAR_TRANSACTION: assert isinstance(vertex, Transaction) - transaction_verification.verify(vertex, reject_locked_reward=reject_locked_reward) + self.verifiers.tx.verify(vertex, reject_locked_reward=reject_locked_reward) case TxVersion.TOKEN_CREATION_TRANSACTION: assert isinstance(vertex, TokenCreationTransaction) - token_creation_transaction_verification.verify(vertex, reject_locked_reward=reject_locked_reward) + self.verifiers.token_creation_tx.verify(vertex, reject_locked_reward=reject_locked_reward) case _: raise NotImplementedError diff --git a/hathor/verification/vertex_verifier.py b/hathor/verification/vertex_verifier.py new file mode 100644 index 000000000..360450116 --- /dev/null +++ b/hathor/verification/vertex_verifier.py @@ -0,0 +1,22 @@ +# Copyright 2023 Hathor Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from hathor.conf.settings import HathorSettings + + +class VertexVerifier: + __slots__ = ('_settings', ) + + def __init__(self, *, settings: HathorSettings): + self._settings = settings