@aztec/archiver 0.0.1-commit.8c0b8ff → 0.0.1-commit.8cb2d04d8

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 (87) hide show
  1. package/README.md +12 -6
  2. package/dest/archiver.d.ts +6 -5
  3. package/dest/archiver.d.ts.map +1 -1
  4. package/dest/archiver.js +11 -5
  5. package/dest/config.d.ts +3 -3
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +3 -2
  8. package/dest/errors.d.ts +28 -2
  9. package/dest/errors.d.ts.map +1 -1
  10. package/dest/errors.js +36 -2
  11. package/dest/factory.d.ts +2 -2
  12. package/dest/factory.d.ts.map +1 -1
  13. package/dest/factory.js +1 -3
  14. package/dest/l1/calldata_retriever.d.ts +1 -1
  15. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  16. package/dest/l1/calldata_retriever.js +2 -1
  17. package/dest/l1/data_retrieval.d.ts +8 -5
  18. package/dest/l1/data_retrieval.d.ts.map +1 -1
  19. package/dest/l1/data_retrieval.js +26 -21
  20. package/dest/modules/data_source_base.d.ts +6 -4
  21. package/dest/modules/data_source_base.d.ts.map +1 -1
  22. package/dest/modules/data_source_base.js +10 -4
  23. package/dest/modules/data_store_updater.d.ts +5 -7
  24. package/dest/modules/data_store_updater.d.ts.map +1 -1
  25. package/dest/modules/data_store_updater.js +14 -56
  26. package/dest/modules/instrumentation.d.ts +15 -2
  27. package/dest/modules/instrumentation.d.ts.map +1 -1
  28. package/dest/modules/instrumentation.js +27 -6
  29. package/dest/modules/l1_synchronizer.d.ts +3 -2
  30. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  31. package/dest/modules/l1_synchronizer.js +143 -131
  32. package/dest/modules/validation.d.ts +1 -1
  33. package/dest/modules/validation.d.ts.map +1 -1
  34. package/dest/modules/validation.js +2 -2
  35. package/dest/store/block_store.d.ts +38 -4
  36. package/dest/store/block_store.d.ts.map +1 -1
  37. package/dest/store/block_store.js +187 -63
  38. package/dest/store/contract_class_store.d.ts +2 -3
  39. package/dest/store/contract_class_store.d.ts.map +1 -1
  40. package/dest/store/contract_class_store.js +1 -65
  41. package/dest/store/kv_archiver_store.d.ts +28 -13
  42. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  43. package/dest/store/kv_archiver_store.js +33 -14
  44. package/dest/store/l2_tips_cache.d.ts +2 -1
  45. package/dest/store/l2_tips_cache.d.ts.map +1 -1
  46. package/dest/store/l2_tips_cache.js +27 -7
  47. package/dest/store/log_store.d.ts +6 -3
  48. package/dest/store/log_store.d.ts.map +1 -1
  49. package/dest/store/log_store.js +47 -10
  50. package/dest/store/message_store.d.ts +5 -1
  51. package/dest/store/message_store.d.ts.map +1 -1
  52. package/dest/store/message_store.js +20 -8
  53. package/dest/test/fake_l1_state.d.ts +2 -1
  54. package/dest/test/fake_l1_state.d.ts.map +1 -1
  55. package/dest/test/fake_l1_state.js +36 -6
  56. package/dest/test/mock_l1_to_l2_message_source.d.ts +1 -1
  57. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  58. package/dest/test/mock_l1_to_l2_message_source.js +2 -1
  59. package/dest/test/mock_l2_block_source.d.ts +7 -2
  60. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  61. package/dest/test/mock_l2_block_source.js +28 -3
  62. package/dest/test/noop_l1_archiver.d.ts +1 -1
  63. package/dest/test/noop_l1_archiver.d.ts.map +1 -1
  64. package/dest/test/noop_l1_archiver.js +0 -1
  65. package/package.json +13 -13
  66. package/src/archiver.ts +19 -10
  67. package/src/config.ts +9 -2
  68. package/src/errors.ts +60 -2
  69. package/src/factory.ts +1 -3
  70. package/src/l1/calldata_retriever.ts +2 -1
  71. package/src/l1/data_retrieval.ts +25 -21
  72. package/src/modules/data_source_base.ts +23 -4
  73. package/src/modules/data_store_updater.ts +17 -83
  74. package/src/modules/instrumentation.ts +39 -7
  75. package/src/modules/l1_synchronizer.ts +166 -168
  76. package/src/modules/validation.ts +2 -2
  77. package/src/store/block_store.ts +243 -73
  78. package/src/store/contract_class_store.ts +1 -103
  79. package/src/store/kv_archiver_store.ts +52 -24
  80. package/src/store/l2_tips_cache.ts +58 -13
  81. package/src/store/log_store.ts +62 -20
  82. package/src/store/message_store.ts +26 -9
  83. package/src/structs/inbox_message.ts +1 -1
  84. package/src/test/fake_l1_state.ts +53 -9
  85. package/src/test/mock_l1_to_l2_message_source.ts +1 -0
  86. package/src/test/mock_l2_block_source.ts +37 -2
  87. package/src/test/noop_l1_archiver.ts +0 -1
@@ -13,15 +13,19 @@ import {
13
13
  L2Block,
14
14
  type ValidateCheckpointResult,
15
15
  } from '@aztec/stdlib/block';
16
- import type { CheckpointData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
16
+ import type {
17
+ CheckpointData,
18
+ CommonCheckpointData,
19
+ ProposedCheckpointData,
20
+ ProposedCheckpointInput,
21
+ PublishedCheckpoint,
22
+ } from '@aztec/stdlib/checkpoint';
17
23
  import type {
18
24
  ContractClassPublic,
19
25
  ContractClassPublicWithCommitment,
20
26
  ContractDataSource,
21
27
  ContractInstanceUpdateWithAddress,
22
28
  ContractInstanceWithAddress,
23
- ExecutablePrivateFunctionWithMembershipProof,
24
- UtilityFunctionWithMembershipProof,
25
29
  } from '@aztec/stdlib/contract';
26
30
  import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
27
31
  import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
@@ -38,7 +42,7 @@ import { ContractInstanceStore } from './contract_instance_store.js';
38
42
  import { LogStore } from './log_store.js';
39
43
  import { MessageStore } from './message_store.js';
40
44
 
41
- export const ARCHIVER_DB_VERSION = 5;
45
+ export const ARCHIVER_DB_VERSION = 6;
42
46
  export const MAX_FUNCTION_SIGNATURES = 1000;
43
47
  export const MAX_FUNCTION_NAME_LEN = 256;
44
48
 
@@ -190,15 +194,6 @@ export class KVArchiverDataStore implements ContractDataSource {
190
194
  return this.#contractClassStore.getBytecodeCommitment(contractClassId);
191
195
  }
192
196
 
193
- /** Adds private functions to a contract class. */
194
- addFunctions(
195
- contractClassId: Fr,
196
- privateFunctions: ExecutablePrivateFunctionWithMembershipProof[],
197
- utilityFunctions: UtilityFunctionWithMembershipProof[],
198
- ): Promise<boolean> {
199
- return this.#contractClassStore.addFunctions(contractClassId, privateFunctions, utilityFunctions);
200
- }
201
-
202
197
  /**
203
198
  * Add new contract instances from an L2 block to the store's list.
204
199
  * @param data - List of contract instances to be added.
@@ -265,7 +260,7 @@ export class KVArchiverDataStore implements ContractDataSource {
265
260
  * @returns The number of the latest block
266
261
  */
267
262
  getLatestBlockNumber(): Promise<BlockNumber> {
268
- return this.#blockStore.getLatestBlockNumber();
263
+ return this.#blockStore.getLatestL2BlockNumber();
269
264
  }
270
265
 
271
266
  /**
@@ -470,10 +465,11 @@ export class KVArchiverDataStore implements ContractDataSource {
470
465
  * array implies no logs match that tag.
471
466
  * @param tags - The tags to search for.
472
467
  * @param page - The page number (0-indexed) for pagination. Returns at most 10 logs per tag per page.
468
+ * @param upToBlockNumber - If set, only return logs from blocks up to and including this block number.
473
469
  */
474
- getPrivateLogsByTags(tags: SiloedTag[], page?: number): Promise<TxScopedL2Log[][]> {
470
+ getPrivateLogsByTags(tags: SiloedTag[], page?: number, upToBlockNumber?: BlockNumber): Promise<TxScopedL2Log[][]> {
475
471
  try {
476
- return this.#logStore.getPrivateLogsByTags(tags, page);
472
+ return this.#logStore.getPrivateLogsByTags(tags, page, upToBlockNumber);
477
473
  } catch (err) {
478
474
  return Promise.reject(err);
479
475
  }
@@ -485,14 +481,16 @@ export class KVArchiverDataStore implements ContractDataSource {
485
481
  * @param contractAddress - The contract address to search logs for.
486
482
  * @param tags - The tags to search for.
487
483
  * @param page - The page number (0-indexed) for pagination. Returns at most 10 logs per tag per page.
484
+ * @param upToBlockNumber - If set, only return logs from blocks up to and including this block number.
488
485
  */
489
486
  getPublicLogsByTagsFromContract(
490
487
  contractAddress: AztecAddress,
491
488
  tags: Tag[],
492
489
  page?: number,
490
+ upToBlockNumber?: BlockNumber,
493
491
  ): Promise<TxScopedL2Log[][]> {
494
492
  try {
495
- return this.#logStore.getPublicLogsByTagsFromContract(contractAddress, tags, page);
493
+ return this.#logStore.getPublicLogsByTagsFromContract(contractAddress, tags, page, upToBlockNumber);
496
494
  } catch (err) {
497
495
  return Promise.reject(err);
498
496
  }
@@ -560,13 +558,6 @@ export class KVArchiverDataStore implements ContractDataSource {
560
558
  await this.#blockStore.setSynchedL1BlockNumber(l1BlockNumber);
561
559
  }
562
560
 
563
- /**
564
- * Stores the l1 block that messages have been synched until
565
- */
566
- async setMessageSynchedL1Block(l1Block: L1BlockId) {
567
- await this.#messageStore.setSynchedL1Block(l1Block);
568
- }
569
-
570
561
  /**
571
562
  * Returns the number of the most recent proven block
572
563
  * @returns The number of the most recent proven block
@@ -599,6 +590,11 @@ export class KVArchiverDataStore implements ContractDataSource {
599
590
  return this.#messageStore.rollbackL1ToL2MessagesToCheckpoint(targetCheckpointNumber);
600
591
  }
601
592
 
593
+ /** Atomically updates the message sync state: the L1 sync point and the inbox tree-in-progress marker. */
594
+ public setMessageSyncState(l1Block: L1BlockId, treeInProgress: bigint | undefined): Promise<void> {
595
+ return this.#messageStore.setMessageSyncState(l1Block, treeInProgress);
596
+ }
597
+
602
598
  /** Returns an async iterator to all L1 to L2 messages on the range. */
603
599
  public iterateL1ToL2Messages(range: CustomRange<bigint> = {}): AsyncIterableIterator<InboxMessage> {
604
600
  return this.#messageStore.iterateL1ToL2Messages(range);
@@ -619,6 +615,38 @@ export class KVArchiverDataStore implements ContractDataSource {
619
615
  return this.#blockStore.setPendingChainValidationStatus(status);
620
616
  }
621
617
 
618
+ /**
619
+ * Gets the L2 block number of the proposed checkpoint.
620
+ * @returns The block number of the proposed checkpoint, or the checkpointed block number if none.
621
+ */
622
+ public getProposedCheckpointL2BlockNumber(): Promise<BlockNumber> {
623
+ return this.#blockStore.getProposedCheckpointL2BlockNumber();
624
+ }
625
+
626
+ /** Returns the checkpoint data at the proposed tip */
627
+ public getProposedCheckpoint(): Promise<CommonCheckpointData | undefined> {
628
+ return this.#blockStore.getProposedCheckpoint();
629
+ }
630
+
631
+ /** Returns the proposed checkpoint data, or undefined if no proposed checkpoint exists. No fallback to confirmed. */
632
+ public getProposedCheckpointOnly(): Promise<ProposedCheckpointData | undefined> {
633
+ return this.#blockStore.getProposedCheckpointOnly();
634
+ }
635
+
636
+ /**
637
+ * Set proposed checkpoint
638
+ * @param proposedCheckpoint
639
+ * @returns
640
+ */
641
+ public setProposedCheckpoint(proposedCheckpoint: ProposedCheckpointInput): Promise<void> {
642
+ return this.#blockStore.setProposedCheckpoint(proposedCheckpoint);
643
+ }
644
+
645
+ /** Deletes the proposed checkpoint from storage. */
646
+ public deleteProposedCheckpoint(): Promise<void> {
647
+ return this.#blockStore.deleteProposedCheckpoint();
648
+ }
649
+
622
650
  /**
623
651
  * Gets the number of the latest L2 block processed.
624
652
  * @returns The number of the latest L2 block processed.
@@ -1,6 +1,12 @@
1
- import { GENESIS_BLOCK_HEADER_HASH, INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
1
+ import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
2
2
  import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
3
- import { type BlockData, type CheckpointId, GENESIS_CHECKPOINT_HEADER_HASH, type L2Tips } from '@aztec/stdlib/block';
3
+ import {
4
+ type BlockData,
5
+ type CheckpointId,
6
+ GENESIS_BLOCK_HEADER_HASH,
7
+ GENESIS_CHECKPOINT_HEADER_HASH,
8
+ type L2Tips,
9
+ } from '@aztec/stdlib/block';
4
10
 
5
11
  import type { BlockStore } from './block_store.js';
6
12
 
@@ -26,9 +32,16 @@ export class L2TipsCache {
26
32
  }
27
33
 
28
34
  private async loadFromStore(): Promise<L2Tips> {
29
- const [latestBlockNumber, provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber] = await Promise.all([
30
- this.blockStore.getLatestBlockNumber(),
35
+ const [
36
+ latestBlockNumber,
37
+ provenBlockNumber,
38
+ proposedCheckpointBlockNumber,
39
+ checkpointedBlockNumber,
40
+ finalizedBlockNumber,
41
+ ] = await Promise.all([
42
+ this.blockStore.getLatestL2BlockNumber(),
31
43
  this.blockStore.getProvenBlockNumber(),
44
+ this.blockStore.getProposedCheckpointL2BlockNumber(),
32
45
  this.blockStore.getCheckpointedL2BlockNumber(),
33
46
  this.blockStore.getFinalizedL2BlockNumber(),
34
47
  ]);
@@ -42,19 +55,34 @@ export class L2TipsCache {
42
55
  const getBlockData = (blockNumber: BlockNumber) =>
43
56
  blockNumber > beforeInitialBlockNumber ? this.blockStore.getBlockData(blockNumber) : genesisBlockHeader;
44
57
 
45
- const [latestBlockData, provenBlockData, checkpointedBlockData, finalizedBlockData] = await Promise.all(
46
- [latestBlockNumber, provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber].map(getBlockData),
47
- );
58
+ const [latestBlockData, provenBlockData, proposedCheckpointBlockData, checkpointedBlockData, finalizedBlockData] =
59
+ await Promise.all(
60
+ [
61
+ latestBlockNumber,
62
+ provenBlockNumber,
63
+ proposedCheckpointBlockNumber,
64
+ checkpointedBlockNumber,
65
+ finalizedBlockNumber,
66
+ ].map(getBlockData),
67
+ );
48
68
 
49
- if (!latestBlockData || !provenBlockData || !finalizedBlockData || !checkpointedBlockData) {
69
+ if (
70
+ !latestBlockData ||
71
+ !provenBlockData ||
72
+ !finalizedBlockData ||
73
+ !checkpointedBlockData ||
74
+ !proposedCheckpointBlockData
75
+ ) {
50
76
  throw new Error('Failed to load block data for L2 tips');
51
77
  }
52
78
 
53
- const [provenCheckpointId, finalizedCheckpointId, checkpointedCheckpointId] = await Promise.all([
54
- this.getCheckpointIdForBlock(provenBlockData),
55
- this.getCheckpointIdForBlock(finalizedBlockData),
56
- this.getCheckpointIdForBlock(checkpointedBlockData),
57
- ]);
79
+ const [provenCheckpointId, finalizedCheckpointId, proposedCheckpointId, checkpointedCheckpointId] =
80
+ await Promise.all([
81
+ this.getCheckpointIdForBlock(provenBlockData),
82
+ this.getCheckpointIdForBlock(finalizedBlockData),
83
+ this.getCheckpointIdForProposedCheckpoint(checkpointedBlockData),
84
+ this.getCheckpointIdForBlock(checkpointedBlockData),
85
+ ]);
58
86
 
59
87
  return {
60
88
  proposed: { number: latestBlockNumber, hash: latestBlockData.blockHash.toString() },
@@ -62,6 +90,10 @@ export class L2TipsCache {
62
90
  block: { number: provenBlockNumber, hash: provenBlockData.blockHash.toString() },
63
91
  checkpoint: provenCheckpointId,
64
92
  },
93
+ proposedCheckpoint: {
94
+ block: { number: proposedCheckpointBlockNumber, hash: proposedCheckpointBlockData.blockHash.toString() },
95
+ checkpoint: proposedCheckpointId,
96
+ },
65
97
  finalized: {
66
98
  block: { number: finalizedBlockNumber, hash: finalizedBlockData.blockHash.toString() },
67
99
  checkpoint: finalizedCheckpointId,
@@ -73,6 +105,19 @@ export class L2TipsCache {
73
105
  };
74
106
  }
75
107
 
108
+ private async getCheckpointIdForProposedCheckpoint(
109
+ checkpointedBlockData: Pick<BlockData, 'checkpointNumber'>,
110
+ ): Promise<CheckpointId> {
111
+ const checkpointData = await this.blockStore.getProposedCheckpointOnly();
112
+ if (!checkpointData) {
113
+ return this.getCheckpointIdForBlock(checkpointedBlockData);
114
+ }
115
+ return {
116
+ number: checkpointData.checkpointNumber,
117
+ hash: checkpointData.header.hash().toString(),
118
+ };
119
+ }
120
+
76
121
  private async getCheckpointIdForBlock(blockData: Pick<BlockData, 'checkpointNumber'>): Promise<CheckpointId> {
77
122
  const checkpointData = await this.blockStore.getCheckpointData(blockData.checkpointNumber);
78
123
  if (!checkpointData) {
@@ -1,7 +1,6 @@
1
1
  import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
2
2
  import { BlockNumber } from '@aztec/foundation/branded-types';
3
3
  import { compactArray, filterAsync } from '@aztec/foundation/collection';
4
- import { Fr } from '@aztec/foundation/curves/bn254';
5
4
  import { createLogger } from '@aztec/foundation/log';
6
5
  import { BufferReader, numToUInt32BE } from '@aztec/foundation/serialize';
7
6
  import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
@@ -22,6 +21,7 @@ import {
22
21
  } from '@aztec/stdlib/logs';
23
22
  import { TxHash } from '@aztec/stdlib/tx';
24
23
 
24
+ import { OutOfOrderLogInsertionError } from '../errors.js';
25
25
  import type { BlockStore } from './block_store.js';
26
26
 
27
27
  /**
@@ -165,10 +165,21 @@ export class LogStore {
165
165
 
166
166
  for (const taggedLogBuffer of currentPrivateTaggedLogs) {
167
167
  if (taggedLogBuffer.logBuffers && taggedLogBuffer.logBuffers.length > 0) {
168
- privateTaggedLogs.set(
169
- taggedLogBuffer.tag,
170
- taggedLogBuffer.logBuffers!.concat(privateTaggedLogs.get(taggedLogBuffer.tag)!),
171
- );
168
+ const newLogs = privateTaggedLogs.get(taggedLogBuffer.tag)!;
169
+ if (newLogs.length === 0) {
170
+ continue;
171
+ }
172
+ const lastExisting = TxScopedL2Log.fromBuffer(taggedLogBuffer.logBuffers.at(-1)!);
173
+ const firstNew = TxScopedL2Log.fromBuffer(newLogs[0]);
174
+ if (lastExisting.blockNumber > firstNew.blockNumber) {
175
+ throw new OutOfOrderLogInsertionError(
176
+ 'private',
177
+ taggedLogBuffer.tag,
178
+ lastExisting.blockNumber,
179
+ firstNew.blockNumber,
180
+ );
181
+ }
182
+ privateTaggedLogs.set(taggedLogBuffer.tag, taggedLogBuffer.logBuffers.concat(newLogs));
172
183
  }
173
184
  }
174
185
 
@@ -200,10 +211,21 @@ export class LogStore {
200
211
 
201
212
  for (const taggedLogBuffer of currentPublicTaggedLogs) {
202
213
  if (taggedLogBuffer.logBuffers && taggedLogBuffer.logBuffers.length > 0) {
203
- publicTaggedLogs.set(
204
- taggedLogBuffer.tag,
205
- taggedLogBuffer.logBuffers!.concat(publicTaggedLogs.get(taggedLogBuffer.tag)!),
206
- );
214
+ const newLogs = publicTaggedLogs.get(taggedLogBuffer.tag)!;
215
+ if (newLogs.length === 0) {
216
+ continue;
217
+ }
218
+ const lastExisting = TxScopedL2Log.fromBuffer(taggedLogBuffer.logBuffers.at(-1)!);
219
+ const firstNew = TxScopedL2Log.fromBuffer(newLogs[0]);
220
+ if (lastExisting.blockNumber > firstNew.blockNumber) {
221
+ throw new OutOfOrderLogInsertionError(
222
+ 'public',
223
+ taggedLogBuffer.tag,
224
+ lastExisting.blockNumber,
225
+ firstNew.blockNumber,
226
+ );
227
+ }
228
+ publicTaggedLogs.set(taggedLogBuffer.tag, taggedLogBuffer.logBuffers.concat(newLogs));
207
229
  }
208
230
  }
209
231
 
@@ -279,13 +301,11 @@ export class LogStore {
279
301
  }
280
302
 
281
303
  #unpackBlockHash(reader: BufferReader): BlockHash {
282
- const blockHash = reader.remainingBytes() > 0 ? reader.readObject(Fr) : undefined;
283
-
284
- if (!blockHash) {
304
+ if (reader.remainingBytes() === 0) {
285
305
  throw new Error('Failed to read block hash from log entry buffer');
286
306
  }
287
307
 
288
- return new BlockHash(blockHash);
308
+ return BlockHash.fromBuffer(reader);
289
309
  }
290
310
 
291
311
  deleteLogs(blocks: L2Block[]): Promise<boolean> {
@@ -353,17 +373,30 @@ export class LogStore {
353
373
  * array implies no logs match that tag.
354
374
  * @param tags - The tags to search for.
355
375
  * @param page - The page number (0-indexed) for pagination.
376
+ * @param upToBlockNumber - If set, only return logs from blocks up to and including this block number.
356
377
  * @returns An array of log arrays, one per tag. Returns at most MAX_LOGS_PER_TAG logs per tag per page. If
357
378
  * MAX_LOGS_PER_TAG logs are returned for a tag, the caller should fetch the next page to check for more logs.
358
379
  */
359
- async getPrivateLogsByTags(tags: SiloedTag[], page: number = 0): Promise<TxScopedL2Log[][]> {
380
+ async getPrivateLogsByTags(
381
+ tags: SiloedTag[],
382
+ page: number = 0,
383
+ upToBlockNumber?: BlockNumber,
384
+ ): Promise<TxScopedL2Log[][]> {
360
385
  const logs = await Promise.all(tags.map(tag => this.#privateLogsByTag.getAsync(tag.toString())));
386
+
361
387
  const start = page * MAX_LOGS_PER_TAG;
362
388
  const end = start + MAX_LOGS_PER_TAG;
363
389
 
364
- return logs.map(
365
- logBuffers => logBuffers?.slice(start, end).map(logBuffer => TxScopedL2Log.fromBuffer(logBuffer)) ?? [],
366
- );
390
+ return logs.map(logBuffers => {
391
+ const deserialized = logBuffers?.slice(start, end).map(buf => TxScopedL2Log.fromBuffer(buf)) ?? [];
392
+ if (upToBlockNumber !== undefined) {
393
+ const cutoff = deserialized.findIndex(log => log.blockNumber > upToBlockNumber);
394
+ if (cutoff !== -1) {
395
+ return deserialized.slice(0, cutoff);
396
+ }
397
+ }
398
+ return deserialized;
399
+ });
367
400
  }
368
401
 
369
402
  /**
@@ -372,6 +405,7 @@ export class LogStore {
372
405
  * @param contractAddress - The contract address to search logs for.
373
406
  * @param tags - The tags to search for.
374
407
  * @param page - The page number (0-indexed) for pagination.
408
+ * @param upToBlockNumber - If set, only return logs from blocks up to and including this block number.
375
409
  * @returns An array of log arrays, one per tag. Returns at most MAX_LOGS_PER_TAG logs per tag per page. If
376
410
  * MAX_LOGS_PER_TAG logs are returned for a tag, the caller should fetch the next page to check for more logs.
377
411
  */
@@ -379,6 +413,7 @@ export class LogStore {
379
413
  contractAddress: AztecAddress,
380
414
  tags: Tag[],
381
415
  page: number = 0,
416
+ upToBlockNumber?: BlockNumber,
382
417
  ): Promise<TxScopedL2Log[][]> {
383
418
  const logs = await Promise.all(
384
419
  tags.map(tag => {
@@ -389,9 +424,16 @@ export class LogStore {
389
424
  const start = page * MAX_LOGS_PER_TAG;
390
425
  const end = start + MAX_LOGS_PER_TAG;
391
426
 
392
- return logs.map(
393
- logBuffers => logBuffers?.slice(start, end).map(logBuffer => TxScopedL2Log.fromBuffer(logBuffer)) ?? [],
394
- );
427
+ return logs.map(logBuffers => {
428
+ const deserialized = logBuffers?.slice(start, end).map(buf => TxScopedL2Log.fromBuffer(buf)) ?? [];
429
+ if (upToBlockNumber !== undefined) {
430
+ const cutoff = deserialized.findIndex(log => log.blockNumber > upToBlockNumber);
431
+ if (cutoff !== -1) {
432
+ return deserialized.slice(0, cutoff);
433
+ }
434
+ }
435
+ return deserialized;
436
+ });
395
437
  }
396
438
 
397
439
  /**
@@ -14,6 +14,7 @@ import {
14
14
  } from '@aztec/kv-store';
15
15
  import { InboxLeaf } from '@aztec/stdlib/messaging';
16
16
 
17
+ import { L1ToL2MessagesNotReadyError } from '../errors.js';
17
18
  import {
18
19
  type InboxMessage,
19
20
  deserializeInboxMessage,
@@ -40,6 +41,8 @@ export class MessageStore {
40
41
  #lastSynchedL1Block: AztecAsyncSingleton<Buffer>;
41
42
  /** Stores total messages stored */
42
43
  #totalMessageCount: AztecAsyncSingleton<bigint>;
44
+ /** Stores the checkpoint number whose message tree is currently being filled on L1. */
45
+ #inboxTreeInProgress: AztecAsyncSingleton<bigint>;
43
46
 
44
47
  #log = createLogger('archiver:message_store');
45
48
 
@@ -48,6 +51,7 @@ export class MessageStore {
48
51
  this.#l1ToL2MessageIndices = db.openMap('archiver_l1_to_l2_message_indices');
49
52
  this.#lastSynchedL1Block = db.openSingleton('archiver_last_l1_block_id');
50
53
  this.#totalMessageCount = db.openSingleton('archiver_l1_to_l2_message_count');
54
+ this.#inboxTreeInProgress = db.openSingleton('archiver_inbox_tree_in_progress');
51
55
  }
52
56
 
53
57
  public async getTotalL1ToL2MessageCount(): Promise<bigint> {
@@ -157,15 +161,6 @@ export class MessageStore {
157
161
  lastMessage = message;
158
162
  }
159
163
 
160
- // Update the L1 sync point to that of the last message added.
161
- const currentSyncPoint = await this.getSynchedL1Block();
162
- if (!currentSyncPoint || currentSyncPoint.l1BlockNumber < lastMessage!.l1BlockNumber) {
163
- await this.setSynchedL1Block({
164
- l1BlockNumber: lastMessage!.l1BlockNumber,
165
- l1BlockHash: lastMessage!.l1BlockHash,
166
- });
167
- }
168
-
169
164
  // Update total message count with the number of inserted messages.
170
165
  await this.increaseTotalMessageCount(messageCount);
171
166
  });
@@ -185,7 +180,29 @@ export class MessageStore {
185
180
  return msg ? deserializeInboxMessage(msg) : undefined;
186
181
  }
187
182
 
183
+ /** Returns the inbox tree-in-progress checkpoint number from L1, or undefined if not yet set. */
184
+ public getInboxTreeInProgress(): Promise<bigint | undefined> {
185
+ return this.#inboxTreeInProgress.getAsync();
186
+ }
187
+
188
+ /** Atomically updates the message sync state: the L1 sync point and the inbox tree-in-progress marker. */
189
+ public setMessageSyncState(l1Block: L1BlockId, treeInProgress: bigint | undefined): Promise<void> {
190
+ return this.db.transactionAsync(async () => {
191
+ await this.setSynchedL1Block(l1Block);
192
+ if (treeInProgress !== undefined) {
193
+ await this.#inboxTreeInProgress.set(treeInProgress);
194
+ } else {
195
+ await this.#inboxTreeInProgress.delete();
196
+ }
197
+ });
198
+ }
199
+
188
200
  public async getL1ToL2Messages(checkpointNumber: CheckpointNumber): Promise<Fr[]> {
201
+ const treeInProgress = await this.#inboxTreeInProgress.getAsync();
202
+ if (treeInProgress !== undefined && BigInt(checkpointNumber) >= treeInProgress) {
203
+ throw new L1ToL2MessagesNotReadyError(checkpointNumber, treeInProgress);
204
+ }
205
+
189
206
  const messages: Fr[] = [];
190
207
 
191
208
  const [startIndex, endIndex] = InboxLeaf.indexRangeForCheckpoint(checkpointNumber);
@@ -8,7 +8,7 @@ export type InboxMessage = {
8
8
  index: bigint;
9
9
  leaf: Fr;
10
10
  checkpointNumber: CheckpointNumber;
11
- l1BlockNumber: bigint; // L1 block number - NOT Aztec L2
11
+ l1BlockNumber: bigint;
12
12
  l1BlockHash: Buffer32;
13
13
  rollingHash: Buffer16;
14
14
  };
@@ -1,5 +1,6 @@
1
1
  import type { BlobClientInterface } from '@aztec/blob-client/client';
2
2
  import { type Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
3
+ import { INITIAL_CHECKPOINT_NUMBER } from '@aztec/constants';
3
4
  import type { CheckpointProposedLog, InboxContract, MessageSentLog, RollupContract } from '@aztec/ethereum/contracts';
4
5
  import { MULTI_CALL_3_ADDRESS } from '@aztec/ethereum/contracts';
5
6
  import type { ViemPublicClient } from '@aztec/ethereum/types';
@@ -465,21 +466,44 @@ export class FakeL1State {
465
466
  createMockInboxContract(_publicClient: MockProxy<ViemPublicClient>): MockProxy<InboxContract> {
466
467
  const mockInbox = mock<InboxContract>();
467
468
 
468
- mockInbox.getState.mockImplementation(() =>
469
- Promise.resolve({
470
- messagesRollingHash: this.messagesRollingHash,
471
- totalMessagesInserted: BigInt(this.messages.length),
472
- treeInProgress: 0n,
473
- }),
474
- );
469
+ mockInbox.getState.mockImplementation((opts: { blockTag?: string; blockNumber?: bigint } = {}) => {
470
+ // Filter messages visible at the given block number (or all if not specified)
471
+ const blockNumber = opts.blockNumber ?? this.l1BlockNumber;
472
+ const visibleMessages = this.messages.filter(m => m.l1BlockNumber <= blockNumber);
473
+
474
+ // treeInProgress must be > any sealed checkpoint. On L1, a checkpoint can only be proposed
475
+ // after its messages are sealed, so treeInProgress > checkpointNumber for all published checkpoints.
476
+ const maxFromMessages =
477
+ visibleMessages.length > 0 ? Math.max(...visibleMessages.map(m => Number(m.checkpointNumber))) + 1 : 0;
478
+ const maxFromCheckpoints =
479
+ this.checkpoints.length > 0
480
+ ? Math.max(
481
+ ...this.checkpoints
482
+ .filter(cp => !cp.pruned && cp.l1BlockNumber <= blockNumber)
483
+ .map(cp => Number(cp.checkpointNumber)),
484
+ 0,
485
+ ) + 1
486
+ : 0;
487
+ const treeInProgress = Math.max(maxFromMessages, maxFromCheckpoints, INITIAL_CHECKPOINT_NUMBER);
488
+
489
+ // Compute rolling hash only for visible messages
490
+ const rollingHash =
491
+ visibleMessages.length > 0 ? visibleMessages[visibleMessages.length - 1].rollingHash : Buffer16.ZERO;
492
+
493
+ return Promise.resolve({
494
+ messagesRollingHash: rollingHash,
495
+ totalMessagesInserted: BigInt(visibleMessages.length),
496
+ treeInProgress: BigInt(treeInProgress),
497
+ });
498
+ });
475
499
 
476
500
  // Mock the wrapper methods for fetching message events
477
501
  mockInbox.getMessageSentEvents.mockImplementation((fromBlock: bigint, toBlock: bigint) =>
478
502
  Promise.resolve(this.getMessageSentLogs(fromBlock, toBlock)),
479
503
  );
480
504
 
481
- mockInbox.getMessageSentEventByHash.mockImplementation((hash: string, fromBlock: bigint, toBlock: bigint) =>
482
- Promise.resolve(this.getMessageSentLogs(fromBlock, toBlock, hash)),
505
+ mockInbox.getMessageSentEventByHash.mockImplementation((msgHash: string, l1BlockHash: string) =>
506
+ Promise.resolve(this.getMessageSentLogByHash(msgHash, l1BlockHash) as MessageSentLog),
483
507
  );
484
508
 
485
509
  return mockInbox;
@@ -599,6 +623,26 @@ export class FakeL1State {
599
623
  }));
600
624
  }
601
625
 
626
+ private getMessageSentLogByHash(msgHash: string, l1BlockHash: string): MessageSentLog | undefined {
627
+ const msg = this.messages.find(
628
+ msg => msg.leaf.toString() === msgHash && Buffer32.fromBigInt(msg.l1BlockNumber).toString() === l1BlockHash,
629
+ );
630
+ if (!msg) {
631
+ return undefined;
632
+ }
633
+ return {
634
+ l1BlockNumber: msg.l1BlockNumber,
635
+ l1BlockHash: Buffer32.fromBigInt(msg.l1BlockNumber),
636
+ l1TransactionHash: `0x${msg.l1BlockNumber.toString(16)}` as `0x${string}`,
637
+ args: {
638
+ checkpointNumber: msg.checkpointNumber,
639
+ index: msg.index,
640
+ leaf: msg.leaf,
641
+ rollingHash: msg.rollingHash,
642
+ },
643
+ };
644
+ }
645
+
602
646
  private async makeRollupTx(
603
647
  checkpoint: Checkpoint,
604
648
  signers: Secp256k1Signer[],
@@ -44,6 +44,7 @@ export class MockL1ToL2MessageSource implements L1ToL2MessageSource {
44
44
  checkpointed: tip,
45
45
  proven: tip,
46
46
  finalized: tip,
47
+ proposedCheckpoint: tip,
47
48
  });
48
49
  }
49
50
  }