diff --git a/core/block_validator.go b/core/block_validator.go index 77fd8b97e2..4c68505d12 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -85,7 +85,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { var blobs int for i, tx := range block.Transactions() { if v.config.IsOsaka(block.Number(), block.Time()) && tx.Gas() > params.MaxTxGas { - return fmt.Errorf("transaction exceeds maximum allowed gas limit (has %d gas)", tx.Gas()) + return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, tx.Gas()) } // Count the number of blobs to validate against the header's blobGasUsed 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/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index e506da228d..634248c40b 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 03c2c9b550..baa45a4410 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -531,11 +531,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 + } } } } @@ -1263,16 +1271,17 @@ 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 removeHashes []common.Hash + var hashes []common.Hash pool.all.Range(func(hash common.Hash, tx *types.Transaction) bool { if tx.Gas() > params.MaxTxGas { - removeHashes = append(removeHashes, hash) + hashes = append(hashes, hash) } return true }) - for _, hash := range removeHashes { - pool.all.Remove(hash) + for _, hash := range hashes { + pool.removeTx(hash, true, true) } } } diff --git a/core/txpool/subpool.go b/core/txpool/subpool.go index 8cfc14f164..10537092e6 100644 --- a/core/txpool/subpool.go +++ b/core/txpool/subpool.go @@ -74,9 +74,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/txpool.go b/core/txpool/txpool.go index e75eaeead6..cc8f74c1b8 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -76,9 +76,6 @@ type TxPool struct { term chan struct{} // Termination channel to detect a closed pool sync chan chan error // Testing / simulator channel to block until internal reset is done - - headLock sync.RWMutex - head *types.Header // this reflects the state from the latest pool reset } // New creates a new transaction pool to gather, sort and filter inbound @@ -107,7 +104,6 @@ func New(gasTip uint64, chain BlockChain, subpools []SubPool) (*TxPool, error) { quit: make(chan chan error), term: make(chan struct{}), sync: make(chan chan error), - head: head, } reserver := NewReservationTracker() for i, subpool := range subpools { @@ -207,9 +203,6 @@ func (p *TxPool) loop(head *types.Header) { } select { case resetDone <- newHead: - p.headLock.Lock() - p.head = newHead - p.headLock.Unlock() case <-p.term: } }(oldHead, newHead) @@ -264,14 +257,6 @@ func (p *TxPool) loop(head *types.Header) { errc <- nil } -// Head returns the header which corresponds to the most recent successful pool -// reset. -func (p *TxPool) Head() *types.Header { - p.headLock.RLock() - defer p.headLock.RUnlock() - return types.CopyHeader(p.head) -} - // SetGasTip updates the minimum gas tip required by the transaction pool for a // new transaction, and drops all transactions below this threshold. func (p *TxPool) SetGasTip(tip *big.Int) { diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 624f906405..7dd108150b 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -87,7 +87,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types 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("transaction gas exceeded max allowed (%d): %d", params.MaxTxGas, tx.Gas()) + 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. diff --git a/miner/worker.go b/miner/worker.go index 42eb7352ab..9e9f9270b0 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -59,8 +59,7 @@ type environment struct { sidecars []*types.BlobTxSidecar blobs int - witness *stateless.Witness - chainConfig *params.ChainConfig + witness *stateless.Witness } const ( @@ -254,13 +253,12 @@ func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase } // Note the passed coinbase may be different with header.Coinbase. return &environment{ - signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time), - state: state, - coinbase: coinbase, - header: header, - witness: state.Witness(), - evm: vm.NewEVM(core.NewEVMBlockContext(header, miner.chain, &coinbase), state, miner.chainConfig, vm.Config{}), - chainConfig: miner.chainConfig, + signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time), + state: state, + coinbase: coinbase, + header: header, + witness: state.Witness(), + evm: vm.NewEVM(core.NewEVMBlockContext(header, miner.chain, &coinbase), state, miner.chainConfig, vm.Config{}), }, nil } @@ -427,23 +425,6 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran return nil } -// fiterTxsAboveGas removes any transactions from the set which exceed a gas threshold. -// If a transaction is removed, all higher-nonce transactions from the same account -// will also be filtered out. -func filterTxsAboveGas(txs map[common.Address][]*txpool.LazyTransaction, maxGas uint64) { - for sender, senderTxs := range txs { - for i, tx := range senderTxs { - if tx.Gas > maxGas { - if i == 0 { - delete(txs, sender) - } else { - txs[sender] = txs[sender][:i] - } - } - } - } -} - // fillTransactions retrieves the pending transactions from the txpool and fills them // into the given sealing block. The transaction selection and ordering strategy can // be customized with the plugin in the future. @@ -463,7 +444,11 @@ 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) filter.OnlyPlainTxs, filter.OnlyBlobTxs = false, true @@ -473,16 +458,6 @@ func (miner *Miner) fillTransactions(interrupt *atomic.Int32, env *environment) prioPlainTxs, normalPlainTxs := make(map[common.Address][]*txpool.LazyTransaction), pendingPlainTxs prioBlobTxs, normalBlobTxs := make(map[common.Address][]*txpool.LazyTransaction), pendingBlobTxs - // if we have just entered the Osaka fork, it is possible due to the async - // nature of txpool resets that the state of the pool has not finished resetting - // to a post-fork block. If so, it may not have removed txs that exceed the - // eip-7825 transaction maximum gas limit. - poolHead := miner.txpool.Head() - if env.chainConfig.IsOsaka(env.header.Number, env.header.Time) && !env.chainConfig.IsOsaka(poolHead.Number, poolHead.Time) { - filterTxsAboveGas(prioPlainTxs, params.MaxTxGas) - filterTxsAboveGas(prioBlobTxs, params.MaxTxGas) - } - for _, account := range prio { if txs := normalPlainTxs[account]; len(txs) > 0 { delete(normalPlainTxs, account)