diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index 058db160c8..60693689fe 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -111,6 +111,7 @@ type BatchPoster struct { batchReverted atomic.Bool // indicates whether data poster batch was reverted nextRevertCheckBlock int64 // the last parent block scanned for reverting batches + postedFirstBatch bool // indicates if batch poster has posted the first batch accessList func(SequencerInboxAccs, AfterDelayedMessagesRead int) types.AccessList } @@ -127,6 +128,10 @@ const ( l1BlockBoundIgnore ) +type BatchPosterDangerousConfig struct { + AllowPostingFirstBatchWhenSequencerMessageCountMismatch bool `koanf:"allow-posting-first-batch-when-sequencer-message-count-mismatch"` +} + type BatchPosterConfig struct { Enable bool `koanf:"enable"` DisableDapFallbackStoreDataOnChain bool `koanf:"disable-dap-fallback-store-data-on-chain" reload:"hot"` @@ -156,6 +161,7 @@ type BatchPosterConfig struct { L1BlockBoundBypass time.Duration `koanf:"l1-block-bound-bypass" reload:"hot"` UseAccessLists bool `koanf:"use-access-lists" reload:"hot"` GasEstimateBaseFeeMultipleBips arbmath.Bips `koanf:"gas-estimate-base-fee-multiple-bips"` + Dangerous BatchPosterDangerousConfig `koanf:"dangerous"` gasRefunder common.Address l1BlockBound l1BlockBound @@ -1265,7 +1271,37 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) } } - data, kzgBlobs, err := b.encodeAddBatch(new(big.Int).SetUint64(batchPosition.NextSeqNum), batchPosition.MessageCount, b.building.msgCount, sequencerMsg, b.building.segments.delayedMsg, b.building.use4844) + prevMessageCount := batchPosition.MessageCount + if b.config().Dangerous.AllowPostingFirstBatchWhenSequencerMessageCountMismatch && !b.postedFirstBatch { + // AllowPostingFirstBatchWhenSequencerMessageCountMismatch can be used when the + // message count stored in batch poster's database gets out + // of sync with the sequencerReportedSubMessageCount stored in the parent chain. + // + // An example of when this out of sync issue can happen: + // 1. Batch poster is running fine, but then it shutdowns for more than 24h. + // 2. While the batch poster is down, someone sends a transaction to the parent chain + // smart contract to move a message from the delayed inbox to the main inbox. + // This will not update sequencerReportedSubMessageCount in the parent chain. + // 3. When batch poster starts again, the inbox reader will update the + // message count that is maintained in the batch poster's database to be equal to + // (sequencerReportedSubMessageCount that is stored in parent chain) + + // (the amount of delayed messages that were moved from the delayed inbox to the main inbox). + // At this moment the message count stored on batch poster's database gets out of sync with + // the sequencerReportedSubMessageCount stored in the parent chain. + + // When the first batch is posted, sequencerReportedSubMessageCount in + // the parent chain will be updated to be equal to the new message count provided + // by the batch poster, which will make this out of sync issue disappear. + // That is why this strategy is only applied for the first batch posted after + // startup. + + // If prevMessageCount is set to zero, sequencer inbox's smart contract allows + // to post a batch even if sequencerReportedSubMessageCount is not equal + // to the provided prevMessageCount + prevMessageCount = 0 + } + + data, kzgBlobs, err := b.encodeAddBatch(new(big.Int).SetUint64(batchPosition.NextSeqNum), prevMessageCount, b.building.msgCount, sequencerMsg, b.building.segments.delayedMsg, b.building.use4844) if err != nil { return false, err } @@ -1306,6 +1342,7 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) if err != nil { return false, err } + b.postedFirstBatch = true log.Info( "BatchPoster: batch sent", "sequenceNumber", batchPosition.NextSeqNum,