From 9822bdb46b94b1a6abcd1e07779053aa29a527cb Mon Sep 17 00:00:00 2001 From: Marcelo Salhab Brogliato Date: Mon, 6 Nov 2023 21:07:33 -0600 Subject: [PATCH] feat(consensus): Use DAG of funds to set first_block Co-authored-by: Jan Segre --- hathor/consensus/block_consensus.py | 9 ++- .../include_funds_for_first_block.py | 37 ++++++++++ .../storage/transaction_storage.py | 10 ++- tests/consensus/test_first_block.py | 71 +++++++++++++++++++ 4 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 hathor/transaction/storage/migrations/include_funds_for_first_block.py create mode 100644 tests/consensus/test_first_block.py diff --git a/hathor/consensus/block_consensus.py b/hathor/consensus/block_consensus.py index 419a66268..d77dee210 100644 --- a/hathor/consensus/block_consensus.py +++ b/hathor/consensus/block_consensus.py @@ -432,7 +432,7 @@ def remove_first_block_markers(self, block: Block) -> None: storage = block.storage from hathor.transaction.storage.traversal import BFSTimestampWalk - bfs = BFSTimestampWalk(storage, is_dag_verifications=True, is_left_to_right=False) + bfs = BFSTimestampWalk(storage, is_dag_verifications=True, is_dag_funds=True, is_left_to_right=False) for tx in bfs.run(block, skip_root=True): if tx.is_block: bfs.skip_neighbors(tx) @@ -469,9 +469,12 @@ def _score_block_dfs(self, block: BaseTransaction, used: set[bytes], else: from hathor.transaction.storage.traversal import BFSTimestampWalk - bfs = BFSTimestampWalk(storage, is_dag_verifications=True, is_left_to_right=False) + bfs = BFSTimestampWalk(storage, is_dag_verifications=True, is_dag_funds=True, is_left_to_right=False) for tx in bfs.run(parent, skip_root=False): - assert not tx.is_block + assert tx.hash is not None + if tx.is_block: + bfs.skip_neighbors(tx) + continue if tx.hash in used: bfs.skip_neighbors(tx) diff --git a/hathor/transaction/storage/migrations/include_funds_for_first_block.py b/hathor/transaction/storage/migrations/include_funds_for_first_block.py new file mode 100644 index 000000000..0dddb4c8a --- /dev/null +++ b/hathor/transaction/storage/migrations/include_funds_for_first_block.py @@ -0,0 +1,37 @@ +# 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 typing import TYPE_CHECKING + +from structlog import get_logger + +from hathor.transaction.storage.migrations import BaseMigration + +if TYPE_CHECKING: + from hathor.transaction.storage import TransactionStorage + +logger = get_logger() + + +class Migration(BaseMigration): + def skip_empty_db(self) -> bool: + return True + + def get_db_name(self) -> str: + return 'include_funds_for_first_block' + + def run(self, storage: 'TransactionStorage') -> None: + raise Exception('Cannot migrate your database due to an incompatible change in the metadata. ' + 'Please, delete your data folder and use the latest available snapshot or sync ' + 'from beginning.') diff --git a/hathor/transaction/storage/transaction_storage.py b/hathor/transaction/storage/transaction_storage.py index 26e226a7d..ab06157f5 100644 --- a/hathor/transaction/storage/transaction_storage.py +++ b/hathor/transaction/storage/transaction_storage.py @@ -37,7 +37,13 @@ TransactionIsNotABlock, TransactionNotInAllowedScopeError, ) -from hathor.transaction.storage.migrations import BaseMigration, MigrationState, change_score_acc_weight_metadata +from hathor.transaction.storage.migrations import ( + BaseMigration, + MigrationState, + add_closest_ancestor_block, + change_score_acc_weight_metadata, + include_funds_for_first_block, +) from hathor.transaction.storage.tx_allow_scope import TxAllowScope, tx_allow_context from hathor.transaction.transaction import Transaction from hathor.transaction.transaction_metadata import TransactionMetadata @@ -88,6 +94,8 @@ class TransactionStorage(ABC): # history of migrations that have to be applied in the order defined here _migration_factories: list[type[BaseMigration]] = [ change_score_acc_weight_metadata.Migration, + add_closest_ancestor_block.Migration, + include_funds_for_first_block.Migration, ] _migrations: list[BaseMigration] diff --git a/tests/consensus/test_first_block.py b/tests/consensus/test_first_block.py new file mode 100644 index 000000000..3544b2e7a --- /dev/null +++ b/tests/consensus/test_first_block.py @@ -0,0 +1,71 @@ +from tests import unittest + + +class FirstBlockTestCase(unittest.TestCase): + _enable_sync_v1 = True + _enable_sync_v2 = True + + def setUp(self) -> None: + super().setUp() + + from hathor.simulator.patches import SimulatorCpuMiningService + from hathor.simulator.simulator import _build_vertex_verifiers + + cpu_mining_service = SimulatorCpuMiningService() + + builder = self.get_builder() \ + .set_vertex_verifiers_builder(_build_vertex_verifiers) \ + .set_cpu_mining_service(cpu_mining_service) + + self.manager = self.create_peer_from_builder(builder) + self.dag_builder = self.get_dag_builder(self.manager) + + def test_first_block(self) -> None: + artifacts = self.dag_builder.build_from_str(""" + blockchain genesis b[1..50] + + b30 < dummy + + tx10.out[0] <<< tx50 + tx20.out[0] <<< tx50 + tx30 <-- tx50 + tx40 <-- tx50 + + tx41.out[0] <<< tx40 + tx42 <-- tx40 + tx43 <-- tx40 + + b31 --> tx10 + + b32 --> tx30 + b32 --> tx43 + + b33 --> tx50 + """) + + for node, vertex in artifacts.list: + self.manager.on_new_tx(vertex, fails_silently=False) + + b31 = artifacts.by_name['b31'].vertex + b32 = artifacts.by_name['b32'].vertex + b33 = artifacts.by_name['b33'].vertex + + tx10 = artifacts.by_name['tx10'].vertex + tx20 = artifacts.by_name['tx20'].vertex + tx30 = artifacts.by_name['tx30'].vertex + tx40 = artifacts.by_name['tx40'].vertex + tx41 = artifacts.by_name['tx41'].vertex + tx42 = artifacts.by_name['tx42'].vertex + tx43 = artifacts.by_name['tx43'].vertex + tx50 = artifacts.by_name['tx50'].vertex + + self.assertEqual(tx10.get_metadata().first_block, b31.hash) + + self.assertEqual(tx30.get_metadata().first_block, b32.hash) + self.assertEqual(tx43.get_metadata().first_block, b32.hash) + + self.assertEqual(tx50.get_metadata().first_block, b33.hash) + self.assertEqual(tx20.get_metadata().first_block, b33.hash) + self.assertEqual(tx40.get_metadata().first_block, b33.hash) + self.assertEqual(tx41.get_metadata().first_block, b33.hash) + self.assertEqual(tx42.get_metadata().first_block, b33.hash)