Skip to content

Call to Standardize debug Methods #651

@Alleysira

Description

@Alleysira

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"}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions