Skip to content

rpc/jsonrpc: keep trace_call's unrequested trace empty and stop attaching the tracer for stateDiff - #23572

Merged
lupin012 merged 8 commits into
mainfrom
lupin012/fix_trace_call_unrequested_trace
Aug 27, 2026
Merged

rpc/jsonrpc: keep trace_call's unrequested trace empty and stop attaching the tracer for stateDiff#23572
lupin012 merged 8 commits into
mainfrom
lupin012/fix_trace_call_unrequested_trace

Conversation

@lupin012

@lupin012 lupin012 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Closes #23326.

The report expected an unrequested trace to be null; the reporter withdrew that, since trace follows OpenEthereum's TraceResults.trace: Vec<Trace> and is an array, never null. The captured response shows the real defect: with ["stateDiff"] the trace field carries the full call trace.

Three defects, all present on release/3.6 too:

  1. trace populated when not requested. With ["stateDiff"] or ["vmTrace"], trace_call returns the full call trace. Call was the only ad-hoc path missing the final reset that doCall, doCallBlock and RawTransaction have.
  2. Tracer attached for stateDiff-only requests. Call and RawTransaction attached it for all three trace types, collecting a trace they then discard.
  3. Empty trace-type list panics. No trace type means the tracer has no result to write into, but the hooks reached the EVM anyway, so the first OnEnter nil-dereferenced. Recovered per request, so the client gets method handler crashed. Reachable on trace_call and trace_rawTransaction.

Changes

  • Call: reset Trace when trace was not requested. Still needed after the tracer change, because the OE tracer appends call frames regardless of the requested types, so vmTrace-only fills Trace as a side effect.
  • Call and RawTransaction: build a vm.Config like the sibling paths, so the hooks exist only when a trace is collected. Closes both the wasted work and the panic. parseOeTracerConfig stays outside the branch, so custom tracers keep being rejected for any trace types.
  • ReplayTransaction: drop a TraceCallResult it built, populated and never returned. doCall already rejects unrecognized trace types.
  • Docs: unrequested types were documented as null, true for stateDiff and vmTrace but not for trace.

Behaviour change

trace_call with ["stateDiff"] or ["vmTrace"] now returns "trace": []. This matches OpenEthereum, the other trace_* endpoints, and cmd/rpcdaemon/postman/Trace_Testing.json, whose "trace_call - stateDiff only" case expects "trace": [] and fails against the current code.

Tests

Regression coverage for the three defects, plus non-regression on ["trace"], on trace combined with the other types, and on a custom tracer rejected for a stateDiff-only request.

Also verified over HTTP with the old and new rpcdaemon side by side against one dev-chain node:

traceTypes before after
["stateDiff"], ["vmTrace"], ["vmTrace","stateDiff"] trace length 1 trace length 0
[] method handler crashed trace length 0
["trace"], ["trace","stateDiff"] trace length 1 trace length 1

trace_rawTransaction with [] goes from method handler crashed to "trace": []; with ["stateDiff"] it was already correct.

trace_call returned the full call trace for stateDiff-only and
vmTrace-only requests. Call was the only ad-hoc path missing the final
reset that doCall, doCallBlock and RawTransaction all have.

The reset is not redundant: the OE tracer appends call frames to the
result regardless of the requested trace types, so a vmTrace-only
request fills Trace as a side effect.

The field stays an empty array rather than null. That is what
OpenEthereum returns, what the other trace_* endpoints already return,
and what cmd/rpcdaemon/postman/Trace_Testing.json asserts.
Call and RawTransaction attached the tracer for stateDiff-only requests
as well, collecting a trace they then discard. doCall and doCallBlock
already attach it only for trace and vmTrace.

Both also passed the tracer hooks to the EVM unconditionally. With an
empty trace-type list the tracer has no result to write into, so the
first OnEnter dereferenced a nil pointer and the request failed with
"method handler crashed". Reachable on trace_call and on
trace_rawTransaction.

Both paths now build a vm.Config the way the sibling paths do, so the
hooks exist only when a trace is actually collected.

parseOeTracerConfig stays outside the branch: trace_* must keep
rejecting custom tracers whatever the requested trace types are.
ReplayTransaction built and populated a TraceCallResult that it never
returned. doCall already rejects unrecognized trace types, so dropping
the block keeps the error behaviour, now covered by a test.

The trace docs said unrequested trace types are null. That holds for
stateDiff and vmTrace but not for trace, which is an empty array.
@lupin012
lupin012 marked this pull request as ready for review August 27, 2026 08:53
@lupin012
lupin012 added this pull request to the merge queue Aug 27, 2026
Merged via the queue into main with commit d0514ff Aug 27, 2026
136 checks passed
@lupin012
lupin012 deleted the lupin012/fix_trace_call_unrequested_trace branch August 27, 2026 15:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants