From 2fd4240823f4b0e1d9bbbdafa82797d82daae0ad Mon Sep 17 00:00:00 2001 From: antazoey Date: Thu, 9 May 2024 07:41:55 -0600 Subject: [PATCH] fix: handle HTTPError-based not-implemented RPCs and parity-trace detection error handling easement (#2080) --- src/ape_ethereum/provider.py | 16 +++++++++++++++- tests/functional/test_provider.py | 26 +++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/ape_ethereum/provider.py b/src/ape_ethereum/provider.py index b2cfd4a2a7..2c0010b231 100644 --- a/src/ape_ethereum/provider.py +++ b/src/ape_ethereum/provider.py @@ -25,6 +25,7 @@ get_calltree_from_parity_trace, ) from pydantic.dataclasses import dataclass +from requests import HTTPError from web3 import HTTPProvider, IPCProvider, Web3 from web3.exceptions import ContractLogicError as Web3ContractLogicError from web3.exceptions import ( @@ -1078,7 +1079,16 @@ def _create_trace_frame(self, evm_frame: EvmTraceFrame) -> TraceFrame: def _make_request(self, endpoint: str, parameters: Optional[List] = None) -> Any: parameters = parameters or [] - result = self.web3.provider.make_request(RPCEndpoint(endpoint), parameters) + + try: + result = self.web3.provider.make_request(RPCEndpoint(endpoint), parameters) + except HTTPError as err: + if "method not allowed" in str(err).lower(): + raise APINotImplementedError( + f"RPC method '{endpoint}' is not implemented by this node instance." + ) + + raise ProviderError(str(err)) from err if "error" in result: error = result["error"] @@ -1422,6 +1432,10 @@ def get_call_tree(self, txn_hash: str) -> CallTreeNode: except (ValueError, APINotImplementedError, ProviderError): self.can_use_parity_traces = False return self._get_geth_call_tree(txn_hash) + except Exception as err: + logger.error(f"Unknown exception while checking for Parity-trace support: {err} ") + self.can_use_parity_traces = False + return self._get_geth_call_tree(txn_hash) # Parity style works. self.can_use_parity_traces = True diff --git a/tests/functional/test_provider.py b/tests/functional/test_provider.py index c12432e5a2..58624c4a02 100644 --- a/tests/functional/test_provider.py +++ b/tests/functional/test_provider.py @@ -7,6 +7,7 @@ from eth_typing import HexStr from eth_utils import ValidationError from hexbytes import HexBytes +from requests import HTTPError from web3.exceptions import ContractPanicError from ape.exceptions import ( @@ -358,7 +359,8 @@ def test_make_request_not_exists(eth_tester_provider): @pytest.mark.parametrize("msg", ("Method not found", "Method ape_thisDoesNotExist not found")) def test_make_request_not_exists_dev_nodes(eth_tester_provider, mock_web3, msg): """ - Simulate what *most* of the dev providers do, like hardhat, anvil, and ganache. + Handle an issue found from Base-sepolia where not-implemented RPCs + caused HTTPErrors. """ real_web3 = eth_tester_provider._web3 mock_web3.eth = real_web3.eth @@ -378,6 +380,28 @@ def custom_make_request(rpc, params): eth_tester_provider._make_request("ape_thisDoesNotExist") +def test_make_request_handles_http_error_method_not_allowed(eth_tester_provider, mock_web3): + """ + Simulate what *most* of the dev providers do, like hardhat, anvil, and ganache. + """ + real_web3 = eth_tester_provider._web3 + mock_web3.eth = real_web3.eth + eth_tester_provider._web3 = mock_web3 + + def custom_make_request(rpc, params): + if rpc == "ape_thisDoesNotExist": + raise HTTPError("Client error: Method Not Allowed") + + return real_web3.provider.make_request(rpc, params) + + mock_web3.provider.make_request.side_effect = custom_make_request + with pytest.raises( + APINotImplementedError, + match="RPC method 'ape_thisDoesNotExist' is not implemented by this node instance.", + ): + eth_tester_provider._make_request("ape_thisDoesNotExist") + + def test_base_fee(eth_tester_provider): actual = eth_tester_provider.base_fee assert actual > 0