diff --git a/core/block_validator.go b/core/block_validator.go index b21421b933..15af49741d 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -168,7 +168,7 @@ func (v *BlockValidator) ValidateL1Messages(block *types.Block) error { // skipped messages // TODO: consider verifying that skipped messages overflow for index := queueIndex; index < txQueueIndex; index++ { - log.Debug("Skipped L1 message", "block", block.Hash().String(), "queueIndex", index) + log.Debug("Skipped L1 message", "queueIndex", index, "tx", tx.Hash().String(), "block", block.Hash().String()) if exists := it.Next(); !exists { // the message in this block is not available in our local db. diff --git a/miner/worker.go b/miner/worker.go index da9c05baef..df09e9fb75 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1002,6 +1002,18 @@ loop: logs, err := w.commitTransaction(tx, coinbase) switch { + case errors.Is(err, core.ErrGasLimitReached) && tx.IsL1MessageTx(): + // If this block already contains some L1 messages, + // terminate here and try again in the next block. + if w.current.l1TxCount > 0 { + break loop + } + // A single L1 message leads to out-of-gas. Skip it. + queueIndex := tx.AsL1MessageTx().QueueIndex + log.Info("Skipping L1 message", "queueIndex", queueIndex, "tx", tx.Hash().String(), "block", w.current.header.Number, "reason", "gas limit exceeded") + w.current.nextL1MsgIndex = queueIndex + 1 + txs.Shift() + case errors.Is(err, core.ErrGasLimitReached): // Pop the current out-of-gas transaction without shifting in the next from the account log.Trace("Gas limit exceeded for current block", "sender", from) @@ -1021,8 +1033,10 @@ loop: // Everything ok, collect the logs and shift in the next transaction from the same account coalescedLogs = append(coalescedLogs, logs...) if tx.IsL1MessageTx() { + queueIndex := tx.AsL1MessageTx().QueueIndex + log.Debug("Including L1 message", "queueIndex", queueIndex, "tx", tx.Hash().String()) w.current.l1TxCount++ - w.current.nextL1MsgIndex = tx.AsL1MessageTx().QueueIndex + 1 + w.current.nextL1MsgIndex = queueIndex + 1 } w.current.tcount++ w.current.blockSize += tx.Size() @@ -1038,7 +1052,7 @@ loop: // don't pop or shift, just quit the loop immediately; // though it might still be possible to add some "smaller" txs, // but it's a trade-off between tracing overhead & block usage rate - log.Trace("Circuit capacity limit reached in a block", "acc_rows", w.current.accRows, "tx", tx.Hash()) + log.Trace("Circuit capacity limit reached in a block", "acc_rows", w.current.accRows, "tx", tx.Hash().String()) circuitCapacityReached = true break loop @@ -1047,33 +1061,39 @@ loop: // because we shouldn't skip the entire txs from the same account. // This is also useful for skipping "problematic" L1MessageTxs. queueIndex := tx.AsL1MessageTx().QueueIndex - log.Trace("Circuit capacity limit reached for a single tx", "tx", tx.Hash(), "queueIndex", queueIndex) + log.Trace("Circuit capacity limit reached for a single tx", "tx", tx.Hash().String(), "queueIndex", queueIndex) + log.Info("Skipping L1 message", "queueIndex", queueIndex, "tx", tx.Hash().String(), "block", w.current.header.Number, "reason", "row consumption overflow") w.current.nextL1MsgIndex = queueIndex + 1 txs.Shift() case (errors.Is(err, circuitcapacitychecker.ErrTxRowConsumptionOverflow) && !tx.IsL1MessageTx()): // Circuit capacity check: L2MessageTx row consumption too high, skip the account. // This is also useful for skipping "problematic" L2MessageTxs. - log.Trace("Circuit capacity limit reached for a single tx", "tx", tx.Hash()) + log.Trace("Circuit capacity limit reached for a single tx", "tx", tx.Hash().String()) txs.Pop() case (errors.Is(err, circuitcapacitychecker.ErrUnknown) && tx.IsL1MessageTx()): // Circuit capacity check: unknown circuit capacity checker error for L1MessageTx, // shift to the next from the account because we shouldn't skip the entire txs from the same account queueIndex := tx.AsL1MessageTx().QueueIndex - log.Trace("Unknown circuit capacity checker error for L1MessageTx", "tx", tx.Hash(), "queueIndex", queueIndex) + log.Trace("Unknown circuit capacity checker error for L1MessageTx", "tx", tx.Hash().String(), "queueIndex", queueIndex) + log.Info("Skipping L1 message", "queueIndex", queueIndex, "tx", tx.Hash().String(), "block", w.current.header.Number, "reason", "unknown row consumption error") w.current.nextL1MsgIndex = queueIndex + 1 txs.Shift() case (errors.Is(err, circuitcapacitychecker.ErrUnknown) && !tx.IsL1MessageTx()): // Circuit capacity check: unknown circuit capacity checker error for L2MessageTx, skip the account - log.Trace("Unknown circuit capacity checker error for L2MessageTx", "tx", tx.Hash()) + log.Trace("Unknown circuit capacity checker error for L2MessageTx", "tx", tx.Hash().String()) txs.Pop() default: // Strange error, discard the transaction and get the next in line (note, the // nonce-too-high clause will prevent us from executing in vain). - log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err) + log.Debug("Transaction failed, account skipped", "hash", tx.Hash().String(), "err", err) + if tx.IsL1MessageTx() { + queueIndex := tx.AsL1MessageTx().QueueIndex + log.Info("Skipping L1 message", "queueIndex", queueIndex, "tx", tx.Hash().String(), "block", w.current.header.Number, "reason", "strange error", "err", err) + } txs.Shift() } } diff --git a/miner/worker_test.go b/miner/worker_test.go index 78ce61df03..ad59676f4b 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -731,3 +731,65 @@ func TestL1MsgCorrectOrder(t *testing.T) { t.Fatalf("timeout") } } + +func TestL1MessageOverGasLimit(t *testing.T) { + assert := assert.New(t) + var ( + engine consensus.Engine + chainConfig *params.ChainConfig + db = rawdb.NewMemoryDatabase() + ) + msgs := []types.L1MessageTx{ + {QueueIndex: 0, Gas: 10000000, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{2}}, + {QueueIndex: 1, Gas: 21016, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{2}}, // same sender + {QueueIndex: 2, Gas: 21016, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{3}}} // different sender + rawdb.WriteL1Messages(db, msgs) + + chainConfig = params.AllCliqueProtocolChanges + chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} + engine = clique.New(chainConfig.Clique, db) + chainConfig.Scroll.L1Config = ¶ms.L1Config{ + NumL1MessagesPerBlock: 3, + } + + chainConfig.LondonBlock = big.NewInt(0) + w, b := newTestWorker(t, chainConfig, engine, db, 0) + defer w.close() + + // This test chain imports the mined blocks. + b.genesis.MustCommit(db) + chain, _ := core.NewBlockChain(db, nil, b.chain.Config(), engine, vm.Config{ + Debug: true, + Tracer: vm.NewStructLogger(&vm.LogConfig{EnableMemory: true, EnableReturnData: true})}, nil, nil, false) + defer chain.Stop() + + // Ignore empty commit here for less noise. + w.skipSealHook = func(task *task) bool { + return len(task.receipts) == 0 + } + + // Wait for mined blocks. + sub := w.mux.Subscribe(core.NewMinedBlockEvent{}) + defer sub.Unsubscribe() + + // Start mining! + w.start() + + select { + case ev := <-sub.Chan(): + block := ev.Data.(core.NewMinedBlockEvent).Block + if _, err := chain.InsertChain([]*types.Block{block}); err != nil { + t.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err) + } + + // Should contain 2, not 1 + // i.e. we should only skip 1 message + assert.Equal(2, len(block.Transactions())) + + queueIndex := rawdb.ReadFirstQueueIndexNotInL2Block(db, block.Hash()) + assert.NotNil(queueIndex) + assert.Equal(uint64(3), *queueIndex) + case <-time.After(3 * time.Second): + t.Fatalf("timeout") + } +} diff --git a/params/version.go b/params/version.go index ef1265bc48..31e70d0312 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 4 // Major version component of the current release VersionMinor = 3 // Minor version component of the current release - VersionPatch = 30 // Patch version component of the current release + VersionPatch = 31 // Patch version component of the current release VersionMeta = "sepolia" // Version metadata to append to the version string )