Skip to content

Commit 6182cb6

Browse files
committed
refactor(fast-inbox): make per-block message insertion the only path in the checkpoint builder (A-1384)
Every block now carries its own L1-to-L2 message bundle, so the up-front whole-checkpoint insertion has no callers left. Delete the `insertMessagesPerBlock` toggle and the checkpoint-wide `l1ToL2Messages` argument of `startCheckpoint`/`startNewCheckpoint` (which would otherwise be silently double-counted into the checkpoint's rolling hash), and promote the bundle from `addBlock`'s options bag to a required argument, since a block always states what it consumes. `openCheckpoint` keeps the list for the resume path, where it seeds the rolling hash of blocks already built, and now rejects a non-empty list when starting fresh. Tests that relied on the up-front path insert the bundle through the checkpoint's first block instead.
1 parent 1c7b5d7 commit 6182cb6

13 files changed

Lines changed: 74 additions & 103 deletions

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,19 +146,18 @@ describe('LightweightCheckpointBuilder benchmarks', () => {
146146
const constants = makeCheckpointConstants(slotNumber);
147147
const fork = await worldState.fork();
148148

149-
const builder = await LightweightCheckpointBuilder.startNewCheckpoint(
149+
const builder = LightweightCheckpointBuilder.startNewCheckpoint(
150150
CheckpointNumber(1),
151151
constants,
152152
[],
153-
[],
154153
Fr.ZERO,
155154
fork,
156155
);
157156

158157
const globalVariables = makeGlobalVariables(blockNumber, slotNumber);
159158
const txs = await timesAsync(numTxs, i => makeTx(globalVariables, 5000 + i));
160159

161-
const { timings } = await builder.addBlock(globalVariables, txs, { insertTxsEffects: true });
160+
const { timings } = await builder.addBlock(globalVariables, txs, [], { insertTxsEffects: true });
162161

163162
const prefix = `addBlock/${label}/${numTxs} txs`;
164163
for (const [step, ms] of Object.entries(timings)) {

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

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -97,24 +97,22 @@ describe('LightweightCheckpointBuilder', () => {
9797
const blockNumber = BlockNumber(1);
9898

9999
const constants = makeCheckpointConstants(slotNumber);
100-
const l1ToL2Messages: Fr[] = [];
101100
const previousCheckpointOutHashes: Fr[] = [];
102101

103102
const fork = await worldState.fork();
104103

105104
// Use LightweightCheckpointBuilder directly
106-
const checkpointBuilder = await LightweightCheckpointBuilder.startNewCheckpoint(
105+
const checkpointBuilder = LightweightCheckpointBuilder.startNewCheckpoint(
107106
checkpointNumber,
108107
constants,
109-
l1ToL2Messages,
110108
previousCheckpointOutHashes,
111109
Fr.ZERO,
112110
fork,
113111
);
114112

115113
// Build empty block
116114
const globalVariables = makeGlobalVariables(blockNumber, slotNumber);
117-
const { block } = await checkpointBuilder.addBlock(globalVariables, [], { insertTxsEffects: true });
115+
const { block } = await checkpointBuilder.addBlock(globalVariables, [], [], { insertTxsEffects: true });
118116

119117
expect(block.header.globalVariables.blockNumber).toEqual(blockNumber);
120118

@@ -138,16 +136,14 @@ describe('LightweightCheckpointBuilder', () => {
138136
const blockNumber = BlockNumber(1);
139137

140138
const constants = makeCheckpointConstants(slotNumber);
141-
const l1ToL2Messages: Fr[] = [];
142139
// There are two checkpoints before this one.
143140
const previousCheckpointOutHashes = [Fr.random(), Fr.random()];
144141

145142
const fork = await worldState.fork();
146143

147-
const checkpointBuilder = await LightweightCheckpointBuilder.startNewCheckpoint(
144+
const checkpointBuilder = LightweightCheckpointBuilder.startNewCheckpoint(
148145
checkpointNumber,
149146
constants,
150-
l1ToL2Messages,
151147
previousCheckpointOutHashes,
152148
Fr.ZERO,
153149
fork,
@@ -161,7 +157,7 @@ describe('LightweightCheckpointBuilder', () => {
161157
tx.txEffect.l2ToL1Msgs.push(...msgs);
162158

163159
// Build block with tx - insertTxsEffects will handle inserting side effects
164-
const { block } = await checkpointBuilder.addBlock(globalVariables, [tx], {
160+
const { block } = await checkpointBuilder.addBlock(globalVariables, [tx], [], {
165161
insertTxsEffects: true,
166162
});
167163

@@ -190,15 +186,13 @@ describe('LightweightCheckpointBuilder', () => {
190186
const blockNumber = BlockNumber(1);
191187

192188
const constants = makeCheckpointConstants(slotNumber);
193-
const l1ToL2Messages: Fr[] = [];
194189
const previousCheckpointOutHashes: Fr[] = [];
195190

196191
const fork = await worldState.fork();
197192

198-
const checkpointBuilder = await LightweightCheckpointBuilder.startNewCheckpoint(
193+
const checkpointBuilder = LightweightCheckpointBuilder.startNewCheckpoint(
199194
checkpointNumber,
200195
constants,
201-
l1ToL2Messages,
202196
previousCheckpointOutHashes,
203197
Fr.ZERO,
204198
fork,
@@ -209,7 +203,7 @@ describe('LightweightCheckpointBuilder', () => {
209203
const txs = await timesAsync(3, i => makeProcessedTx(globalVariables, 1000 + i));
210204

211205
// Build block with txs - insertTxsEffects will handle inserting side effects
212-
const { block } = await checkpointBuilder.addBlock(globalVariables, txs, {
206+
const { block } = await checkpointBuilder.addBlock(globalVariables, txs, [], {
213207
insertTxsEffects: true,
214208
});
215209

@@ -230,15 +224,13 @@ describe('LightweightCheckpointBuilder', () => {
230224
const slotNumber = SlotNumber(15);
231225

232226
const constants = makeCheckpointConstants(slotNumber);
233-
const l1ToL2Messages: Fr[] = [];
234227
const previousCheckpointOutHashes: Fr[] = [];
235228

236229
const fork = await worldState.fork();
237230

238-
const checkpointBuilder = await LightweightCheckpointBuilder.startNewCheckpoint(
231+
const checkpointBuilder = LightweightCheckpointBuilder.startNewCheckpoint(
239232
checkpointNumber,
240233
constants,
241-
l1ToL2Messages,
242234
previousCheckpointOutHashes,
243235
Fr.ZERO,
244236
fork,
@@ -256,7 +248,7 @@ describe('LightweightCheckpointBuilder', () => {
256248
const txs = await timesAsync(txsPerBlock, j => makeProcessedTx(globalVariables, 2000 + i * 10 + j));
257249

258250
// Build block - insertTxsEffects will handle inserting side effects
259-
const { block } = await checkpointBuilder.addBlock(globalVariables, txs, {
251+
const { block } = await checkpointBuilder.addBlock(globalVariables, txs, [], {
260252
insertTxsEffects: true,
261253
});
262254

@@ -284,15 +276,13 @@ describe('LightweightCheckpointBuilder', () => {
284276
const slotNumber = SlotNumber(15);
285277

286278
const constants = makeCheckpointConstants(slotNumber);
287-
const l1ToL2Messages: Fr[] = [];
288279
const previousCheckpointOutHashes: Fr[] = [];
289280

290281
const fork = await worldState.fork();
291282

292-
const checkpointBuilder = await LightweightCheckpointBuilder.startNewCheckpoint(
283+
const checkpointBuilder = LightweightCheckpointBuilder.startNewCheckpoint(
293284
checkpointNumber,
294285
constants,
295-
l1ToL2Messages,
296286
previousCheckpointOutHashes,
297287
Fr.ZERO,
298288
fork,
@@ -309,15 +299,13 @@ describe('LightweightCheckpointBuilder', () => {
309299
const slotNumber = SlotNumber(15);
310300

311301
const constants = makeCheckpointConstants(slotNumber);
312-
const l1ToL2Messages: Fr[] = [];
313302
const previousCheckpointOutHashes: Fr[] = [];
314303

315304
const fork = await worldState.fork();
316305

317-
const checkpointBuilder = await LightweightCheckpointBuilder.startNewCheckpoint(
306+
const checkpointBuilder = LightweightCheckpointBuilder.startNewCheckpoint(
318307
checkpointNumber,
319308
constants,
320-
l1ToL2Messages,
321309
previousCheckpointOutHashes,
322310
Fr.ZERO,
323311
fork,
@@ -326,13 +314,13 @@ describe('LightweightCheckpointBuilder', () => {
326314
// Add first block with txs - insertTxsEffects will handle inserting side effects
327315
const globalVariables1 = makeGlobalVariables(BlockNumber(1), slotNumber);
328316
const txs1 = await timesAsync(2, i => makeProcessedTx(globalVariables1, 3000 + i));
329-
await checkpointBuilder.addBlock(globalVariables1, txs1, {
317+
await checkpointBuilder.addBlock(globalVariables1, txs1, [], {
330318
insertTxsEffects: true,
331319
});
332320

333321
// Try to add second block with no txs - this should fail
334322
const globalVariables2 = makeGlobalVariables(BlockNumber(2), slotNumber);
335-
await expect(checkpointBuilder.addBlock(globalVariables2, [], { insertTxsEffects: true })).rejects.toThrow(
323+
await expect(checkpointBuilder.addBlock(globalVariables2, [], [], { insertTxsEffects: true })).rejects.toThrow(
336324
/first block/,
337325
);
338326

@@ -344,15 +332,13 @@ describe('LightweightCheckpointBuilder', () => {
344332
const slotNumber = SlotNumber(15);
345333

346334
const constants = makeCheckpointConstants(slotNumber);
347-
const l1ToL2Messages: Fr[] = [];
348335
const previousCheckpointOutHashes: Fr[] = [];
349336

350337
const fork = await worldState.fork();
351338

352-
const checkpointBuilder = await LightweightCheckpointBuilder.startNewCheckpoint(
339+
const checkpointBuilder = LightweightCheckpointBuilder.startNewCheckpoint(
353340
checkpointNumber,
354341
constants,
355-
l1ToL2Messages,
356342
previousCheckpointOutHashes,
357343
Fr.ZERO,
358344
fork,
@@ -363,7 +349,7 @@ describe('LightweightCheckpointBuilder', () => {
363349
const wrongBlockNumber = BlockNumber(5);
364350
const globalVariables = makeGlobalVariables(wrongBlockNumber, slotNumber);
365351

366-
await expect(checkpointBuilder.addBlock(globalVariables, [], { insertTxsEffects: true })).rejects.toThrow(
352+
await expect(checkpointBuilder.addBlock(globalVariables, [], [], { insertTxsEffects: true })).rejects.toThrow(
367353
/Archive tree next leaf index mismatch/,
368354
);
369355

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

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {
3030
/**
3131
* Builds a checkpoint and its header and the blocks in it from a set of processed tx without running any circuits.
3232
*
33-
* It updates the l1-to-l2 message tree when starting a new checkpoint, and then updates the archive tree when each block is added.
33+
* Each added block inserts its own L1-to-L2 message bundle into the message tree and then updates the archive tree.
3434
* Finally completes the checkpoint by computing its header.
3535
*/
3636
export class LightweightCheckpointBuilder {
@@ -60,29 +60,24 @@ export class LightweightCheckpointBuilder {
6060
this.logger.debug('Starting new checkpoint', { constants, l1ToL2Messages, feeAssetPriceModifier });
6161
}
6262

63-
static async startNewCheckpoint(
63+
/**
64+
* Starts a fresh checkpoint. The checkpoint's L1-to-L2 messages are not supplied here: every block brings its own
65+
* bundle to {@link addBlock}, which inserts it into the tree and accumulates it into the checkpoint's message list.
66+
*/
67+
static startNewCheckpoint(
6468
checkpointNumber: CheckpointNumber,
6569
constants: CheckpointGlobalVariables,
66-
l1ToL2Messages: Fr[],
6770
previousCheckpointOutHashes: Fr[],
6871
previousInboxRollingHash: Fr,
6972
db: MerkleTreeWriteOperations,
7073
bindings?: LoggerBindings,
7174
feeAssetPriceModifier: bigint = 0n,
72-
// Streaming Inbox (AZIP-22 Fast Inbox): messages are inserted per block via `addBlock`, so `l1ToL2Messages` here
73-
// is empty and the up-front checkpoint-wide insertion is skipped.
74-
insertMessagesPerBlock: boolean = false,
75-
): Promise<LightweightCheckpointBuilder> {
76-
// Insert l1-to-l2 messages into the tree (legacy flow: the whole checkpoint's messages up front).
77-
if (!insertMessagesPerBlock) {
78-
await appendL1ToL2MessagesToTree(db, l1ToL2Messages);
79-
}
80-
75+
): LightweightCheckpointBuilder {
8176
return new LightweightCheckpointBuilder(
8277
checkpointNumber,
8378
constants,
8479
feeAssetPriceModifier,
85-
l1ToL2Messages,
80+
[],
8681
previousCheckpointOutHashes,
8782
previousInboxRollingHash,
8883
db,
@@ -93,8 +88,8 @@ export class LightweightCheckpointBuilder {
9388
/**
9489
* Resumes building a checkpoint from existing blocks. This is used for validator re-execution
9590
* where blocks have already been built and their effects are already in the database.
96-
* Unlike startNewCheckpoint, this does NOT append l1ToL2Messages to the tree since they
97-
* were already added when the blocks were originally built.
91+
* `l1ToL2Messages` is the whole checkpoint's message list as consumed by the existing blocks: it seeds the
92+
* checkpoint's rolling hash and is not inserted into the tree, since the blocks already inserted it.
9893
*/
9994
static async resumeCheckpoint(
10095
checkpointNumber: CheckpointNumber,
@@ -172,18 +167,20 @@ export class LightweightCheckpointBuilder {
172167
/**
173168
* Adds a new block to the checkpoint. The tx effects must have already been inserted into the db if
174169
* this is called after tx processing, if that's not the case, then set `insertTxsEffects` to true.
170+
* @param l1ToL2Messages - The message leaves this block consumes from the Inbox, in insertion order.
175171
*/
176172
public async addBlock(
177173
globalVariables: GlobalVariables,
178174
txs: ProcessedTx[],
179-
opts: { insertTxsEffects?: boolean; expectedEndState?: StateReference; l1ToL2Messages?: Fr[] } = {},
175+
l1ToL2Messages: Fr[],
176+
opts: { insertTxsEffects?: boolean; expectedEndState?: StateReference } = {},
180177
): Promise<{ block: L2Block; timings: Record<string, number> }> {
181178
const timings: Record<string, number> = {};
182179
const isFirstBlock = this.blocks.length === 0;
183180

184181
// A non-first block with no txs is only allowed when it inserts a non-empty L1-to-L2 message bundle: the
185182
// message-only block shape (AZIP-22 Fast Inbox), proven by the msgs-only block root.
186-
if (!isFirstBlock && txs.length === 0 && (opts.l1ToL2Messages?.length ?? 0) === 0) {
183+
if (!isFirstBlock && txs.length === 0 && l1ToL2Messages.length === 0) {
187184
throw new Error('Cannot add an empty non-first block that carries no L1-to-L2 messages.');
188185
}
189186

@@ -213,9 +210,7 @@ export class LightweightCheckpointBuilder {
213210
// tree's current next-available index). The logical messages are accumulated only once the block is fully built
214211
// (below), so a mid-build failure does not pollute the checkpoint's rolling hash; the rolling hash is recomputed
215212
// over them at checkpoint completion.
216-
if (opts.l1ToL2Messages !== undefined) {
217-
await appendL1ToL2MessagesToTree(this.db, opts.l1ToL2Messages);
218-
}
213+
await appendL1ToL2MessagesToTree(this.db, l1ToL2Messages);
219214

220215
const [msGetEndState, endState] = await elapsed(() => this.db.getStateReference());
221216
timings.getEndState = msGetEndState;
@@ -254,9 +249,7 @@ export class LightweightCheckpointBuilder {
254249

255250
// Accumulate the streaming bundle now that the block is fully built, so a mid-build throw above leaves the
256251
// checkpoint's message list (and thus its inHash/rolling hash) consistent with the blocks actually built.
257-
if (opts.l1ToL2Messages !== undefined) {
258-
this.l1ToL2Messages.push(...opts.l1ToL2Messages);
259-
}
252+
this.l1ToL2Messages.push(...l1ToL2Messages);
260253

261254
const [msSpongeAbsorb] = await elapsed(() => this.spongeBlob.absorb(blockBlobFields));
262255
timings.spongeAbsorb = msSpongeAbsorb;

yarn-project/prover-client/src/mocks/test_context.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,10 +241,9 @@ export class TestContext {
241241

242242
const cleanFork = await this.worldState.fork();
243243
const previousCheckpointOutHashes = this.checkpointOutHashes;
244-
const builder = await LightweightCheckpointBuilder.startNewCheckpoint(
244+
const builder = LightweightCheckpointBuilder.startNewCheckpoint(
245245
checkpointNumber,
246246
{ ...constants, timestamp },
247-
l1ToL2Messages,
248247
previousCheckpointOutHashes,
249248
Fr.ZERO,
250249
cleanFork,
@@ -256,7 +255,7 @@ export class TestContext {
256255
const txs = blockTxs[i];
257256
const state = blockEndStates[i];
258257

259-
const { block } = await builder.addBlock(blockGlobalVariables[i], txs, {
258+
const { block } = await builder.addBlock(blockGlobalVariables[i], txs, i === 0 ? l1ToL2Messages : [], {
260259
expectedEndState: state,
261260
insertTxsEffects: true,
262261
});

yarn-project/sequencer-client/src/publisher/l1_publisher.integration.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -499,16 +499,15 @@ describe('L1Publisher integration', () => {
499499

500500
// Test uses 1-block-per-checkpoint
501501
const checkpointNumber = CheckpointNumber.fromBlockNumber(globalVariables.blockNumber);
502-
const builder = await LightweightCheckpointBuilder.startNewCheckpoint(
502+
const builder = LightweightCheckpointBuilder.startNewCheckpoint(
503503
checkpointNumber,
504504
checkpointConstants,
505-
l1ToL2Messages,
506505
previousCheckpointOutHashes,
507506
previousInboxRollingHash,
508507
tempFork,
509508
);
510509

511-
await builder.addBlock(globalVariables, txs, { insertTxsEffects: true });
510+
await builder.addBlock(globalVariables, txs, l1ToL2Messages, { insertTxsEffects: true });
512511
const checkpoint = await builder.completeCheckpoint();
513512

514513
await tempFork.close();

yarn-project/sequencer-client/src/sequencer/automine/automine_sequencer.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,12 +498,10 @@ export class AutomineSequencer {
498498
checkpointNumber,
499499
checkpointGlobals,
500500
feeAssetPriceModifier,
501-
[],
502501
previousCheckpointOutHashes,
503502
previousInboxRollingHash,
504503
fork,
505504
this.log.getBindings(),
506-
true,
507505
);
508506

509507
// Block building only executes txs, and automine publishes straight to L1, so the proofs are never needed.

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,11 +1441,9 @@ describe('CheckpointProposalJob', () => {
14411441

14421442
expect(checkpoint).toBeDefined();
14431443

1444-
// Streaming skips the bulk per-checkpoint fetch and tells the builder to insert messages per block.
1444+
// Streaming skips the bulk per-checkpoint fetch; every message reaches the builder through a block.
14451445
expect(l1ToL2MessageSource.getL1ToL2Messages).not.toHaveBeenCalled();
14461446
expect(checkpointsBuilder.startCheckpointCalls).toHaveLength(1);
1447-
expect(checkpointsBuilder.startCheckpointCalls[0].l1ToL2Messages).toEqual([]);
1448-
expect(checkpointsBuilder.startCheckpointCalls[0].insertMessagesPerBlock).toBe(true);
14491447

14501448
// The first block consumes the selected bundle; the second consumes nothing.
14511449
expect(checkpointBuilder.buildBlockCalls).toHaveLength(2);

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -648,9 +648,8 @@ export class CheckpointProposalJob implements Traceable {
648648
this.checkpointSimulationOverridesPlan,
649649
);
650650

651-
// Under the streaming Inbox (AZIP-22 Fast Inbox) messages are selected per block, so the checkpoint-level bulk
652-
// list stays empty and the legacy inHash is fed zero; the running values are computed block by block.
653-
const l1ToL2Messages: Fr[] = [];
651+
// Under the streaming Inbox (AZIP-22 Fast Inbox) messages are selected per block, so the legacy inHash is fed
652+
// zero; the running values are computed block by block.
654653
const inHash = Fr.ZERO;
655654

656655
// Collect the out hashes of all the checkpoints before this one in the same epoch.
@@ -697,12 +696,10 @@ export class CheckpointProposalJob implements Traceable {
697696
this.checkpointNumber,
698697
checkpointGlobalVariables,
699698
feeAssetPriceModifier,
700-
l1ToL2Messages,
701699
previousCheckpointOutHashes,
702700
previousInboxRollingHash,
703701
fork,
704702
this.log.getBindings(),
705-
true,
706703
);
707704

708705
// Options for the validator client when creating block and checkpoint proposals

0 commit comments

Comments
 (0)