diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 253ebe1111..9235de68fb 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -339,7 +339,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, for _, receipt := range receipts { 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)) } // EIP-7002 diff --git a/core/chain_makers.go b/core/chain_makers.go index 46cd98de61..016629b301 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -322,7 +322,7 @@ func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) { for _, r := range b.receipts { 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)) } // create EVM for system calls diff --git a/core/state_processor.go b/core/state_processor.go index 9dcb4cf07c..b4bbf51a9c 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -144,7 +144,7 @@ func postExecution(ctx context.Context, config *params.ChainConfig, block *types if config.IsPrague(block.Number(), block.Time()) { requests = [][]byte{} // 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) } // EIP-7002 @@ -348,21 +348,47 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte 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 -// BeaconDepositContract. -func ParseDepositLogs(requests *[][]byte, logs []*types.Log, config *params.ChainConfig) error { +// BeaconDepositContract. From the Amsterdam fork onwards the number of +// 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) + count := 0 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) if err != nil { return fmt.Errorf("unable to parse deposit data: %v", err) } 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 { *requests = append(*requests, deposits) } diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index e3a14bf5d6..c4b8928e3e 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -398,7 +398,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, if sim.chainConfig.IsPrague(header.Number, header.Time) { requests = [][]byte{} // 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 } // EIP-7002 diff --git a/miner/worker.go b/miner/worker.go index 42e3695025..ca6d4bcfea 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -43,6 +43,10 @@ var ( errBlockInterruptedByNewHead = errors.New("new head arrived while building block") errBlockInterruptedByRecommit = errors.New("recommit interrupt 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. @@ -66,11 +70,12 @@ type environment struct { coinbase common.Address evm *vm.EVM - header *types.Header - txs []*types.Transaction - receipts []*types.Receipt - sidecars []*types.BlobTxSidecar - blobs int + header *types.Header + txs []*types.Transaction + receipts []*types.Receipt + sidecars []*types.BlobTxSidecar + blobs int + depositRequests int // running count of EIP-6110 deposit requests, capped by EIP-8254 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) { requests = [][]byte{} // 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} } // EIP-7002 @@ -372,10 +377,13 @@ func (miner *Miner) commitTransaction(ctx context.Context, env *environment, tx if tx.Type() == types.BlobTxType { return miner.commitBlobTransaction(env, tx) } - receipt, err := miner.applyTransaction(env, tx) + receipt, snap, gp, err := miner.applyTransaction(env, tx) if err != nil { return err } + if err := miner.checkDepositCap(env, receipt, snap, gp); err != nil { + return err + } env.txs = append(env.txs, tx) env.receipts = append(env.receipts, receipt) env.size += tx.Size() @@ -396,10 +404,13 @@ func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transactio if env.blobs+len(sc.Blobs) > maxBlobs { return errors.New("max data blobs reached") } - receipt, err := miner.applyTransaction(env, tx) + receipt, snap, gp, err := miner.applyTransaction(env, tx) if err != nil { return err } + if err := miner.checkDepositCap(env, receipt, snap, gp); err != nil { + return err + } txNoBlob := tx.WithoutBlobTxSidecar() env.txs = append(env.txs, txNoBlob) 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. -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 ( snap = env.state.Snapshot() gp = env.gasPool.Snapshot() @@ -421,10 +434,32 @@ func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (* if err != nil { env.state.RevertToSnapshot(snap) env.gasPool.Set(gp) - return nil, err + return nil, 0, nil, err } 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 { @@ -522,6 +557,13 @@ func (miner *Miner) commitTransactions(ctx context.Context, env *environment, pl err := miner.commitTransaction(ctx, env, tx) 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): // 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()) diff --git a/params/protocol_params.go b/params/protocol_params.go index 9da275c486..b990699b9f 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -186,6 +186,8 @@ const ( HistoryServeWindow = 8191 // Number of blocks to serve historical block hashes for, EIP-2935. 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