core: fix 8037 gas accounting (#34631)

To check whether a transaction can be applied, we validate that
`blockGasLimit > txGasLimit + (cumulativeRegularGasUsed +
cumulativeStateGasUsed)`. However, the check should only be applied to
the bottleneck resource, i.e. `blockGasLimit >
max(txRegularGasUsed+cumulativeRegularGasUsed, txStateGasUsed+
cumulativeStateGasUsed)`.

The changes here break multiple tests.  I am trying to determine why.

---------

Co-authored-by: qu0b <stefan@starflinger.eu>
This commit is contained in:
jwasinger 2026-04-05 18:12:48 -04:00 committed by GitHub
parent 710ffb03ad
commit 985cdac8a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 16 additions and 13 deletions

View file

@ -73,18 +73,20 @@ func (gp *GasPool) ReturnGas(returned uint64, gasUsed uint64) error {
return nil
}
// ReturnGasAmsterdam handles 2D gas accounting for Amsterdam (EIP-8037).
// It undoes the SubGas deduction fully and accumulates per-dimension block totals.
func (gp *GasPool) ReturnGasAmsterdam(returned, txRegular, txState, receiptGasUsed uint64) error {
if gp.remaining > math.MaxUint64-returned {
return fmt.Errorf("%w: remaining: %d, returned: %d", ErrGasLimitOverflow, gp.remaining, returned)
}
// Undo SubGas deduction fully (Amsterdam uses cumulative tracking)
gp.remaining += returned
// Accumulate 2D block dimensions
// ReturnGasAmsterdam calculates the new remaining gas in the pool after the
// execution of a message. The remaining gas in the pool is
// block.gasLimit - max(cumulative_regular, cumulative_state)
func (gp *GasPool) ReturnGasAmsterdam(txRegular, txState, receiptGasUsed uint64) error {
gp.cumulativeRegular += txRegular
gp.cumulativeState += txState
gp.cumulativeUsed += receiptGasUsed
blockUsed := max(gp.cumulativeRegular, gp.cumulativeState)
if gp.initial < blockUsed {
return fmt.Errorf("%w: block gas overflow: initial %d, used %d (regular: %d, state: %d)",
ErrGasLimitReached, gp.initial, blockUsed, gp.cumulativeRegular, gp.cumulativeState)
}
gp.remaining = gp.initial - blockUsed
return nil
}

View file

@ -201,7 +201,7 @@ func (p *ParallelStateProcessor) resultHandler(block *types.Block, preTxReads ba
// 1. if the block has transactions, receive the execution results from all of them and return an error on resCh if any txs err'd
// 2. once all txs are executed, compute the post-tx state transition and produce the ProcessResult sending it on resCh (or an error if the post-tx state didn't match what is reported in the BAL)
var results []txExecResult
gp := NewGasPool(block.GasLimit())
var cumulativeStateGas, cumulativeRegularGas uint64
var execErr error
var numTxComplete int
@ -217,9 +217,10 @@ func (p *ParallelStateProcessor) resultHandler(block *types.Block, preTxReads ba
// short-circuit if invalid block was detected
if res.err != nil {
execErr = res.err
} else if err := gp.SubGas(res.receipt.CumulativeGasUsed); err != nil {
execErr = err
} else if bottleneck := max(cumulativeRegularGas+res.txRegular, cumulativeStateGas+res.txState); bottleneck > block.GasLimit() {
execErr = fmt.Errorf("block used too much gas in bottleneck dimension: %d. block gas limit is %d", bottleneck, block.GasLimit())
} else {
cumulativeStateGas += res.txState
results = append(results, res)
accesses.Merge(res.stateReads)
}

View file

@ -608,7 +608,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
txState := (gas.StateGas - authRefund) + execGasUsed.StateGasCharged
txRegular := gas.RegularGas + execGasUsed.RegularGasUsed
txRegular = max(txRegular, floorDataGas)
if err := st.gp.ReturnGasAmsterdam(returned, txRegular, txState, st.gasUsed()); err != nil {
if err := st.gp.ReturnGasAmsterdam(txRegular, txState, st.gasUsed()); err != nil {
return nil, err
}
} else {