diff --git a/core/block_validator.go b/core/block_validator.go index 591e472bc1..77fd8b97e2 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -84,6 +84,10 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { // Blob transactions may be present after the Cancun fork. 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()) + } + // Count the number of blobs to validate against the header's blobGasUsed blobs += len(tx.BlobHashes()) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 1de7147426..e51bcb72ca 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -1255,6 +1255,20 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, } pool.mu.Lock() if reset != nil { + if reset.newHead != nil && reset.oldHead != nil { + if pool.chainconfig.IsOsaka(reset.newHead.Number, reset.newHead.Time) && !pool.chainconfig.IsOsaka(reset.oldHead.Number, reset.oldHead.Time) { + var removeHashes []common.Hash + pool.all.Range(func(hash common.Hash, tx *types.Transaction) bool { + if tx.Gas() > params.MaxTxGas { + removeHashes = append(removeHashes, hash) + } + return true + }) + for _, hash := range removeHashes { + pool.all.Remove(hash) + } + } + } // Reset from the old head to the new, rescheduling any reorged transactions pool.reset(reset.oldHead, reset.newHead) @@ -1279,7 +1293,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, // because of another transaction (e.g. higher gas price). if reset != nil { pool.demoteUnexecutables() - if reset.newHead != nil { + if reset.newHead != reset.oldHead { if pool.chainconfig.IsLondon(new(big.Int).Add(reset.newHead.Number, big.NewInt(1))) { pendingBaseFee := eip1559.CalcBaseFee(pool.chainconfig, reset.newHead) pool.priced.SetBaseFee(pendingBaseFee) diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index b5470cd7fc..0b69d385a0 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -75,6 +75,9 @@ 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 @@ -103,6 +106,7 @@ 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 { @@ -202,6 +206,9 @@ 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) @@ -256,6 +263,14 @@ 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 fef1a99f7b..bfadae69f2 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("transaction gas exceeded max allowed (%d): %d", 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..870446a8de 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -60,7 +60,8 @@ type environment struct { sidecars []*types.BlobTxSidecar blobs int - witness *stateless.Witness + witness *stateless.Witness + chainConfig *params.ChainConfig } const ( @@ -254,12 +255,13 @@ 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{}), + 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, }, nil } @@ -443,6 +445,23 @@ 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. @@ -472,6 +491,16 @@ 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) diff --git a/params/protocol_params.go b/params/protocol_params.go index 6b06dadaef..6fcd0674bc 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -28,17 +28,18 @@ const ( MaxGasLimit uint64 = 0x7fffffffffffffff // Maximum the gas limit (2^63-1). GenesisGasLimit uint64 = 4712388 // Gas limit of the Genesis block. - 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. - CallValueTransferGas uint64 = 9000 // Paid for CALL when the value transfer is non-zero. - CallNewAccountGas uint64 = 25000 // Paid for CALL when the destination address didn't exist prior. - TxGas uint64 = 21000 // Per transaction not creating a contract. NOTE: Not payable on data of calls between transactions. - TxGasContractCreation uint64 = 53000 // Per transaction that creates a contract. NOTE: Not payable on data of calls between transactions. - TxDataZeroGas uint64 = 4 // Per byte of data attached to a transaction that equals zero. NOTE: Not payable on data of calls between transactions. - QuadCoeffDiv uint64 = 512 // Divisor for the quadratic particle of the memory cost equation. - LogDataGas uint64 = 8 // Per byte in a LOG* operation's data. - CallStipend uint64 = 2300 // Free gas given at beginning of call. + 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. + CallValueTransferGas uint64 = 9000 // Paid for CALL when the value transfer is non-zero. + CallNewAccountGas uint64 = 25000 // Paid for CALL when the destination address didn't exist prior. + TxGas uint64 = 21000 // Per transaction not creating a contract. NOTE: Not payable on data of calls between transactions. + MaxTxGas uint64 = 30_000_000 // eip-7825 maximum transaction gas limit + TxGasContractCreation uint64 = 53000 // Per transaction that creates a contract. NOTE: Not payable on data of calls between transactions. + TxDataZeroGas uint64 = 4 // Per byte of data attached to a transaction that equals zero. NOTE: Not payable on data of calls between transactions. + QuadCoeffDiv uint64 = 512 // Divisor for the quadratic particle of the memory cost equation. + LogDataGas uint64 = 8 // Per byte in a LOG* operation's data. + CallStipend uint64 = 2300 // Free gas given at beginning of call. Keccak256Gas uint64 = 30 // Once per KECCAK256 operation. Keccak256WordGas uint64 = 6 // Once per word of the KECCAK256 operation's data.