feat(l1): implement flatCallTracer#6705
Conversation
|
Lines of code reportTotal lines added: Detailed view |
🤖 Kimi Code ReviewThe PR implements Minor suggestions:
Positive notes:
The implementation correctly follows the geth Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Codex Code ReviewNo material findings from the diff review. The Residual risk: this was static review only. I could not run the test suite in this environment because Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
🤖 Claude Code ReviewNow I have a complete picture of the PR. Here is my review: PR #6705 —
|
Greptile SummaryThis PR adds
Confidence Score: 4/5Safe to merge; the change is additive and isolated to a new tracer variant with no impact on existing tracers. The flattening logic, trace-address computation, and per-variant serialization are all well-tested and correct. The only notable gap is that crates/networking/rpc/tracing.rs — specifically the FlatCallAction::Call construction for STATICCALL and DELEGATECALL variants.
|
| Filename | Overview |
|---|---|
| crates/networking/rpc/tracing.rs | Adds FlatCallTracer variant to TracerType and all handler/serialization logic for flatCallTracer; minor deviation from geth reference on value field for STATICCALL/DELEGATECALL. |
| test/tests/rpc/debug_flat_call_tracer_tests.rs | New integration tests covering value-transfer tx, contract deployment tx, unknown-hash error path, and block-level tracing for flatCallTracer. |
| test/tests/rpc/helpers.rs | New shared test helpers (store setup, block building, RPC dispatch) used by all rpc integration tests; private key is test-only and intentionally hardcoded. |
| test/tests/rpc/mod.rs | Registers the two new modules (debug_flat_call_tracer_tests, helpers) in the rpc test module. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["RPC: debug_traceTransaction\n/ debug_traceBlockByNumber"] --> B{TracerType?}
B -->|flatCallTracer| C["trace_transaction_calls()\n/ trace_block_calls()"]
C --> D["CallTrace: Vec<CallTraceFrame>\n(nested tree)"]
D --> E["flatten_call_trace(root)"]
E --> F["flatten_recursive(frame, trace_address, result)"]
F --> G{frame.call_type}
G -->|CALL/CALLCODE\nSTATICCALL/DELEGATECALL| H["FlatCallAction::Call\n{callType, from, to, gas, input, value}"]
G -->|CREATE/CREATE2| I["FlatCallAction::Create\n{creationMethod, from, gas, init, value}"]
G -->|SELFDESTRUCT| J["FlatCallAction::Suicide\n{address, balance, refundAddress}"]
F --> K{frame.error?}
K -->|Some| L["result = None"]
K -->|None + Suicide| L
K -->|None + Call| M["FlatCallResult::Call\n{gasUsed, output}"]
K -->|None + Create| N["FlatCallResult::Create\n{address, code, gasUsed}"]
F --> O["Push FlatCallFrame\n{action, error, result,\nsubtraces=calls.len(),\ntraceAddress}"]
O --> P["Recurse for each child\nwith traceAddress ++ [i]"]
P --> Q["Vec<FlatCallFrame>\n(flat array, DFS order)"]
Comments Outside Diff (1)
-
crates/networking/rpc/tracing.rs, line 188-204 (link)valuealways emitted for STATICCALL and DELEGATECALLGeth's reference
flatCallTracer(call_flat.go) setsValuetonilfor STATICCALL and DELEGATECALL frames, which causes the field to be omitted from JSON output (json:"value,omitempty"). The current implementation always populatesvalue: frame.value. For DELEGATECALL specifically, the underlying call tracer may capture the parent'smsg.valuecontext intoframe.value(so it can be non-zero), which would be serialized as a non-zero ether amount even though DELEGATECALL never transfers ether. Tools doing strict geth compatibility checks would see unexpectedvaluefields on these frame types.Prompt To Fix With AI
This is a comment left during a code review. Path: crates/networking/rpc/tracing.rs Line: 188-204 Comment: **`value` always emitted for STATICCALL and DELEGATECALL** Geth's reference `flatCallTracer` (`call_flat.go`) sets `Value` to `nil` for STATICCALL and DELEGATECALL frames, which causes the field to be omitted from JSON output (`json:"value,omitempty"`). The current implementation always populates `value: frame.value`. For DELEGATECALL specifically, the underlying call tracer may capture the parent's `msg.value` context into `frame.value` (so it can be non-zero), which would be serialized as a non-zero ether amount even though DELEGATECALL never transfers ether. Tools doing strict geth compatibility checks would see unexpected `value` fields on these frame types. How can I resolve this? If you propose a fix, please make it concise.
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:188-204
**`value` always emitted for STATICCALL and DELEGATECALL**
Geth's reference `flatCallTracer` (`call_flat.go`) sets `Value` to `nil` for STATICCALL and DELEGATECALL frames, which causes the field to be omitted from JSON output (`json:"value,omitempty"`). The current implementation always populates `value: frame.value`. For DELEGATECALL specifically, the underlying call tracer may capture the parent's `msg.value` context into `frame.value` (so it can be non-zero), which would be serialized as a non-zero ether amount even though DELEGATECALL never transfers ether. Tools doing strict geth compatibility checks would see unexpected `value` fields on these frame types.
Reviews (1): Last reviewed commit: "fix(rpc): correct flatCallTracer shape f..." | Re-trigger Greptile
Three correctness bugs versus geth's built-in 4byteTracer (eth/tracers/native/4byte.go), each of which would cause clients matching against geth output to mismatch every entry: - The reported size was `len(calldata)` instead of `len(calldata) - 4`. Geth's key shape is `"0xSELECTOR-N"` where N is the argument-bytes length excluding the selector itself. - The top-level transaction call was being counted. Geth skips depth 0 and only records nested invocations. - Every CallType was counted, including STATICCALL, CALLCODE, CREATE, CREATE2, and SELFDESTRUCT. Geth filters on the opcode and only counts CALL and DELEGATECALL — for create variants and selfdestruct the input isn't a function selector, and the geth tracer doesn't surface STATICCALL or CALLCODE either. Also skip precompile-targeted invocations (addresses 0x01..=0x11 plus P256VERIFY at 0x100) — matching geth's per-fork `isPrecompiled` check fork-agnostically, since any precompile address is a precompile once its fork activates. Unit tests now lock in each rule: top-frame skip, short-input skip, call-type filter, precompile skip, arg-size key, duplicate counting. Integration tests gain block-level coverage and a missing-tx failure case. Variant doc clarifies the size semantics and skipped categories. Test setup boilerplate moves out of the misnamed `debug_trace_tests.rs` into a shared `rpc::helpers` module so #6694, #6701, #6705, #6704 can all rebase to drop their duplicate copies; the file is renamed to `debug_four_byte_tracer_tests.rs` to fit its contents.
Three correctness bugs versus geth's built-in 4byteTracer (eth/tracers/native/4byte.go), each of which would cause clients matching against geth output to mismatch every entry: - The reported size was `len(calldata)` instead of `len(calldata) - 4`. Geth's key shape is `"0xSELECTOR-N"` where N is the argument-bytes length excluding the selector itself. - The top-level transaction call was being counted. Geth skips depth 0 and only records nested invocations. - Every CallType was counted, including STATICCALL, CALLCODE, CREATE, CREATE2, and SELFDESTRUCT. Geth filters on the opcode and only counts CALL and DELEGATECALL — for create variants and selfdestruct the input isn't a function selector, and the geth tracer doesn't surface STATICCALL or CALLCODE either. Also skip precompile-targeted invocations (addresses 0x01..=0x11 plus P256VERIFY at 0x100) — matching geth's per-fork `isPrecompiled` check fork-agnostically, since any precompile address is a precompile once its fork activates. Unit tests now lock in each rule: top-frame skip, short-input skip, call-type filter, precompile skip, arg-size key, duplicate counting. Integration tests gain block-level coverage and a missing-tx failure case. Variant doc clarifies the size semantics and skipped categories. Test setup boilerplate moves out of the misnamed `debug_trace_tests.rs` into a shared `rpc::helpers` module so #6694, #6701, #6705, #6704 can all rebase to drop their duplicate copies; the file is renamed to `debug_four_byte_tracer_tests.rs` to fit its contents.
…ock tracing Add support for the `flatCallTracer` tracer type which returns a flat array of call frames following the Parity/OpenEthereum trace format. Each frame includes `traceAddress` (position in call tree) and `subtraces` (number of direct subcalls) instead of nested `calls`. The action/result fields use the standard Parity format: - action: callType, from, to, gas, input, value - result: gasUsed, output - type: "call", "create", or "suicide" Implemented by post-processing the existing call tracer output, recursively flattening the call tree with depth-first ordering.
Trace a real transfer with flatCallTracer and assert the response is a flat array of call frames with the Parity trace format.
The previous implementation was structurally wrong for two of the three
flat-frame variants:
- `create` actions emitted `input` and a result of `{gasUsed, output}`.
geth's flatCallTracer (and Parity's spec) require `init` in the action
and `{address, code, gasUsed}` in the result so callers can recover the
deployed contract's address and runtime bytecode.
- `suicide` (SELFDESTRUCT) frames emitted the call-shape action and a
bogus `{gasUsed, output}` result. The Parity shape is
`{address, balance, refundAddress}` with no result at all.
Split `FlatCallAction` / `FlatCallResult` into per-variant enums (untagged
so the outer `type` field continues to discriminate), map the LEVM call
tracer's SELFDESTRUCT frame fields onto the suicide shape (`from` →
destructed contract, `to` → refund target, `value` → balance), and emit
`init`/`address`/`code` for create frames. Use `&'static str` for the
static label fields to avoid per-frame allocations.
Unit tests now assert against the serialized JSON projection — the wire
shape clients actually receive — and cover create/create2, selfdestruct,
and failed-frame `result` omission. Integration tests gain a CREATE
end-to-end (deploys a tiny init code and verifies the action/result
shape), a `debug_traceBlockByNumber` test, and a missing-tx failure case
that Codex's review flagged as a gap.
Test setup boilerplate moves out of the misnamed `debug_trace_tests.rs`
into a shared `rpc::helpers` module so #6694 and #6701 can rebase to
re-use it instead of duplicating ~140 lines per feature. The file is
renamed to `debug_flat_call_tracer_tests.rs`.
bc32935 to
36358c3
Compare
Summary
flatCallTracertracer type fordebug_traceTransactionand block tracing endpointstraceAddress(position in call tree as array of indices) andsubtraces(direct child count)action/resultsub-object structure:action.callType,action.from,action.to, etc."call","create", and"suicide"Closes part of #6572
Closes #6647