@aztec/archiver 4.0.0-nightly.20260116 → 4.0.0-nightly.20260118

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +10 -2
  2. package/dest/archiver.d.ts +3 -2
  3. package/dest/archiver.d.ts.map +1 -1
  4. package/dest/archiver.js +7 -6
  5. package/dest/modules/data_source_base.d.ts +2 -1
  6. package/dest/modules/data_source_base.d.ts.map +1 -1
  7. package/dest/modules/data_source_base.js +3 -0
  8. package/dest/modules/data_store_updater.d.ts +29 -6
  9. package/dest/modules/data_store_updater.d.ts.map +1 -1
  10. package/dest/modules/data_store_updater.js +117 -29
  11. package/dest/modules/l1_synchronizer.d.ts +5 -3
  12. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  13. package/dest/modules/l1_synchronizer.js +98 -52
  14. package/dest/store/block_store.d.ts +16 -2
  15. package/dest/store/block_store.d.ts.map +1 -1
  16. package/dest/store/block_store.js +62 -8
  17. package/dest/store/kv_archiver_store.d.ts +14 -2
  18. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  19. package/dest/store/kv_archiver_store.js +14 -0
  20. package/dest/store/log_store.d.ts +1 -1
  21. package/dest/store/log_store.d.ts.map +1 -1
  22. package/dest/store/log_store.js +69 -48
  23. package/dest/test/fake_l1_state.d.ts +18 -1
  24. package/dest/test/fake_l1_state.d.ts.map +1 -1
  25. package/dest/test/fake_l1_state.js +36 -17
  26. package/dest/test/mock_l2_block_source.d.ts +2 -1
  27. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  28. package/dest/test/mock_l2_block_source.js +4 -0
  29. package/package.json +13 -13
  30. package/src/archiver.ts +8 -6
  31. package/src/modules/data_source_base.ts +4 -0
  32. package/src/modules/data_store_updater.ts +143 -42
  33. package/src/modules/l1_synchronizer.ts +113 -61
  34. package/src/store/block_store.ts +79 -10
  35. package/src/store/kv_archiver_store.ts +19 -1
  36. package/src/store/log_store.ts +112 -76
  37. package/src/test/fake_l1_state.ts +62 -24
  38. package/src/test/mock_l2_block_source.ts +5 -0
@@ -1,5 +1,5 @@
1
1
  import { INITIAL_CHECKPOINT_NUMBER, INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
2
- import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
2
+ import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
3
3
  import { Fr } from '@aztec/foundation/curves/bn254';
4
4
  import { toArray } from '@aztec/foundation/iterable';
5
5
  import { createLogger } from '@aztec/foundation/log';
@@ -350,6 +350,23 @@ export class BlockStore {
350
350
  await this.#blockArchiveIndex.set(block.archive.root.toString(), block.number);
351
351
  }
352
352
 
353
+ /** Deletes a block and all associated data (tx effects, indices). */
354
+ private async deleteBlock(block: L2BlockNew): Promise<void> {
355
+ // Delete the block from the main blocks map
356
+ await this.#blocks.delete(block.number);
357
+
358
+ // Delete all tx effects for this block
359
+ await Promise.all(block.body.txEffects.map(tx => this.#txEffects.delete(tx.txHash.toString())));
360
+
361
+ // Delete block txs mapping
362
+ const blockHash = (await block.hash()).toString();
363
+ await this.#blockTxs.delete(blockHash);
364
+
365
+ // Clean up indices
366
+ await this.#blockHashIndex.delete(blockHash);
367
+ await this.#blockArchiveIndex.delete(block.archive.root.toString());
368
+ }
369
+
353
370
  /**
354
371
  * Unwinds checkpoints from the database
355
372
  * @param from - The tip of the chain, passed for verification purposes,
@@ -387,16 +404,11 @@ export class BlockStore {
387
404
  this.#log.warn(`Cannot remove block ${blockNumber} from the store since we don't have it`);
388
405
  continue;
389
406
  }
390
- await this.#blocks.delete(block.number);
391
- await Promise.all(block.body.txEffects.map(tx => this.#txEffects.delete(tx.txHash.toString())));
392
- const blockHash = (await block.hash()).toString();
393
- await this.#blockTxs.delete(blockHash);
394
-
395
- // Clean up indices
396
- await this.#blockHashIndex.delete(blockHash);
397
- await this.#blockArchiveIndex.delete(block.archive.root.toString());
398
407
 
399
- this.#log.debug(`Unwound block ${blockNumber} ${blockHash} for checkpoint ${checkpointNumber}`);
408
+ await this.deleteBlock(block);
409
+ this.#log.debug(
410
+ `Unwound block ${blockNumber} ${(await block.hash()).toString()} for checkpoint ${checkpointNumber}`,
411
+ );
400
412
  }
401
413
  }
402
414
 
@@ -454,6 +466,61 @@ export class BlockStore {
454
466
  return converted.filter(isDefined);
455
467
  }
456
468
 
469
+ /**
470
+ * Gets all blocks that have the given slot number.
471
+ * Iterates backwards through blocks for efficiency since we usually query for the last slot.
472
+ * @param slotNumber - The slot number to search for.
473
+ * @returns All blocks with the given slot number, in ascending block number order.
474
+ */
475
+ async getBlocksForSlot(slotNumber: SlotNumber): Promise<L2BlockNew[]> {
476
+ const blocks: L2BlockNew[] = [];
477
+
478
+ // Iterate backwards through all blocks and filter by slot number
479
+ // This is more efficient since we usually query for the most recent slot
480
+ for await (const [blockNumber, blockStorage] of this.#blocks.entriesAsync({ reverse: true })) {
481
+ const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage);
482
+ const blockSlot = block?.header.globalVariables.slotNumber;
483
+ if (block && blockSlot === slotNumber) {
484
+ blocks.push(block);
485
+ } else if (blockSlot && blockSlot < slotNumber) {
486
+ break; // Blocks are stored in slot ascending order, so we can stop searching
487
+ }
488
+ }
489
+
490
+ // Reverse to return blocks in ascending order (block number order)
491
+ return blocks.reverse();
492
+ }
493
+
494
+ /**
495
+ * Removes all blocks with block number > blockNumber.
496
+ * @param blockNumber - The block number to remove after.
497
+ * @returns The removed blocks (for event emission).
498
+ */
499
+ async unwindBlocksAfter(blockNumber: BlockNumber): Promise<L2BlockNew[]> {
500
+ return await this.db.transactionAsync(async () => {
501
+ const removedBlocks: L2BlockNew[] = [];
502
+
503
+ // Get the latest block number to determine the range
504
+ const latestBlockNumber = await this.getLatestBlockNumber();
505
+
506
+ // Iterate from blockNumber + 1 to latestBlockNumber
507
+ for (let bn = blockNumber + 1; bn <= latestBlockNumber; bn++) {
508
+ const block = await this.getBlock(BlockNumber(bn));
509
+
510
+ if (block === undefined) {
511
+ this.#log.warn(`Cannot remove block ${bn} from the store since we don't have it`);
512
+ continue;
513
+ }
514
+
515
+ removedBlocks.push(block);
516
+ await this.deleteBlock(block);
517
+ this.#log.debug(`Removed block ${bn} ${(await block.hash()).toString()}`);
518
+ }
519
+
520
+ return removedBlocks;
521
+ });
522
+ }
523
+
457
524
  async getProvenBlockNumber(): Promise<BlockNumber> {
458
525
  const provenCheckpointNumber = await this.getProvenCheckpointNumber();
459
526
  if (provenCheckpointNumber === INITIAL_CHECKPOINT_NUMBER - 1) {
@@ -538,6 +605,7 @@ export class BlockStore {
538
605
  }
539
606
  return this.getCheckpointedBlock(BlockNumber(blockNumber));
540
607
  }
608
+
541
609
  async getCheckpointedBlockByArchive(archive: Fr): Promise<CheckpointedL2Block | undefined> {
542
610
  const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString());
543
611
  if (blockNumber === undefined) {
@@ -672,6 +740,7 @@ export class BlockStore {
672
740
  const header = BlockHeader.fromBuffer(blockStorage.header);
673
741
  const archive = AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive);
674
742
  const blockHash = blockStorage.blockHash;
743
+ header.setHash(Fr.fromBuffer(blockHash));
675
744
  const blockHashString = bufferToHex(blockHash);
676
745
  const blockTxsBuffer = await this.#blockTxs.getAsync(blockHashString);
677
746
  if (blockTxsBuffer === undefined) {
@@ -1,5 +1,5 @@
1
1
  import type { L1BlockId } from '@aztec/ethereum/l1-types';
2
- import type { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
2
+ import type { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
3
3
  import type { Fr } from '@aztec/foundation/curves/bn254';
4
4
  import { toArray } from '@aztec/foundation/iterable';
5
5
  import { createLogger } from '@aztec/foundation/log';
@@ -601,4 +601,22 @@ export class KVArchiverDataStore implements ContractDataSource {
601
601
  getCheckpointData(checkpointNumber: CheckpointNumber): Promise<CheckpointData | undefined> {
602
602
  return this.#blockStore.getCheckpointData(checkpointNumber);
603
603
  }
604
+
605
+ /**
606
+ * Gets all blocks that have the given slot number.
607
+ * @param slotNumber - The slot number to search for.
608
+ * @returns All blocks with the given slot number.
609
+ */
610
+ getBlocksForSlot(slotNumber: SlotNumber): Promise<L2BlockNew[]> {
611
+ return this.#blockStore.getBlocksForSlot(slotNumber);
612
+ }
613
+
614
+ /**
615
+ * Removes all blocks with block number > blockNumber.
616
+ * @param blockNumber - The block number to remove after.
617
+ * @returns The removed blocks (for event emission).
618
+ */
619
+ removeBlocksAfter(blockNumber: BlockNumber): Promise<L2BlockNew[]> {
620
+ return this.#blockStore.unwindBlocksAfter(blockNumber);
621
+ }
604
622
  }
@@ -1,5 +1,6 @@
1
1
  import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
2
2
  import { BlockNumber } from '@aztec/foundation/branded-types';
3
+ import { filterAsync } from '@aztec/foundation/collection';
3
4
  import { Fr } from '@aztec/foundation/curves/bn254';
4
5
  import { createLogger } from '@aztec/foundation/log';
5
6
  import { BufferReader, numToUInt32BE } from '@aztec/foundation/serialize';
@@ -144,92 +145,127 @@ export class LogStore {
144
145
  return { privateTaggedLogs, publicTaggedLogs };
145
146
  }
146
147
 
147
- /**
148
- * Append new logs to the store's list.
149
- * @param blocks - The blocks for which to add the logs.
150
- * @returns True if the operation is successful.
151
- */
152
- addLogs(blocks: L2BlockNew[]): Promise<boolean> {
153
- const { privateTaggedLogs, publicTaggedLogs } = this.#extractTaggedLogs(blocks);
148
+ async #addPrivateLogs(blocks: L2BlockNew[]): Promise<void> {
149
+ const newBlocks = await filterAsync(
150
+ blocks,
151
+ async block => !(await this.#privateLogKeysByBlock.hasAsync(block.number)),
152
+ );
154
153
 
154
+ const { privateTaggedLogs } = this.#extractTaggedLogs(newBlocks);
155
155
  const keysOfPrivateLogsToUpdate = Array.from(privateTaggedLogs.keys());
156
- const keysOfPublicLogsToUpdate = Array.from(publicTaggedLogs.keys());
157
156
 
158
- return this.db.transactionAsync(async () => {
159
- const currentPrivateTaggedLogs = await Promise.all(
160
- keysOfPrivateLogsToUpdate.map(async key => ({
161
- tag: key,
162
- logBuffers: await this.#privateLogsByTag.getAsync(key),
163
- })),
164
- );
165
- currentPrivateTaggedLogs.forEach(taggedLogBuffer => {
166
- if (taggedLogBuffer.logBuffers && taggedLogBuffer.logBuffers.length > 0) {
167
- privateTaggedLogs.set(
168
- taggedLogBuffer.tag,
169
- taggedLogBuffer.logBuffers!.concat(privateTaggedLogs.get(taggedLogBuffer.tag)!),
170
- );
171
- }
172
- });
157
+ const currentPrivateTaggedLogs = await Promise.all(
158
+ keysOfPrivateLogsToUpdate.map(async key => ({
159
+ tag: key,
160
+ logBuffers: await this.#privateLogsByTag.getAsync(key),
161
+ })),
162
+ );
173
163
 
174
- const currentPublicTaggedLogs = await Promise.all(
175
- keysOfPublicLogsToUpdate.map(async key => ({
176
- key,
177
- logBuffers: await this.#publicLogsByContractAndTag.getAsync(key),
178
- })),
179
- );
180
- currentPublicTaggedLogs.forEach(taggedLogBuffer => {
181
- if (taggedLogBuffer.logBuffers && taggedLogBuffer.logBuffers.length > 0) {
182
- publicTaggedLogs.set(
183
- taggedLogBuffer.key,
184
- taggedLogBuffer.logBuffers!.concat(publicTaggedLogs.get(taggedLogBuffer.key)!),
185
- );
186
- }
187
- });
164
+ for (const taggedLogBuffer of currentPrivateTaggedLogs) {
165
+ if (taggedLogBuffer.logBuffers && taggedLogBuffer.logBuffers.length > 0) {
166
+ privateTaggedLogs.set(
167
+ taggedLogBuffer.tag,
168
+ taggedLogBuffer.logBuffers!.concat(privateTaggedLogs.get(taggedLogBuffer.tag)!),
169
+ );
170
+ }
171
+ }
172
+
173
+ for (const block of newBlocks) {
174
+ const privateTagsInBlock: string[] = [];
175
+ for (const [tag, logs] of privateTaggedLogs.entries()) {
176
+ await this.#privateLogsByTag.set(tag, logs);
177
+ privateTagsInBlock.push(tag);
178
+ }
179
+ await this.#privateLogKeysByBlock.set(block.number, privateTagsInBlock);
180
+ }
181
+ }
188
182
 
189
- for (const block of blocks) {
190
- const blockHash = await block.hash();
183
+ async #addPublicLogs(blocks: L2BlockNew[]): Promise<void> {
184
+ const newBlocks = await filterAsync(
185
+ blocks,
186
+ async block => !(await this.#publicLogKeysByBlock.hasAsync(block.number)),
187
+ );
191
188
 
192
- const privateTagsInBlock: string[] = [];
193
- for (const [tag, logs] of privateTaggedLogs.entries()) {
194
- await this.#privateLogsByTag.set(tag, logs);
195
- privateTagsInBlock.push(tag);
196
- }
197
- await this.#privateLogKeysByBlock.set(block.number, privateTagsInBlock);
189
+ const { publicTaggedLogs } = this.#extractTaggedLogs(newBlocks);
190
+ const keysOfPublicLogsToUpdate = Array.from(publicTaggedLogs.keys());
198
191
 
199
- const publicKeysInBlock: string[] = [];
200
- for (const [key, logs] of publicTaggedLogs.entries()) {
201
- await this.#publicLogsByContractAndTag.set(key, logs);
202
- publicKeysInBlock.push(key);
203
- }
204
- await this.#publicLogKeysByBlock.set(block.number, publicKeysInBlock);
205
-
206
- const publicLogsInBlock = block.body.txEffects
207
- .map((txEffect, txIndex) =>
208
- [
209
- numToUInt32BE(txIndex),
210
- numToUInt32BE(txEffect.publicLogs.length),
211
- txEffect.publicLogs.map(log => log.toBuffer()),
212
- ].flat(),
213
- )
214
- .flat();
215
-
216
- const contractClassLogsInBlock = block.body.txEffects
217
- .map((txEffect, txIndex) =>
218
- [
219
- numToUInt32BE(txIndex),
220
- numToUInt32BE(txEffect.contractClassLogs.length),
221
- txEffect.contractClassLogs.map(log => log.toBuffer()),
222
- ].flat(),
223
- )
224
- .flat();
225
-
226
- await this.#publicLogsByBlock.set(block.number, this.#packWithBlockHash(blockHash, publicLogsInBlock));
227
- await this.#contractClassLogsByBlock.set(
228
- block.number,
229
- this.#packWithBlockHash(blockHash, contractClassLogsInBlock),
192
+ const currentPublicTaggedLogs = await Promise.all(
193
+ keysOfPublicLogsToUpdate.map(async key => ({
194
+ tag: key,
195
+ logBuffers: await this.#publicLogsByContractAndTag.getAsync(key),
196
+ })),
197
+ );
198
+
199
+ for (const taggedLogBuffer of currentPublicTaggedLogs) {
200
+ if (taggedLogBuffer.logBuffers && taggedLogBuffer.logBuffers.length > 0) {
201
+ publicTaggedLogs.set(
202
+ taggedLogBuffer.tag,
203
+ taggedLogBuffer.logBuffers!.concat(publicTaggedLogs.get(taggedLogBuffer.tag)!),
230
204
  );
231
205
  }
206
+ }
207
+
208
+ for (const block of newBlocks) {
209
+ const blockHash = await block.hash();
210
+ const publicTagsInBlock: string[] = [];
211
+ for (const [tag, logs] of publicTaggedLogs.entries()) {
212
+ await this.#publicLogsByContractAndTag.set(tag, logs);
213
+ publicTagsInBlock.push(tag);
214
+ }
215
+ await this.#publicLogKeysByBlock.set(block.number, publicTagsInBlock);
216
+
217
+ const publicLogsInBlock = block.body.txEffects
218
+ .map((txEffect, txIndex) =>
219
+ [
220
+ numToUInt32BE(txIndex),
221
+ numToUInt32BE(txEffect.publicLogs.length),
222
+ txEffect.publicLogs.map(log => log.toBuffer()),
223
+ ].flat(),
224
+ )
225
+ .flat();
226
+
227
+ await this.#publicLogsByBlock.set(block.number, this.#packWithBlockHash(blockHash, publicLogsInBlock));
228
+ }
229
+ }
232
230
 
231
+ async #addContractClassLogs(blocks: L2BlockNew[]): Promise<void> {
232
+ const newBlocks = await filterAsync(
233
+ blocks,
234
+ async block => !(await this.#contractClassLogsByBlock.hasAsync(block.number)),
235
+ );
236
+
237
+ for (const block of newBlocks) {
238
+ const blockHash = await block.hash();
239
+
240
+ const contractClassLogsInBlock = block.body.txEffects
241
+ .map((txEffect, txIndex) =>
242
+ [
243
+ numToUInt32BE(txIndex),
244
+ numToUInt32BE(txEffect.contractClassLogs.length),
245
+ txEffect.contractClassLogs.map(log => log.toBuffer()),
246
+ ].flat(),
247
+ )
248
+ .flat();
249
+
250
+ await this.#contractClassLogsByBlock.set(
251
+ block.number,
252
+ this.#packWithBlockHash(blockHash, contractClassLogsInBlock),
253
+ );
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Append new logs to the store's list.
259
+ * @param blocks - The blocks for which to add the logs.
260
+ * @returns True if the operation is successful.
261
+ */
262
+ addLogs(blocks: L2BlockNew[]): Promise<boolean> {
263
+ return this.db.transactionAsync(async () => {
264
+ await Promise.all([
265
+ this.#addPrivateLogs(blocks),
266
+ this.#addPublicLogs(blocks),
267
+ this.#addContractClassLogs(blocks),
268
+ ]);
233
269
  return true;
234
270
  });
235
271
  }
@@ -10,8 +10,9 @@ import { Fr } from '@aztec/foundation/curves/bn254';
10
10
  import { EthAddress } from '@aztec/foundation/eth-address';
11
11
  import { createLogger } from '@aztec/foundation/log';
12
12
  import { RollupAbi } from '@aztec/l1-artifacts';
13
- import { CommitteeAttestation, CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
13
+ import { CommitteeAttestation, CommitteeAttestationsAndSigners, L2BlockNew } from '@aztec/stdlib/block';
14
14
  import { Checkpoint } from '@aztec/stdlib/checkpoint';
15
+ import { getSlotAtTimestamp } from '@aztec/stdlib/epoch-helpers';
15
16
  import { InboxLeaf } from '@aztec/stdlib/messaging';
16
17
  import {
17
18
  makeAndSignCommitteeAttestationsAndSigners,
@@ -39,6 +40,8 @@ export type FakeL1StateConfig = {
39
40
  rollupAddress: EthAddress;
40
41
  /** Inbox address for mock contracts. */
41
42
  inboxAddress: EthAddress;
43
+ /** Aztec slot duration in seconds */
44
+ slotDuration: number;
42
45
  };
43
46
 
44
47
  /** Options for adding a checkpoint. */
@@ -47,6 +50,8 @@ type AddCheckpointOptions = {
47
50
  l1BlockNumber: bigint;
48
51
  /** Number of L2 blocks in the checkpoint. Default: 1 */
49
52
  numBlocks?: number;
53
+ /** Or the actual blocks for the checkpoint */
54
+ blocks?: L2BlockNew[];
50
55
  /** Number of transactions per block. Default: 4 */
51
56
  txsPerBlock?: number;
52
57
  /** Max number of effects per tx (for generating large blobs). Default: undefined */
@@ -157,38 +162,32 @@ export class FakeL1State {
157
162
  });
158
163
  }
159
164
 
165
+ /**
166
+ * Creates blocks for a checkpoint without adding them to L1 state.
167
+ * Useful for creating blocks to pass to addBlock() for testing provisional block handling.
168
+ * Returns the blocks directly.
169
+ */
170
+ public async makeBlocks(checkpointNumber: CheckpointNumber, options: Partial<AddCheckpointOptions>) {
171
+ return (await this.makeCheckpointAndMessages(checkpointNumber, options)).checkpoint.blocks;
172
+ }
173
+
160
174
  /**
161
175
  * Creates and adds a checkpoint with its L1-to-L2 messages.
162
176
  * Returns both the checkpoint and the message leaves.
163
177
  * Auto-chains from lastArchive, auto-updates pending status if L1 block >= checkpoint's L1 block.
164
178
  */
165
- async addCheckpoint(checkpointNumber: CheckpointNumber, options: AddCheckpointOptions): Promise<AddCheckpointResult> {
179
+ public async addCheckpoint(
180
+ checkpointNumber: CheckpointNumber,
181
+ options: AddCheckpointOptions,
182
+ ): Promise<AddCheckpointResult> {
166
183
  this.log.warn(`Adding checkpoint ${checkpointNumber}`);
167
184
 
168
- const {
169
- l1BlockNumber,
170
- numBlocks = 1,
171
- txsPerBlock = 4,
172
- maxEffects,
173
- signers = [],
174
- slotNumber,
175
- previousArchive = this.lastArchive,
176
- timestamp,
177
- numL1ToL2Messages = 3,
178
- messagesL1BlockNumber = l1BlockNumber - 3n,
179
- } = options;
185
+ const { l1BlockNumber, signers = [], messagesL1BlockNumber = l1BlockNumber - 3n } = options;
180
186
 
181
187
  // Create the checkpoint using the stdlib helper
182
- // Only pass slotNumber and timestamp if they're defined to avoid overwriting defaults
183
- const { checkpoint, messages, lastArchive } = await mockCheckpointAndMessages(checkpointNumber, {
184
- startBlockNumber: this.getNextBlockNumber(checkpointNumber),
185
- numBlocks,
186
- numTxsPerBlock: txsPerBlock,
187
- numL1ToL2Messages,
188
- previousArchive,
189
- ...(slotNumber !== undefined ? { slotNumber } : {}),
190
- ...(timestamp !== undefined ? { timestamp } : {}),
191
- ...(maxEffects !== undefined ? { maxEffects } : {}),
188
+ const { checkpoint, messages, lastArchive } = await this.makeCheckpointAndMessages(checkpointNumber, {
189
+ ...options,
190
+ numL1ToL2Messages: options.numL1ToL2Messages ?? 3,
192
191
  });
193
192
 
194
193
  // Store the messages internally so they match the checkpoint's inHash
@@ -219,6 +218,45 @@ export class FakeL1State {
219
218
  return { checkpoint, messages };
220
219
  }
221
220
 
221
+ /** Creates a checkpoint and messages without adding them to L1 state. */
222
+ private makeCheckpointAndMessages(checkpointNumber: CheckpointNumber, options: Partial<AddCheckpointOptions>) {
223
+ const {
224
+ numBlocks = 1,
225
+ txsPerBlock = 4,
226
+ maxEffects,
227
+ slotNumber,
228
+ previousArchive = this.lastArchive,
229
+ timestamp,
230
+ l1BlockNumber,
231
+ numL1ToL2Messages = 0,
232
+ blocks,
233
+ } = options;
234
+
235
+ return mockCheckpointAndMessages(checkpointNumber, {
236
+ startBlockNumber: this.getNextBlockNumber(checkpointNumber),
237
+ numBlocks,
238
+ blocks,
239
+ numTxsPerBlock: txsPerBlock,
240
+ numL1ToL2Messages,
241
+ previousArchive,
242
+ slotNumber: slotNumber ?? (l1BlockNumber !== undefined ? this.getL2SlotAtL1Block(l1BlockNumber) : undefined),
243
+ timestamp: timestamp ?? (l1BlockNumber !== undefined ? this.getTimestampAtL1Block(l1BlockNumber) : undefined),
244
+ ...(maxEffects !== undefined ? { maxEffects } : {}),
245
+ });
246
+ }
247
+
248
+ /** Returns the L2 slot at the given L1 block (assuming all L1 blocks are mined) */
249
+ public getL2SlotAtL1Block(l1BlockNumber: bigint): SlotNumber {
250
+ const timestamp = this.getTimestampAtL1Block(l1BlockNumber);
251
+ return getSlotAtTimestamp(timestamp, this.config);
252
+ }
253
+
254
+ /** Returns the timestamp at the given L1 block (assuming all L1 blocks are mined) */
255
+ public getTimestampAtL1Block(l1BlockNumber: bigint): bigint {
256
+ const { l1GenesisTime, l1StartBlock, ethereumSlotDuration } = this.config;
257
+ return l1GenesisTime + (l1BlockNumber - l1StartBlock) * BigInt(ethereumSlotDuration);
258
+ }
259
+
222
260
  /**
223
261
  * Sets the current L1 block number.
224
262
  * Auto-updates pending checkpoint number based on visible checkpoints.
@@ -255,6 +255,11 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
255
255
  return Promise.resolve(blocks);
256
256
  }
257
257
 
258
+ getBlocksForSlot(slotNumber: SlotNumber): Promise<L2BlockNew[]> {
259
+ const blocks = this.l2Blocks.filter(b => b.header.globalVariables.slotNumber === slotNumber);
260
+ return Promise.resolve(blocks.map(b => b.toL2Block()));
261
+ }
262
+
258
263
  async getBlockHeadersForEpoch(epochNumber: EpochNumber): Promise<BlockHeader[]> {
259
264
  const blocks = await this.getBlocksForEpoch(epochNumber);
260
265
  return blocks.map(b => b.getBlockHeader());