From 01036bed831186d892a3e3e35b32fcf304d4cc29 Mon Sep 17 00:00:00 2001 From: Giulio rebuffo Date: Tue, 28 Apr 2026 17:25:16 +0200 Subject: [PATCH] core: skip tx gas cap after Amsterdam (#34841) EIP-7825 caps the transaction gas limit at `MaxTxGas`, but after Amsterdam/EIP-8037 the transaction gas limit can include state gas reservoir in addition to the regular gas dimension. Applying the Osaka cap to the full `tx.Gas()` rejects otherwise valid Amsterdam transactions that need more than `MaxTxGas` total gas because of state gas, while their regular gas use remains within the intended limit. This changes geth to stop applying the full transaction gas cap once Amsterdam is active: - txpool stateless validation no longer rejects `tx.Gas() > MaxTxGas` under Amsterdam - legacy pool reorg cleanup does not purge high-total-gas transactions at the Osaka transition if Amsterdam is also active - execution precheck mirrors the txpool behavior and does not reject high-total-gas messages under Amsterdam The block gas limit check remains in place, so transactions still cannot request more total gas than the current block gas limit. Validation run: ``` go test ./core/txpool ./core/txpool/legacypool go test ./core -run TestStateProcessorErrors ``` --------- Co-authored-by: Gary Rong --- cmd/evm/internal/t8ntool/transaction.go | 4 +++- core/state_transition.go | 3 ++- core/txpool/legacypool/legacypool.go | 6 ++++-- core/txpool/validation.go | 2 +- eth/gasestimator/gasestimator.go | 8 ++++---- miner/worker.go | 2 +- 6 files changed, 15 insertions(+), 10 deletions(-) diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go index ad89876601..7e068c06af 100644 --- a/cmd/evm/internal/t8ntool/transaction.go +++ b/cmd/evm/internal/t8ntool/transaction.go @@ -185,7 +185,9 @@ func Transaction(ctx *cli.Context) error { } } - if chainConfig.IsOsaka(new(big.Int), 0) && tx.Gas() > params.MaxTxGas { + isOsaka := chainConfig.IsOsaka(new(big.Int), 0) + isAmsterdam := chainConfig.IsAmsterdam(new(big.Int), 0) + if isOsaka && !isAmsterdam && tx.Gas() > params.MaxTxGas { r.Error = errors.New("gas limit exceeds maximum") } results = append(results, r) diff --git a/core/state_transition.go b/core/state_transition.go index c3ebffd060..c7b0593857 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -344,9 +344,10 @@ func (st *stateTransition) preCheck() error { } } isOsaka := st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time) + isAmsterdam := st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time) if !msg.SkipTransactionChecks { // Verify tx gas limit does not exceed EIP-7825 cap. - if isOsaka && msg.GasLimit > params.MaxTxGas { + if isOsaka && !isAmsterdam && msg.GasLimit > params.MaxTxGas { return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit) } // Make sure the sender is an EOA diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 78a0161c41..00630de04c 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -1222,8 +1222,10 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, pool.mu.Lock() if reset != nil { if reset.newHead != nil && reset.oldHead != nil { - // Discard the transactions with the gas limit higher than the cap. - if pool.chainconfig.IsOsaka(reset.newHead.Number, reset.newHead.Time) && !pool.chainconfig.IsOsaka(reset.oldHead.Number, reset.oldHead.Time) { + // Discard the transactions with the gas limit higher than the cap at the + // Osaka fork boundary. + if pool.chainconfig.IsOsaka(reset.newHead.Number, reset.newHead.Time) && + !pool.chainconfig.IsOsaka(reset.oldHead.Number, reset.oldHead.Time) { var hashes []common.Hash pool.all.Range(func(hash common.Hash, tx *types.Transaction) bool { if tx.Gas() > params.MaxTxGas { diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 85bf65ac40..6891dc94d2 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -92,7 +92,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types return err } } - if rules.IsOsaka && tx.Gas() > params.MaxTxGas { + if rules.IsOsaka && !rules.IsAmsterdam && tx.Gas() > params.MaxTxGas { return fmt.Errorf("%w (cap: %d, tx: %d)", core.ErrGasLimitTooHigh, params.MaxTxGas, tx.Gas()) } // Transactions can't be negative. This may never happen using RLP decoded diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go index efb089d456..ace0752037 100644 --- a/eth/gasestimator/gasestimator.go +++ b/eth/gasestimator/gasestimator.go @@ -63,10 +63,10 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin } // Cap the maximum gas allowance according to EIP-7825 if the estimation targets Osaka - if hi > params.MaxTxGas { - if opts.Config.IsOsaka(opts.Header.Number, opts.Header.Time) { - hi = params.MaxTxGas - } + isOsaka := opts.Config.IsOsaka(opts.Header.Number, opts.Header.Time) + isAmsterdam := opts.Config.IsAmsterdam(opts.Header.Number, opts.Header.Time) + if hi > params.MaxTxGas && isOsaka && !isAmsterdam { + hi = params.MaxTxGas } // Normalize the max fee per gas the call is willing to spend. diff --git a/miner/worker.go b/miner/worker.go index 158f1b26c0..42e3695025 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -563,7 +563,7 @@ func (miner *Miner) fillTransactions(ctx context.Context, interrupt *atomic.Int3 if env.header.ExcessBlobGas != nil { filter.BlobFee = uint256.MustFromBig(eip4844.CalcBlobFee(miner.chainConfig, env.header)) } - if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) { + if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) && !miner.chainConfig.IsAmsterdam(env.header.Number, env.header.Time) { filter.GasLimitCap = params.MaxTxGas } filter.BlobTxs = false