diff --git a/hathor/indexes/height_index.py b/hathor/indexes/height_index.py index abebfdc55..b9a9a5143 100644 --- a/hathor/indexes/height_index.py +++ b/hathor/indexes/height_index.py @@ -13,13 +13,16 @@ # limitations under the License. from abc import abstractmethod -from typing import NamedTuple, Optional +from typing import TYPE_CHECKING, NamedTuple, Optional from hathor.indexes.base_index import BaseIndex from hathor.indexes.scope import Scope from hathor.transaction import BaseTransaction, Block from hathor.types import VertexId +if TYPE_CHECKING: # pragma: no cover + from hathor.transaction.storage import TransactionStorage + SCOPE = Scope( include_blocks=True, include_txs=False, @@ -85,6 +88,18 @@ def get(self, height: int) -> Optional[bytes]: """ raise NotImplementedError + def find_by_timestamp(self, timestamp: float, tx_storage: 'TransactionStorage') -> Optional[Block]: + """ This method starts from the tip and advances to the parent until it finds a block with lower timestamp. + """ + # TODO: optimize + if timestamp < self.get_genesis_block_entry().timestamp: + return None + block = tx_storage.get_transaction(self.get_tip()) + assert isinstance(block, Block) + while block.timestamp > timestamp: + block = block.get_block_parent() + return block + @abstractmethod def get_tip(self) -> bytes: """ Return the best block hash, it returns the genesis when there is no other block diff --git a/hathor/indexes/manager.py b/hathor/indexes/manager.py index e681f716b..a16ab229b 100644 --- a/hathor/indexes/manager.py +++ b/hathor/indexes/manager.py @@ -51,14 +51,12 @@ class IndexesManager(ABC): log = get_logger() info: InfoIndex - all_tips: TipsIndex - block_tips: TipsIndex - tx_tips: TipsIndex - + all_tips: Optional[TipsIndex] + block_tips: Optional[TipsIndex] + tx_tips: Optional[TipsIndex] sorted_all: TimestampIndex sorted_blocks: TimestampIndex sorted_txs: TimestampIndex - height: HeightIndex mempool_tips: Optional[MempoolTipsIndex] addresses: Optional[AddressIndex] @@ -94,6 +92,11 @@ def iter_all_indexes(self) -> Iterator[BaseIndex]: self.utxo, ]) + @abstractmethod + def enable_tips_indexes(self) -> None: + """Enable tips indexs. It does nothing if it has already been enabled.""" + raise NotImplementedError + @abstractmethod def enable_address_index(self, pubsub: 'PubSubManager') -> None: """Enable address index. It does nothing if it has already been enabled.""" @@ -198,18 +201,21 @@ def add_tx(self, tx: BaseTransaction) -> bool: # These two calls return False when a transaction changes from # voided to executed and vice-versa. - r1 = self.all_tips.add_tx(tx) - r2 = self.sorted_all.add_tx(tx) - assert r1 == r2 + r1 = self.sorted_all.add_tx(tx) + if self.all_tips is not None: + r2 = self.all_tips.add_tx(tx) + assert r1 == r2 if tx.is_block: - r3 = self.block_tips.add_tx(tx) - r4 = self.sorted_blocks.add_tx(tx) - assert r3 == r4 + r3 = self.sorted_blocks.add_tx(tx) + if self.block_tips is not None: + r4 = self.block_tips.add_tx(tx) + assert r3 == r4 else: - r3 = self.tx_tips.add_tx(tx) - r4 = self.sorted_txs.add_tx(tx) - assert r3 == r4 + r3 = self.sorted_txs.add_tx(tx) + if self.tx_tips is not None: + r4 = self.tx_tips.add_tx(tx) + assert r3 == r4 if self.addresses: self.addresses.add_tx(tx) @@ -234,7 +240,8 @@ def del_tx(self, tx: BaseTransaction, *, remove_all: bool = False, relax_assert: # We delete from indexes in two cases: (i) mark tx as voided, and (ii) remove tx. # We only remove tx from all_tips and sorted_all when it is removed from the storage. # For clarity, when a tx is marked as voided, it is not removed from all_tips and sorted_all. - self.all_tips.del_tx(tx, relax_assert=relax_assert) + if self.all_tips is not None: + self.all_tips.del_tx(tx, relax_assert=relax_assert) self.sorted_all.del_tx(tx) if self.addresses: self.addresses.remove_tx(tx) @@ -248,11 +255,13 @@ def del_tx(self, tx: BaseTransaction, *, remove_all: bool = False, relax_assert: self.mempool_tips.update(tx, remove=True) if tx.is_block: - self.block_tips.del_tx(tx, relax_assert=relax_assert) self.sorted_blocks.del_tx(tx) + if self.block_tips is not None: + self.block_tips.del_tx(tx, relax_assert=relax_assert) else: - self.tx_tips.del_tx(tx, relax_assert=relax_assert) self.sorted_txs.del_tx(tx) + if self.tx_tips is not None: + self.tx_tips.del_tx(tx, relax_assert=relax_assert) if self.tokens: self.tokens.del_tx(tx) @@ -263,12 +272,11 @@ def __init__(self) -> None: from hathor.indexes.memory_height_index import MemoryHeightIndex from hathor.indexes.memory_info_index import MemoryInfoIndex from hathor.indexes.memory_timestamp_index import MemoryTimestampIndex - from hathor.indexes.memory_tips_index import MemoryTipsIndex self.info = MemoryInfoIndex() - self.all_tips = MemoryTipsIndex(scope_type=TipsScopeType.ALL) - self.block_tips = MemoryTipsIndex(scope_type=TipsScopeType.BLOCKS) - self.tx_tips = MemoryTipsIndex(scope_type=TipsScopeType.TXS) + self.all_tips = None + self.block_tips = None + self.tx_tips = None self.sorted_all = MemoryTimestampIndex(scope_type=TimestampScopeType.ALL) self.sorted_blocks = MemoryTimestampIndex(scope_type=TimestampScopeType.BLOCKS) @@ -283,6 +291,15 @@ def __init__(self) -> None: # XXX: this has to be at the end of __init__, after everything has been initialized self.__init_checks__() + def enable_tips_indexes(self) -> None: + from hathor.indexes.memory_tips_index import MemoryTipsIndex + if self.all_tips is None: + self.all_tips = MemoryTipsIndex(scope_type=TipsScopeType.ALL) + if self.block_tips is None: + self.block_tips = MemoryTipsIndex(scope_type=TipsScopeType.BLOCKS) + if self.tx_tips is None: + self.tx_tips = MemoryTipsIndex(scope_type=TipsScopeType.TXS) + def enable_address_index(self, pubsub: 'PubSubManager') -> None: from hathor.indexes.memory_address_index import MemoryAddressIndex if self.addresses is None: @@ -306,7 +323,6 @@ def enable_mempool_index(self) -> None: class RocksDBIndexesManager(IndexesManager): def __init__(self, rocksdb_storage: 'RocksDBStorage') -> None: - from hathor.indexes.partial_rocksdb_tips_index import PartialRocksDBTipsIndex from hathor.indexes.rocksdb_height_index import RocksDBHeightIndex from hathor.indexes.rocksdb_info_index import RocksDBInfoIndex from hathor.indexes.rocksdb_timestamp_index import RocksDBTimestampIndex @@ -315,9 +331,9 @@ def __init__(self, rocksdb_storage: 'RocksDBStorage') -> None: self.info = RocksDBInfoIndex(self._db) self.height = RocksDBHeightIndex(self._db) - self.all_tips = PartialRocksDBTipsIndex(self._db, scope_type=TipsScopeType.ALL) - self.block_tips = PartialRocksDBTipsIndex(self._db, scope_type=TipsScopeType.BLOCKS) - self.tx_tips = PartialRocksDBTipsIndex(self._db, scope_type=TipsScopeType.TXS) + self.all_tips = None + self.block_tips = None + self.tx_tips = None self.sorted_all = RocksDBTimestampIndex(self._db, scope_type=TimestampScopeType.ALL) self.sorted_blocks = RocksDBTimestampIndex(self._db, scope_type=TimestampScopeType.BLOCKS) @@ -331,6 +347,15 @@ def __init__(self, rocksdb_storage: 'RocksDBStorage') -> None: # XXX: this has to be at the end of __init__, after everything has been initialized self.__init_checks__() + def enable_tips_indexes(self) -> None: + from hathor.indexes.partial_rocksdb_tips_index import PartialRocksDBTipsIndex + if self.all_tips is None: + self.all_tips = PartialRocksDBTipsIndex(self._db, scope_type=TipsScopeType.ALL) + if self.block_tips is None: + self.block_tips = PartialRocksDBTipsIndex(self._db, scope_type=TipsScopeType.BLOCKS) + if self.tx_tips is None: + self.tx_tips = PartialRocksDBTipsIndex(self._db, scope_type=TipsScopeType.TXS) + def enable_address_index(self, pubsub: 'PubSubManager') -> None: from hathor.indexes.rocksdb_address_index import RocksDBAddressIndex if self.addresses is None: diff --git a/hathor/manager.py b/hathor/manager.py index 362cbcfcb..d76eb1092 100644 --- a/hathor/manager.py +++ b/hathor/manager.py @@ -689,6 +689,42 @@ def generate_parent_txs(self, timestamp: Optional[float]) -> 'ParentTxs': This method tries to return a stable result, such that for a given timestamp and storage state it will always return the same. """ + # return self._generate_parent_txs_from_tips_index(timestamp) + # XXX: prefer txs_tips index since it's been tested more + assert self.tx_storage.indexes is not None + if self.tx_storage.indexes.tx_tips is not None: + return self._generate_parent_txs_from_tips_index(timestamp) + else: + return self._generate_parent_txs_from_mempool_index(timestamp) + + def _generate_parent_txs_from_mempool_index(self, timestamp: Optional[float]) -> 'ParentTxs': + # XXX: this implementation is naive, it will return a working result but not necessarily actual tips, + # particularly when the timestamp is in the past it will just return tx parents of a previous block that + # is within the timestamp, this is because we don't need to support that case for normal usage + if timestamp is None: + timestamp = self.reactor.seconds() + assert self.tx_storage.indexes is not None + assert self.tx_storage.indexes.height is not None + assert self.tx_storage.indexes.mempool_tips is not None + tips = [tx for tx in self.tx_storage.indexes.mempool_tips.iter(self.tx_storage) if tx.timestamp < timestamp] + max_timestamp = max(tx.timestamp for tx in tips) if tips else 0 + can_include: list[bytes] = [not_none(tx.hash) for tx in tips] + must_include = [] + if len(can_include) < 2: + best_block = self.tx_storage.indexes.height.find_by_timestamp(timestamp, self.tx_storage) + assert best_block is not None + all_best_block_parent_txs = list(map(self.tx_storage.get_transaction, best_block.parents[1:])) + best_block_parent_txs = [tx for tx in all_best_block_parent_txs if tx.timestamp < timestamp] + max_timestamp = max(max_timestamp, *list(tx.timestamp for tx in best_block_parent_txs)) + if len(can_include) < 1: + can_include.extend(not_none(tx.hash) for tx in best_block_parent_txs) + else: + must_include = can_include + can_include = [not_none(tx.hash) for tx in best_block_parent_txs] + assert len(can_include) + len(must_include) >= 2 + return ParentTxs(max_timestamp, can_include, must_include) + + def _generate_parent_txs_from_tips_index(self, timestamp: Optional[float]) -> 'ParentTxs': if timestamp is None: timestamp = self.reactor.seconds() can_include_intervals = sorted(self.tx_storage.get_tx_tips(timestamp - 1)) diff --git a/hathor/p2p/manager.py b/hathor/p2p/manager.py index d7e7f422b..ff15cfc57 100644 --- a/hathor/p2p/manager.py +++ b/hathor/p2p/manager.py @@ -237,9 +237,12 @@ def set_manager(self, manager: 'HathorManager') -> None: raise TypeError('Class built incorrectly without any enabled sync version') self.manager = manager + assert self.manager.tx_storage.indexes is not None + indexes = self.manager.tx_storage.indexes + if self.is_sync_version_available(SyncVersion.V1_1): + self.log.debug('enable sync-v1 indexes') + indexes.enable_tips_indexes() if self.is_sync_version_available(SyncVersion.V2): - assert self.manager.tx_storage.indexes is not None - indexes = self.manager.tx_storage.indexes self.log.debug('enable sync-v2 indexes') indexes.enable_mempool_index() diff --git a/hathor/transaction/storage/transaction_storage.py b/hathor/transaction/storage/transaction_storage.py index 9b90af63f..f73e5a4a4 100644 --- a/hathor/transaction/storage/transaction_storage.py +++ b/hathor/transaction/storage/transaction_storage.py @@ -640,15 +640,23 @@ def first_timestamp(self) -> int: raise NotImplementedError @abstractmethod - def get_best_block_tips(self, timestamp: Optional[float] = None, *, skip_cache: bool = False) -> list[bytes]: + def get_best_block_tips(self, *, skip_cache: bool = False) -> list[bytes]: """ Return a list of blocks that are heads in a best chain. It must be used when mining. When more than one block is returned, it means that there are multiple best chains and you can choose any of them. """ - if timestamp is None and not skip_cache and self._best_block_tips_cache is not None: - return self._best_block_tips_cache[:] + # ignoring cache because current implementation is ~O(1) + assert self.indexes is not None + return [self.indexes.height.get_tip()] + + @abstractmethod + def get_past_best_block_tips(self, timestamp: Optional[float] = None) -> list[bytes]: + """ Return a list of blocks that are heads in a best chain. It must be used when mining. + When more than one block is returned, it means that there are multiple best chains and + you can choose any of them. + """ best_score = 0.0 best_tip_blocks: list[bytes] = [] @@ -1043,6 +1051,7 @@ def iter_mempool_tips_from_tx_tips(self) -> Iterator[Transaction]: This method requires indexes to be enabled. """ assert self.indexes is not None + assert self.indexes.tx_tips is not None tx_tips = self.indexes.tx_tips for interval in tx_tips[self.latest_timestamp + 1]: @@ -1204,8 +1213,11 @@ def remove_cache(self) -> None: """Remove all caches in case we don't need it.""" self.indexes = None - def get_best_block_tips(self, timestamp: Optional[float] = None, *, skip_cache: bool = False) -> list[bytes]: - return super().get_best_block_tips(timestamp, skip_cache=skip_cache) + def get_best_block_tips(self, *, skip_cache: bool = False) -> list[bytes]: + return super().get_best_block_tips(skip_cache=skip_cache) + + def get_past_best_block_tips(self, timestamp: Optional[float] = None) -> list[bytes]: + return super().get_past_best_block_tips(timestamp) def get_n_height_tips(self, n_blocks: int) -> list[HeightInfo]: block = self.get_best_block() @@ -1224,6 +1236,7 @@ def get_block_tips(self, timestamp: Optional[float] = None) -> set[Interval]: if self.indexes is None: raise NotImplementedError assert self.indexes is not None + assert self.indexes.block_tips is not None if timestamp is None: timestamp = self.latest_timestamp return self.indexes.block_tips[timestamp] @@ -1232,6 +1245,7 @@ def get_tx_tips(self, timestamp: Optional[float] = None) -> set[Interval]: if self.indexes is None: raise NotImplementedError assert self.indexes is not None + assert self.indexes.tx_tips is not None if timestamp is None: timestamp = self.latest_timestamp tips = self.indexes.tx_tips[timestamp] @@ -1249,6 +1263,7 @@ def get_all_tips(self, timestamp: Optional[float] = None) -> set[Interval]: if self.indexes is None: raise NotImplementedError assert self.indexes is not None + assert self.indexes.all_tips is not None if timestamp is None: timestamp = self.latest_timestamp diff --git a/tests/p2p/test_double_spending.py b/tests/p2p/test_double_spending.py index 21b74d620..67be4e610 100644 --- a/tests/p2p/test_double_spending.py +++ b/tests/p2p/test_double_spending.py @@ -89,8 +89,9 @@ def test_simple_double_spending(self) -> None: self.assertEqual([tx1.hash, tx2.hash], spent_meta.spent_outputs[txin.index]) # old indexes - self.assertNotIn(tx1.hash, [x.data for x in self.manager1.tx_storage.get_tx_tips()]) - self.assertNotIn(tx2.hash, [x.data for x in self.manager1.tx_storage.get_tx_tips()]) + if self.manager1.tx_storage.indexes.tx_tips is not None: + self.assertNotIn(tx1.hash, [x.data for x in self.manager1.tx_storage.get_tx_tips()]) + self.assertNotIn(tx2.hash, [x.data for x in self.manager1.tx_storage.get_tx_tips()]) # new indexes if self.manager1.tx_storage.indexes.mempool_tips is not None: @@ -119,9 +120,10 @@ def test_simple_double_spending(self) -> None: self.assertEqual([tx1.hash, tx2.hash, tx3.hash], spent_meta.spent_outputs[txin.index]) # old indexes - self.assertNotIn(tx1.hash, [x.data for x in self.manager1.tx_storage.get_tx_tips()]) - self.assertNotIn(tx2.hash, [x.data for x in self.manager1.tx_storage.get_tx_tips()]) - self.assertIn(tx3.hash, [x.data for x in self.manager1.tx_storage.get_tx_tips()]) + if self.manager1.tx_storage.indexes.tx_tips is not None: + self.assertNotIn(tx1.hash, [x.data for x in self.manager1.tx_storage.get_tx_tips()]) + self.assertNotIn(tx2.hash, [x.data for x in self.manager1.tx_storage.get_tx_tips()]) + self.assertIn(tx3.hash, [x.data for x in self.manager1.tx_storage.get_tx_tips()]) # new indexes if self.manager1.tx_storage.indexes.mempool_tips is not None: diff --git a/tests/tx/test_indexes3.py b/tests/tx/test_indexes3.py index c7a513acf..f4101120e 100644 --- a/tests/tx/test_indexes3.py +++ b/tests/tx/test_indexes3.py @@ -46,6 +46,37 @@ def setUp(self): # slightly different meaning self.manager = self._build_randomized_blockchain() + @pytest.mark.flaky(max_runs=3, min_passes=1) + def test_topological_iterators(self): + tx_storage = self.manager.tx_storage + + # XXX: sanity check that we've at least produced something + total_count = tx_storage.get_vertices_count() + self.assertGreater(total_count, 3) + + # XXX: sanity check that the children metadata is properly set (this is needed for one of the iterators) + for tx in tx_storage.get_all_transactions(): + for parent_tx in map(tx_storage.get_transaction, tx.parents): + self.assertIn(tx.hash, parent_tx.get_metadata().children) + + # test iterators, name is used to aid in assert messages + iterators = [ + ('dfs', tx_storage._topological_sort_dfs()), + ('timestamp_index', tx_storage._topological_sort_timestamp_index()), + ('metadata', tx_storage._topological_sort_metadata()), + ] + for name, it in iterators: + # collect all transactions, while checking that inputs/parents are consistent + txs = list(it) + # must be complete + self.assertEqual(len(txs), total_count, f'iterator "{name}" does not cover all txs') + # must be topological + self.assertIsTopological(iter(txs), f'iterator "{name}" is not topological') + + +class SyncV1SimulatorIndexesTestCase(unittest.SyncV1Params, BaseSimulatorIndexesTestCase): + __test__ = True + @pytest.mark.flaky(max_runs=3, min_passes=1) def test_tips_index_initialization(self): # XXX: this test makes use of the internals of TipsIndex @@ -82,37 +113,6 @@ def test_tips_index_initialization(self): self.assertEqual(newinit_block_tips_tree, base_block_tips_tree) self.assertEqual(newinit_tx_tips_tree, base_tx_tips_tree) - @pytest.mark.flaky(max_runs=3, min_passes=1) - def test_topological_iterators(self): - tx_storage = self.manager.tx_storage - - # XXX: sanity check that we've at least produced something - total_count = tx_storage.get_vertices_count() - self.assertGreater(total_count, 3) - - # XXX: sanity check that the children metadata is properly set (this is needed for one of the iterators) - for tx in tx_storage.get_all_transactions(): - for parent_tx in map(tx_storage.get_transaction, tx.parents): - self.assertIn(tx.hash, parent_tx.get_metadata().children) - - # test iterators, name is used to aid in assert messages - iterators = [ - ('dfs', tx_storage._topological_sort_dfs()), - ('timestamp_index', tx_storage._topological_sort_timestamp_index()), - ('metadata', tx_storage._topological_sort_metadata()), - ] - for name, it in iterators: - # collect all transactions, while checking that inputs/parents are consistent - txs = list(it) - # must be complete - self.assertEqual(len(txs), total_count, f'iterator "{name}" does not cover all txs') - # must be topological - self.assertIsTopological(iter(txs), f'iterator "{name}" is not topological') - - -class SyncV1SimulatorIndexesTestCase(unittest.SyncV1Params, BaseSimulatorIndexesTestCase): - __test__ = True - class SyncV2SimulatorIndexesTestCase(unittest.SyncV2Params, BaseSimulatorIndexesTestCase): __test__ = True diff --git a/tests/tx/test_indexes4.py b/tests/tx/test_indexes4.py index 7777d69e1..6a4c28225 100644 --- a/tests/tx/test_indexes4.py +++ b/tests/tx/test_indexes4.py @@ -73,9 +73,13 @@ def test_index_initialization(self): raise AssertionError('no voided tx found') # base tips indexes - base_all_tips_tree = tx_storage.indexes.all_tips.tree.copy() - base_block_tips_tree = tx_storage.indexes.block_tips.tree.copy() - base_tx_tips_tree = tx_storage.indexes.tx_tips.tree.copy() + use_tips_indexes = tx_storage.indexes.all_tips is not None + if use_tips_indexes: + assert tx_storage.indexes.block_tips is not None + assert tx_storage.indexes.tx_tips is not None + base_all_tips_tree = tx_storage.indexes.all_tips.tree.copy() + base_block_tips_tree = tx_storage.indexes.block_tips.tree.copy() + base_tx_tips_tree = tx_storage.indexes.tx_tips.tree.copy() base_address_index = deepcopy(tx_storage.indexes.addresses.index) base_utxo_index = deepcopy(tx_storage.indexes.utxo._index) @@ -84,15 +88,17 @@ def test_index_initialization(self): tx_storage.indexes.enable_address_index(self.manager.pubsub) tx_storage._manually_initialize_indexes() - reinit_all_tips_tree = tx_storage.indexes.all_tips.tree.copy() - reinit_block_tips_tree = tx_storage.indexes.block_tips.tree.copy() - reinit_tx_tips_tree = tx_storage.indexes.tx_tips.tree.copy() + if use_tips_indexes: + reinit_all_tips_tree = tx_storage.indexes.all_tips.tree.copy() + reinit_block_tips_tree = tx_storage.indexes.block_tips.tree.copy() + reinit_tx_tips_tree = tx_storage.indexes.tx_tips.tree.copy() reinit_address_index = deepcopy(tx_storage.indexes.addresses.index) reinit_utxo_index = deepcopy(tx_storage.indexes.utxo._index) - self.assertEqual(reinit_all_tips_tree, base_all_tips_tree) - self.assertEqual(reinit_block_tips_tree, base_block_tips_tree) - self.assertEqual(reinit_tx_tips_tree, base_tx_tips_tree) + if use_tips_indexes: + self.assertEqual(reinit_all_tips_tree, base_all_tips_tree) + self.assertEqual(reinit_block_tips_tree, base_block_tips_tree) + self.assertEqual(reinit_tx_tips_tree, base_tx_tips_tree) self.assertEqual(reinit_address_index, base_address_index) self.assertEqual(reinit_utxo_index, base_utxo_index) @@ -101,15 +107,17 @@ def test_index_initialization(self): tx_storage.indexes.enable_address_index(self.manager.pubsub) tx_storage._manually_initialize_indexes() - newinit_all_tips_tree = tx_storage.indexes.all_tips.tree.copy() - newinit_block_tips_tree = tx_storage.indexes.block_tips.tree.copy() - newinit_tx_tips_tree = tx_storage.indexes.tx_tips.tree.copy() + if use_tips_indexes: + newinit_all_tips_tree = tx_storage.indexes.all_tips.tree.copy() + newinit_block_tips_tree = tx_storage.indexes.block_tips.tree.copy() + newinit_tx_tips_tree = tx_storage.indexes.tx_tips.tree.copy() newinit_address_index = deepcopy(tx_storage.indexes.addresses.index) newinit_utxo_index = deepcopy(tx_storage.indexes.utxo._index) - self.assertEqual(newinit_all_tips_tree, base_all_tips_tree) - self.assertEqual(newinit_block_tips_tree, base_block_tips_tree) - self.assertEqual(newinit_tx_tips_tree, base_tx_tips_tree) + if use_tips_indexes: + self.assertEqual(newinit_all_tips_tree, base_all_tips_tree) + self.assertEqual(newinit_block_tips_tree, base_block_tips_tree) + self.assertEqual(newinit_tx_tips_tree, base_tx_tips_tree) self.assertEqual(newinit_address_index, base_address_index) self.assertEqual(newinit_utxo_index, base_utxo_index) diff --git a/tests/tx/test_tips.py b/tests/tx/test_tips.py index c1ae8bfad..74c7beb3f 100644 --- a/tests/tx/test_tips.py +++ b/tests/tx/test_tips.py @@ -177,7 +177,12 @@ def get_tips(self): assert self.manager.tx_storage.indexes.mempool_tips is not None return self.manager.tx_storage.indexes.mempool_tips.get() + # XXX: disable this test for sync-v2-only + def test_choose_tips(self): + pass + # sync-bridge should behave like sync-v2 class SyncBridgeTipsTestCase(unittest.SyncBridgeParams, SyncV2TipsTestCase): - pass + def test_choose_tips(self): + BaseTipsTestCase.test_choose_tips(self) diff --git a/tests/unittest.py b/tests/unittest.py index f92bc0f50..2c350c4fc 100644 --- a/tests/unittest.py +++ b/tests/unittest.py @@ -329,10 +329,31 @@ def assertTipsEqual(self, manager1: HathorManager, manager2: HathorManager) -> N self.assertTipsEqualSyncV1(manager1, manager2) def assertTipsNotEqual(self, manager1: HathorManager, manager2: HathorManager) -> None: - s1 = set(manager1.tx_storage.get_all_tips()) - s2 = set(manager2.tx_storage.get_all_tips()) + s1 = self._get_all_tips_form_best_index(manager1) + s2 = self._get_all_tips_form_best_index(manager2) self.assertNotEqual(s1, s2) + def _get_all_tips_form_best_index(self, manager: HathorManager) -> set[bytes]: + assert manager.tx_storage.indexes is not None + if manager.tx_storage.indexes.all_tips is not None: + return self._get_all_tips_from_syncv1_indexes(manager.tx_storage) + else: + return self._get_all_tips_from_syncv2_indexes(manager.tx_storage) + + def _get_all_tips_from_syncv1_indexes(self, tx_storage: TransactionStorage) -> set[bytes]: + assert tx_storage.indexes is not None + assert tx_storage.indexes.all_tips is not None + intervals = tx_storage.indexes.all_tips[tx_storage.latest_timestamp] + tips = set(i.data for i in intervals) + return tips + + def _get_all_tips_from_syncv2_indexes(self, tx_storage: TransactionStorage) -> set[bytes]: + assert tx_storage.indexes is not None + assert tx_storage.indexes.mempool_tips is not None + tx_tips = tx_storage.indexes.mempool_tips.get() + block_tip = tx_storage.indexes.height.get_tip() + return tx_tips | {block_tip} + def assertTipsEqualSyncV1(self, manager1: HathorManager, manager2: HathorManager) -> None: # XXX: this is the original implementation of assertTipsEqual s1 = set(manager1.tx_storage.get_all_tips())