Skip to content

Commit

Permalink
feat(reliable-integration): change ordering to send voided txs first
Browse files Browse the repository at this point in the history
  • Loading branch information
glevco committed Nov 24, 2023
1 parent 7b6eb34 commit 59bdf45
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 6 deletions.
48 changes: 48 additions & 0 deletions hathor/cli/events_simulator/scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ class Scenario(Enum):
SINGLE_CHAIN_ONE_BLOCK = 'SINGLE_CHAIN_ONE_BLOCK'
SINGLE_CHAIN_BLOCKS_AND_TRANSACTIONS = 'SINGLE_CHAIN_BLOCKS_AND_TRANSACTIONS'
REORG = 'REORG'
UNVOIDED_TRANSACTION = 'UNVOIDED_TRANSACTION'

def simulate(self, simulator: 'Simulator', manager: 'HathorManager') -> None:
simulate_fns = {
Scenario.ONLY_LOAD: simulate_only_load,
Scenario.SINGLE_CHAIN_ONE_BLOCK: simulate_single_chain_one_block,
Scenario.SINGLE_CHAIN_BLOCKS_AND_TRANSACTIONS: simulate_single_chain_blocks_and_transactions,
Scenario.REORG: simulate_reorg,
Scenario.UNVOIDED_TRANSACTION: simulate_unvoided_transaction,
}

simulate_fn = simulate_fns[self]
Expand Down Expand Up @@ -92,3 +94,49 @@ def simulate_reorg(simulator: 'Simulator', manager: 'HathorManager') -> None:
connection = FakeConnection(manager, manager2)
simulator.add_connection(connection)
simulator.run(60)


def simulate_unvoided_transaction(simulator: 'Simulator', manager: 'HathorManager') -> None:
from hathor.conf.get_settings import get_settings
from hathor.simulator.utils import add_new_block, add_new_blocks, gen_new_tx
from hathor.util import not_none

settings = get_settings()
assert manager.wallet is not None
address = manager.wallet.get_unused_address(mark_as_used=False)

add_new_blocks(manager, settings.REWARD_SPEND_MIN_BLOCKS + 1)
simulator.run(60)

# A tx is created with weight 19.0005
tx = gen_new_tx(manager, address, 1000)
tx.weight = 19.0005
tx.update_hash()
assert manager.propagate_tx(tx, fails_silently=False)
simulator.run(60)

# A clone is created with a greater timestamp and a lower weight. It's a voided twin tx.
tx2 = tx.clone(include_metadata=False)
tx2.timestamp += 60
tx2.weight = 19
tx2.update_hash()
assert manager.propagate_tx(tx2, fails_silently=False)
simulator.run(60)

# Only the second tx is voided
assert not tx.get_metadata().voided_by
assert tx2.get_metadata().voided_by

# We add a block confirming the second tx, increasing its acc weight
block = add_new_block(manager, propagate=False)
block.parents = [
block.parents[0],
settings.GENESIS_TX1_HASH,
not_none(tx2.hash),
]
assert manager.propagate_tx(block, fails_silently=False)
simulator.run(60)

# The first tx gets voided and the second gets unvoided
assert tx.get_metadata().voided_by
assert not tx2.get_metadata().voided_by
17 changes: 15 additions & 2 deletions hathor/consensus/consensus.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,7 @@ def _unsafe_update(self, base: BaseTransaction) -> None:
reorg_size=reorg_size)

# finally signal an index update for all affected transactions
sorted_txs_affected = sorted(context.txs_affected, key=lambda tx: not_none(tx.timestamp), reverse=True)
for tx_affected in sorted_txs_affected:
for tx_affected in _sorted_affected_txs(context.txs_affected):
assert tx_affected.storage is not None
assert tx_affected.storage.indexes is not None
tx_affected.storage.indexes.update(tx_affected)
Expand Down Expand Up @@ -167,3 +166,17 @@ def filter_out_soft_voided_entries(self, tx: BaseTransaction, voided_by: set[byt
if not (self.soft_voided_tx_ids & tx3_voided_by):
ret.add(h)
return ret


def _sorted_affected_txs(affected_txs: set[BaseTransaction]) -> list[BaseTransaction]:
"""
Sort affected txs by voided first, then descending timestamp (reverse topological order).
This is useful for generating Reliable Integration events.
"""
def sorter(tx: BaseTransaction) -> tuple[bool, int]:
meta = tx.get_metadata()
is_voided = bool(meta.voided_by)

return is_voided, not_none(tx.timestamp)

return sorted(affected_txs, key=sorter, reverse=True)
4 changes: 2 additions & 2 deletions hathor/transaction/base_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -825,13 +825,13 @@ def serialize_output(tx: BaseTransaction, tx_out: TxOutput) -> dict[str, Any]:

return ret

def clone(self) -> 'BaseTransaction':
def clone(self, *, include_metadata: bool = True) -> 'BaseTransaction':
"""Return exact copy without sharing memory, including metadata if loaded.
:return: Transaction or Block copy
"""
new_tx = self.create_from_struct(self.get_struct())
if hasattr(self, '_metadata'):
if hasattr(self, '_metadata') and include_metadata:
assert self._metadata is not None # FIXME: is this actually true or do we have to check if not None
new_tx._metadata = self._metadata.clone()
new_tx.storage = self.storage
Expand Down
2 changes: 1 addition & 1 deletion tests/event/test_event_reorg.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ def test_reorg_events(self):
(EventType.NEW_VERTEX_ACCEPTED, {'hash': blocks[9].hash_hex}),
(EventType.REORG_STARTED, {'reorg_size': 2, 'previous_best_block': blocks[9].hash_hex,
'new_best_block': b0.hash_hex}),
(EventType.VERTEX_METADATA_CHANGED, {'hash': b0.hash_hex}),
(EventType.VERTEX_METADATA_CHANGED, {'hash': blocks[9].hash_hex}),
(EventType.VERTEX_METADATA_CHANGED, {'hash': blocks[8].hash_hex}),
(EventType.VERTEX_METADATA_CHANGED, {'hash': b0.hash_hex}),
(EventType.REORG_FINISHED, {}),
(EventType.NEW_VERTEX_ACCEPTED, {'hash': b0.hash_hex}),
]
Expand Down
Loading

0 comments on commit 59bdf45

Please sign in to comment.