Skip to content

Commit d699e35

Browse files
author
Stuart Reed
authored
Proper RPC error responses (ethereum#3061)
* Refactor response/error handling * Return RPC error or result * Tester provider tests to handle rpc errors * Update test error response to include data attr * Remove data attribute from response * Simplify response method
1 parent 65fdaa5 commit d699e35

File tree

4 files changed

+61
-21
lines changed

4 files changed

+61
-21
lines changed

newsfragments/3061.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Return structured JSON-RPC errors for missing or unimplemented eth-tester methods.

tests/core/providers/test_tester_provider.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,29 @@ def test_eth_tester_provider_properly_handles_eth_tester_error_messages(
6262
TransactionFailed, match="execution reverted: The error message."
6363
):
6464
provider.make_request(RPCEndpoint("eth_blockNumber"), [])
65+
66+
67+
def test_eth_tester_provider_properly_handles_eth_tester_key_error_messages():
68+
provider = EthereumTesterProvider(api_endpoints={})
69+
response = provider.make_request(RPCEndpoint("eth_blockNumber"), [])
70+
71+
assert response["error"]["code"] == -32601
72+
assert response["error"]["message"] == "Unknown RPC Endpoint: eth_blockNumber"
73+
74+
75+
def test_eth_tester_provider_properly_handles_eth_tester_not_implmented_error_messages(
76+
mocker,
77+
):
78+
mocker.patch(
79+
"eth_tester.main.EthereumTester.get_block_by_number",
80+
side_effect=NotImplementedError("The error message."),
81+
)
82+
83+
provider = EthereumTesterProvider()
84+
response = provider.make_request(RPCEndpoint("eth_blockNumber"), [])
85+
86+
assert response["error"]["code"] == -32601
87+
assert (
88+
response["error"]["message"]
89+
== "RPC Endpoint has not been implemented: eth_blockNumber"
90+
)

tests/integration/test_ethereum_tester.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
NetModuleTest,
3232
Web3ModuleTest,
3333
)
34+
from web3.exceptions import (
35+
MethodUnavailable,
36+
)
3437
from web3.providers.eth_tester import (
3538
EthereumTesterProvider,
3639
)
@@ -264,7 +267,8 @@ def func_wrapper(self, eth_tester, *args, **kwargs):
264267

265268
class TestEthereumTesterEthModule(EthModuleTest):
266269
test_eth_max_priority_fee_with_fee_history_calculation = not_implemented(
267-
EthModuleTest.test_eth_max_priority_fee_with_fee_history_calculation, ValueError
270+
EthModuleTest.test_eth_max_priority_fee_with_fee_history_calculation,
271+
MethodUnavailable,
268272
)
269273
test_eth_max_priority_fee_with_fee_history_calculation_error_dict = not_implemented(
270274
EthModuleTest.test_eth_max_priority_fee_with_fee_history_calculation_error_dict,
@@ -290,16 +294,16 @@ class TestEthereumTesterEthModule(EthModuleTest):
290294
EthModuleTest.test_eth_sign_transaction_ens_names, ValueError
291295
)
292296
test_eth_submit_hashrate = not_implemented(
293-
EthModuleTest.test_eth_submit_hashrate, ValueError
297+
EthModuleTest.test_eth_submit_hashrate, MethodUnavailable
294298
)
295299
test_eth_submit_work = not_implemented(
296-
EthModuleTest.test_eth_submit_work, ValueError
300+
EthModuleTest.test_eth_submit_work, MethodUnavailable
297301
)
298302
test_eth_get_raw_transaction = not_implemented(
299-
EthModuleTest.test_eth_get_raw_transaction, ValueError
303+
EthModuleTest.test_eth_get_raw_transaction, MethodUnavailable
300304
)
301305
test_eth_get_raw_transaction_raises_error = not_implemented(
302-
EthModuleTest.test_eth_get_raw_transaction, ValueError
306+
EthModuleTest.test_eth_get_raw_transaction, MethodUnavailable
303307
)
304308
test_eth_get_raw_transaction_by_block = not_implemented(
305309
EthModuleTest.test_eth_get_raw_transaction_by_block, ValueError
@@ -588,7 +592,12 @@ class TestEthereumTesterNetModule(NetModuleTest):
588592
class TestEthereumTesterPersonalModule(GoEthereumPersonalModuleTest):
589593
test_personal_sign_and_ecrecover = not_implemented(
590594
GoEthereumPersonalModuleTest.test_personal_sign_and_ecrecover,
591-
ValueError,
595+
MethodUnavailable,
596+
)
597+
598+
test_personal_list_wallets = not_implemented(
599+
GoEthereumPersonalModuleTest.test_personal_list_wallets,
600+
MethodUnavailable,
592601
)
593602

594603
# Test overridden here since eth-tester returns False
@@ -598,9 +607,3 @@ def test_personal_unlock_account_failure(self, w3, unlockable_account_dual_type)
598607
unlockable_account_dual_type, "bad-password"
599608
)
600609
assert result is False
601-
602-
@pytest.mark.xfail(
603-
raises=ValueError, reason="list_wallets not implemented in eth-tester"
604-
)
605-
def test_personal_list_wallets(self, w3: "Web3") -> None:
606-
super().test_personal_list_wallets(w3)

web3/providers/eth_tester/main.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
)
3636
from web3.types import (
3737
RPCEndpoint,
38+
RPCError,
3839
RPCResponse,
3940
)
4041

@@ -135,6 +136,19 @@ def is_connected(self, show_traceback: bool = False) -> Literal[True]:
135136
return True
136137

137138

139+
def _make_response(result: Any, message: str = "") -> RPCResponse:
140+
if isinstance(result, Exception):
141+
return RPCResponse(
142+
{
143+
"id": 1,
144+
"jsonrpc": "2.0",
145+
"error": RPCError({"code": -32601, "message": message, "data": None}),
146+
}
147+
)
148+
149+
return RPCResponse({"id": 1, "jsonrpc": "2.0", "result": result})
150+
151+
138152
def _make_request(
139153
method: RPCEndpoint,
140154
params: Any,
@@ -151,14 +165,12 @@ def _make_request(
151165

152166
try:
153167
delegator = api_endpoints[namespace][endpoint]
154-
except KeyError:
155-
return RPCResponse({"error": f"Unknown RPC Endpoint: {method}"})
168+
except KeyError as e:
169+
return _make_response(e, f"Unknown RPC Endpoint: {method}")
156170
try:
157171
response = delegator(ethereum_tester_instance, params)
158-
except NotImplementedError:
159-
return RPCResponse(
160-
{"error": f"RPC Endpoint has not been implemented: {method}"}
161-
)
172+
except NotImplementedError as e:
173+
return _make_response(e, f"RPC Endpoint has not been implemented: {method}")
162174
except TransactionFailed as e:
163175
first_arg = e.args[0]
164176
try:
@@ -175,6 +187,4 @@ def _make_request(
175187
reason = first_arg
176188
raise TransactionFailed(f"execution reverted: {reason}")
177189
else:
178-
return {
179-
"result": response,
180-
}
190+
return _make_response(response)

0 commit comments

Comments
 (0)