Skip to content

debug: specify callTracer output and add EIP-8037 two-dimensional gas to tracing - #852

Open
qu0b wants to merge 2 commits into
ethereum:mainfrom
qu0b:qu0b/2d-gas-tracing
Open

debug: specify callTracer output and add EIP-8037 two-dimensional gas to tracing#852
qu0b wants to merge 2 commits into
ethereum:mainfrom
qu0b:qu0b/2d-gas-tracing

Conversation

@qu0b

@qu0b qu0b commented Jul 21, 2026

Copy link
Copy Markdown

Adds the EIP-8037 regular/state gas breakdown (and the EIP-3529 gasRefund) to the debug tracing schemas, specifies the previously-undefined callTracer output, and introduces a stateGasTracer as the normative core for EIP-8037 tracing.

What's included

schemas/state-gas-tracer.yaml (new) — a named stateGasTracer returning the per-transaction {gasUsed, regularGasUsed, stateGasUsed, gasRefund}. A survey of all seven EL clients' tracer implementations on their glamsterdam-devnet-6 branches (geth, erigon, reth, besu, nethermind, ethrex, nimbus-eth1) showed every client already computes and carries these values on its transaction-result type for block-level accounting; exposure is 60–300 LOC per client, and for nimbus-eth1 (which has no callTracer at all) this is the only practical compliance path.

schemas/call-tracer.yaml (new)CallFrame/CallLog, defining the callTracer result. CallFrame is deliberately a partial schema covering only the gas-related properties (additionalProperties: true): full call-frame shapes already diverge between clients (sub-frame gas semantics differ in erigon and besu), so enshrining one client's frame layout would make others non-compliant at birth. The new fields — regularGasUsed and stateGasUsed (gross, pre-refund, the EIP-7778 per-transaction block contributions) and gasRefund — are SHOULD on the top-level frame for clients that implement the callTracer.

Top-frame invariant (Amsterdam+): regularGasUsed + stateGasUsed == gasUsed + gasRefund, unless the EIP-7623 calldata floor binds.

schemas/opcode-tracer.yaml — per-step stateGasCost/stateGasReservoir and per-transaction regularGasUsed/stateGasUsed/gasRefund. Per-step stateGasCost is MAY and signed: three independent implementations (ethrex, reth, erigon) track per-step state gas as a signed value that can go negative under EIP-8037 source-based refunds, and intrinsic, code-deposit and EIP-7702 state gas is charged outside opcode steps in every surveyed client, so per-step sums cannot reconstruct the transaction total. The scalar gasCost is pinned as the regular-dimension charge including reservoir spillover.

debug/trace.yaml — wires the new result schemas into the result unions of the three trace methods, reconciles the now-outdated "named tracers are out of scope" prose, and adds a post-Amsterdam callTracer example.

Notes

  • CallFrame.calls is typed as a generic object because the reference tooling (specgen -deref) does not dereference recursive schemas; the recursion is documented in prose.
  • Passes make build, make test (speccheck) and make lint (no new warnings).

qu0b added 2 commits July 3, 2026 12:05
… to tracing

Adds the EIP-8037 regular/state gas breakdown (and the EIP-3529 gasRefund) to
the debug tracing schemas, and specifies the previously-undefined callTracer
output.

- schemas/call-tracer.yaml (new): CallFrame/CallLog. Defines the callTracer
  result, including regularGasUsed and stateGasUsed (gross, pre-refund, the
  EIP-7778 per-transaction block contributions) and gasRefund on the top frame.
  Top-frame invariant (Amsterdam+): regularGasUsed + stateGasUsed ==
  gasUsed + gasRefund, unless the EIP-7623 calldata floor binds.
- schemas/opcode-tracer.yaml: per-step stateGasCost/stateGasReservoir; per-tx
  regularGasUsed/stateGasUsed/gasRefund; TraceConfig note updated.
- debug/trace.yaml: wire CallFrame into the result unions of the three trace
  methods, reconcile the now-outdated "named tracers are out of scope" prose,
  and add a post-Amsterdam callTracer example.

Notes: CallFrame.calls is typed as a generic object because the reference
tooling (specgen -deref) does not dereference recursive schemas; the recursion
is documented in prose. Passes make build, make test (speccheck) and make lint
(no new warnings).
Restructured after a survey of all seven EL clients' tracer
implementations on their glamsterdam-devnet-6 branches (geth, erigon,
reth, besu, nethermind, ethrex, nimbus-eth1):

- New StateGasTrace schema: a named stateGasTracer returning the
  per-transaction {gasUsed, regularGasUsed, stateGasUsed, gasRefund}.
  Every surveyed client already computes and carries these values on
  its transaction-result type for block-level accounting; exposure is
  60-300 LOC per client, and for nimbus-eth1 (which has no callTracer
  at all) this is the only practical compliance path.

- CallFrame shrunk to a partial schema covering only the gas-related
  properties (additionalProperties: true). Full call-frame shapes
  already diverge between clients (sub-frame gas semantics differ in
  erigon and besu), so enshrining one client's frame layout would make
  others non-compliant at birth. This also removes the recursive
  calls.items workaround. The three new fields are SHOULD on the
  top-level frame for clients that implement the callTracer.

- Per-step stateGasCost downgraded from MUST-when-nonzero to MAY and
  made signed: three independent implementations (ethrex, reth,
  erigon) track per-step state gas as a signed value that can go
  negative under EIP-8037 source-based refunds, and intrinsic,
  code-deposit and EIP-7702 state gas is charged outside opcode steps
  in every surveyed client, so per-step sums cannot reconstruct the
  transaction total. The scalar gasCost is now pinned as the
  regular-dimension charge including reservoir spillover.
@MysticRyuujin

Copy link
Copy Markdown
Contributor

I was working on a spec for callTracer too, but I was waiting for other work in this repo before publishing a draft PR.

Current blockers:

The only real part where my spec/fixtures differ from yours is the callFrame. You've kept it loose (additionalProperties: true) on the assumption that clients diverge too much to pin down. I went the other way with a full frame shape with presence rules, validated by fixtures generated against real clients and so far they mostly agree, so I don't think it needs to stay open-ended.

I'd rather start with trying to get full spec output agreement and only loosen the rules if we can't come to an agreement. We had some luck when we spec'd the opcode tracer, so I'd like to try to keep it tight.

cc @fjl for guidance on how we can get hive updated all the way to Glamsterdam

@MysticRyuujin

MysticRyuujin commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

I have two hive PRs that are needed to ensure that we can properly spec and test callTracer:

ethereum/hive#1587
ethereum/hive#1588

Tracking:
ethereum/hive#1589

onbjerg added a commit to paradigmxyz/revm-inspectors that referenced this pull request Aug 12, 2026
Replaces #475.

Implements the EIP-8037 tracing additions from
ethereum/execution-apis#852: two-dimensional gas fields on default and
call traces, signed per-opcode state-gas accounting, `stateGasTracer`,
and mux tracer support. Fork gating uses the actual execution spec,
including Amsterdam transactions with zero net state gas and empty-code
calls, while pre-Amsterdam responses and legacy builder behavior remain
unchanged.

Upstream support landed in alloy-rs/alloy#4113 and is now consumed from
the released `alloy-rpc-types-trace` crate.

Prompted by: @onbjerg

---------

Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com>
Co-authored-by: onbjerg <8862627+onbjerg@users.noreply.github.com>
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