From ccdc670cc7d419ad3e802fe9415e71c955ed99f5 Mon Sep 17 00:00:00 2001 From: Danut Date: Fri, 7 Jun 2024 14:24:47 +0200 Subject: [PATCH] fix: revert message retrieval (#31) --- .pre-commit-config.yaml | 3 +- pantos/common/blockchains/ethereum.py | 41 ++++++++----- tests/blockchains/test_ethereum.py | 87 ++++++++++++++++++++++----- 3 files changed, 102 insertions(+), 29 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fb5de0f..1c18654 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,8 @@ repos: hooks: - id: mypy files: ^pantos/common - additional_dependencies: ['types-requests', 'types-PyYAML'] + args: ['--namespace-packages', '--explicit-package-bases' ] + additional_dependencies: ['types-requests', 'types-PyYAML', 'web3[tester]'] stages: [ commit ] - repo: https://github.com/PyCQA/isort rev: 5.12.0 diff --git a/pantos/common/blockchains/ethereum.py b/pantos/common/blockchains/ethereum.py index 448a55d..a4a90c3 100644 --- a/pantos/common/blockchains/ethereum.py +++ b/pantos/common/blockchains/ethereum.py @@ -30,6 +30,10 @@ """The names of methods of the blockchain interactor object which send transactions.""" +_NO_ARCHIVE_NODE_RPC_ERROR_MESSAGE = 'missing trie node' + +_NO_ARCHIVE_NODE_LOG_MESSAGE = 'due to the absence of an archive node' + _logger = logging.getLogger(__name__) @@ -428,19 +432,28 @@ def __get_node_connections( def __retrieve_revert_message( self, transaction_hash: str, node_connections: NodeConnections[web3.Web3]) -> str: - required_keys = [ - 'from', 'to', 'gas', 'gasPrice', 'value', 'data', 'nonce', - 'maxFeePerGas', 'maxPriorityFeePerGas', 'chainId' - ] - full_tx = node_connections.eth.get_transaction( - typing.cast(web3.types.HexStr, transaction_hash)).get() - block_number = full_tx['blockNumber'] - replay_tx = {k: v for k, v in full_tx.items() if k in required_keys} - revert_message = '' + revert_message = 'unknown' try: - node_connections.eth.call( - typing.cast(web3.types.TxParams, replay_tx), - block_number).get() - except web3.exceptions.ContractLogicError as e: - revert_message = str(e) + full_tx = node_connections.eth.get_transaction( + typing.cast(web3.types.HexStr, transaction_hash)).get() + replay_tx = { + 'from': full_tx['from'], + 'to': full_tx['to'], + 'value': full_tx['value'], + 'data': full_tx['input'] + } + context_block_number = full_tx['blockNumber'] - 1 + try: + node_connections.eth.call( + typing.cast(web3.types.TxParams, replay_tx), + context_block_number).get() + except web3.exceptions.ContractLogicError as error: + revert_message = str(error) + except ValueError as error: + if _NO_ARCHIVE_NODE_RPC_ERROR_MESSAGE in error.args[0].get( + 'message'): + revert_message += f' {_NO_ARCHIVE_NODE_LOG_MESSAGE}' + except Exception: + _logger.warning('unable to retrieve the revert message', + exc_info=True) return revert_message diff --git a/tests/blockchains/test_ethereum.py b/tests/blockchains/test_ethereum.py index c9a0901..559524d 100644 --- a/tests/blockchains/test_ethereum.py +++ b/tests/blockchains/test_ethereum.py @@ -11,6 +11,9 @@ from pantos.common.blockchains.base import TransactionNonceTooLowError from pantos.common.blockchains.base import TransactionUnderpricedError from pantos.common.blockchains.enums import Blockchain +from pantos.common.blockchains.ethereum import _NO_ARCHIVE_NODE_LOG_MESSAGE +from pantos.common.blockchains.ethereum import \ + _NO_ARCHIVE_NODE_RPC_ERROR_MESSAGE from pantos.common.blockchains.ethereum import _TRANSACTION_METHOD_NAMES from pantos.common.blockchains.ethereum import EthereumUtilities from pantos.common.blockchains.ethereum import EthereumUtilitiesError @@ -511,23 +514,79 @@ def test_create_single_node_connection_not_connected_error( def test_retrieve_revert_message_correct(ethereum_utilities, w3, - node_connections, transaction_id): - eth_utils = ethereum_utilities - with unittest.mock.patch.object(w3.eth, 'get_transaction', - return_value={'blockNumber': 1}): + node_connections, transaction_id, + transaction_contract_address): + default_account = w3.eth.accounts[0] + with unittest.mock.patch.object( + w3.eth, 'get_transaction', return_value={ + 'from': default_account, + 'to': transaction_contract_address, + 'value': 0, + 'input': "", + 'blockNumber': 1, + }): with unittest.mock.patch.object( w3.eth, 'call', side_effect=web3.exceptions.ContractLogicError( 'revert message')): - assert eth_utils._EthereumUtilities__retrieve_revert_message( - transaction_id, node_connections) == 'revert message' + assert \ + ethereum_utilities._EthereumUtilities__retrieve_revert_message( + transaction_id, node_connections) == 'revert message' + + +def test_retrieve_revert_message_no_archive_node_available_error( + ethereum_utilities, w3, node_connections, transaction_id, + transaction_contract_address): + default_account = w3.eth.accounts[0] + with unittest.mock.patch.object( + w3.eth, 'get_transaction', return_value={ + 'from': default_account, + 'to': transaction_contract_address, + 'value': 0, + 'input': "", + 'blockNumber': 1, + }): + with unittest.mock.patch.object( + w3.eth, 'call', side_effect=ValueError({ + 'message': f'{_NO_ARCHIVE_NODE_RPC_ERROR_MESSAGE} 0x...' + })): + assert \ + ethereum_utilities._EthereumUtilities__retrieve_revert_message( + transaction_id, node_connections) == \ + f'unknown {_NO_ARCHIVE_NODE_LOG_MESSAGE}' -def test_retrieve_revert_message_correct_no_error(ethereum_utilities, w3, - node_connections, - transaction_id): - eth_utils = ethereum_utilities - with unittest.mock.patch.object(w3.eth, 'get_transaction', - return_value={'blockNumber': 1}): +def test_retrieve_revert_message_correct_no_error( + ethereum_utilities, w3, node_connections, transaction_id, + transaction_contract_address): + default_account = w3.eth.accounts[0] + with unittest.mock.patch.object( + w3.eth, 'get_transaction', return_value={ + 'from': default_account, + 'to': transaction_contract_address, + 'value': 0, + 'input': "", + 'blockNumber': 1, + }): with unittest.mock.patch.object(w3.eth, 'call', return_value=''): - assert eth_utils._EthereumUtilities__retrieve_revert_message( - transaction_id, node_connections) == '' + assert \ + ethereum_utilities._EthereumUtilities__retrieve_revert_message( + transaction_id, node_connections) == 'unknown' + + +def test_retrieve_revert_message_correct_error(ethereum_utilities, w3, + node_connections, + transaction_id, + transaction_contract_address): + default_account = w3.eth.accounts[0] + with unittest.mock.patch.object( + w3.eth, 'get_transaction', return_value={ + 'from': default_account, + 'to': transaction_contract_address, + 'value': 0, + 'input': "", + 'blockNumber': 1, + }): + with unittest.mock.patch.object(w3.eth, 'call', side_effect=Exception): + assert \ + ethereum_utilities._EthereumUtilities__retrieve_revert_message( + transaction_id, node_connections) == 'unknown'