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

Merged
merged 6 commits into from
Jul 8, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 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())

Expand Down
16 changes: 15 additions & 1 deletion core/txpool/legacypool/legacypool.go
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,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)

Expand All @@ -1286,7 +1300,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)
Expand Down
15 changes: 15 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 // this reflects the state from the latest pool reset
}

// New creates a new transaction pool to gather, sort and filter inbound
Expand Down Expand Up @@ -104,6 +107,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 {
Expand Down Expand Up @@ -203,6 +207,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 +264,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 (%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 {
Expand Down
43 changes: 36 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 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.
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 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This constant should not be in the middle of the list here, but have its own paragraph.

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