Skip to content

bal-devnet-3: block 503 gas mismatch (EIP-8037 state-gas accounting) #20791

Description

@mh0lt

Summary

On bal-devnet-3, erigon's parallel executor fails block 503 with an EIP-8037 state-gas accounting mismatch:

invalid block, block=503, gas used by execution: 6265830, in header: 6641318

Diff: −375,488 of state-gas. Reproduces deterministically on feat/eip-8159-eth71-bal-exchange after the CPSB formula fix (72f2c8e922), on a freshly initialised datadir.

Block details

  • Chain: bal-devnet-3 (chain ID 7098917910), ≈43 min after Glamsterdam activation.
  • Block hash: 0xa9c9b682e5573f24662e6c2145c3fedcdac55baa1590803a82cad0e93b34fa2b
  • gasLimit = 80_000_000, canonical gasUsed = 6_641_318.
  • Tx 0: CREATE, 68-byte initcode 0x600b3803…, deploys 0x4195b465…e8d3. Canonical receipt gasUsed = 229_533; we produce 143_005 (diff −86_528).
  • Tx 1: call into deployed contract with 1003 ETH value, 6_464-byte calldata (0xc481aaf8…). Canonical receipt gasUsed = 7_120_000 (OOG); we match 7_120_000.

Canonical block.gasUsed is max(Σ blockRegular, Σ blockState) per execution/protocol/state_processor.go:44; state is the larger term here (difference is the EIP-8037 state-gas portion).

Two distinct discrepancies, both in state-gas accounting

A. Block-level: Σ blockState is 375,488 too low

Per-tx breakdown from an exec3 run with a diagnostic counter (added to useMdGas, not committed):

tx0  msgGas=300000   initRegular=225856  initState=74144
     regConsumed=45      stateConsumed=37734   totalSpill=37734
     blockRegular=31127  blockState=111878     blockRegularUsed=31127
     txnGasUsed=143005   refund=0              vmerr=nil

tx1  msgGas=7120000  initRegular=7120000 initState=0
     regConsumed=856372  stateConsumed=6153952 totalSpill=6153952
     blockRegular=966048 blockState=6153952    blockRegularUsed=966048
     txnGasUsed=7120000  refund=0              vmerr=\"out of gas\"

Aggregates — Σ blockState = 6_265_830 (we produce); 6_641_318 (canonical) → −375,488.

tx1.totalSpill == tx1.stateConsumed == 6_153_952. The reservoir is effectively 0 for every sub-cap tx because SplitTxnGasLimit returns reservoir=0 whenever executionGas ≤ regularGasBudget (true for any tx with gasLimit ≤ MaxTxnGasLimit = 16_777_216), so every state op goes through the spill path.

tx1 ends OOG. The OOG-triggering state op calls useMdGas, finds initial.State = 0, attempts to spill the full cost from initial.Regular, and fails. In that failure path (execution/vm/interpreter.go:140-144) we return ok=false and do not increment stateGasConsumed — the attempted state cost is lost.

Hypothesis (unverified): the attempted state cost of the final failed-spill op equals the 375,488 gap, and canonical counts it toward block.gasUsed even though the tx OOGs on it. To confirm, increment stateGasConsumed on the ok=false branch and rerun — if the diff becomes exactly zero, hypothesis holds.

B. Receipt-level: tx0.txnGasUsed is 86,528 too low

tx0 is successful (vmerr=nil), so nothing was thrown away in a failed useMdGas. We still produce txnGasUsed = 143_005 where canonical expects 229_533.

txnGasUsed = initialGas.Total() − gasRemaining.Total() + revertedSpillGas = 300_000 − 156_995 + 0 = 143_005.

So gasRemaining.Total() is 86,528 too high — we return too much unused gas. Given this is a CREATE that ran 68-byte initcode and deployed a near-empty runtime with 3 logs, likely leak sites:

  • EIP-8037 CREATE state-gas for code deposit (200 gas per deployed byte, possibly split regular vs state in a way that's off by ~86 K).
  • Initcode EIP-3860 accounting.
  • A state-gas path inside execution/vm/evm.go:595-644 (CREATE finalisation).

Independent of (A) — tx0 does not OOG, so the failed-spill hypothesis does not apply. Same consensus-root category (state-gas charging), different root cause.

Where to look

Cross-references:

  • ethereum/execution-specs (src/ethereum/forks/amsterdam/vm/) — reference behaviour for failed-spill accounting
  • Geth handling of OOG during a state op
  • origin/feature/lystopad/hoodi-amsterdam-fix — iterated on this area; do not bulk apply, but individual commits worth reading:
    • 563e81a36e fix Amsterdam REVERT spill accounting — depth-0 REVERT spill handling; formula over-counts for OOG txs as measured
    • 78ae4b789c blockRegular via mdGasUsed.Regular − RevertedSpillGas — formula reframe with the same over-count problem

Reproduction

From an erigon checkout with feat/eip-8159-eth71-bal-exchange (or any branch carrying 72f2c8e922 CPSB formula fix):

make erigon
rm -rf /home/erigon/bal-devnet-3/node-a/{chaindata,caplin,dev-beacon,downloader,logs,migrations,nodes,snapshots,temp,txpool,LOCK,nodekey}
./build/bin/erigon init --datadir=/home/erigon/bal-devnet-3/node-a /home/erigon/bal-devnet-3/genesis.json

Start with USE_STATE_CACHE=false and bal-devnet-3 bootnodes. Drive via Lighthouse checkpoint-sync from https://checkpoint-sync.bal-devnet-3.ethpandaops.io. Block 503 fails within ~30 s of CL sync engaging. (Setup auto-bootstrapped via .claude/skills/launch-bal-devnet-3/.)

To re-enable the diagnostic per-tx breakdown: add totalSpillGas uint64 to EVM, increment it at execution/vm/interpreter.go:142 alongside stateGasConsumed, and add a log.Warn(\"[amsterdam] tx gas breakdown\", …) at the end of the Amsterdam branch in execution/protocol/txn_executor.go:621. Diagnostic-only, not a fix.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    Glamsterdamhttps://eips.ethereum.org/EIPS/eip-7773

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions