fix: skip L1 message that leads to gas limit exceeded (#455)

* skip L1 message that leads to gas limit exceeded

* add test

* do not skip l1 messages from the same account
This commit is contained in:
Péter Garamvölgyi 2023-08-08 01:59:11 +02:00 committed by GitHub
parent 2a1788f7f3
commit eda99eec35
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 91 additions and 9 deletions

View file

@ -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.

View file

@ -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()
}
}

View file

@ -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 = &params.CliqueConfig{Period: 1, Epoch: 30000}
engine = clique.New(chainConfig.Clique, db)
chainConfig.Scroll.L1Config = &params.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")
}
}

View file

@ -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
)