Skip to content

debug: standardize callTracer output and add debug_traceCall - #855

Draft
MysticRyuujin wants to merge 3 commits into
ethereum:mainfrom
MysticRyuujin:calltracer-spec-fixtures
Draft

debug: standardize callTracer output and add debug_traceCall#855
MysticRyuujin wants to merge 3 commits into
ethereum:mainfrom
MysticRyuujin:calltracer-spec-fixtures

Conversation

@MysticRyuujin

@MysticRyuujin MysticRyuujin commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Specifies the callTracer output (undefined until now) for debug_traceTransaction,
debug_traceBlockByNumber/ByHash, and a new debug_traceCall. Same approach as the
opcode-tracer spec (#762); other named tracers keep the unconstrained escape-hatch branch.

Backed by a six-client hive rpc-compat run reproducible from this repo's fixtures. Per-client
conformance below.

What's in it

  • CallFrame / CallLog / CallTracerConfig / CallTracerBlockEntry
    (src/schemas/call-tracer.yaml) — per-field presence/omission rules; recursion via an $id
    self-ref.
  • debug_traceCall: GenericTransaction + optional block param (default latest) +
    TraceCallConfig (reuses the eth_simulateV1 overrides). A revert is not a JSON-RPC error.
  • Tooling: specgen passes absolute-URI $refs so the recursive schema survives; speccheck now
    selects the anyOf branch by the requested tracer — without it, trace-result validation was
    vacuous (including the existing opcode branch). Negative tests included.
  • Test chain: regenerated from upstream hivechain, which now carries the calltree contract and
    the tx-calltree / tx-callrevert modifiers (cmd/hivechain: add calltree contract and tx mods for callTracer testing hive#1587, merged), so the fixtures exercise nested
    call trees — inner reverts, STATICCALL write protection, DELEGATECALL logs, CALLCODE,
    precompile calls, and CREATE followed by SELFDESTRUCT. Every fixture is refilled because the
    block hashes change; the chain parameters are otherwise unchanged (-lastfork bpo2 -length 54).

Decisions already baked into the spec (raise it if you object, but these are settled by the
data, not open for a round-trip):

  • error is exactly "execution reverted" for REVERT, free-form otherwise — the only string all
    clients agree on.
  • root gasUsed = receipt gasUsed (4/5 clients; besu to align).
  • reverted-frame logs are cleared (receipt consistency; reth to align).

Conformance — 32 debug_trace* fixtures replayed per client via hive rpc-compat, 2026-07-29:

Client Version Result Changes needed
go-ethereum v1.17.6-unstable 32/32 ✅ make debug_traceCall block param optional (one-liner, see below)
erigon 3.7.0-dev 32/32 ✅ none
nethermind 1.40.0-unstable 32/32 ✅ none
reth 2.4.1 26/32 error-path only: returns -32001 where the spec/reference returns -32000 on an out-of-range block in debug_traceCall, plus trace-genesis and two block-not-found cases
besu 26.7-develop 26/32 honor onlyTopCall (still returns nested calls); accept a block-hash param in debug_traceCall; two opcode-tracer storage-encoding cases
ethrex v22.0.0-main 2/32 debug_trace* not usable yet; tracked as not-yet-implemented, not a conformance gap

Erigon and Nethermind are now fully green, and besu's gaps have narrowed considerably since the
earlier survey — withLog/logs, the chained-DELEGATECALL from, the CREATE to and the root
gasUsed all pass now.

One thing I need a nod on: debug_traceCall's block param is spec'd optional, default
latest
(matching #812/#814). That needs a one-line geth change (follow-up PR). If we'd rather
keep it required, say so and I'll drop it.

One thing left open — logs[].index: spec'd optional and undefined for now. It's the only
field the geth family disagrees on — geth block-global (receipt index), erigon/reth tx-local
(each recomputing under onlyTopCall), nethermind omits it: three meanings plus an absence.
Can't be standardized without forcing two clients to change for a value nothing depends on, so
I'm flagging it rather than blessing one — fine to leave undefined unless someone needs it.
(position, its neighbour, is standardized.)

Glamsterdam follow-up: this specs the fork-independent frame shape only. The EIP-8037
two-dimensional gas fields (regularGasUsed/stateGasUsed/gasRefund) proposed in #852 are the
natural next layer — CallFrame leaves additionalProperties open, so they drop in as optional
fields once Glamsterdam is scheduled and clients emit them, without reopening this spec. Thanks
@qu0b for the EIP-8037 groundwork and the client survey.

Draft because: the cross-client divergence report is still to follow, and the
debug_traceCall block-param question above needs an answer. ethereum/hive#1587 is merged and
this branch is rebased on main with the chain regenerated from upstream hivechain, so the
fixtures are fully reproducible. ethereum/hive#1588 is still open; until it lands, hive enforces
the exact-match tracer fixtures but validates the speconly subset only vacuously. speccheck
enforces those fixtures at generation time, so that is a hive reporting gap, not a spec gap, and
no longer a hard blocker here.

The conformance run above is reproducible from this branch's fixtures with hive rpc-compat; the
exact invocation, and the client-file needed to pin each client, are noted in
GLAMSTERDAM.md.
That branch is a separate Glamsterdam integration superset — nothing in this PR depends on it, and
this spec is deliberately fork-independent.

Specify the callTracer output for debug_traceTransaction,
debug_traceBlockByNumber/ByHash, and a new debug_traceCall. Add the
recursive CallFrame/CallLog/CallTracerConfig/CallTracerBlockEntry schemas,
specgen absolute-URI $ref passthrough, and speccheck tracer-aware anyOf
branch selection.

Fixtures regenerated separately against the osaka+calltree chain.
The chain now comes from a hivechain build carrying the calltree contract and
the tx-calltree / tx-callrevert block modifiers (hive #1587, also included in
hive #1589), so the fixtures can exercise nested call trees: inner reverts,
STATICCALL write protection, DELEGATECALL logs, CALLCODE, precompile calls, and
CREATE followed by SELFDESTRUCT. Ten blocks carry a calltree invocation.

Regenerated with -lastfork bpo2 -length 54, unchanged from the previous chain
parameters, so the only behavioural difference is the added call-family
coverage. Every fixture is refilled because the block hashes change.

speccheck passes and the debug_trace* fixtures were replayed against six
clients with hive rpc-compat.
…fs stubbed

The docs dereferencer fetches absolute-URI $refs over HTTP; the CallFrame
recursion anchor resolves in-document via $id and is not published as a URL.
@MysticRyuujin
MysticRyuujin force-pushed the calltracer-spec-fixtures branch from e4872f4 to 4861c60 Compare August 11, 2026 19:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant