Skip to content

debug: specify callTracer output and add debug_traceCall - #855

Open
MysticRyuujin wants to merge 7 commits into
ethereum:mainfrom
MysticRyuujin:calltracer-spec-fixtures
Open

debug: specify callTracer output and add debug_traceCall#855
MysticRyuujin wants to merge 7 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 branch.

Stacked on the Amsterdam chain bump (#867); the first three commits are that PR. Review the last four commits here.

What's in it

  • CallFrame / CallLog / CallTracerConfig / CallTracerBlockEntry (src/schemas/call-tracer.yaml) with per-field presence and omission rules; recursion via an $id self-ref.
  • debug_traceCall: GenericTransaction, optional block param (default latest), and TraceCallConfig (reuses the eth_simulateV1 overrides). A revert is a traced result, not a JSON-RPC error.
  • Tooling: specgen passes absolute-URI $refs through so the recursive schema survives, and speccheck selects the anyOf branch for the tracer each fixture requests. Without that selection, trace-result validation was vacuous, including for the existing opcode branch. Negative tests prove both.
  • 19 fixtures exercising nested call trees: inner reverts, STATICCALL write protection, DELEGATECALL logs, CALLCODE, precompile calls, and CREATE followed by SELFDESTRUCT. The scenarios come from the calltree contract in hivechain (cmd/hivechain: add calltree contract and tx mods for callTracer testing hive#1587, merged), so the fixtures regenerate from upstream. The set includes the omitted-block-param form of debug_traceCall, which fills now that eth/tracers: make debug_traceCall block parameter optional go-ethereum#35583 is merged.

Decisions already baked into the spec (raise it if you object, but the data settled these):

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

Conformance: the original 18 fixtures replayed per client via hive rpc-compat against the glamsterdam-devnet-8 images, 2026-08-25. Every other debug_* failure these clients show also occurs on main's fixtures, so the rows below are the whole delta.

client result changes needed
go-ethereum 18/18 none; ethereum/go-ethereum#35583 (merged 2026-08-28) made the debug_traceCall block param optional
erigon 18/18 none
reth 17/18 return -32000 instead of -32001 for an out-of-range block in debug_traceCall
besu 15/18 honor onlyTopCall (still returns nested calls); accept a block hash as the block param
nethermind 13/18 stop base-fee validation of debug_traceCall calls that carry no fee fields; all five failures are max fee per gas less than block base fee errors where the reference traces
ethrex 1/18 debug_trace* not usable yet; tracked as not-yet-implemented, not a conformance gap

The new trace-call-omitted-block fixture postdates that run. It is speconly, and the devnet-8 geth image rejects the omitted param, so expect that one failure against devnet-8 images until clients pick up the change.

One thing left open, logs[].index: optional and undefined for now. geth counts block-globally, erigon/reth transaction-locally, nethermind omits it: three meanings plus an absence, and nothing depends on the value. position, its neighbor, is standardized.

EIP-8037 / #852: this PR specs the fork-independent frame shape only. CallFrame keeps additionalProperties open, so the regularGasUsed/stateGasUsed/gasRefund fields proposed in #852 drop in as optional fields on top, and the Amsterdam chain underneath already produces post-fork traces to generate fixtures against. Thanks @qu0b for the EIP-8037 groundwork and the client survey.

Known issue: hive replays the exact-match tracer fixtures correctly either way, but validates the speconly subset against the whole result schema, where the unconstrained named-tracer branch accepts anything. Until ethereum/hive#1588 adds the same tracer-aware branch selection speccheck uses here, that hive-side validation stays vacuous for trace methods. speccheck already enforces it when fixtures are generated, so this is a hive reporting gap, not a spec gap, and not a blocker.

Update 2026-08-28: rebased on the current #867, which is itself rebased on main and pins go-ethereum master. Added the omitted-block-param fixture that was waiting on ethereum/go-ethereum#35583. Fill, speccheck, lint, and the tools tests all pass.

@MysticRyuujin
MysticRyuujin force-pushed the calltracer-spec-fixtures branch 2 times, most recently from e4872f4 to 4861c60 Compare August 11, 2026 19:48
@MysticRyuujin
MysticRyuujin force-pushed the calltracer-spec-fixtures branch from 4861c60 to 7c9b696 Compare August 25, 2026 14:15
@MysticRyuujin MysticRyuujin changed the title debug: standardize callTracer output and add debug_traceCall debug: specify callTracer output and add debug_traceCall Aug 25, 2026
@MysticRyuujin
MysticRyuujin marked this pull request as ready for review August 25, 2026 14:16
rjl493456442 pushed a commit to ethereum/go-ethereum that referenced this pull request Aug 28, 2026
`debug_traceCall` requires the block number or hash parameter today.
`TraceCall` takes `rpc.BlockNumberOrHash` by value, so an omitted
parameter returns "missing value for required argument 1".

The pending callTracer spec (ethereum/execution-apis#855) marks the
parameter optional with a default of `latest`, consistent with the
state-method defaults from #35100 (execution-apis #812/#814 lineage).

This changes the argument to `*rpc.BlockNumberOrHash` and defaults to
latest when nil. A regression test calls `debug_traceCall` through the
RPC server with the parameter omitted and compares against an explicit
`latest`. No behavior change when the parameter is supplied.
Pin go-ethereum to glamsterdam-devnet-8 (matching hive master) and move
mkchain to -lastfork amsterdam. Adapt the affected generators: EIP-7708
transfer logs in eth_simulateV1 log counts, EIP-2780 intrinsic repricing
in eth_estimateGas, EIP-7843 slotNumber in testing_buildBlockV1, and fund
the complete-eth-sends recipient so account-creation state gas does not
push the 21000-gas send out of gas.
Chain generated from upstream hivechain (hive master). Amsterdam
activates at block 57 of 60. All fixtures refilled; block hashes
change, so the whole tree drifts.
Picks up the post-glam8 RPC fixes: estimateGas returns used gas for
plain transfers (#35592), debug_traceCall block param optional (#35583),
eth_config skips unconfigured forks (#35553). The testing_commitBlockV1
generators from ethereum#801 now send slotNumber post-Amsterdam, and the plain
transfer estimate expectation is 15000 per EIP-2780.
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.
…fs stubbed

The docs plugin fetches every $ref URL over HTTP. The CallFrame
recursion $id is deliberately unpublished, so the build failed.
build-docs-spec.mjs writes openrpc-docs.json with in-document $id
refs stubbed, and docusaurus reads that copy.
go-ethereum #35583 made the block parameter optional, so the omitted
form now fills against the pinned geth.
@MysticRyuujin
MysticRyuujin force-pushed the calltracer-spec-fixtures branch from 7c9b696 to 9590ccc Compare August 28, 2026 13:44
taratorio pushed a commit to nerolation/erigon that referenced this pull request Aug 31, 2026
…ntech#23683)

`debug_traceCall` took the block selector by value
(`rpc.BlockNumberOrHash`), so omitting it — or passing `null` — was
rejected by the positional argument parser with `missing value for
required argument 1`. It was the last state-reading method still
requiring the parameter: `eth_call`, `eth_estimateGas`,
`eth_getBalance`, `eth_getCode`, `eth_getProof`, `eth_createAccessList`,
`trace_call` and friends already take `*rpc.BlockNumberOrHash` and
default to `latest`.

The parameter is now a pointer and defaults to `latest`, matching geth
(`ethereum/go-ethereum#35583`) and the pending spec in
`ethereum/execution-apis#855`, which marks it optional with default
`latest`. `pending` is still rejected, as before.

Also renames the shared helper `orLatest` to `blockOrLatest`
(mechanical, 9 call sites) and adds unit tests for it.

## Testing

- New `TestDebugTraceCallBlockParamDefaultsToLatest` drives the change
through the JSON-RPC dispatch layer (where the failure was): an omitted
and a `null` block selector must produce the same trace as an explicit
`"latest"`. Red before the change with `missing value for required
argument 1` on both cases.
- New `TestBlockOrLatest` covers the helper: nil defaults to latest, an
explicit number is kept, a hash keeps `requireCanonical`, `pending` is
passed through for the caller to reject.
- `go test -short ./rpc/jsonrpc/ ./rpc/` and `golangci-lint` on
`rpc/jsonrpc/...` are clean.
LukaszRozmej added a commit to NethermindEth/nethermind that referenced this pull request Sep 1, 2026
…13078)

* fix(rpc): omit `to` on failed top-level CREATE frames in callTracer

The execution-apis `CallFrame` schema (ethereum/execution-apis#855) requires
`to` to be omitted on CREATE/CREATE2 frames that failed, since no contract was
deployed. NativeCallTracer applied that only to nested frames; the root frame
of a failed deployment kept the would-be contract address, unlike geth, which
runs the same processOutput at depth 0.

Also pins the spec's `debug_traceCall` revert contract: a revert is a traced
result carrying error/revertReason/output, not a JSON-RPC error.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>

* test: address review on the callTracer failed-CREATE fix

- Cover a non-reverting halt (INVALID) as well as REVERT: the `to`-omission
  fires on any EVM halt, not just REVERT. Replaces the two-bool parameters
  with a CreateOutcome enum so the fourth case stays readable.
- Assert no JSON-RPC error before dereferencing `result` in the traceCall
  create test, so a pre-flight rejection reports the error object instead of
  a NullReferenceException.
- Build the state override from a dictionary instead of a raw-string JSON
  round-trip.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
@bomanaps

bomanaps commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

The index should be block-global so to match receipt logIndex since that is the only semantics where it has real cross-reference value, and for debug_traceTransaction implementations would need to replay prior transactions to produce the correct value but debug_traceBlock gets it for free from accumulated state. I prefer we define it as block-global rather than leave it undefined and differ per client.

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.

2 participants