diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go index 2bc4f73b60..0f39df0753 100644 --- a/cmd/evm/internal/t8ntool/transaction.go +++ b/cmd/evm/internal/t8ntool/transaction.go @@ -183,6 +183,9 @@ func Transaction(ctx *cli.Context) error { if chainConfig.IsShanghai(new(big.Int), 0) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize { r.Error = errors.New("max initcode size exceeded") } + if chainConfig.IsOsaka(new(big.Int), 0) && tx.Gas() > params.MaxTxGas { + r.Error = errors.New("gas limit exceeds maximum") + } results = append(results, r) } out, err := json.MarshalIndent(results, "", " ") diff --git a/core/error.go b/core/error.go index de95e64636..c41a7f872c 100644 --- a/core/error.go +++ b/core/error.go @@ -122,6 +122,9 @@ var ( // Message validation errors: ErrEmptyAuthList = errors.New("EIP-7702 transaction with empty auth list") ErrSetCodeTxCreate = errors.New("EIP-7702 transaction cannot be used to create contract") + + // -- EIP-7825 errors -- + ErrGasLimitTooHigh = errors.New("transaction gas limit too high") ) // EIP-7702 state transition errors. diff --git a/core/state_transition.go b/core/state_transition.go index 3ec718c52c..de4558253b 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -394,6 +394,10 @@ func (st *stateTransition) preCheck() error { return fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, msg.From) } } + // Verify tx gas limit does not exceed EIP-7825 cap. + if st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time) && msg.GasLimit > params.MaxTxGas { + return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit) + } return st.buyGas() } diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 99f7230786..dbf65033f8 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -1638,6 +1638,11 @@ func (p *BlobPool) Pending(filter txpool.PendingFilter) map[common.Address][]*tx break // blobfee too low, cannot be included, discard rest of txs from the account } } + if filter.GasLimitCap != 0 { + if tx.execGas > filter.GasLimitCap { + break // execution gas limit is too high + } + } // Transaction was accepted according to the filter, append to the pending list lazies = append(lazies, &txpool.LazyTransaction{ Pool: p, diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 1de7147426..93a003c172 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -530,11 +530,19 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address] txs := list.Flatten() // If the miner requests tip enforcement, cap the lists now - if minTipBig != nil { + if minTipBig != nil || filter.GasLimitCap != 0 { for i, tx := range txs { - if tx.EffectiveGasTipIntCmp(minTipBig, baseFeeBig) < 0 { - txs = txs[:i] - break + if minTipBig != nil { + if tx.EffectiveGasTipIntCmp(minTipBig, baseFeeBig) < 0 { + txs = txs[:i] + break + } + } + if filter.GasLimitCap != 0 { + if tx.Gas() > filter.GasLimitCap { + txs = txs[:i] + break + } } } } @@ -1255,6 +1263,21 @@ 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) { + var hashes []common.Hash + pool.all.Range(func(hash common.Hash, tx *types.Transaction) bool { + if tx.Gas() > params.MaxTxGas { + hashes = append(hashes, hash) + } + return true + }) + for _, hash := range hashes { + pool.removeTx(hash, true, true) + } + } + } // Reset from the old head to the new, rescheduling any reorged transactions pool.reset(reset.oldHead, reset.newHead) diff --git a/core/txpool/subpool.go b/core/txpool/subpool.go index ed4a42940d..f1f6056686 100644 --- a/core/txpool/subpool.go +++ b/core/txpool/subpool.go @@ -73,9 +73,10 @@ type LazyResolver interface { // a very specific call site in mind and each one can be evaluated very cheaply // by the pool implementations. Only add new ones that satisfy those constraints. type PendingFilter struct { - MinTip *uint256.Int // Minimum miner tip required to include a transaction - BaseFee *uint256.Int // Minimum 1559 basefee needed to include a transaction - BlobFee *uint256.Int // Minimum 4844 blobfee needed to include a blob transaction + MinTip *uint256.Int // Minimum miner tip required to include a transaction + BaseFee *uint256.Int // Minimum 1559 basefee needed to include a transaction + BlobFee *uint256.Int // Minimum 4844 blobfee needed to include a blob transaction + GasLimitCap uint64 // Maximum gas can be used for a single transaction execution (0 means no limit) OnlyPlainTxs bool // Return only plain EVM transactions (peer-join announces, block space filling) OnlyBlobTxs bool // Return only blob transactions (block blob-space filling) diff --git a/core/txpool/validation.go b/core/txpool/validation.go index fef1a99f7b..92381fd76c 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -90,6 +90,9 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types if rules.IsShanghai && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize { return fmt.Errorf("%w: code size %v, limit %v", core.ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSize) } + if rules.IsOsaka && 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 // transactions but may occur for transactions created using the RPC. if tx.Value().Sign() < 0 { diff --git a/miner/worker.go b/miner/worker.go index 198745ad27..9e60ce9a16 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -462,6 +462,9 @@ func (miner *Miner) fillTransactions(interrupt *atomic.Int32, env *environment) 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) { + filter.GasLimitCap = params.MaxTxGas + } filter.OnlyPlainTxs, filter.OnlyBlobTxs = true, false pendingPlainTxs := miner.txpool.Pending(filter) diff --git a/params/protocol_params.go b/params/protocol_params.go index 3676a7bdf1..2319019a95 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -28,6 +28,8 @@ const ( MaxGasLimit uint64 = 0x7fffffffffffffff // Maximum the gas limit (2^63-1). GenesisGasLimit uint64 = 4712388 // Gas limit of the Genesis block. + MaxTxGas uint64 = 30_000_000 // Maximum transaction gas limit after eip-7825. + MaximumExtraDataSize uint64 = 32 // Maximum size extra data may be after Genesis. ExpByteGas uint64 = 10 // Times ceil(log256(exponent)) for the EXP instruction. SloadGas uint64 = 50 // Multiplied by the number of 32-byte words that are copied (round up) for any *COPY operation and added.