feat(l1): implement debug_traceBlock RPC endpoint#6696
Conversation
Add debug_traceBlock which accepts a hex-encoded RLP block, decodes it, and traces all transactions. Extracts shared block tracing logic into a `trace_block` helper to avoid duplication with traceBlockByNumber. Part of #6572
|
Lines of code reportTotal lines added: Detailed view |
Build a real block with a signed transfer, RLP-encode it, and
pass it to the debug_traceBlock RPC endpoint. Assert the response
contains the expected {txHash, result} structure with a CALL trace.
🤖 Kimi Code ReviewOverall Assessment: Good implementation of Critical Issues1. DoS Vulnerability: No Size Limit on RLP InputFile: The method accepts arbitrary hex-encoded RLP data without size validation. A malicious client could send a multi-gigabyte payload causing OOM. // Current code - vulnerable
let hex_str: String = serde_json::from_value(params[0].clone())?;
// ... decode without size check
let block = Block::decode(&rlp_bytes)?;Recommendation: Add a size limit check before decoding (e.g., 10MB max for raw block data). 2. Unnecessary Clone in Parameter ParsingFile: let hex_str: String = serde_json::from_value(params[0].clone())?;Recommendation: Use let hex_str = params[0].as_str()
.ok_or(RpcErr::BadParams("Expected string".to_owned()))?;Minor Issues3. Expensive Block Clone in HandlerFile: trace_block(self.block.clone(), &self.trace_config, &context).awaitSince 4. Test Dependency on L2 CrateFile: use ethrex_l2_rpc::signer::{LocalSigner, Signable, Signer};This creates a dependency from general RPC tests to L2-specific code. If 5. Variable ShadowingFile:
let hex_data = hex_str.strip_prefix("0x").ok_or(...)?;Positive Feedback
Suggested Patch for Issue 1 (Size Limit)const MAX_TRACE_BLOCK_SIZE: usize = 10 * 1024 * 1024; // 10MB
fn parse(params: &Option<Vec<serde_json::Value>>) -> Result<Self, RpcErr> {
// ... existing validation ...
let hex_str = params[0].as_str()
.ok_or(RpcErr::BadParams("Expected string".to_owned()))?;
let hex_str = hex_str.strip_prefix("0x").ok_or(...)?;
if hex_str.len() > MAX_TRACE_BLOCK_SIZE * 2 {
return Err(RpcErr::BadParams("Block data too large".to_owned()));
}
// ... rest of parsing ...
}Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Claude Code ReviewHere is my review of PR #6696: PR Review:
|
Greptile SummaryThis PR implements the
Confidence Score: 4/5Safe to merge; the refactoring is clean and the new endpoint behaves correctly, with one small hardening gap worth addressing before the endpoint is exposed to untrusted traffic. The implementation is straightforward and well-tested. The only concern is that TraceBlockRequest::parse performs hex::decode and Block::decode on an unbounded caller-supplied string — every other debug parse path deals with fixed-size hashes or block numbers, making this the sole entry point where a single request can force large allocations. Adding a size cap before decode would close that gap. crates/networking/rpc/tracing.rs — specifically the TraceBlockRequest::parse method around the hex/RLP decode step
|
| Filename | Overview |
|---|---|
| crates/networking/rpc/tracing.rs | Adds TraceBlockRequest (RLP-input handler) and extracts shared trace_block() helper; no size guard on the hex/RLP input |
| crates/networking/rpc/rpc.rs | Adds debug_traceBlock route to the dispatch table; trivial one-liner change, correct placement |
| test/tests/rpc/debug_trace_tests.rs | Integration test for debug_traceBlock happy path; unit-level error cases are covered in tracing.rs |
| test/tests/rpc/mod.rs | Module declaration for new debug_trace_tests; no logic changes |
Sequence Diagram
sequenceDiagram
participant Client
participant RPC as rpc.rs (map_debug_requests)
participant Handler as TraceBlockRequest
participant Helper as trace_block()
participant BC as Blockchain
Client->>RPC: debug_traceBlock(hexRlpBlock, traceConfig?)
RPC->>Handler: parse(params)
Handler->>Handler: strip 0x prefix
Handler->>Handler: hex::decode → rlp_bytes
Handler->>Handler: Block::decode(rlp_bytes)
Handler->>Handler: deserialize TraceConfig
Handler-->>RPC: "TraceBlockRequest{block, trace_config}"
RPC->>Handler: handle(context)
Handler->>Helper: trace_block(block.clone(), trace_config, context)
alt callTracer
Helper->>BC: trace_block_calls(block, reexec, timeout, ...)
BC-->>Helper: "Vec<(H256, Vec<CallTraceFrame>)>"
Helper-->>RPC: "JSON array of {txHash, result}"
else prestateTracer
Helper->>BC: trace_block_prestate(block, reexec, timeout, ...)
BC-->>Helper: "Vec<(H256, PrestateResult)>"
Helper-->>RPC: "JSON array of {txHash, result}"
else opcodeTracer
Helper->>BC: trace_block_opcodes(block, reexec, timeout, cfg)
BC-->>Helper: "Vec<(H256, StructLoggerResult)>"
Helper-->>RPC: "JSON array of {txHash, result}"
end
RPC-->>Client: JSON response
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
crates/networking/rpc/tracing.rs:274-279
No size limit is enforced on the hex-encoded RLP input before calling `hex::decode` and `Block::decode`. A caller can submit an arbitrarily large payload, forcing the server to allocate and parse several hundred MB of data per request. All other RPC parse paths deal with small structured values (hashes, block numbers), so this is the only path with an unbounded allocation. Even a rough cap (e.g. 4–8 MB of raw bytes) would eliminate the trivial exhaustion case.
```suggestion
let hex_str: String = serde_json::from_value(params[0].clone())?;
let hex_str = hex_str.strip_prefix("0x").ok_or(RpcErr::BadParams(
"Block data must be 0x-prefixed".to_owned(),
))?;
const MAX_RLP_HEX_LEN: usize = 8 * 1024 * 1024 * 2; // 8 MB of raw bytes → 16 MB hex chars
if hex_str.len() > MAX_RLP_HEX_LEN {
return Err(RpcErr::BadParams("Block RLP exceeds maximum allowed size".to_owned()));
}
let rlp_bytes =
hex::decode(hex_str).map_err(|e| RpcErr::BadParams(format!("Invalid hex: {e}")))?;
```
Reviews (1): Last reviewed commit: "style: run cargo fmt on test and source ..." | Re-trigger Greptile
| let hex_str: String = serde_json::from_value(params[0].clone())?; | ||
| let hex_str = hex_str.strip_prefix("0x").ok_or(RpcErr::BadParams( | ||
| "Block data must be 0x-prefixed".to_owned(), | ||
| ))?; | ||
| let rlp_bytes = | ||
| hex::decode(hex_str).map_err(|e| RpcErr::BadParams(format!("Invalid hex: {e}")))?; |
There was a problem hiding this comment.
No size limit is enforced on the hex-encoded RLP input before calling
hex::decode and Block::decode. A caller can submit an arbitrarily large payload, forcing the server to allocate and parse several hundred MB of data per request. All other RPC parse paths deal with small structured values (hashes, block numbers), so this is the only path with an unbounded allocation. Even a rough cap (e.g. 4–8 MB of raw bytes) would eliminate the trivial exhaustion case.
| let hex_str: String = serde_json::from_value(params[0].clone())?; | |
| let hex_str = hex_str.strip_prefix("0x").ok_or(RpcErr::BadParams( | |
| "Block data must be 0x-prefixed".to_owned(), | |
| ))?; | |
| let rlp_bytes = | |
| hex::decode(hex_str).map_err(|e| RpcErr::BadParams(format!("Invalid hex: {e}")))?; | |
| let hex_str: String = serde_json::from_value(params[0].clone())?; | |
| let hex_str = hex_str.strip_prefix("0x").ok_or(RpcErr::BadParams( | |
| "Block data must be 0x-prefixed".to_owned(), | |
| ))?; | |
| const MAX_RLP_HEX_LEN: usize = 8 * 1024 * 1024 * 2; // 8 MB of raw bytes → 16 MB hex chars | |
| if hex_str.len() > MAX_RLP_HEX_LEN { | |
| return Err(RpcErr::BadParams("Block RLP exceeds maximum allowed size".to_owned())); | |
| } | |
| let rlp_bytes = | |
| hex::decode(hex_str).map_err(|e| RpcErr::BadParams(format!("Invalid hex: {e}")))?; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/networking/rpc/tracing.rs
Line: 274-279
Comment:
No size limit is enforced on the hex-encoded RLP input before calling `hex::decode` and `Block::decode`. A caller can submit an arbitrarily large payload, forcing the server to allocate and parse several hundred MB of data per request. All other RPC parse paths deal with small structured values (hashes, block numbers), so this is the only path with an unbounded allocation. Even a rough cap (e.g. 4–8 MB of raw bytes) would eliminate the trivial exhaustion case.
```suggestion
let hex_str: String = serde_json::from_value(params[0].clone())?;
let hex_str = hex_str.strip_prefix("0x").ok_or(RpcErr::BadParams(
"Block data must be 0x-prefixed".to_owned(),
))?;
const MAX_RLP_HEX_LEN: usize = 8 * 1024 * 1024 * 2; // 8 MB of raw bytes → 16 MB hex chars
if hex_str.len() > MAX_RLP_HEX_LEN {
return Err(RpcErr::BadParams("Block RLP exceeds maximum allowed size".to_owned()));
}
let rlp_bytes =
hex::decode(hex_str).map_err(|e| RpcErr::BadParams(format!("Invalid hex: {e}")))?;
```
How can I resolve this? If you propose a fix, please make it concise.
🤖 Codex Code ReviewFindings
Aside from those points, the shared Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
Summary
debug_traceBlockwhich accepts a hex-encoded RLP block, decodes it, and traces all transactionstrace_blockhelper used by bothtraceBlockByNumberandtraceBlockcallTracer,prestateTracer,opcodeTracerCloses part of #6572
Test plan
debug_traceBlockwith valid RLP block returns traces