diff --git a/hathor/daa.py b/hathor/daa.py index ed4655f65..afd309726 100644 --- a/hathor/daa.py +++ b/hathor/daa.py @@ -21,19 +21,17 @@ from enum import IntFlag from math import log -from typing import TYPE_CHECKING, ClassVar, Optional +from typing import TYPE_CHECKING, Callable, ClassVar, Optional from structlog import get_logger from hathor.conf.settings import HathorSettings from hathor.profiler import get_cpu_profiler from hathor.types import VertexId -from hathor.util import iwindows, not_none +from hathor.util import iwindows if TYPE_CHECKING: from hathor.transaction import Block, Transaction - from hathor.transaction.storage.simple_memory_storage import SimpleMemoryStorage - from hathor.transaction.storage.vertex_storage_protocol import VertexStorageProtocol logger = get_logger() cpu = get_cpu_profiler() @@ -60,7 +58,7 @@ def __init__(self, *, settings: HathorSettings, test_mode: TestMode = TestMode.D DifficultyAdjustmentAlgorithm.singleton = self @cpu.profiler(key=lambda _, block: 'calculate_block_difficulty!{}'.format(block.hash.hex())) - def calculate_block_difficulty(self, block: 'Block', memory_storage: 'SimpleMemoryStorage') -> float: + def calculate_block_difficulty(self, block: 'Block', parent_block_getter: Callable[['Block'], 'Block']) -> float: """ Calculate block weight according to the ascendants of `block`, using calculate_next_weight.""" if self.TEST_MODE & TestMode.TEST_BLOCK_WEIGHT: return 1.0 @@ -68,27 +66,35 @@ def calculate_block_difficulty(self, block: 'Block', memory_storage: 'SimpleMemo if block.is_genesis: return self.MIN_BLOCK_WEIGHT - parent_block = memory_storage.get_parent_block(block) - - return self.calculate_next_weight(parent_block, block.timestamp, memory_storage) + parent_block = parent_block_getter(block) + return self.calculate_next_weight(parent_block, block.timestamp, parent_block_getter) def _calculate_N(self, parent_block: 'Block') -> int: """Calculate the N value for the `calculate_next_weight` algorithm.""" return min(2 * self._settings.BLOCK_DIFFICULTY_N_BLOCKS, parent_block.get_height() - 1) - def get_block_dependencies(self, block: 'Block') -> list[VertexId]: + def get_block_dependencies( + self, + block: 'Block', + parent_block_getter: Callable[['Block'], 'Block'], + ) -> list[VertexId]: """Return the ids of the required blocks to call `calculate_block_difficulty` for the provided block.""" - parent_block = block.get_block_parent() + parent_block = parent_block_getter(block) N = self._calculate_N(parent_block) - ids: list[VertexId] = [not_none(parent_block.hash)] + ids: list[VertexId] = [parent_block.hash] while len(ids) <= N + 1: - parent_block = parent_block.get_block_parent() - ids.append(not_none(parent_block.hash)) + parent_block = parent_block_getter(parent_block) + ids.append(parent_block.hash) return ids - def calculate_next_weight(self, parent_block: 'Block', timestamp: int, storage: 'VertexStorageProtocol') -> float: + def calculate_next_weight( + self, + parent_block: 'Block', + timestamp: int, + parent_block_getter: Callable[['Block'], 'Block'], + ) -> float: """ Calculate the next block weight, aka DAA/difficulty adjustment algorithm. The algorithm used is described in [RFC 22](https://gitlab.com/HathorNetwork/rfcs/merge_requests/22). @@ -111,8 +117,7 @@ def calculate_next_weight(self, parent_block: 'Block', timestamp: int, storage: blocks: list['Block'] = [] while len(blocks) < N + 1: blocks.append(root) - root = storage.get_parent_block(root) - assert root is not None + root = parent_block_getter(root) # TODO: revise if this assertion can be safely removed assert blocks == sorted(blocks, key=lambda tx: -tx.timestamp) diff --git a/hathor/manager.py b/hathor/manager.py index 362cbcfcb..354f7aaf1 100644 --- a/hathor/manager.py +++ b/hathor/manager.py @@ -816,7 +816,10 @@ def _make_block_template(self, parent_block: Block, parent_txs: 'ParentTxs', cur parent_block_metadata.score, 2 * self._settings.WEIGHT_TOL ) - weight = max(self.daa.calculate_next_weight(parent_block, timestamp, self.tx_storage), min_significant_weight) + weight = max( + self.daa.calculate_next_weight(parent_block, timestamp, self.tx_storage.get_parent_block), + min_significant_weight + ) height = parent_block.get_height() + 1 parents = [parent_block.hash] + parent_txs.must_include parents_any = parent_txs.can_include diff --git a/hathor/simulator/simulator.py b/hathor/simulator/simulator.py index 6155df3b8..5eb4e20e0 100644 --- a/hathor/simulator/simulator.py +++ b/hathor/simulator/simulator.py @@ -253,7 +253,7 @@ def _build_vertex_verifiers( """ return VertexVerifiers.create( settings=settings, - vertex_verifier=SimulatorVertexVerifier(settings=settings, daa=daa), + vertex_verifier=SimulatorVertexVerifier(settings=settings), daa=daa, feature_service=feature_service, ) diff --git a/hathor/stratum/stratum.py b/hathor/stratum/stratum.py index 6cc6d7dea..78a9f29ae 100644 --- a/hathor/stratum/stratum.py +++ b/hathor/stratum/stratum.py @@ -526,7 +526,7 @@ def handle_submit(self, params: dict, msgid: Optional[str]) -> None: self.log.debug('share received', block=tx, block_base=block_base.hex(), block_base_hash=block_base_hash.hex()) - verifier = VertexVerifier(settings=self._settings, daa=self.manager.daa) + verifier = VertexVerifier(settings=self._settings) try: verifier.verify_pow(tx, override_weight=job.weight) diff --git a/hathor/transaction/storage/simple_memory_storage.py b/hathor/transaction/storage/simple_memory_storage.py deleted file mode 100644 index 6e521f052..000000000 --- a/hathor/transaction/storage/simple_memory_storage.py +++ /dev/null @@ -1,99 +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.transaction import Block, Transaction -from hathor.transaction.base_transaction import BaseTransaction -from hathor.transaction.storage import TransactionStorage -from hathor.transaction.storage.exceptions import TransactionDoesNotExist -from hathor.types import VertexId - - -class SimpleMemoryStorage: - """ - Instances of this class simply facilitate storing some data in memory, specifically for pre-fetched verification - dependencies. - """ - __slots__ = ('_blocks', '_transactions',) - - def __init__(self) -> None: - self._blocks: dict[VertexId, BaseTransaction] = {} - self._transactions: dict[VertexId, BaseTransaction] = {} - - @property - def _vertices(self) -> dict[VertexId, BaseTransaction]: - """Blocks and Transactions together.""" - return {**self._blocks, **self._transactions} - - def get_block(self, block_id: VertexId) -> Block: - """Return a block from the storage, throw if it's not found.""" - block = self._get_vertex(self._blocks, block_id) - assert isinstance(block, Block) - return block - - def get_transaction(self, tx_id: VertexId) -> Transaction: - """Return a transaction from the storage, throw if it's not found.""" - tx = self._get_vertex(self._transactions, tx_id) - assert isinstance(tx, Transaction) - return tx - - @staticmethod - def _get_vertex(storage: dict[VertexId, BaseTransaction], vertex_id: VertexId) -> BaseTransaction: - """Return a vertex from a storage, throw if it's not found.""" - if vertex := storage.get(vertex_id): - return vertex - - raise TransactionDoesNotExist(f'Vertex "{vertex_id.hex()}" does not exist in this SimpleMemoryStorage.') - - def get_parent_block(self, block: Block) -> Block: - """Get the parent block of a block.""" - parent_hash = block.get_block_parent_hash() - - return self.get_block(parent_hash) - - def add_vertices_from_storage(self, storage: TransactionStorage, ids: list[VertexId]) -> None: - """ - Add multiple vertices to this storage. It automatically fetches data from the provided TransactionStorage - and a list of ids. - """ - for vertex_id in ids: - self.add_vertex_from_storage(storage, vertex_id) - - def add_vertex_from_storage(self, storage: TransactionStorage, vertex_id: VertexId) -> None: - """ - Add a vertex to this storage. It automatically fetches data from the provided TransactionStorage and a list - of ids. - """ - if vertex_id in self._vertices: - return - - vertex = storage.get_transaction(vertex_id) - clone = vertex.clone(include_metadata=True, include_storage=False) - - if isinstance(vertex, Block): - self._blocks[vertex_id] = clone - return - - if isinstance(vertex, Transaction): - self._transactions[vertex_id] = clone - return - - raise NotImplementedError - - def get_vertex(self, vertex_id: VertexId) -> BaseTransaction: - # TODO: Currently unused, will be implemented in a next PR. - raise NotImplementedError - - def get_best_block_tips(self) -> list[VertexId]: - # TODO: Currently unused, will be implemented in a next PR. - raise NotImplementedError diff --git a/hathor/verification/block_verifier.py b/hathor/verification/block_verifier.py index ff0c74a86..2110bbd91 100644 --- a/hathor/verification/block_verifier.py +++ b/hathor/verification/block_verifier.py @@ -25,8 +25,6 @@ TransactionDataError, WeightError, ) -from hathor.transaction.storage.simple_memory_storage import SimpleMemoryStorage -from hathor.util import not_none class BlockVerifier: @@ -53,11 +51,8 @@ def verify_height(self, block: Block) -> None: def verify_weight(self, block: Block) -> None: """Validate minimum block difficulty.""" - memory_storage = SimpleMemoryStorage() - dependencies = self._daa.get_block_dependencies(block) - memory_storage.add_vertices_from_storage(not_none(block.storage), dependencies) - - min_block_weight = self._daa.calculate_block_difficulty(block, memory_storage) + assert block.storage is not None + min_block_weight = self._daa.calculate_block_difficulty(block, block.storage.get_parent_block) if block.weight < min_block_weight - self._settings.WEIGHT_TOL: raise WeightError(f'Invalid new block {block.hash_hex}: weight ({block.weight}) is ' f'smaller than the minimum weight ({min_block_weight})') diff --git a/hathor/verification/vertex_verifier.py b/hathor/verification/vertex_verifier.py index 8947dd591..d3ef72046 100644 --- a/hathor/verification/vertex_verifier.py +++ b/hathor/verification/vertex_verifier.py @@ -15,7 +15,6 @@ from typing import Optional from hathor.conf.settings import HathorSettings -from hathor.daa import DifficultyAdjustmentAlgorithm from hathor.transaction import BaseTransaction from hathor.transaction.exceptions import ( DuplicatedParents, @@ -40,11 +39,10 @@ class VertexVerifier: - __slots__ = ('_settings', '_daa') + __slots__ = ('_settings',) - def __init__(self, *, settings: HathorSettings, daa: DifficultyAdjustmentAlgorithm): + def __init__(self, *, settings: HathorSettings) -> None: self._settings = settings - self._daa = daa def verify_parents(self, vertex: BaseTransaction) -> None: """All parents must exist and their timestamps must be smaller than ours. @@ -158,7 +156,7 @@ def verify_outputs(self, vertex: BaseTransaction) -> None: )) def verify_number_of_outputs(self, vertex: BaseTransaction) -> None: - """Verify number of outputs does not exceeds the limit""" + """Verify number of outputs does not exceed the limit""" if len(vertex.outputs) > self._settings.MAX_NUM_OUTPUTS: raise TooManyOutputs('Maximum number of outputs exceeded') diff --git a/hathor/verification/vertex_verifiers.py b/hathor/verification/vertex_verifiers.py index 98477c397..31e3fe190 100644 --- a/hathor/verification/vertex_verifiers.py +++ b/hathor/verification/vertex_verifiers.py @@ -44,7 +44,7 @@ def create_defaults( Create a VertexVerifiers instance using the default verifier for each vertex type, from all required dependencies. """ - vertex_verifier = VertexVerifier(settings=settings, daa=daa) + vertex_verifier = VertexVerifier(settings=settings) return cls.create( settings=settings, diff --git a/tests/simulation/test_simulator.py b/tests/simulation/test_simulator.py index 5ef65d9e2..b2f8083ce 100644 --- a/tests/simulation/test_simulator.py +++ b/tests/simulation/test_simulator.py @@ -14,7 +14,7 @@ def test_verify_pow(self) -> None: # just get one of the genesis, we don't really need to create any transaction tx = next(iter(manager1.tx_storage.get_all_genesis())) # optional argument must be valid, it just has to not raise any exception, there's no assert for that - VertexVerifier(settings=self._settings, daa=manager1.daa).verify_pow(tx, override_weight=0.) + VertexVerifier(settings=self._settings).verify_pow(tx, override_weight=0.) def test_one_node(self) -> None: manager1 = self.create_peer() diff --git a/tests/tx/test_genesis.py b/tests/tx/test_genesis.py index a5bf0f430..fe08117bf 100644 --- a/tests/tx/test_genesis.py +++ b/tests/tx/test_genesis.py @@ -37,7 +37,7 @@ def setUp(self): self.storage = TransactionMemoryStorage() def test_pow(self): - verifier = VertexVerifier(settings=self._settings, daa=self._daa) + verifier = VertexVerifier(settings=self._settings) genesis = self.storage.get_all_genesis() for g in genesis: self.assertEqual(g.calculate_hash(), g.hash)