core: implement EIP-8254: Cap deposit requests per block

This commit is contained in:
MariusVanDerWijden 2026-05-07 15:26:14 +02:00
parent aaa2b66285
commit 94f55e5575
No known key found for this signature in database
6 changed files with 89 additions and 19 deletions

View file

@ -339,7 +339,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
for _, receipt := range receipts { for _, receipt := range receipts {
allLogs = append(allLogs, receipt.Logs...) allLogs = append(allLogs, receipt.Logs...)
} }
if err := core.ParseDepositLogs(&requests, allLogs, chainConfig); err != nil { if err := core.ParseDepositLogs(&requests, allLogs, chainConfig, vmContext.BlockNumber, vmContext.Time); err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err)) return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err))
} }
// EIP-7002 // EIP-7002

View file

@ -322,7 +322,7 @@ func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) {
for _, r := range b.receipts { for _, r := range b.receipts {
blockLogs = append(blockLogs, r.Logs...) blockLogs = append(blockLogs, r.Logs...)
} }
if err := ParseDepositLogs(&requests, blockLogs, b.cm.config); err != nil { if err := ParseDepositLogs(&requests, blockLogs, b.cm.config, b.header.Number, b.header.Time); err != nil {
panic(fmt.Sprintf("failed to parse deposit log: %v", err)) panic(fmt.Sprintf("failed to parse deposit log: %v", err))
} }
// create EVM for system calls // create EVM for system calls

View file

@ -143,7 +143,7 @@ func postExecution(ctx context.Context, config *params.ChainConfig, block *types
if config.IsPrague(block.Number(), block.Time()) { if config.IsPrague(block.Number(), block.Time()) {
requests = [][]byte{} requests = [][]byte{}
// EIP-6110 // EIP-6110
if err := ParseDepositLogs(&requests, allLogs, config); err != nil { if err := ParseDepositLogs(&requests, allLogs, config, block.Number(), block.Time()); err != nil {
return requests, fmt.Errorf("failed to parse deposit logs: %w", err) return requests, fmt.Errorf("failed to parse deposit logs: %w", err)
} }
// EIP-7002 // EIP-7002
@ -347,21 +347,47 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
return nil return nil
} }
var depositTopic = common.HexToHash("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5") // DepositTopic is the topic hash of the EIP-6110 DepositEvent.
var DepositTopic = common.HexToHash("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5")
// IsDepositLog reports whether the log is an EIP-6110 deposit event emitted
// by the given deposit contract address.
func IsDepositLog(log *types.Log, depositContract common.Address) bool {
return log.Address == depositContract && len(log.Topics) > 0 && log.Topics[0] == DepositTopic
}
// CountDepositLogs returns the number of EIP-6110 deposit events present in
// the given logs.
func CountDepositLogs(logs []*types.Log, depositContract common.Address) int {
count := 0
for _, log := range logs {
if IsDepositLog(log, depositContract) {
count++
}
}
return count
}
// ParseDepositLogs extracts the EIP-6110 deposit values from logs emitted by // ParseDepositLogs extracts the EIP-6110 deposit values from logs emitted by
// BeaconDepositContract. // BeaconDepositContract. From the Amsterdam fork onwards the number of
func ParseDepositLogs(requests *[][]byte, logs []*types.Log, config *params.ChainConfig) error { // deposits per block is capped to params.MaxDepositRequestsPerBlock by
// EIP-8254.
func ParseDepositLogs(requests *[][]byte, logs []*types.Log, config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) error {
deposits := make([]byte, 1) // note: first byte is 0x00 (== deposit request type) deposits := make([]byte, 1) // note: first byte is 0x00 (== deposit request type)
count := 0
for _, log := range logs { for _, log := range logs {
if log.Address == config.DepositContractAddress && len(log.Topics) > 0 && log.Topics[0] == depositTopic { if IsDepositLog(log, config.DepositContractAddress) {
request, err := types.DepositLogToRequest(log.Data) request, err := types.DepositLogToRequest(log.Data)
if err != nil { if err != nil {
return fmt.Errorf("unable to parse deposit data: %v", err) return fmt.Errorf("unable to parse deposit data: %v", err)
} }
deposits = append(deposits, request...) deposits = append(deposits, request...)
count++
} }
} }
if config.IsAmsterdam(blockNumber, blockTime) && count > params.MaxDepositRequestsPerBlock {
return fmt.Errorf("too many deposit requests in block: have %d, max %d", count, params.MaxDepositRequestsPerBlock)
}
if len(deposits) > 1 { if len(deposits) > 1 {
*requests = append(*requests, deposits) *requests = append(*requests, deposits)
} }

View file

@ -398,7 +398,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
if sim.chainConfig.IsPrague(header.Number, header.Time) { if sim.chainConfig.IsPrague(header.Number, header.Time) {
requests = [][]byte{} requests = [][]byte{}
// EIP-6110 // EIP-6110
if err := core.ParseDepositLogs(&requests, allLogs, sim.chainConfig); err != nil { if err := core.ParseDepositLogs(&requests, allLogs, sim.chainConfig, header.Number, header.Time); err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
// EIP-7002 // EIP-7002

View file

@ -43,6 +43,10 @@ var (
errBlockInterruptedByNewHead = errors.New("new head arrived while building block") errBlockInterruptedByNewHead = errors.New("new head arrived while building block")
errBlockInterruptedByRecommit = errors.New("recommit interrupt while building block") errBlockInterruptedByRecommit = errors.New("recommit interrupt while building block")
errBlockInterruptedByTimeout = errors.New("timeout while building block") errBlockInterruptedByTimeout = errors.New("timeout while building block")
// errDepositCapExceeded is returned when including a transaction would
// push the block over the EIP-8254 deposit-request cap.
errDepositCapExceeded = errors.New("deposit request cap exceeded")
) )
// maxBlobsPerBlock returns the maximum number of blobs per block. // maxBlobsPerBlock returns the maximum number of blobs per block.
@ -66,11 +70,12 @@ type environment struct {
coinbase common.Address coinbase common.Address
evm *vm.EVM evm *vm.EVM
header *types.Header header *types.Header
txs []*types.Transaction txs []*types.Transaction
receipts []*types.Receipt receipts []*types.Receipt
sidecars []*types.BlobTxSidecar sidecars []*types.BlobTxSidecar
blobs int blobs int
depositRequests int // running count of EIP-6110 deposit requests, capped by EIP-8254
witness *stateless.Witness witness *stateless.Witness
} }
@ -212,7 +217,7 @@ func (miner *Miner) generateWork(ctx context.Context, genParam *generateParams,
if miner.chainConfig.IsPrague(work.header.Number, work.header.Time) { if miner.chainConfig.IsPrague(work.header.Number, work.header.Time) {
requests = [][]byte{} requests = [][]byte{}
// EIP-6110 deposits // EIP-6110 deposits
if err := core.ParseDepositLogs(&requests, allLogs, miner.chainConfig); err != nil { if err := core.ParseDepositLogs(&requests, allLogs, miner.chainConfig, work.header.Number, work.header.Time); err != nil {
return &newPayloadResult{err: err} return &newPayloadResult{err: err}
} }
// EIP-7002 // EIP-7002
@ -372,10 +377,13 @@ func (miner *Miner) commitTransaction(ctx context.Context, env *environment, tx
if tx.Type() == types.BlobTxType { if tx.Type() == types.BlobTxType {
return miner.commitBlobTransaction(env, tx) return miner.commitBlobTransaction(env, tx)
} }
receipt, err := miner.applyTransaction(env, tx) receipt, snap, gp, err := miner.applyTransaction(env, tx)
if err != nil { if err != nil {
return err return err
} }
if err := miner.checkDepositCap(env, receipt, snap, gp); err != nil {
return err
}
env.txs = append(env.txs, tx) env.txs = append(env.txs, tx)
env.receipts = append(env.receipts, receipt) env.receipts = append(env.receipts, receipt)
env.size += tx.Size() env.size += tx.Size()
@ -396,10 +404,13 @@ func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transactio
if env.blobs+len(sc.Blobs) > maxBlobs { if env.blobs+len(sc.Blobs) > maxBlobs {
return errors.New("max data blobs reached") return errors.New("max data blobs reached")
} }
receipt, err := miner.applyTransaction(env, tx) receipt, snap, gp, err := miner.applyTransaction(env, tx)
if err != nil { if err != nil {
return err return err
} }
if err := miner.checkDepositCap(env, receipt, snap, gp); err != nil {
return err
}
txNoBlob := tx.WithoutBlobTxSidecar() txNoBlob := tx.WithoutBlobTxSidecar()
env.txs = append(env.txs, txNoBlob) env.txs = append(env.txs, txNoBlob)
env.receipts = append(env.receipts, receipt) env.receipts = append(env.receipts, receipt)
@ -412,7 +423,9 @@ func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transactio
} }
// applyTransaction runs the transaction. If execution fails, state and gas pool are reverted. // applyTransaction runs the transaction. If execution fails, state and gas pool are reverted.
func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*types.Receipt, error) { // On success the pre-execution snapshot/gas-pool checkpoint are returned so the
// caller can revert if a post-execution constraint (e.g. EIP-8254 deposit cap) fails.
func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*types.Receipt, int, *core.GasPool, error) {
var ( var (
snap = env.state.Snapshot() snap = env.state.Snapshot()
gp = env.gasPool.Snapshot() gp = env.gasPool.Snapshot()
@ -421,10 +434,32 @@ func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*
if err != nil { if err != nil {
env.state.RevertToSnapshot(snap) env.state.RevertToSnapshot(snap)
env.gasPool.Set(gp) env.gasPool.Set(gp)
return nil, err return nil, 0, nil, err
} }
env.header.GasUsed = env.gasPool.Used() env.header.GasUsed = env.gasPool.Used()
return receipt, nil return receipt, snap, gp, nil
}
// checkDepositCap enforces the EIP-8254 cap on deposit requests per block.
// If including the receipt would push the running deposit count over
// params.MaxDepositRequestsPerBlock, the state and gas pool are reverted to
// the pre-execution checkpoint and errDepositCapExceeded is returned.
func (miner *Miner) checkDepositCap(env *environment, receipt *types.Receipt, snap int, gp *core.GasPool) error {
if !miner.chainConfig.IsAmsterdam(env.header.Number, env.header.Time) {
return nil
}
added := core.CountDepositLogs(receipt.Logs, miner.chainConfig.DepositContractAddress)
if added == 0 {
return nil
}
if env.depositRequests+added > params.MaxDepositRequestsPerBlock {
env.state.RevertToSnapshot(snap)
env.gasPool.Set(gp)
env.header.GasUsed = env.gasPool.Used()
return errDepositCapExceeded
}
env.depositRequests += added
return nil
} }
func (miner *Miner) commitTransactions(ctx context.Context, env *environment, plainTxs, blobTxs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error { func (miner *Miner) commitTransactions(ctx context.Context, env *environment, plainTxs, blobTxs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error {
@ -522,6 +557,13 @@ func (miner *Miner) commitTransactions(ctx context.Context, env *environment, pl
err := miner.commitTransaction(ctx, env, tx) err := miner.commitTransaction(ctx, env, tx)
switch { switch {
case errors.Is(err, errDepositCapExceeded):
// EIP-8254: including this tx would push the block past the
// deposit-request cap. Skip this sender; subsequent senders may
// still produce non-deposit-bearing transactions that fit.
log.Trace("Skipping tx that would exceed deposit-request cap", "hash", ltx.Hash, "sender", from)
txs.Pop()
case errors.Is(err, core.ErrNonceTooLow): case errors.Is(err, core.ErrNonceTooLow):
// New head notification data race between the transaction pool and miner, shift // New head notification data race between the transaction pool and miner, shift
log.Trace("Skipping transaction with low nonce", "hash", ltx.Hash, "sender", from, "nonce", tx.Nonce()) log.Trace("Skipping transaction with low nonce", "hash", ltx.Hash, "sender", from, "nonce", tx.Nonce())

View file

@ -186,6 +186,8 @@ const (
HistoryServeWindow = 8191 // Number of blocks to serve historical block hashes for, EIP-2935. HistoryServeWindow = 8191 // Number of blocks to serve historical block hashes for, EIP-2935.
MaxBlockSize = 8_388_608 // maximum size of an RLP-encoded block MaxBlockSize = 8_388_608 // maximum size of an RLP-encoded block
MaxDepositRequestsPerBlock = 8192 // Maximum number of EIP-6110 deposit requests per block, EIP-8254.
) )
// Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation // Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation