Skip to content

Commit

Permalink
Add tests for faulted tx conditions - revert or faulted from strategy.
Browse files Browse the repository at this point in the history
  • Loading branch information
derekpierre committed Feb 29, 2024
1 parent 4bb93a9 commit 88caca6
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 36 deletions.
6 changes: 3 additions & 3 deletions atxm/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def _log_gas_weather(base_fee: Wei, tip: Wei) -> None:
log.info(f"Gas conditions: base {base_fee_gwei} gwei | tip {tip_gwei} gwei")


def __get_receipt_from_txhash(w3: Web3, txhash: TxHash) -> Optional[TxReceipt]:
def _get_receipt_from_txhash(w3: Web3, txhash: TxHash) -> Optional[TxReceipt]:
try:
receipt = w3.eth.get_transaction_receipt(txhash)
except TransactionNotFound:
Expand All @@ -61,7 +61,7 @@ def _get_receipt(w3: Web3, pending_tx: PendingTx) -> Optional[TxReceipt]:
log.error(f"[error] Transaction {pending_tx.txhash.hex()} not found")
return

receipt = __get_receipt_from_txhash(w3=w3, txhash=txdata["hash"])
receipt = _get_receipt_from_txhash(w3=w3, txhash=txdata["hash"])
if not receipt:
return

Expand All @@ -86,7 +86,7 @@ def _get_receipt(w3: Web3, pending_tx: PendingTx) -> Optional[TxReceipt]:

def _get_confirmations(w3: Web3, tx: Union[PendingTx, FinalizedTx]) -> int:
current_block = w3.eth.block_number
tx_receipt = __get_receipt_from_txhash(w3=w3, txhash=tx.txhash)
tx_receipt = _get_receipt_from_txhash(w3=w3, txhash=tx.txhash)
if not tx_receipt:
log.info(f"Transaction {tx.txhash.hex()} is pending or unconfirmed")
return 0
Expand Down
123 changes: 90 additions & 33 deletions tests/test_faults.py
Original file line number Diff line number Diff line change
@@ -1,58 +1,115 @@
import pytest
import pytest_twisted
from twisted.internet import reactor
from twisted.internet.task import deferLater
from web3.exceptions import TransactionNotFound
from web3.types import TxReceipt

from atxm.exceptions import Fault
from atxm.exceptions import Fault, TransactionFaulted
from atxm.strategies import AsyncTxStrategy
from atxm.tx import FaultedTx
from atxm.utils import _get_receipt_from_txhash


@pytest.fixture
def mock_eth_get_transaction(mocker, w3):
return mocker.patch.object(
w3.eth,
"get_transaction",
side_effect=TransactionNotFound
)

def _broadcast_tx(machine, eip1559_transaction, account, mocker):
fault_hook = mocker.Mock()

@pytest_twisted.inlineCallbacks
def test_timeout(
machine, clock, eip1559_transaction, account, interval,
mock_wake_sleep, mocker, mock_eth_get_transaction
):
hook = mocker.Mock()
atx = machine.queue_transaction(
params=eip1559_transaction,
signer=account,
on_fault=hook,
on_fault=fault_hook,
)

machine.start()
while not machine.pending:
yield clock.advance(interval)
machine.stop()
assert not machine.running
# broadcast tx
machine._cycle()

assert machine.pending == atx
assert atx.final is False
assert atx.fault is None

atx.created -= 9999999999
machine.start()
while machine.pending:
yield clock.advance(interval)
machine.stop()
return atx, fault_hook


def _verify_faulted_tx(machine, atx, fault_hook, expected_fault: Fault):
assert atx.final is False
assert isinstance(atx.fault, Fault)
assert isinstance(atx, FaultedTx)
assert isinstance(atx.fault, Fault)
assert atx.fault == expected_fault

# check async tx advanced through the state machine
assert atx not in machine.queued

assert machine.pending is None
assert atx.final is False

yield deferLater(reactor, 0.2, lambda: None)
assert hook.call_count == 1
assert fault_hook.call_count == 1
fault_hook.assert_called_with(atx)


def test_revert(
chain,
w3,
machine,
clock,
eip1559_transaction,
account,
interval,
mock_wake_sleep,
mocker,
):
atx, fault_hook = _broadcast_tx(machine, eip1559_transaction, account, mocker)

assert machine.pending

chain.mine(1)

# force receipt to symbolize a revert of the tx
receipt = _get_receipt_from_txhash(w3, atx.txhash)
revert_receipt = dict(receipt)
revert_receipt["status"] = 0

mocker.patch.object(
w3.eth, "get_transaction_receipt", return_value=TxReceipt(revert_receipt)
)

while fault_hook.call_count == 0:
# ensure tx processed
machine._cycle()

_verify_faulted_tx(machine, atx, fault_hook, expected_fault=Fault.REVERT)


def test_strategy_fault(
w3, machine, clock, eip1559_transaction, account, interval, mock_wake_sleep, mocker
):
faulty_strategy = mocker.Mock(spec=AsyncTxStrategy)
machine._strategies.insert(0, faulty_strategy) # add first

atx, fault_hook = _broadcast_tx(machine, eip1559_transaction, account, mocker)

faulty_message = "mocked fault"
faulty_strategy.execute.side_effect = TransactionFaulted(
tx=atx, fault=Fault.ERROR, message=faulty_message
)

mocker.patch.object(
w3.eth, "get_transaction_receipt", side_effect=TransactionNotFound
)

while fault_hook.call_count == 0:
# ensure tx processed
machine._cycle()

_verify_faulted_tx(machine, atx, fault_hook, expected_fault=Fault.ERROR)
assert atx.error == faulty_message


def test_timeout_strategy_fault(
w3, machine, clock, eip1559_transaction, account, interval, mock_wake_sleep, mocker
):
atx, fault_hook = _broadcast_tx(machine, eip1559_transaction, account, mocker)

atx.created -= 9999999999
mocker.patch.object(w3.eth, "get_transaction", side_effect=TransactionNotFound)

while fault_hook.call_count == 0:
# ensure tx processed
machine._cycle()

_verify_faulted_tx(machine, atx, fault_hook, expected_fault=Fault.TIMEOUT)

0 comments on commit 88caca6

Please sign in to comment.