-
Notifications
You must be signed in to change notification settings - Fork 456
Description
Overview
Hello developers,
I'm running a devnet chain for JSON-RPC testing. During testing, I noticed discrepancies in the JSON-RPC responses of debug_ methods among geth, besu, nethermind, and reth.
In general, we have observed inconsistencies in debug method responses across four execution clients. Unlike the Engine API—which provides well-defined error codes and messages—the debug methods lack standardized error specifications. This absence of well-defined error codes and messages leads to inconsistent behavior and complicates the debugging process.
For example, according to the execution-apis schema, the method debug_getRawBlock is expected to return an RLP-encoded block. However, the specification does not define the error behavior when the requested block is not found.
Since the purpose of execution-apis is to enable downstream tooling and infrastructure to treat different Ethereum clients as modules, we believe it is important to standardize the return of error codes and messages for debug methods as well.
Thanks for your attenntion.
Related methods:
- debug_getBadBlocks
- debug_getRawBlock
- debug_getRawHeader
- debug_getRawReceipts
- debug_getRawTransaction
Clients
Tested client version.
Consensus
- Prysm/v6.0.0 (linux amd64)
- Lighthouse/v7.0.1-e42406d/x86_64-linux
- Nimbus/v25.4.1-77cfa7-stateofus
- Teku/v25.4.1/linux-x86_64/-eclipseadoptium-openjdk64bitservervm-java-21
Execution
- Geth/v1.15.12-unstable-3e356d69-20250507/linux-amd64/go1.24.3
- Besu/v25.4.1/linux-x86_64/openjdk-java-21
- Nethermind/v1.32.0-unstable+ff9befb5/linux-x64/dotnet9.0.4
- Reth/v1.3.12-6f8e725/x86_64-unknown-linux-gnu
Results
Please note that we locally add server field in the response json for information and sort the key.
debug_getBadBlocks
debug_getBadBlocks. Reth didn't implemented this. see issue.
# geth
{"id": 1, "jsonrpc": "2.0", "result": [], "server": "http://127.0.0.1:56406"}
# besu
{"id": 1, "jsonrpc": "2.0", "result": [], "server": "http://127.0.0.1:55742"}
# nethermind
{"id": 1, "jsonrpc": "2.0", "result": [], "server": "http://127.0.0.1:57012"}
# reth
{"error": {"code": -32603, "message": "unimplemented"}, "id": 1, "jsonrpc": "2.0", "server": "http://127.0.0.1:56156"}debug_getRawBlock
When a block cannot be found, geth returns 0x, whereas the other three clients return an error. Among those, besu returns a different error code (32000), while the others return 32001.
# geth
{"id": 1, "jsonrpc": "2.0", "result": "0x", "server": "http://127.0.0.1:56406"}
# besu
{"error": {"code": -32000, "message": "Block not found"}, "id": 1, "jsonrpc": "2.0", "server": "http://127.0.0.1:55742"}
# nethermind
{"error": {"code": -32001, "message": "Block BlockNumber, 3277422 was not found"}, "id": 1, "jsonrpc": "2.0", "server": "http://127.0.0.1:57012"}
# reth
{"error": {"code": -32001, "message": "block not found: 0x32026e"}, "id": 1, "jsonrpc": "2.0", "server": "http://127.0.0.1:56156"}debug_getRawHeader
If the raw block cannot be found, geth and reth return an empty 0x, while besu and nethermind return an error—but with different error codes.
# geth
{"id": 1, "jsonrpc": "2.0", "result": "0x", "server": "http://127.0.0.1:56406"}
# besu
{"error": {"code": -32000, "message": "Block not found"}, "id": 1, "jsonrpc": "2.0", "server": "http://127.0.0.1:55742"}
# nethermind
{"error": {"code": -32001, "message": "Block BlockNumber, 3277422 was not found"}, "id": 1, "jsonrpc": "2.0", "server": "http://127.0.0.1:57012"}
# reth
{"id": 1, "jsonrpc": "2.0", "result": "0x", "server": "http://127.0.0.1:56156"}debug_getRawReceipts
If the block cannot be found, geth returns null, reth returns an empty [], while besu and nethermind return an error—with different error codes.
# geth
{"id": 1, "jsonrpc": "2.0", "result": null, "server": "http://127.0.0.1:56406"}
# besu
{"error": {"code": -32000, "message": "Block not found"}, "id": 1, "jsonrpc": "2.0", "server": "http://127.0.0.1:55742"}
# nethermind
{"error": {"code": -32603, "data": "System.IO.InvalidDataException: Block 3277422 could not be found\n at Nethermind.JsonRpc.Modules.DebugModule.DebugBridge.GetReceiptsForBlock(BlockParameter blockParam) in /src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugBridge.cs:line 121\n at Nethermind.JsonRpc.Modules.DebugModule.DebugRpcModule.debug_getRawReceipts(BlockParameter blockParameter) in /src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs:line 337\n at System.Reflection.MethodInvoker.InvokeImpl(Object obj, Object arg1, Object arg2, Object arg3, Object arg4)\n at System.Reflection.MethodInvoker.Invoke(Object obj, Span`1 arguments)\n at Nethermind.JsonRpc.JsonRpcService.ExecuteAsync(JsonRpcRequest request, String methodName, ResolvedMethodInfo method, JsonRpcContext context) in /src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs:line 175", "message": "Internal error"}, "id": 1, "jsonrpc": "2.0", "server": "http://127.0.0.1:57012"}
# reth
{"id": 1, "jsonrpc": "2.0", "result": [], "server": "http://127.0.0.1:56156"}debug_getRawTransaction
For debug_getRawTransaction, if the transaction cannot be found, geth returns 0x, besu and reth return null, while nethermind returns an error.
# geth
{"id": 1, "jsonrpc": "2.0", "result": "0x", "server": "http://127.0.0.1:56406"}
# besu
{"id": 1, "jsonrpc": "2.0", "result": null, "server": "http://127.0.0.1:55742"}
# nethermind
{"error": {"code": -32001, "message": "Transaction 0x3a2fd1a5ea9ffee477f449be53a49398533d2c006a5815023920d1c397298df3 was not found"}, "id": 1, "jsonrpc": "2.0", "server": "http://127.0.0.1:57012"}
# reth
{"id": 1, "jsonrpc": "2.0", "result": null, "server": "http://127.0.0.1:56156"}