From 08b003193d55bdb26dd6f504dd9e1b0a537136ff Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 4 Apr 2024 14:06:22 -0400 Subject: [PATCH] Update tests to reflect that failure callbacks must be provided when queueing a transaction. Update tests to reflect that txs are no longer requeued because of failure, but instead remain at the front of the queue unless the user takes more aggressive action to remove the tx from the queue. --- tests/conftest.py | 10 +++ tests/test_api.py | 4 + tests/test_faults.py | 44 ++++++++-- tests/test_machine.py | 139 +++++++++++++++++++++--------- tests/test_tracker.py | 194 ++++++++++++++++++++---------------------- 5 files changed, 244 insertions(+), 147 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0ec0cfd..d300f0d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -85,6 +85,16 @@ def machine(w3, strategies): _machine.stop() +@pytest.fixture +def broadcast_failure_hook(mocker): + return mocker.Mock() + + +@pytest.fixture +def fault_hook(mocker): + return mocker.Mock() + + @pytest.fixture def clock(machine): return machine._task.clock diff --git a/tests/test_api.py b/tests/test_api.py index f0489ac..726d331 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -12,12 +12,16 @@ def test_machine( machine, clock, mock_wake_sleep, + broadcast_failure_hook, + fault_hook, ): assert not machine.busy async_txs = machine.queue_transactions( params=[legacy_transaction, eip1559_transaction], signer=account, info={"message": "something wonderful is happening..."}, + on_broadcast_failure=broadcast_failure_hook, + on_fault=fault_hook, ) assert len(async_txs) == 2 diff --git a/tests/test_faults.py b/tests/test_faults.py index 3fe125b..b31f0e4 100644 --- a/tests/test_faults.py +++ b/tests/test_faults.py @@ -10,12 +10,13 @@ from atxm.utils import _get_receipt_from_txhash -def _broadcast_tx(machine, eip1559_transaction, account, mocker): - fault_hook = mocker.Mock() - +def _broadcast_tx( + machine, eip1559_transaction, account, broadcast_failure_hook, fault_hook +): atx = machine.queue_transaction( params=eip1559_transaction, signer=account, + on_broadcast_failure=broadcast_failure_hook, on_fault=fault_hook, ) @@ -26,7 +27,7 @@ def _broadcast_tx(machine, eip1559_transaction, account, mocker): assert atx.final is False assert atx.fault is None - return atx, fault_hook + return atx @pytest_twisted.inlineCallbacks @@ -58,9 +59,13 @@ def test_revert( account, interval, mock_wake_sleep, + broadcast_failure_hook, + fault_hook, mocker, ): - atx, fault_hook = _broadcast_tx(machine, eip1559_transaction, account, mocker) + atx = _broadcast_tx( + machine, eip1559_transaction, account, broadcast_failure_hook, fault_hook + ) assert machine.pending @@ -78,14 +83,25 @@ def test_revert( @pytest.mark.usefixtures("disable_auto_mining") def test_strategy_fault( - w3, machine, clock, eip1559_transaction, account, interval, mock_wake_sleep, mocker + w3, + machine, + clock, + eip1559_transaction, + account, + interval, + mock_wake_sleep, + broadcast_failure_hook, + fault_hook, + mocker, ): faulty_strategy = mocker.Mock(spec=AsyncTxStrategy) # TODO: consider whether strategies should just be overridden through the constructor machine._strategies.insert(0, faulty_strategy) # add first - atx, fault_hook = _broadcast_tx(machine, eip1559_transaction, account, mocker) + atx = _broadcast_tx( + machine, eip1559_transaction, account, broadcast_failure_hook, fault_hook + ) faulty_message = "mocked fault" faulty_strategy.execute.side_effect = TransactionFaulted( @@ -99,9 +115,19 @@ def test_strategy_fault( @pytest.mark.usefixtures("disable_auto_mining") def test_timeout_strategy_fault( - w3, machine, clock, eip1559_transaction, account, interval, mock_wake_sleep, mocker + w3, + machine, + clock, + eip1559_transaction, + account, + interval, + mock_wake_sleep, + broadcast_failure_hook, + fault_hook, ): - atx, fault_hook = _broadcast_tx(machine, eip1559_transaction, account, mocker) + atx = _broadcast_tx( + machine, eip1559_transaction, account, broadcast_failure_hook, fault_hook + ) atx.created -= 9999999999 diff --git a/tests/test_machine.py b/tests/test_machine.py index 3889050..f745815 100644 --- a/tests/test_machine.py +++ b/tests/test_machine.py @@ -55,6 +55,8 @@ def test_queue_from_parameter_handling( account, eip1559_transaction, mock_wake_sleep, + broadcast_failure_hook, + fault_hook, ): # 1. "from" parameter does not match account with pytest.raises(ValueError): @@ -70,6 +72,8 @@ def test_queue_from_parameter_handling( _ = machine.queue_transaction( params=tx_params, signer=account, + on_broadcast_failure=broadcast_failure_hook, + on_fault=fault_hook, ) # 2. no "from" parameter @@ -80,6 +84,8 @@ def test_queue_from_parameter_handling( atx = machine.queue_transaction( params=tx_params, signer=account, + on_broadcast_failure=broadcast_failure_hook, + on_fault=fault_hook, ) assert atx.params["from"] == account.address, "same as signer account" @@ -89,6 +95,8 @@ def test_queue_from_parameter_handling( atx = machine.queue_transaction( params=tx_params, signer=account, + on_broadcast_failure=broadcast_failure_hook, + on_fault=fault_hook, ) assert atx.params["from"] == account.address @@ -100,6 +108,8 @@ def test_queue( account, eip1559_transaction, mock_wake_sleep, + broadcast_failure_hook, + fault_hook, ): wake, _ = mock_wake_sleep @@ -112,6 +122,8 @@ def test_queue( atx = machine.queue_transaction( params=eip1559_transaction, signer=account, + on_broadcast_failure=broadcast_failure_hook, + on_fault=fault_hook, ) assert isinstance(atx, FutureTx) @@ -152,6 +164,8 @@ def test_wake_after_queuing_when_idle_and_not_already_running( params=eip1559_transaction, signer=account, info={"message": "something wonderful is happening..."}, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), ) assert stop_spy.call_count == 0, "no task to stop" @@ -184,6 +198,8 @@ def test_wake_after_queuing_when_idle_and_already_running( params=eip1559_transaction, signer=account, info={"message": "something wonderful is happening..."}, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), ) assert stop_spy.call_count == 1, "task stopped" @@ -200,6 +216,8 @@ def test_wake_no_call_after_queuing_when_already_busy( eip1559_transaction, account, mock_wake_sleep, + broadcast_failure_hook, + fault_hook, ): wake, _ = mock_wake_sleep @@ -210,6 +228,8 @@ def test_wake_no_call_after_queuing_when_already_busy( params=eip1559_transaction, signer=account, info={"message": "something wonderful is happening..."}, + on_broadcast_failure=broadcast_failure_hook, + on_fault=fault_hook, ) assert wake.call_count == 1 @@ -222,6 +242,8 @@ def test_wake_no_call_after_queuing_when_already_busy( params=eip1559_transaction, signer=account, info={"message": "something wonderful is happening..."}, + on_broadcast_failure=broadcast_failure_hook, + on_fault=fault_hook, ) assert wake.call_count == 1 # remains unchanged @@ -232,6 +254,8 @@ def test_wake_no_call_after_queuing_when_already_paused( eip1559_transaction, account, mock_wake_sleep, + broadcast_failure_hook, + fault_hook, ): wake, sleep = mock_wake_sleep @@ -248,6 +272,8 @@ def test_wake_no_call_after_queuing_when_already_paused( params=eip1559_transaction, signer=account, info={"message": "something wonderful is happening..."}, + on_broadcast_failure=broadcast_failure_hook, + on_fault=fault_hook, ) assert wake.call_count == 0 @@ -263,6 +289,8 @@ def test_broadcast( account, mocker, mock_wake_sleep, + broadcast_failure_hook, + fault_hook, ): wake, _ = mock_wake_sleep @@ -270,11 +298,13 @@ def test_broadcast( assert not machine.busy # Queue a transaction - hook = mocker.Mock() + broadcast_hook = mocker.Mock() atx = machine.queue_transaction( params=eip1559_transaction, signer=account, - on_broadcast=hook, + on_broadcast=broadcast_hook, + on_broadcast_failure=broadcast_failure_hook, + on_fault=fault_hook, info={"message": "something wonderful is happening..."}, ) @@ -301,7 +331,7 @@ def test_broadcast( # wait for the hook to be called yield deferLater(reactor, 0.2, lambda: None) - assert hook.call_count == 1 + assert broadcast_hook.call_count == 1 assert atx.retries == 0 @@ -327,6 +357,8 @@ def test_broadcast_non_recoverable_error( state_observer, eip1559_transaction, account, + broadcast_failure_hook, + fault_hook, mocker, mock_wake_sleep, ): @@ -336,13 +368,13 @@ def test_broadcast_non_recoverable_error( assert not machine.busy # Queue a transaction - broadcast_failure_hook = mocker.Mock() broadcast_hook = mocker.Mock() atx = machine.queue_transaction( params=eip1559_transaction, signer=account, on_broadcast=broadcast_hook, on_broadcast_failure=broadcast_failure_hook, + on_fault=fault_hook, info={"message": "something wonderful is happening..."}, ) @@ -357,15 +389,14 @@ def test_broadcast_non_recoverable_error( mocker.patch.object(w3.eth, "send_raw_transaction", side_effect=error) machine.start(now=True) - yield clock.advance(1) # wait for the hook to be called yield deferLater(reactor, 0.2, lambda: None) assert broadcast_failure_hook.call_count == 1 broadcast_failure_hook.assert_called_with(atx, error) - # The transaction failed and is not requeued - assert len(machine.queued) == 0 + # tx remains in queue + assert len(machine.queued) == 1 # run a few cycles for i in range(2): @@ -373,14 +404,13 @@ def test_broadcast_non_recoverable_error( assert broadcast_hook.call_count == 0 - assert atx.requeues == 0 + assert atx.retries == 0 # tx failed and not requeued - assert machine.current_state == machine._IDLE + assert machine.current_state == machine._BUSY - assert len(state_observer.transitions) == 2 + assert len(state_observer.transitions) == 1 assert state_observer.transitions[0] == (machine._IDLE, machine._BUSY) - assert state_observer.transitions[1] == (machine._BUSY, machine._IDLE) machine.stop() @@ -400,9 +430,11 @@ def test_broadcast_recoverable_error( account, mocker, mock_wake_sleep, + broadcast_failure_hook, + fault_hook, ): # need more freedom with redo attempts for test - mocker.patch.object(machine, "_MAX_REDO_ATTEMPTS", 10) + mocker.patch.object(machine, "_MAX_RETRY_ATTEMPTS", 10) wake, _ = mock_wake_sleep @@ -417,6 +449,7 @@ def test_broadcast_recoverable_error( signer=account, on_broadcast=broadcast_hook, on_broadcast_failure=broadcast_failure_hook, + on_fault=fault_hook, info={"message": "something wonderful is happening..."}, ) @@ -436,7 +469,7 @@ def test_broadcast_recoverable_error( for i in range(5): yield clock.advance(1) assert len(machine.queued) == 1 # remains in queue and not broadcasted - assert atx.requeues >= i + assert atx.retries >= i # call real method from now on mocker.patch.object(w3.eth, "send_raw_transaction", side_effect=real_method) @@ -474,7 +507,7 @@ def test_broadcast_recoverable_error( @pytest.mark.parametrize( "recoverable_error", [TooManyRequests, ProviderConnectionError, TimeExhausted] ) -def test_broadcast_recoverable_error_requeues_exceeded( +def test_broadcast_recoverable_error_retries_exceeded( recoverable_error, clock, w3, @@ -484,6 +517,8 @@ def test_broadcast_recoverable_error_requeues_exceeded( account, mocker, mock_wake_sleep, + broadcast_failure_hook, + fault_hook, ): wake, _ = mock_wake_sleep @@ -491,13 +526,13 @@ def test_broadcast_recoverable_error_requeues_exceeded( assert not machine.busy # Queue a transaction - broadcast_failure_hook = mocker.Mock() broadcast_hook = mocker.Mock() atx = machine.queue_transaction( params=eip1559_transaction, signer=account, on_broadcast=broadcast_hook, on_broadcast_failure=broadcast_failure_hook, + on_fault=fault_hook, info={"message": "something wonderful is happening..."}, ) @@ -511,13 +546,13 @@ def test_broadcast_recoverable_error_requeues_exceeded( assert _is_recoverable_send_tx_error(error) mocker.patch.object(w3.eth, "send_raw_transaction", side_effect=error) - # repeat some cycles; tx fails then gets requeued since error is "recoverable" + # repeat some cycles; tx fails then gets retried since error is "recoverable" machine.start(now=True) # one less than max attempts - for i in range(machine._MAX_REDO_ATTEMPTS - 1): + for i in range(machine._MAX_RETRY_ATTEMPTS - 1): assert len(machine.queued) == 1 # remains in queue and not broadcasted yield clock.advance(1) - assert atx.requeues >= i + assert atx.retries >= i # push over the retry limit yield clock.advance(1) @@ -527,23 +562,20 @@ def test_broadcast_recoverable_error_requeues_exceeded( assert broadcast_failure_hook.call_count == 1 broadcast_failure_hook.assert_called_with(atx, error) - assert atx.requeues == machine._MAX_REDO_ATTEMPTS + # The transaction failed but remains in the queue, unless the user does something + assert len(machine.queued) == 1 - # The transaction failed and is not requeued - assert len(machine.queued) == 0 + # retries are reset + assert atx.retries == 0 # run a few cycles - for i in range(2): + for i in range(machine._MAX_RETRY_ATTEMPTS - 1): yield clock.advance(1) assert broadcast_hook.call_count == 0 - # tx failed and not requeued - assert machine.current_state == machine._IDLE - - assert len(state_observer.transitions) == 2 + assert len(state_observer.transitions) == 1 assert state_observer.transitions[0] == (machine._IDLE, machine._BUSY) - assert state_observer.transitions[1] == (machine._BUSY, machine._IDLE) machine.stop() @@ -563,11 +595,13 @@ def test_finalize( assert machine.current_state == machine._IDLE # Queue a transaction - hook = mocker.Mock() + on_finalized_hook = mocker.Mock() atx = machine.queue_transaction( params=eip1559_transaction, signer=account, - on_finalized=hook, + on_finalized=on_finalized_hook, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), ) # There is one queued transaction @@ -602,7 +636,7 @@ def test_finalize( # wait for the hook to be called yield deferLater(reactor, 0.2, lambda: None) - assert hook.call_count == 1 + assert on_finalized_hook.call_count == 1 yield clock.advance(1) @@ -625,6 +659,8 @@ def test_follow( eip1559_transaction, account, mock_wake_sleep, + broadcast_failure_hook, + fault_hook, ): machine.start() assert machine.current_state == machine._IDLE @@ -632,6 +668,8 @@ def test_follow( atx = machine.queue_transaction( params=eip1559_transaction, signer=account, + on_broadcast_failure=broadcast_failure_hook, + on_fault=fault_hook, ) # advance to broadcast the transaction @@ -688,6 +726,8 @@ def test_use_strategies_speedup_used( params=eip1559_transaction, signer=account, on_broadcast=broadcast_hook, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), ) update_spy = mocker.spy(machine._tx_tracker, "update_after_retry") @@ -760,6 +800,8 @@ def test_use_strategies_timeout_used( account, mocker, mock_wake_sleep, + broadcast_failure_hook, + fault_hook, ): fault_hook = mocker.Mock() @@ -767,7 +809,10 @@ def test_use_strategies_timeout_used( assert machine.current_state == machine._IDLE atx = machine.queue_transaction( - params=eip1559_transaction, signer=account, on_fault=fault_hook + params=eip1559_transaction, + signer=account, + on_broadcast_failure=broadcast_failure_hook, + on_fault=fault_hook, ) # advance to broadcast the transaction @@ -837,7 +882,11 @@ def test_use_strategies_that_dont_make_updates( broadcast_hook = mocker.Mock() atx = machine.queue_transaction( - params=eip1559_transaction, signer=account, on_broadcast=broadcast_hook + params=eip1559_transaction, + signer=account, + on_broadcast=broadcast_hook, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), ) # advance to broadcast the transaction @@ -912,9 +961,11 @@ def test_retry_with_errors_but_recovers( account, mocker, mock_wake_sleep, + broadcast_failure_hook, + fault_hook, ): # need more freedom with redo attempts for test - mocker.patch.object(machine, "_MAX_REDO_ATTEMPTS", 10) + mocker.patch.object(machine, "_MAX_RETRY_ATTEMPTS", 10) # strategies that don't make updates strategy_1 = mocker.Mock(spec=AsyncTxStrategy) @@ -930,12 +981,12 @@ def test_retry_with_errors_but_recovers( assert machine.current_state == machine._IDLE broadcast_hook = mocker.Mock() - fault_hook = mocker.Mock() atx = machine.queue_transaction( params=eip1559_transaction, signer=account, on_fault=fault_hook, on_broadcast=broadcast_hook, + on_broadcast_failure=broadcast_failure_hook, ) # advance to broadcast the transaction @@ -1023,6 +1074,8 @@ def test_retry_with_errors_retries_exceeded( account, mocker, mock_wake_sleep, + broadcast_failure_hook, + fault_hook, ): # strategies that don't make updates strategy_1 = mocker.Mock(spec=AsyncTxStrategy) @@ -1038,10 +1091,10 @@ def test_retry_with_errors_retries_exceeded( assert machine.current_state == machine._IDLE broadcast_hook = mocker.Mock() - fault_hook = mocker.Mock() atx = machine.queue_transaction( params=eip1559_transaction, signer=account, + on_broadcast_failure=broadcast_failure_hook, on_fault=fault_hook, on_broadcast=broadcast_hook, ) @@ -1061,7 +1114,7 @@ def test_retry_with_errors_retries_exceeded( mocker.patch.object(w3.eth, "send_raw_transaction", side_effect=error) # retry max attempts - for i in range(machine._MAX_REDO_ATTEMPTS): + for i in range(machine._MAX_RETRY_ATTEMPTS): assert machine.pending is not None yield clock.advance(1) assert atx.retries >= i @@ -1078,7 +1131,7 @@ def test_retry_with_errors_retries_exceeded( assert fault_hook.call_count == 1 fault_hook.assert_called_with(atx) - assert atx.retries == machine._MAX_REDO_ATTEMPTS + assert atx.retries == machine._MAX_RETRY_ATTEMPTS assert len(machine.queued) == 0 assert atx.final is False @@ -1139,6 +1192,8 @@ def test_pause_when_busy(clock, machine, eip1559_transaction, account, mocker): _ = machine.queue_transaction( params=eip1559_transaction, signer=account, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), ) # advance to broadcast the transaction @@ -1172,7 +1227,13 @@ def test_pause_when_busy(clock, machine, eip1559_transaction, account, mocker): @pytest.mark.usefixtures("disable_auto_mining") def test_simple_state_transitions( - ethereum_tester, machine, eip1559_transaction, account, mock_wake_sleep + ethereum_tester, + machine, + eip1559_transaction, + account, + mock_wake_sleep, + broadcast_failure_hook, + fault_hook, ): assert machine.current_state == machine._IDLE @@ -1201,6 +1262,8 @@ def test_simple_state_transitions( atx = machine.queue_transaction( params=eip1559_transaction, signer=account, + on_broadcast_failure=broadcast_failure_hook, + on_fault=fault_hook, ) # broadcast tx diff --git a/tests/test_tracker.py b/tests/test_tracker.py index 1454d74..175d4ff 100644 --- a/tests/test_tracker.py +++ b/tests/test_tracker.py @@ -16,7 +16,13 @@ def test_queue(eip1559_transaction, legacy_transaction, mocker): assert tx_tracker.pending is None assert len(tx_tracker.finalized) == 0 - tx = tx_tracker.queue_tx(params=eip1559_transaction) + broadcast_failure_hook_1 = mocker.Mock() + fault_hook_1 = mocker.Mock() + tx = tx_tracker.queue_tx( + params=eip1559_transaction, + on_broadcast_failure=broadcast_failure_hook_1, + on_fault=fault_hook_1, + ) assert len(tx_tracker.queue) == 1 assert isinstance(tx, FutureTx) assert tx_tracker.queue[0] == tx @@ -31,7 +37,14 @@ def test_queue(eip1559_transaction, legacy_transaction, mocker): assert len(tx_tracker.finalized) == 0 tx_2_info = {"description": "it's me!", "message": "me who?"} - tx_2 = tx_tracker.queue_tx(params=legacy_transaction, info=tx_2_info) + broadcast_failure_hook_2 = mocker.Mock() + fault_hook_2 = mocker.Mock() + tx_2 = tx_tracker.queue_tx( + params=legacy_transaction, + info=tx_2_info, + on_broadcast_failure=broadcast_failure_hook_2, + on_fault=fault_hook_2, + ) assert len(tx_tracker.queue) == 2 assert isinstance(tx_2, FutureTx) assert tx_tracker.queue[1] == tx_2 @@ -47,19 +60,19 @@ def test_queue(eip1559_transaction, legacy_transaction, mocker): # check hooks assert tx.on_broadcast is None - assert tx.on_broadcast_failure is None - assert tx.on_fault is None + assert tx.on_broadcast_failure == broadcast_failure_hook_1 + assert tx.on_fault == fault_hook_1 assert tx.on_finalized is None + broadcast_failure_hook_3 = mocker.Mock() + fault_hook_3 = mocker.Mock() broadcast_hook = mocker.Mock() - broadcast_failure_hook = mocker.Mock() - fault_hook = mocker.Mock() finalized_hook = mocker.Mock() tx_3 = tx_tracker.queue_tx( params=eip1559_transaction, on_broadcast=broadcast_hook, - on_broadcast_failure=broadcast_failure_hook, - on_fault=fault_hook, + on_broadcast_failure=broadcast_failure_hook_3, + on_fault=fault_hook_3, on_finalized=finalized_hook, ) assert tx_3.params == eip1559_transaction @@ -68,8 +81,8 @@ def test_queue(eip1559_transaction, legacy_transaction, mocker): assert tx_3.fault is None assert tx_3.id == 2 assert tx_3.on_broadcast == broadcast_hook - assert tx_3.on_broadcast_failure == broadcast_failure_hook - assert tx_3.on_fault == fault_hook + assert tx_3.on_broadcast_failure == broadcast_failure_hook_3 + assert tx_3.on_fault == fault_hook_3 assert tx_3.on_finalized == finalized_hook assert len(tx_tracker.queue) == 3 @@ -80,64 +93,11 @@ def test_queue(eip1559_transaction, legacy_transaction, mocker): assert len(tx_tracker.finalized) == 0 -def test_pop(eip1559_transaction, legacy_transaction): - tx_tracker = _TxTracker(disk_cache=False) - tx_1 = tx_tracker.queue_tx(params=eip1559_transaction) - tx_2 = tx_tracker.queue_tx(params=legacy_transaction) - tx_3 = tx_tracker.queue_tx(params=eip1559_transaction) - - assert len(tx_tracker.queue) == 3 - - for tx in [tx_1, tx_2, tx_3]: - popped_tx = tx_tracker.pop() - assert popped_tx is tx - - with pytest.raises(IndexError): - tx_tracker.pop() - - -def test_requeue(eip1559_transaction, legacy_transaction): - tx_tracker = _TxTracker(disk_cache=False) - tx_1 = tx_tracker.queue_tx(params=eip1559_transaction) - assert tx_1.requeues == 0 - tx_2 = tx_tracker.queue_tx(params=legacy_transaction) - assert tx_2.requeues == 0 - tx_3 = tx_tracker.queue_tx(params=eip1559_transaction) - assert tx_3.requeues == 0 - - assert len(tx_tracker.queue) == 3 - - base_num_requeues = 4 - for i in range(1, base_num_requeues + 1): - prior_pop = None - for _ in tx_tracker.queue: - popped_tx = tx_tracker.pop() - assert popped_tx is not prior_pop, "requeue was an append, not a prepend" - - tx_tracker.requeue(popped_tx) - prior_pop = popped_tx - assert popped_tx.requeues == i - - assert len(tx_tracker.queue) == 3, "remains the same length" - - assert tx_1.requeues == base_num_requeues - assert tx_2.requeues == base_num_requeues - assert tx_3.requeues == base_num_requeues - - _ = tx_tracker.pop() # remove tx_1 - _ = tx_tracker.pop() # remove tx_2 - - tx_tracker.requeue(tx_2) - assert tx_2.requeues == base_num_requeues + 1 - assert tx_1.requeues == base_num_requeues - assert tx_3.requeues == base_num_requeues - - -def test_morph(eip1559_transaction, legacy_transaction, mocker): +def test_morph( + eip1559_transaction, legacy_transaction, broadcast_failure_hook, fault_hook, mocker +): tx_tracker = _TxTracker(disk_cache=False) broadcast_hook = mocker.Mock() - broadcast_failure_hook = mocker.Mock() - fault_hook = mocker.Mock() finalized_hook = mocker.Mock() tx_1 = tx_tracker.queue_tx( params=eip1559_transaction, @@ -146,11 +106,14 @@ def test_morph(eip1559_transaction, legacy_transaction, mocker): on_fault=fault_hook, on_finalized=finalized_hook, ) - tx_2 = tx_tracker.queue_tx(params=legacy_transaction) + tx_2 = tx_tracker.queue_tx( + params=legacy_transaction, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + ) assert tx_1.id != tx_2.id tx_hash = TxHash("0xdeadbeef") - assert tx_tracker.pop() == tx_1 pending_tx = tx_tracker.morph(tx_1, tx_hash) assert isinstance(pending_tx, PendingTx) @@ -166,12 +129,13 @@ def test_morph(eip1559_transaction, legacy_transaction, mocker): assert tx_1.on_broadcast_failure == broadcast_failure_hook assert tx_1.on_fault == fault_hook assert tx_1.on_finalized == finalized_hook + assert tx_1 not in tx_tracker.queue + assert len(tx_tracker.queue) == 1 assert isinstance(tx_2, FutureTx), "unaffected by the morph" assert tx_tracker.pending is not tx_2 tx_2_hash = TxHash("0xdeadbeef2") - assert tx_tracker.pop() == tx_2 pending_tx_2 = tx_tracker.morph(tx_2, tx_2_hash) assert isinstance(pending_tx_2, PendingTx) assert pending_tx_2 is tx_2, "same underlying object" @@ -183,13 +147,16 @@ def test_morph(eip1559_transaction, legacy_transaction, mocker): tx_tracker.pending is not tx_tracker.pending ), "copy of object always returned" + assert tx_2 not in tx_tracker.queue + assert len(tx_tracker.queue) == 0 + @pytest_twisted.inlineCallbacks -def test_fault(eip1559_transaction, legacy_transaction, mocker): +def test_fault( + eip1559_transaction, legacy_transaction, broadcast_failure_hook, fault_hook, mocker +): tx_tracker = _TxTracker(disk_cache=False) broadcast_hook = mocker.Mock() - broadcast_failure_hook = mocker.Mock() - fault_hook = mocker.Mock() finalized_hook = mocker.Mock() tx = tx_tracker.queue_tx( params=eip1559_transaction, @@ -198,7 +165,11 @@ def test_fault(eip1559_transaction, legacy_transaction, mocker): on_fault=fault_hook, on_finalized=finalized_hook, ) - tx_2 = tx_tracker.queue_tx(params=legacy_transaction) + tx_2 = tx_tracker.queue_tx( + params=legacy_transaction, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + ) assert len(tx_tracker.queue) == 2 @@ -211,7 +182,6 @@ def test_fault(eip1559_transaction, legacy_transaction, mocker): tx_tracker.fault(fault_error) tx_hash = TxHash("0xdeadbeef") - assert tx_tracker.pop() == tx pending_tx = tx_tracker.morph(tx, tx_hash) assert tx_tracker.pending.params == tx.params @@ -255,7 +225,6 @@ def test_fault(eip1559_transaction, legacy_transaction, mocker): # repeat with no hook tx_hash_2 = TxHash("0xdeadbeef2") - assert tx_tracker.pop() == tx_2 pending_tx_2 = tx_tracker.morph(tx_2, tx_hash_2) assert tx_tracker.pending.params == tx_2.params @@ -284,16 +253,19 @@ def test_fault(eip1559_transaction, legacy_transaction, mocker): def test_update_after_retry(eip1559_transaction, legacy_transaction, mocker): tx_tracker = _TxTracker(disk_cache=False) - tx = tx_tracker.queue_tx(params=eip1559_transaction) + tx = tx_tracker.queue_tx( + params=eip1559_transaction, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + ) assert tx_tracker.pending is None with pytest.raises(RuntimeError, match="No active transaction"): # there is no active tx - tx_tracker.update_after_retry(mocker.Mock(spec=PendingTx)) + tx_tracker.update_active_after_retry(mocker.Mock(spec=PendingTx)) tx_hash = TxHash("0xdeadbeef") - assert tx_tracker.pop() == tx tx_tracker.morph(tx, tx_hash) assert isinstance(tx, PendingTx) assert tx_tracker.pending.params == tx.params @@ -301,7 +273,7 @@ def test_update_after_retry(eip1559_transaction, legacy_transaction, mocker): with pytest.raises(RuntimeError, match="Mismatch between active tx"): mocked_tx = mocker.Mock(spec=PendingTx) mocked_tx.id = 20 - tx_tracker.update_after_retry(mocked_tx) + tx_tracker.update_active_after_retry(mocked_tx) # first update new_params = legacy_transaction @@ -310,7 +282,7 @@ def test_update_after_retry(eip1559_transaction, legacy_transaction, mocker): pending_tx = tx_tracker.pending # obtain fresh copy pending_tx.params = new_params pending_tx.txhash = new_tx_hash - tx_tracker.update_after_retry(pending_tx) + tx_tracker.update_active_after_retry(pending_tx) assert tx.params == new_params assert tx.txhash == new_tx_hash @@ -321,22 +293,25 @@ def test_update_after_retry(eip1559_transaction, legacy_transaction, mocker): pending_tx = tx_tracker.pending # obtain fresh copy pending_tx.params = new_params pending_tx.txhash = new_tx_hash - tx_tracker.update_after_retry(pending_tx) + tx_tracker.update_active_after_retry(pending_tx) assert tx.params == new_params assert tx.txhash == new_tx_hash def test_update_failed_retry_attempt(eip1559_transaction, legacy_transaction, mocker): tx_tracker = _TxTracker(disk_cache=False) - tx = tx_tracker.queue_tx(params=eip1559_transaction) + tx = tx_tracker.queue_tx( + params=eip1559_transaction, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + ) assert tx_tracker.pending is None with pytest.raises(RuntimeError, match="No active transaction"): # there is no active tx - tx_tracker.update_failed_retry_attempt(mocker.Mock(spec=PendingTx)) + tx_tracker.update_active_after_failed_retry_attempt(mocker.Mock(spec=PendingTx)) tx_hash = TxHash("0xdeadbeef") - assert tx_tracker.pop() == tx tx_tracker.morph(tx, tx_hash) assert isinstance(tx, PendingTx) pending_tx = tx_tracker.pending @@ -346,18 +321,20 @@ def test_update_failed_retry_attempt(eip1559_transaction, legacy_transaction, mo with pytest.raises(RuntimeError, match="Mismatch between active tx"): mocked_tx = mocker.Mock(spec=PendingTx) mocked_tx.id = 20 - tx_tracker.update_failed_retry_attempt(mocked_tx) + tx_tracker.update_active_after_failed_retry_attempt(mocked_tx) assert tx.retries == 0 for i in range(1, 5): - tx_tracker.update_failed_retry_attempt(tx_tracker.pending) + tx_tracker.update_active_after_failed_retry_attempt(tx_tracker.pending) assert tx.retries == i assert tx_tracker.pending.retries == i @pytest_twisted.inlineCallbacks -def test_finalize_active_tx(eip1559_transaction, mocker, tx_receipt): +def test_finalize_active_tx( + eip1559_transaction, tx_receipt, broadcast_failure_hook, fault_hook, mocker +): tx_tracker = _TxTracker(disk_cache=False) with pytest.raises(RuntimeError, match="No pending transaction to finalize"): @@ -365,8 +342,6 @@ def test_finalize_active_tx(eip1559_transaction, mocker, tx_receipt): tx_tracker.finalize_active_tx(mocker.Mock()) broadcast_hook = mocker.Mock() - broadcast_failure_hook = mocker.Mock() - fault_hook = mocker.Mock() finalized_hook = mocker.Mock() tx = tx_tracker.queue_tx( params=eip1559_transaction, @@ -377,7 +352,6 @@ def test_finalize_active_tx(eip1559_transaction, mocker, tx_receipt): ) tx_hash = TxHash("0xdeadbeef") - assert tx_tracker.pop() == tx tx_tracker.morph(tx, tx_hash) assert isinstance(tx, PendingTx) pending_tx = tx_tracker.pending @@ -410,7 +384,7 @@ def test_finalize_active_tx(eip1559_transaction, mocker, tx_receipt): def test_commit_restore( - eip1559_transaction, legacy_transaction, tx_receipt, tempfile_path + eip1559_transaction, legacy_transaction, tx_receipt, tempfile_path, mocker ): tx_tracker = _TxTracker(disk_cache=True, filepath=tempfile_path) @@ -419,16 +393,38 @@ def test_commit_restore( restored_tracker = _TxTracker(disk_cache=True, filepath=tempfile_path) _compare_trackers(tx_tracker, restored_tracker) - tx_1 = tx_tracker.queue_tx(params=eip1559_transaction, info={"name": "tx_1"}) - tx_2 = tx_tracker.queue_tx(params=legacy_transaction) - tx_3 = tx_tracker.queue_tx(params=eip1559_transaction, info={"name": "tx_3"}) - tx_4 = tx_tracker.queue_tx(params=legacy_transaction) - tx_5 = tx_tracker.queue_tx(params=eip1559_transaction, info={"name": "tx_5"}) - tx_6 = tx_tracker.queue_tx(params=legacy_transaction) + hook = mocker.Mock() + + tx_1 = tx_tracker.queue_tx( + params=eip1559_transaction, + info={"name": "tx_1"}, + on_broadcast_failure=hook, + on_fault=hook, + ) + tx_2 = tx_tracker.queue_tx( + params=legacy_transaction, on_broadcast_failure=hook, on_fault=hook + ) + tx_3 = tx_tracker.queue_tx( + params=eip1559_transaction, + info={"name": "tx_3"}, + on_broadcast_failure=hook, + on_fault=hook, + ) + tx_4 = tx_tracker.queue_tx( + params=legacy_transaction, on_broadcast_failure=hook, on_fault=hook + ) + tx_5 = tx_tracker.queue_tx( + params=eip1559_transaction, + info={"name": "tx_5"}, + on_broadcast_failure=hook, + on_fault=hook, + ) + tx_6 = tx_tracker.queue_tx( + params=legacy_transaction, on_broadcast_failure=hook, on_fault=hook + ) # max tx_1 finalized tx_hash = TxHash("0xdeadbeef") - assert tx_tracker.pop() == tx_1 tx_tracker.morph(tx_1, tx_hash) tx_tracker.finalize_active_tx(tx_receipt) @@ -441,7 +437,6 @@ def test_commit_restore( # make tx_2 finalized tx_hash_2 = TxHash("0xdeadbeef2") - assert tx_tracker.pop() == tx_2 tx_tracker.morph(tx_2, tx_hash_2) tx_tracker.finalize_active_tx(tx_receipt) @@ -456,7 +451,6 @@ def test_commit_restore( # make tx_3 active tx_hash_3 = TxHash("0xdeadbeef3") - assert tx_tracker.pop() == tx_3 tx_tracker.morph(tx_3, tx_hash_3) assert tx_tracker.pending == tx_3