Skip to content

Commit a1feb7d

Browse files
committed
fix(fast-inbox): apply streaming cutoff floor on the cap-terminated final block and defer bundle accumulation (A-1382)
- The censorship cutoff floor now applies on whichever block ends the checkpoint, including the block that reaches the per-checkpoint block cap, not only the timetable's last sub-slot, so the checkpoint always consumes through the cutoff. - The lightweight builder accumulates a block's streaming message bundle only after the block is fully built, so a mid-build failure leaves the checkpoint's message list (and its inHash/rolling hash) consistent with the blocks actually built.
1 parent 7d68597 commit a1feb7d

2 files changed

Lines changed: 14 additions & 5 deletions

File tree

yarn-project/prover-client/src/light/lightweight_checkpoint_builder.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,15 +211,15 @@ export class LightweightCheckpointBuilder {
211211
// Streaming Inbox (AZIP-22 Fast Inbox): insert this block's L1-to-L2 message bundle before reading the end state,
212212
// so the block header's L1-to-L2 tree snapshot reflects it. First-in-checkpoint bundles are padded to
213213
// NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP (matching the legacy per-checkpoint insertion and the world-state
214-
// synchronizer); non-first bundles are appended compactly. The rolling hash and inHash are then recomputed over
215-
// the accumulated logical (unpadded) messages at checkpoint completion.
214+
// synchronizer); non-first bundles are appended compactly. The logical (unpadded) messages are accumulated only
215+
// once the block is fully built (below), so a mid-build failure does not pollute the checkpoint's inHash/rolling
216+
// hash; the rolling hash and inHash are recomputed over them at checkpoint completion.
216217
if (opts.l1ToL2Messages !== undefined) {
217218
if (isFirstBlock) {
218219
await appendL1ToL2MessagesToTree(this.db, opts.l1ToL2Messages);
219220
} else {
220221
await this.db.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, opts.l1ToL2Messages);
221222
}
222-
this.l1ToL2Messages.push(...opts.l1ToL2Messages);
223223
}
224224

225225
const [msGetEndState, endState] = await elapsed(() => this.db.getStateReference());
@@ -257,6 +257,12 @@ export class LightweightCheckpointBuilder {
257257
const block = new L2Block(newArchive, header, body, this.checkpointNumber, indexWithinCheckpoint);
258258
this.blocks.push(block);
259259

260+
// Accumulate the streaming bundle now that the block is fully built, so a mid-build throw above leaves the
261+
// checkpoint's message list (and thus its inHash/rolling hash) consistent with the blocks actually built.
262+
if (opts.l1ToL2Messages !== undefined) {
263+
this.l1ToL2Messages.push(...opts.l1ToL2Messages);
264+
}
265+
260266
const [msSpongeAbsorb] = await elapsed(() => this.spongeBlob.absorb(blockBlobFields));
261267
timings.spongeAbsorb = msSpongeAbsorb;
262268
this.blobFields.push(...blockBlobFields);

yarn-project/sequencer-client/src/sequencer/checkpoint_proposal_job.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -950,9 +950,12 @@ export class CheckpointProposalJob implements Traceable {
950950
// Streaming Inbox: select this block's message bundle against the current (not-yet-advanced) consumption
951951
// cursor. The state is only advanced once the block builds successfully, so a failed build (retried in a
952952
// later sub-slot) re-derives the bundle rather than losing it. The builder inserts the bundle and rolls it
953-
// back with the fork on failure.
953+
// back with the fork on failure. The censorship cutoff floor must apply on whichever block ends the
954+
// checkpoint, which includes the block that reaches the per-checkpoint block cap, not just the timetable's
955+
// last sub-slot.
956+
const isCheckpointFinalBlock = timingInfo.isLastBlock || blocksBuilt + 1 >= this.config.maxBlocksPerCheckpoint;
954957
const selection = streamingState
955-
? await this.selectStreamingBundle(streamingState, timingInfo.isLastBlock, nowSeconds)
958+
? await this.selectStreamingBundle(streamingState, isCheckpointFinalBlock, nowSeconds)
956959
: undefined;
957960
const streamingBundle = streamingState ? (selection && selection.consume ? selection.bundle : []) : undefined;
958961

0 commit comments

Comments
 (0)