diff --git a/src/ape_ethereum/provider.py b/src/ape_ethereum/provider.py index 32ab06f426..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"] @@ -1419,7 +1429,11 @@ def get_call_tree(self, txn_hash: str) -> CallTreeNode: try: # Try the Parity traces first, in case node client supports it. tree = self._get_parity_call_tree(txn_hash) - except Exception: + 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) diff --git a/tests/functional/test_provider.py b/tests/functional/test_provider.py index c12432e5a2..6540c15a34 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 ( @@ -378,6 +379,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