Skip to content

miner, core, core/txpool: implement EIP 7825 - Transaction Gas Limit Cap #31824

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions core/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 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())

Expand Down
13 changes: 13 additions & 0 deletions core/txpool/legacypool/legacypool.go
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,19 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest,
}
pool.mu.Lock()
if reset != nil {
// TODO: ensure oldHead refers to a head block that we were previously synced to
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() > 30_000_000 {
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)

Expand Down
14 changes: 14 additions & 0 deletions core/txpool/txpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,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 // the head of the current pool state
}

// New creates a new transaction pool to gather, sort and filter inbound
Expand Down Expand Up @@ -203,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)
Expand Down Expand Up @@ -257,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) {
Expand Down
3 changes: 3 additions & 0 deletions core/txpool/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,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 (30_000_000): %d", 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 {
Expand Down
44 changes: 37 additions & 7 deletions miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ type environment struct {
sidecars []*types.BlobTxSidecar
blobs int

witness *stateless.Witness
witness *stateless.Witness
chainConfig *params.ChainConfig
}

const (
Expand Down Expand Up @@ -253,12 +254,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
}

Expand Down Expand Up @@ -425,6 +427,23 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran
return nil
}

// fiterTxsAboveGas removes any transactions from a sender which exceed masGas.
// 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.
Expand Down Expand Up @@ -454,6 +473,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)
Expand All @@ -464,6 +493,7 @@ func (miner *Miner) fillTransactions(interrupt *atomic.Int32, env *environment)
prioBlobTxs[account] = txs
}
}

// Fill the block with all available pending transactions.
if len(prioPlainTxs) > 0 || len(prioBlobTxs) > 0 {
plainTxs := newTransactionsByPriceAndNonce(env.signer, prioPlainTxs, env.header.BaseFee)
Expand Down
23 changes: 12 additions & 11 deletions params/protocol_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading