Skip to content

debug: add debug_getBlockReceipts with EIP-8037 dimensional gas fields#800

Closed
qu0b wants to merge 1 commit into
ethereum:mainfrom
qu0b:qu0b/debug-getBlockReceipts-eip-8037
Closed

debug: add debug_getBlockReceipts with EIP-8037 dimensional gas fields#800
qu0b wants to merge 1 commit into
ethereum:mainfrom
qu0b:qu0b/debug-getBlockReceipts-eip-8037

Conversation

@qu0b
Copy link
Copy Markdown

@qu0b qu0b commented May 13, 2026

Summary

Mirrors eth_getBlockReceipts. When EIP-8037 is active, each receipt also carries stateGasCharged (pre-refund state-dimension gas) and stateGasRefunded (state-dimension refunds applied). Pre-activation blocks omit both fields.

Header reconstruction (MUST):

header.stateGasUsed   = Σ stateGasCharged − Σ stateGasRefunded
header.regularGasUsed = Σ gasUsed         − header.stateGasUsed

Motivation

bal-devnet-6 clients diverged on regular-vs-state gas attribution by exactly AccountCreationCost = 131,488 because the missing tx-level CREATE-halt refund (now fixed in ethereum/EIPs#11611) was invisible at the receipt level. The current Receipt exposes only the unified gasUsed — a missed refund and "no state operations" produce the same number.

The split stateGasCharged / stateGasRefunded makes the failure mode directly observable at receipt scope. A consumer can verify header.stateGasUsed reconstructs from the receipts and localise any divergence to a specific tx without running a tracer.

Tracking issue: ethereum/execution-specs#2804.

Design choices

  • Two fields, not six. regularGasUsed is derivable as gasUsed − (stateGasCharged − stateGasRefunded); cumulative* fields are derivable by summing per-tx values in order. Only the two non-derivable, state-dimension counters are spec'd.
  • Split stateGasCharged + stateGasRefunded, not a signed net. Per EIPs#11611, execution_state_gas_used may go negative under EIP-7702 unset. JSON-RPC convention is unsigned hex. A single net stateGasUsed would have to be either signed (parser-hostile) or clamped (data loss — "no state ops" indistinguishable from "charge + larger refund"). The split is the only encoding that's both parseable and lossless.
  • No regular-dim refunds. EIP-8037 (per EIPs#11573) removes all regular-dimension refund pathways; regular gas is pay-as-you-go. No field is needed for the regular dim — it's already covered by gasUsed.
  • Pre-activation: fields omitted (not zero). Eliminates absent-vs-zero ambiguity for downstream consumers.
  • debug namespace, additive. No receipts-root change, no consensus impact. Once cross-client implementations are stable, the same two fields can land on eth_getBlockReceipts / eth_getTransactionReceipt without rename.

Implementation cost

Both clients already compute the dimensional values during execution:

  • geth core/vm/gascosts.go has GasBudget{RegularGas, StateGas, StateGasRefund} and threads it through state_transition.go. The StateGasRefund accumulator already exists per-frame. Receipt struct (core/types/receipt.go) is single-dim today; the wiring is the new work.
  • reth crates/ethereum/payload/src/lib.rs tracks block_state_gas_used (net) at payload-build time. Persisted receipt is alloy_consensus::EthereumReceipt, single-dim. Splitting state_gas_used() into charged + refunded requires a revm change.

Per-client wiring is small. No new tracing infrastructure required (geth's core/tracing/hooks.go:171 has an unlanded GasChangeV2Hook TODO — this PR doesn't depend on it).

Mirrors eth_getBlockReceipts. When EIP-8037 is active, each receipt
also carries stateGasCharged (pre-refund state-dimension gas) and
stateGasRefunded (state-dimension refunds applied). Pre-activation
blocks omit both fields.

Header reconstruction:
  header.stateGasUsed   = Σ stateGasCharged − Σ stateGasRefunded
  header.regularGasUsed = Σ gasUsed − header.stateGasUsed

Motivation: bal-devnet-6 clients diverged on regular-vs-state gas
attribution by exactly AccountCreationCost (131488) because the missing
tx-level CREATE-halt refund (now fixed in EIPs#11611) was invisible at
the receipt level. The split charged/refunded counters make the failure
mode directly observable.

Why split and not a single net stateGasUsed: per EIPs#11611,
execution_state_gas_used may go below zero under EIP-7702 unset. JSON-RPC
convention is unsigned hex; the split keeps every wire value uint64
without signed encoding or data loss. Per-tx stateGasRefunded MAY exceed
stateGasCharged; block-boundary Σ charged ≥ Σ refunded.

Conformance fixtures: not-found, pre-Amsterdam (fields omitted),
top-level CREATE halt (the bal-devnet-6 case), and a two-tx block where
the second tx is an EIP-7702 unset whose refund exceeds its charge.
@qu0b
Copy link
Copy Markdown
Author

qu0b commented May 15, 2026

Closed for now, clients are not in favor of this new endpoint, but would rather implement a Tracer that displays the state gas.

@qu0b qu0b closed this May 15, 2026
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