这篇文章上次修改于 287 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

结论

根据 sequencer 介绍,sequencer 一般几分钟就会向 L1 提交一次 batch

如果发生如下情况,则 sequencer 不会向 L1 提交 batch:

  • 没有新的 tx,不过即便没有用户的 tx,在每个 L2 块形成时也会产生一笔 internal tx
  • 没有设置 config.l1BlockBound = l1BlockBoundIgnore,且块号或时间戳超过 L1 bounds
  • 如果延时没达到 1h 且 batch 还没满

    满足如下条件之一则形成一个 batch

    • 数据量 >= 16 MiB
    • segment 数量 >= 100 * 1024
    • compressedBuffer.Len() >= 100000 - 40

另外,如果设置了 config.DataPoster.UseNoOpStorage = true,则会等到 L1 交易确认后才会进行查看下一次 batch

详细信息

只贴关键代码

每隔开 10s 调用一次 maybePostSequencerBatch

func (b *BatchPoster) Start(ctxIn context.Context) {
    ...
    b.CallIteratively(func(ctx context.Context) time.Duration {
        ...
        if !b.redisLock.AttemptLock(ctx) {
            b.building = nil
            return b.config().PollInterval // 10 * time.Second
        }
        posted, err := b.maybePostSequencerBatch(ctx)
        ...
        if err != nil {
            ...
            return b.config().ErrorDelay // 10 * time.Second
        } else if posted {
            return 0
        } else {
            return b.config().PollInterval // 10 * time.Second
        }
    }
    ...
}
func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) {
    // 1. 没有新的 tx,则不会提交 batch
    ...
    msgCount, err := b.streamer.GetMessageCount()
    ...
    if msgCount <= batchPosition.MessageCount {
        // There's nothing after the newest batch, therefore batch posting was not required
        return false, nil
    }
    ...
    forcePostBatch := time.Since(firstMsgTime) >= config.MaxDelay // 1h
    ...
    // 2. 块号或时间戳超过 L1 bounds,则不会提交 batch
    for b.building.msgCount < msgCount {
        ...
        if msg.Message.Header.BlockNumber > l1BoundMaxBlockNumber || msg.Message.Header.Timestamp > l1BoundMaxTimestamp {
            b.lastHitL1Bounds = time.Now()
            log.Info(
                "not posting more messages because block number or timestamp exceed L1 bounds",
                "blockNumber", msg.Message.Header.BlockNumber,
                "l1BoundMaxBlockNumber", l1BoundMaxBlockNumber,
                "timestamp", msg.Message.Header.Timestamp,
                "l1BoundMaxTimestamp", l1BoundMaxTimestamp,
            )
            break
        }
        ...
        success, err := b.building.segments.AddMessage(msg)
        ...
        if !success {
            // this batch is full
            if !config.WaitForMaxDelay {
                forcePostBatch = true
            }
            b.building.haveUsefulMessage = true
            break
        }
        if msg.Message.Header.Kind != arbostypes.L1MessageType_BatchPostingReport {
            b.building.haveUsefulMessage = true
        }
        b.building.msgCount++
    }
    // 3. 如果不是 forcePostBatch 且 batch 还没满,则不会提交 batch
    // 满足如下条件之一则形成一个 batch
    // 1) 数据量 >= 16 MiB
    // 2) segment 数量 >= 100 * 1024
    // 3) compressedBuffer.Len() >= 100000 - 40
    if !forcePostBatch || !b.building.haveUsefulMessage {
        // the batch isn't full yet and we've posted a batch recently
        // don't post anything for now
        return false, nil
    }
    // 原因 2 会可能会导致 b.building.segments.rawSegments 为空
    sequencerMsg, err := b.building.segments.CloseAndGetBytes()
    ...
    if sequencerMsg == nil {
        log.Debug("BatchPoster: batch nil", "sequence nr.", batchPosition.NextSeqNum, "from", batchPosition.MessageCount, "prev delayed", batchPosition.DelayedMessageCount)
        b.building = nil // a closed batchSegments can't be reused
        return false, nil
    }
    ...
    // 可能要等待 L1 交易被确认
    // If we aren't queueing up transactions, wait for the receipt before moving on to the next batch.
    if config.DataPoster.UseNoOpStorage {
        receipt, err := b.l1Reader.WaitForTxApproval(ctx, tx)
        if err != nil {
            return false, fmt.Errorf("error waiting for tx receipt: %w", err)
        }
        log.Info("Got successful receipt from batch poster transaction", "txHash", tx.Hash(), "blockNumber", receipt.BlockNumber, "blockHash", receipt.BlockHash)
    }

    return true, nil
    }
}

原因 3 中,batch full 满足如下条件之一即可

  • 数据量 >= 16 MiB
  • segment 数量 >= 100 * 1024
  • compressedBuffer.Len() >= 100000 - 40
func (s *batchSegments) testForOverflow(isHeader bool) (bool, error) {
    // we've reached the max decompressed size
    if s.totalUncompressedSize > arbstate.MaxDecompressedLen {
        return true, nil
    }
    // we've reached the max number of segments
    if len(s.rawSegments) >= arbstate.MaxSegmentsPerSequencerMessage {
        return true, nil
    }
    ...
    err := s.compressedWriter.Flush()
    ...
    s.lastCompressedSize = s.compressedBuffer.Len()
    s.newUncompressedSize = 0
    if s.lastCompressedSize >= s.sizeLimit {
        return true, nil
    }
    return false, nil
}