@aztec/archiver 0.0.1-commit.04852196a → 0.0.1-commit.04d373f

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 (117) hide show
  1. package/README.md +19 -11
  2. package/dest/archiver.d.ts +36 -19
  3. package/dest/archiver.d.ts.map +1 -1
  4. package/dest/archiver.js +250 -79
  5. package/dest/config.d.ts +6 -3
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +23 -15
  8. package/dest/errors.d.ts +55 -9
  9. package/dest/errors.d.ts.map +1 -1
  10. package/dest/errors.js +81 -14
  11. package/dest/factory.d.ts +12 -8
  12. package/dest/factory.d.ts.map +1 -1
  13. package/dest/factory.js +42 -32
  14. package/dest/index.d.ts +11 -3
  15. package/dest/index.d.ts.map +1 -1
  16. package/dest/index.js +10 -2
  17. package/dest/l1/calldata_retriever.d.ts +2 -1
  18. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  19. package/dest/l1/calldata_retriever.js +15 -5
  20. package/dest/l1/data_retrieval.d.ts +19 -10
  21. package/dest/l1/data_retrieval.d.ts.map +1 -1
  22. package/dest/l1/data_retrieval.js +25 -32
  23. package/dest/l1/trace_tx.d.ts +12 -66
  24. package/dest/l1/trace_tx.d.ts.map +1 -1
  25. package/dest/l1/validate_historical_logs.d.ts +23 -0
  26. package/dest/l1/validate_historical_logs.d.ts.map +1 -0
  27. package/dest/l1/validate_historical_logs.js +108 -0
  28. package/dest/modules/contract_data_source_adapter.d.ts +25 -0
  29. package/dest/modules/contract_data_source_adapter.d.ts.map +1 -0
  30. package/dest/modules/contract_data_source_adapter.js +40 -0
  31. package/dest/modules/data_source_base.d.ts +70 -46
  32. package/dest/modules/data_source_base.d.ts.map +1 -1
  33. package/dest/modules/data_source_base.js +270 -135
  34. package/dest/modules/data_store_updater.d.ts +39 -17
  35. package/dest/modules/data_store_updater.d.ts.map +1 -1
  36. package/dest/modules/data_store_updater.js +183 -122
  37. package/dest/modules/instrumentation.d.ts +7 -2
  38. package/dest/modules/instrumentation.d.ts.map +1 -1
  39. package/dest/modules/instrumentation.js +25 -7
  40. package/dest/modules/l1_synchronizer.d.ts +12 -7
  41. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  42. package/dest/modules/l1_synchronizer.js +430 -205
  43. package/dest/modules/validation.d.ts +4 -3
  44. package/dest/modules/validation.d.ts.map +1 -1
  45. package/dest/modules/validation.js +6 -6
  46. package/dest/store/block_store.d.ts +174 -70
  47. package/dest/store/block_store.d.ts.map +1 -1
  48. package/dest/store/block_store.js +696 -250
  49. package/dest/store/contract_class_store.d.ts +17 -4
  50. package/dest/store/contract_class_store.d.ts.map +1 -1
  51. package/dest/store/contract_class_store.js +24 -68
  52. package/dest/store/contract_instance_store.d.ts +28 -1
  53. package/dest/store/contract_instance_store.d.ts.map +1 -1
  54. package/dest/store/contract_instance_store.js +37 -2
  55. package/dest/store/data_stores.d.ts +68 -0
  56. package/dest/store/data_stores.d.ts.map +1 -0
  57. package/dest/store/data_stores.js +54 -0
  58. package/dest/store/function_names_cache.d.ts +17 -0
  59. package/dest/store/function_names_cache.d.ts.map +1 -0
  60. package/dest/store/function_names_cache.js +30 -0
  61. package/dest/store/l2_tips_cache.d.ts +13 -7
  62. package/dest/store/l2_tips_cache.d.ts.map +1 -1
  63. package/dest/store/l2_tips_cache.js +13 -76
  64. package/dest/store/log_store.d.ts +42 -37
  65. package/dest/store/log_store.d.ts.map +1 -1
  66. package/dest/store/log_store.js +262 -408
  67. package/dest/store/log_store_codec.d.ts +70 -0
  68. package/dest/store/log_store_codec.d.ts.map +1 -0
  69. package/dest/store/log_store_codec.js +101 -0
  70. package/dest/store/message_store.d.ts +11 -1
  71. package/dest/store/message_store.d.ts.map +1 -1
  72. package/dest/store/message_store.js +50 -8
  73. package/dest/test/fake_l1_state.d.ts +20 -1
  74. package/dest/test/fake_l1_state.d.ts.map +1 -1
  75. package/dest/test/fake_l1_state.js +114 -18
  76. package/dest/test/mock_l1_to_l2_message_source.d.ts +1 -1
  77. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  78. package/dest/test/mock_l1_to_l2_message_source.js +2 -1
  79. package/dest/test/mock_l2_block_source.d.ts +51 -46
  80. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  81. package/dest/test/mock_l2_block_source.js +243 -170
  82. package/dest/test/noop_l1_archiver.d.ts +12 -6
  83. package/dest/test/noop_l1_archiver.d.ts.map +1 -1
  84. package/dest/test/noop_l1_archiver.js +26 -9
  85. package/package.json +14 -14
  86. package/src/archiver.ts +301 -86
  87. package/src/config.ts +32 -12
  88. package/src/errors.ts +122 -21
  89. package/src/factory.ts +50 -28
  90. package/src/index.ts +18 -2
  91. package/src/l1/calldata_retriever.ts +16 -5
  92. package/src/l1/data_retrieval.ts +36 -45
  93. package/src/l1/validate_historical_logs.ts +140 -0
  94. package/src/modules/contract_data_source_adapter.ts +55 -0
  95. package/src/modules/data_source_base.ts +336 -171
  96. package/src/modules/data_store_updater.ts +219 -154
  97. package/src/modules/instrumentation.ts +28 -8
  98. package/src/modules/l1_synchronizer.ts +566 -248
  99. package/src/modules/validation.ts +10 -9
  100. package/src/store/block_store.ts +865 -290
  101. package/src/store/contract_class_store.ts +31 -103
  102. package/src/store/contract_instance_store.ts +51 -5
  103. package/src/store/data_stores.ts +104 -0
  104. package/src/store/function_names_cache.ts +37 -0
  105. package/src/store/l2_tips_cache.ts +16 -70
  106. package/src/store/log_store.ts +301 -559
  107. package/src/store/log_store_codec.ts +132 -0
  108. package/src/store/message_store.ts +59 -9
  109. package/src/structs/inbox_message.ts +1 -1
  110. package/src/test/fake_l1_state.ts +142 -29
  111. package/src/test/mock_l1_to_l2_message_source.ts +1 -0
  112. package/src/test/mock_l2_block_source.ts +303 -205
  113. package/src/test/noop_l1_archiver.ts +39 -9
  114. package/dest/store/kv_archiver_store.d.ts +0 -354
  115. package/dest/store/kv_archiver_store.d.ts.map +0 -1
  116. package/dest/store/kv_archiver_store.js +0 -464
  117. package/src/store/kv_archiver_store.ts +0 -671
@@ -1,6 +1,12 @@
1
1
  import { GENESIS_ARCHIVE_ROOT } from '@aztec/constants';
2
2
  import { DefaultL1ContractsConfig } from '@aztec/ethereum/config';
3
- import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
3
+ import {
4
+ BlockNumber,
5
+ CheckpointNumber,
6
+ EpochNumber,
7
+ IndexWithinCheckpoint,
8
+ SlotNumber,
9
+ } from '@aztec/foundation/branded-types';
4
10
  import { Buffer32 } from '@aztec/foundation/buffer';
5
11
  import { Fr } from '@aztec/foundation/curves/bn254';
6
12
  import { EthAddress } from '@aztec/foundation/eth-address';
@@ -9,19 +15,39 @@ import type { FunctionSelector } from '@aztec/stdlib/abi';
9
15
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
10
16
  import {
11
17
  type BlockData,
12
- BlockHash,
13
- CheckpointedL2Block,
18
+ type BlockHash,
19
+ type BlockQuery,
20
+ type BlockTag,
21
+ type BlocksQuery,
22
+ Body,
23
+ type CheckpointQuery,
24
+ type CheckpointsQuery,
25
+ GENESIS_BLOCK_HEADER_HASH,
26
+ GENESIS_CHECKPOINT_HEADER_HASH,
14
27
  L2Block,
15
28
  type L2BlockSource,
16
29
  type L2Tips,
30
+ type ProposedCheckpointQuery,
17
31
  type ValidateCheckpointResult,
18
32
  } from '@aztec/stdlib/block';
19
- import { Checkpoint, type CheckpointData, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
33
+ import {
34
+ Checkpoint,
35
+ type CheckpointData,
36
+ L1PublishedData,
37
+ type ProposedCheckpointData,
38
+ PublishedCheckpoint,
39
+ } from '@aztec/stdlib/checkpoint';
20
40
  import type { ContractClassPublic, ContractDataSource, ContractInstanceWithAddress } from '@aztec/stdlib/contract';
21
- import { EmptyL1RollupConstants, type L1RollupConstants, getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
41
+ import {
42
+ EmptyL1RollupConstants,
43
+ type L1RollupConstants,
44
+ getEpochAtSlot,
45
+ getSlotRangeForEpoch,
46
+ } from '@aztec/stdlib/epoch-helpers';
22
47
  import { computeCheckpointOutHash } from '@aztec/stdlib/messaging';
23
48
  import { CheckpointHeader } from '@aztec/stdlib/rollup';
24
- import { type BlockHeader, TxExecutionResult, TxHash, TxReceipt, TxStatus } from '@aztec/stdlib/tx';
49
+ import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
50
+ import { BlockHeader, TxExecutionResult, TxHash, TxReceipt, TxStatus } from '@aztec/stdlib/tx';
25
51
  import type { UInt64 } from '@aztec/stdlib/types';
26
52
 
27
53
  /**
@@ -34,9 +60,68 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
34
60
  private provenBlockNumber: number = 0;
35
61
  private finalizedBlockNumber: number = 0;
36
62
  private checkpointedBlockNumber: number = 0;
63
+ private proposedCheckpointBlockNumber: number = 0;
64
+
65
+ private initialHeader: BlockHeader = BlockHeader.empty();
66
+ private initialHeaderHash: BlockHash = GENESIS_BLOCK_HEADER_HASH;
67
+ private genesisArchiveRoot?: Fr;
68
+ private genesisBlock?: L2Block;
37
69
 
38
70
  private log = createLogger('archiver:mock_l2_block_source');
39
71
 
72
+ /** Returns the initial header used to synthesize block 0. */
73
+ public getInitialHeader(): BlockHeader {
74
+ return this.initialHeader;
75
+ }
76
+
77
+ /**
78
+ * Sets the initial header used to synthesize block 0. Tests that wire up a real
79
+ * world-state should call this with `worldState.getInitialHeader()` so the L2BlockStream
80
+ * agrees on the genesis hash on both sides. Precomputes and caches the header hash so
81
+ * `getGenesisBlockHash()` can return synchronously.
82
+ */
83
+ public async setInitialHeader(header: BlockHeader): Promise<void> {
84
+ this.initialHeader = header;
85
+ this.initialHeaderHash = await header.hash();
86
+ this.genesisBlock = undefined;
87
+ }
88
+
89
+ /**
90
+ * Returns the precomputed hash of the genesis block header. Defaults to the static
91
+ * {@link GENESIS_BLOCK_HEADER_HASH} unless {@link setInitialHeader} has been called with a
92
+ * custom header.
93
+ */
94
+ public getGenesisBlockHash(): BlockHash {
95
+ return this.initialHeaderHash;
96
+ }
97
+
98
+ /**
99
+ * Sets the post-genesis archive root used to synthesize block 0. Mirrors the real archiver,
100
+ * whose synthetic block 0 carries `new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1)` rather
101
+ * than `AppendOnlyTreeSnapshot.empty()`. Tests wiring up a real world-state should set this so
102
+ * archive-based block lookups against the mock match production semantics.
103
+ */
104
+ public setGenesisArchiveRoot(root: Fr): void {
105
+ this.genesisArchiveRoot = root;
106
+ this.genesisBlock = undefined;
107
+ }
108
+
109
+ private getGenesisBlock(): L2Block {
110
+ if (this.genesisBlock) {
111
+ return this.genesisBlock;
112
+ }
113
+ const archive = this.genesisArchiveRoot
114
+ ? new AppendOnlyTreeSnapshot(this.genesisArchiveRoot, 1)
115
+ : AppendOnlyTreeSnapshot.empty();
116
+ return (this.genesisBlock = new L2Block(
117
+ archive,
118
+ this.initialHeader,
119
+ Body.empty(),
120
+ CheckpointNumber.ZERO,
121
+ IndexWithinCheckpoint(0),
122
+ ));
123
+ }
124
+
40
125
  /** Creates blocks grouped into single-block checkpoints. */
41
126
  public async createBlocks(numBlocks: number) {
42
127
  await this.createCheckpoints(numBlocks, 1);
@@ -84,6 +169,7 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
84
169
  });
85
170
  // Keep tip numbers consistent with remaining blocks.
86
171
  this.checkpointedBlockNumber = Math.min(this.checkpointedBlockNumber, maxBlockNum);
172
+ this.proposedCheckpointBlockNumber = Math.min(this.proposedCheckpointBlockNumber, maxBlockNum);
87
173
  this.provenBlockNumber = Math.min(this.provenBlockNumber, maxBlockNum);
88
174
  this.finalizedBlockNumber = Math.min(this.finalizedBlockNumber, maxBlockNum);
89
175
  this.log.verbose(`Removed ${numBlocks} blocks from the mock L2 block source`);
@@ -100,9 +186,17 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
100
186
  this.finalizedBlockNumber = finalizedBlockNumber;
101
187
  }
102
188
 
189
+ public setProposedCheckpointBlockNumber(blockNumber: number) {
190
+ this.proposedCheckpointBlockNumber = blockNumber;
191
+ }
192
+
103
193
  public setCheckpointedBlockNumber(checkpointedBlockNumber: number) {
104
194
  const prevCheckpointed = this.checkpointedBlockNumber;
105
195
  this.checkpointedBlockNumber = checkpointedBlockNumber;
196
+ // Proposed checkpoint is always at least as advanced as checkpointed
197
+ if (this.proposedCheckpointBlockNumber < checkpointedBlockNumber) {
198
+ this.proposedCheckpointBlockNumber = checkpointedBlockNumber;
199
+ }
106
200
  // Auto-create single-block checkpoints for newly checkpointed blocks that don't have one yet.
107
201
  // This handles blocks added via addProposedBlocks that are now being marked as checkpointed.
108
202
  const newCheckpoints: Checkpoint[] = [];
@@ -150,80 +244,36 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
150
244
  * Gets the number of the latest L2 block processed by the block source implementation.
151
245
  * @returns In this mock instance, returns the number of L2 blocks that we've mocked.
152
246
  */
153
- public getBlockNumber() {
154
- return Promise.resolve(BlockNumber(this.l2Blocks.length));
155
- }
156
-
157
- public getProvenBlockNumber() {
158
- return Promise.resolve(BlockNumber(this.provenBlockNumber));
159
- }
160
-
161
- public getCheckpointedL2BlockNumber() {
162
- return Promise.resolve(BlockNumber(this.checkpointedBlockNumber));
163
- }
164
-
165
- public getFinalizedL2BlockNumber() {
166
- return Promise.resolve(BlockNumber(this.finalizedBlockNumber));
167
- }
168
-
169
- public getCheckpointedBlock(number: BlockNumber): Promise<CheckpointedL2Block | undefined> {
170
- if (number > this.checkpointedBlockNumber) {
171
- return Promise.resolve(undefined);
247
+ public getBlockNumber(): Promise<BlockNumber>;
248
+ public getBlockNumber(query: BlockQuery): Promise<BlockNumber | undefined>;
249
+ public async getBlockNumber(query?: BlockQuery): Promise<BlockNumber | undefined> {
250
+ if (!query) {
251
+ return BlockNumber(this.l2Blocks.length);
172
252
  }
173
- const block = this.l2Blocks[number - 1];
174
- if (!block) {
175
- return Promise.resolve(undefined);
253
+ if ('number' in query) {
254
+ return query.number;
176
255
  }
177
- return Promise.resolve(this.toCheckpointedBlock(block));
178
- }
179
-
180
- public async getCheckpointedBlocks(from: BlockNumber, limit: number): Promise<CheckpointedL2Block[]> {
181
- const result: CheckpointedL2Block[] = [];
182
- for (let i = 0; i < limit; i++) {
183
- const blockNum = from + i;
184
- if (blockNum > this.checkpointedBlockNumber) {
185
- break;
186
- }
187
- const block = await this.getCheckpointedBlock(BlockNumber(blockNum));
188
- if (block) {
189
- result.push(block);
190
- }
256
+ if ('tag' in query) {
257
+ return BlockNumber(this.resolveBlockTag(query.tag));
191
258
  }
192
- return result;
259
+ const block = await this.getBlock(query);
260
+ return block ? block.header.globalVariables.blockNumber : undefined;
193
261
  }
194
262
 
195
- /**
196
- * Gets an l2 block.
197
- * @param number - The block number to return (inclusive).
198
- * @returns The requested L2 block.
199
- */
200
- public getBlock(number: number): Promise<L2Block | undefined> {
201
- const block = this.l2Blocks[number - 1];
202
- return Promise.resolve(block);
203
- }
204
-
205
- /**
206
- * Gets an L2 block (new format).
207
- * @param number - The block number to return.
208
- * @returns The requested L2 block.
209
- */
210
- public getL2Block(number: BlockNumber): Promise<L2Block | undefined> {
211
- const block = this.l2Blocks[number - 1];
212
- return Promise.resolve(block);
263
+ public getProposedCheckpointL2BlockNumber() {
264
+ return Promise.resolve(BlockNumber(this.proposedCheckpointBlockNumber));
213
265
  }
214
266
 
215
- /**
216
- * Gets up to `limit` amount of L2 blocks starting from `from`.
217
- * @param from - Number of the first block to return (inclusive).
218
- * @param limit - The maximum number of blocks to return.
219
- * @returns The requested mocked L2 blocks.
220
- */
221
- public getBlocks(from: number, limit: number): Promise<L2Block[]> {
222
- return Promise.resolve(this.l2Blocks.slice(from - 1, from - 1 + limit));
267
+ public getCheckpoint(query: CheckpointQuery): Promise<PublishedCheckpoint | undefined> {
268
+ const checkpoint = this.resolveCheckpointQuery(query);
269
+ if (!checkpoint) {
270
+ return Promise.resolve(undefined);
271
+ }
272
+ return Promise.resolve(new PublishedCheckpoint(checkpoint, this.mockL1DataForCheckpoint(checkpoint), []));
223
273
  }
224
274
 
225
- public getCheckpoints(from: CheckpointNumber, limit: number) {
226
- const checkpoints = this.checkpointList.slice(from - 1, from - 1 + limit);
275
+ public getCheckpoints(query: CheckpointsQuery): Promise<PublishedCheckpoint[]> {
276
+ const checkpoints = this.resolveCheckpointsQuery(query);
227
277
  return Promise.resolve(
228
278
  checkpoints.map(checkpoint => new PublishedCheckpoint(checkpoint, this.mockL1DataForCheckpoint(checkpoint), [])),
229
279
  );
@@ -234,115 +284,65 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
234
284
  return Promise.resolve(checkpoint);
235
285
  }
236
286
 
237
- public async getCheckpointedBlockByHash(blockHash: BlockHash): Promise<CheckpointedL2Block | undefined> {
238
- for (const block of this.l2Blocks) {
239
- const hash = await block.hash();
240
- if (hash.equals(blockHash)) {
241
- return this.toCheckpointedBlock(block);
242
- }
243
- }
244
- return undefined;
245
- }
246
-
247
- public getCheckpointedBlockByArchive(archive: Fr): Promise<CheckpointedL2Block | undefined> {
248
- const block = this.l2Blocks.find(b => b.archive.root.equals(archive));
249
- if (!block) {
287
+ public getCheckpointData(query: CheckpointQuery): Promise<CheckpointData | undefined> {
288
+ const checkpoint = this.resolveCheckpointQuery(query);
289
+ if (!checkpoint) {
250
290
  return Promise.resolve(undefined);
251
291
  }
252
- return Promise.resolve(this.toCheckpointedBlock(block));
292
+ return Promise.resolve(this.checkpointToData(checkpoint));
253
293
  }
254
294
 
255
- public async getL2BlockByHash(blockHash: BlockHash): Promise<L2Block | undefined> {
256
- for (const block of this.l2Blocks) {
257
- const hash = await block.hash();
258
- if (hash.equals(blockHash)) {
259
- return block;
260
- }
261
- }
262
- return undefined;
263
- }
264
-
265
- public getL2BlockByArchive(archive: Fr): Promise<L2Block | undefined> {
266
- const block = this.l2Blocks.find(b => b.archive.root.equals(archive));
267
- return Promise.resolve(block);
268
- }
269
-
270
- public async getBlockHeaderByHash(blockHash: BlockHash): Promise<BlockHeader | undefined> {
271
- for (const block of this.l2Blocks) {
272
- const hash = await block.hash();
273
- if (hash.equals(blockHash)) {
274
- return block.header;
275
- }
276
- }
277
- return undefined;
295
+ public getCheckpointsData(query: CheckpointsQuery): Promise<CheckpointData[]> {
296
+ const checkpoints = this.resolveCheckpointsQuery(query);
297
+ return Promise.resolve(checkpoints.map(c => this.checkpointToData(c)));
278
298
  }
279
299
 
280
- public getBlockHeaderByArchive(archive: Fr): Promise<BlockHeader | undefined> {
281
- const block = this.l2Blocks.find(b => b.archive.root.equals(archive));
282
- return Promise.resolve(block?.header);
283
- }
284
-
285
- public async getBlockData(number: BlockNumber): Promise<BlockData | undefined> {
286
- const block = this.l2Blocks[number - 1];
287
- if (!block) {
288
- return undefined;
289
- }
300
+ private checkpointToData(checkpoint: Checkpoint): CheckpointData {
290
301
  return {
291
- header: block.header,
292
- archive: block.archive,
293
- blockHash: await block.hash(),
294
- checkpointNumber: block.checkpointNumber,
295
- indexWithinCheckpoint: block.indexWithinCheckpoint,
302
+ checkpointNumber: checkpoint.number,
303
+ header: checkpoint.header,
304
+ archive: checkpoint.archive,
305
+ checkpointOutHash: computeCheckpointOutHash(
306
+ checkpoint.blocks.map(b => b.body.txEffects.map(tx => tx.l2ToL1Msgs)),
307
+ ),
308
+ startBlock: checkpoint.blocks[0].number,
309
+ blockCount: checkpoint.blocks.length,
310
+ feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
311
+ attestations: [],
312
+ l1: this.mockL1DataForCheckpoint(checkpoint),
296
313
  };
297
314
  }
298
315
 
299
- public async getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
300
- const block = this.l2Blocks.find(b => b.archive.root.equals(archive));
301
- if (!block) {
302
- return undefined;
316
+ private resolveCheckpointQuery(query: CheckpointQuery): Checkpoint | undefined {
317
+ if ('number' in query) {
318
+ return this.checkpointList[query.number - 1];
319
+ }
320
+ if ('slot' in query) {
321
+ return this.checkpointList.find(c => c.header.slotNumber === query.slot);
322
+ }
323
+ switch (query.tag) {
324
+ case 'checkpointed':
325
+ return this.checkpointList[this.checkpointList.length - 1];
326
+ case 'proven': {
327
+ const provenCheckpoint = this.checkpointList.filter(c =>
328
+ c.blocks.some(b => b.number <= this.provenBlockNumber),
329
+ );
330
+ return provenCheckpoint.at(-1);
331
+ }
332
+ case 'finalized': {
333
+ const finalizedCheckpoint = this.checkpointList.filter(c =>
334
+ c.blocks.some(b => b.number <= this.finalizedBlockNumber),
335
+ );
336
+ return finalizedCheckpoint.at(-1);
337
+ }
303
338
  }
304
- return {
305
- header: block.header,
306
- archive: block.archive,
307
- blockHash: await block.hash(),
308
- checkpointNumber: block.checkpointNumber,
309
- indexWithinCheckpoint: block.indexWithinCheckpoint,
310
- };
311
- }
312
-
313
- getBlockHeader(number: number | 'latest'): Promise<BlockHeader | undefined> {
314
- return Promise.resolve(this.l2Blocks.at(typeof number === 'number' ? number - 1 : -1)?.header);
315
- }
316
-
317
- getCheckpointsForEpoch(epochNumber: EpochNumber): Promise<Checkpoint[]> {
318
- return Promise.resolve(this.getCheckpointsInEpoch(epochNumber));
319
- }
320
-
321
- getCheckpointsDataForEpoch(epochNumber: EpochNumber): Promise<CheckpointData[]> {
322
- const checkpoints = this.getCheckpointsInEpoch(epochNumber);
323
- return Promise.resolve(
324
- checkpoints.map(
325
- (checkpoint): CheckpointData => ({
326
- checkpointNumber: checkpoint.number,
327
- header: checkpoint.header,
328
- archive: checkpoint.archive,
329
- checkpointOutHash: computeCheckpointOutHash(
330
- checkpoint.blocks.map(b => b.body.txEffects.map(tx => tx.l2ToL1Msgs)),
331
- ),
332
- startBlock: checkpoint.blocks[0].number,
333
- blockCount: checkpoint.blocks.length,
334
- attestations: [],
335
- l1: this.mockL1DataForCheckpoint(checkpoint),
336
- }),
337
- ),
338
- );
339
339
  }
340
340
 
341
- getCheckpointedBlocksForEpoch(epochNumber: EpochNumber): Promise<CheckpointedL2Block[]> {
342
- const checkpoints = this.getCheckpointsInEpoch(epochNumber);
343
- return Promise.resolve(
344
- checkpoints.flatMap(checkpoint => checkpoint.blocks.map(block => this.toCheckpointedBlock(block))),
345
- );
341
+ private resolveCheckpointsQuery(query: CheckpointsQuery): Checkpoint[] {
342
+ if ('from' in query) {
343
+ return this.checkpointList.slice(query.from - 1, query.from - 1 + query.limit);
344
+ }
345
+ return this.getCheckpointsInEpoch(query.epoch);
346
346
  }
347
347
 
348
348
  getBlocksForSlot(slotNumber: SlotNumber): Promise<L2Block[]> {
@@ -350,11 +350,6 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
350
350
  return Promise.resolve(blocks);
351
351
  }
352
352
 
353
- async getCheckpointedBlockHeadersForEpoch(epochNumber: EpochNumber): Promise<BlockHeader[]> {
354
- const checkpointedBlocks = await this.getCheckpointedBlocksForEpoch(epochNumber);
355
- return checkpointedBlocks.map(b => b.block.header);
356
- }
357
-
358
353
  /**
359
354
  * Gets a tx effect.
360
355
  * @param txHash - The hash of the tx corresponding to the tx effect.
@@ -372,7 +367,7 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
372
367
  data: txEffect,
373
368
  l2BlockNumber: block.number,
374
369
  l2BlockHash: await block.hash(),
375
- txIndexInBlock: block.body.txEffects.indexOf(txEffect),
370
+ txIndexInBlock: block.body.txEffects.findIndex(t => t.txHash.equals(txHash)),
376
371
  };
377
372
  }
378
373
 
@@ -394,6 +389,7 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
394
389
  txEffect.transactionFee.toBigInt(),
395
390
  await block.hash(),
396
391
  block.number,
392
+ getEpochAtSlot(block.slot, EmptyL1RollupConstants),
397
393
  );
398
394
  }
399
395
  }
@@ -402,56 +398,77 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
402
398
  }
403
399
 
404
400
  async getL2Tips(): Promise<L2Tips> {
405
- const [latest, proven, finalized, checkpointed] = [
401
+ const [latest, proven, finalized, checkpointed, proposedCheckpoint] = [
406
402
  await this.getBlockNumber(),
407
- await this.getProvenBlockNumber(),
403
+ this.provenBlockNumber,
408
404
  this.finalizedBlockNumber,
409
405
  this.checkpointedBlockNumber,
406
+ await this.getProposedCheckpointL2BlockNumber(),
410
407
  ] as const;
411
408
 
412
409
  const latestBlock = this.l2Blocks[latest - 1];
413
410
  const provenBlock = this.l2Blocks[proven - 1];
414
411
  const finalizedBlock = this.l2Blocks[finalized - 1];
415
412
  const checkpointedBlock = this.l2Blocks[checkpointed - 1];
413
+ const proposedCheckpointBlock = this.l2Blocks[proposedCheckpoint - 1];
414
+
415
+ // For genesis tips (block number 0) report the dynamic initial header hash so consumers
416
+ // running L2BlockStream against this mock agree at block 0 with their local tip store.
417
+ const genesisHash = (await this.initialHeader.hash()).toString();
418
+ const tipHash = async (block: L2Block | undefined, number: number): Promise<string> => {
419
+ if (block) {
420
+ return (await block.hash()).toString();
421
+ }
422
+ return number === 0 ? genesisHash : '';
423
+ };
416
424
 
417
425
  const latestBlockId = {
418
426
  number: BlockNumber(latest),
419
- hash: (await latestBlock?.hash())?.toString(),
427
+ hash: await tipHash(latestBlock, latest),
420
428
  };
421
429
  const provenBlockId = {
422
430
  number: BlockNumber(proven),
423
- hash: (await provenBlock?.hash())?.toString(),
431
+ hash: await tipHash(provenBlock, proven),
424
432
  };
425
433
  const finalizedBlockId = {
426
434
  number: BlockNumber(finalized),
427
- hash: (await finalizedBlock?.hash())?.toString(),
435
+ hash: await tipHash(finalizedBlock, finalized),
428
436
  };
429
437
  const checkpointedBlockId = {
430
438
  number: BlockNumber(checkpointed),
431
- hash: (await checkpointedBlock?.hash())?.toString(),
439
+ hash: await tipHash(checkpointedBlock, checkpointed),
440
+ };
441
+ const proposedCheckpointBlockId = {
442
+ number: BlockNumber(proposedCheckpoint),
443
+ hash: await tipHash(proposedCheckpointBlock, proposedCheckpoint),
432
444
  };
433
445
 
434
- const makeTipId = (blockId: typeof latestBlockId) => ({
435
- block: blockId,
436
- checkpoint: {
437
- number: this.findCheckpointNumberForBlock(blockId.number) ?? CheckpointNumber(0),
438
- hash: blockId.hash,
439
- },
440
- });
446
+ const makeTipId = (blockId: typeof latestBlockId) => {
447
+ const checkpointNumber = this.findCheckpointNumberForBlock(blockId.number) ?? CheckpointNumber(0);
448
+ // Match production semantics: checkpoint 0 is fully synthetic (no real checkpoint header
449
+ // exists at 0), so its hash stays at the protocol constant `GENESIS_CHECKPOINT_HEADER_HASH`
450
+ // even though the block-0 hash is dynamic. See L2TipsCache for the production path.
451
+ const hash = checkpointNumber === 0 ? GENESIS_CHECKPOINT_HEADER_HASH.toString() : blockId.hash;
452
+ return {
453
+ block: blockId,
454
+ checkpoint: { number: checkpointNumber, hash },
455
+ };
456
+ };
441
457
 
442
458
  return {
443
459
  proposed: latestBlockId,
444
460
  checkpointed: makeTipId(checkpointedBlockId),
445
461
  proven: makeTipId(provenBlockId),
446
462
  finalized: makeTipId(finalizedBlockId),
463
+ proposedCheckpoint: makeTipId(proposedCheckpointBlockId),
447
464
  };
448
465
  }
449
466
 
450
- getL2EpochNumber(): Promise<EpochNumber> {
467
+ getSyncedL2EpochNumber(): Promise<EpochNumber> {
451
468
  throw new Error('Method not implemented.');
452
469
  }
453
470
 
454
- getL2SlotNumber(): Promise<SlotNumber> {
471
+ getSyncedL2SlotNumber(): Promise<SlotNumber> {
455
472
  throw new Error('Method not implemented.');
456
473
  }
457
474
 
@@ -463,8 +480,12 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
463
480
  return Promise.resolve(EmptyL1RollupConstants);
464
481
  }
465
482
 
483
+ isPruneDueAtSlot(_slot: SlotNumber): Promise<boolean> {
484
+ return Promise.resolve(false);
485
+ }
486
+
466
487
  getGenesisValues(): Promise<{ genesisArchiveRoot: Fr }> {
467
- return Promise.resolve({ genesisArchiveRoot: new Fr(GENESIS_ARCHIVE_ROOT) });
488
+ return Promise.resolve({ genesisArchiveRoot: this.genesisArchiveRoot ?? new Fr(GENESIS_ARCHIVE_ROOT) });
468
489
  }
469
490
 
470
491
  getL1Timestamp(): Promise<bigint> {
@@ -517,6 +538,95 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
517
538
  return Promise.resolve();
518
539
  }
519
540
 
541
+ async getBlock(query: BlockQuery): Promise<L2Block | undefined> {
542
+ if ('number' in query) {
543
+ if (query.number === 0) {
544
+ return this.getGenesisBlock();
545
+ }
546
+ return this.l2Blocks[query.number - 1];
547
+ }
548
+ if ('hash' in query) {
549
+ const genesis = this.getGenesisBlock();
550
+ if ((await genesis.hash()).equals(query.hash)) {
551
+ return genesis;
552
+ }
553
+ for (const b of this.l2Blocks) {
554
+ const hash = await b.hash();
555
+ if (hash.equals(query.hash)) {
556
+ return b;
557
+ }
558
+ }
559
+ return undefined;
560
+ }
561
+ if ('archive' in query) {
562
+ const genesis = this.getGenesisBlock();
563
+ if (genesis.archive.root.equals(query.archive)) {
564
+ return genesis;
565
+ }
566
+ return this.l2Blocks.find(b => b.archive.root.equals(query.archive));
567
+ }
568
+ const number = this.resolveBlockTag(query.tag);
569
+ if (number === 0) {
570
+ return this.getGenesisBlock();
571
+ }
572
+ return this.l2Blocks[number - 1];
573
+ }
574
+
575
+ private resolveBlockTag(tag: BlockTag): number {
576
+ switch (tag) {
577
+ case 'latest':
578
+ case 'proposed':
579
+ return this.l2Blocks.length;
580
+ case 'checkpointed':
581
+ return this.checkpointedBlockNumber;
582
+ case 'proven':
583
+ return this.provenBlockNumber;
584
+ case 'finalized':
585
+ return this.finalizedBlockNumber;
586
+ }
587
+ }
588
+
589
+ getBlocks(query: BlocksQuery): Promise<L2Block[]> {
590
+ let blocks: L2Block[];
591
+ if ('from' in query) {
592
+ blocks = this.l2Blocks.slice(query.from - 1, query.from - 1 + query.limit);
593
+ } else {
594
+ const epochCheckpoints = this.getCheckpointsInEpoch(query.epoch);
595
+ blocks = epochCheckpoints.flatMap(c => c.blocks);
596
+ }
597
+ if (query.onlyCheckpointed) {
598
+ blocks = blocks.filter(b => b.header.globalVariables.blockNumber <= this.checkpointedBlockNumber);
599
+ }
600
+ return Promise.resolve(blocks);
601
+ }
602
+
603
+ async getBlockData(query: BlockQuery): Promise<BlockData | undefined> {
604
+ const block = await this.getBlock(query);
605
+ if (!block) {
606
+ return undefined;
607
+ }
608
+ return {
609
+ header: block.header,
610
+ archive: block.archive,
611
+ blockHash: await block.hash(),
612
+ checkpointNumber: block.checkpointNumber,
613
+ indexWithinCheckpoint: block.indexWithinCheckpoint,
614
+ };
615
+ }
616
+
617
+ async getBlocksData(query: BlocksQuery): Promise<BlockData[]> {
618
+ const blocks = await this.getBlocks(query);
619
+ return Promise.all(
620
+ blocks.map(async block => ({
621
+ header: block.header,
622
+ archive: block.archive,
623
+ blockHash: await block.hash(),
624
+ checkpointNumber: block.checkpointNumber,
625
+ indexWithinCheckpoint: block.indexWithinCheckpoint,
626
+ })),
627
+ );
628
+ }
629
+
520
630
  isPendingChainInvalid(): Promise<boolean> {
521
631
  return Promise.resolve(false);
522
632
  }
@@ -525,6 +635,10 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
525
635
  return Promise.resolve({ valid: true });
526
636
  }
527
637
 
638
+ getProposedCheckpointData(_query?: ProposedCheckpointQuery): Promise<ProposedCheckpointData | undefined> {
639
+ return Promise.resolve(undefined);
640
+ }
641
+
528
642
  /** Returns checkpoints whose slot falls within the given epoch. */
529
643
  private getCheckpointsInEpoch(epochNumber: EpochNumber): Checkpoint[] {
530
644
  const epochDuration = DefaultL1ContractsConfig.aztecEpochDuration;
@@ -537,22 +651,6 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
537
651
  return new L1PublishedData(BigInt(checkpoint.number), BigInt(checkpoint.number), Buffer32.random().toString());
538
652
  }
539
653
 
540
- /** Creates a CheckpointedL2Block from a block using stored checkpoint info. */
541
- private toCheckpointedBlock(block: L2Block): CheckpointedL2Block {
542
- const checkpoint = this.checkpointList.find(c => c.blocks.some(b => b.number === block.number));
543
- const checkpointNumber = checkpoint?.number ?? block.checkpointNumber;
544
- return new CheckpointedL2Block(
545
- checkpointNumber,
546
- block,
547
- new L1PublishedData(
548
- BigInt(block.number),
549
- BigInt(block.number),
550
- `0x${block.number.toString(16).padStart(64, '0')}`,
551
- ),
552
- [],
553
- );
554
- }
555
-
556
654
  /** Finds the checkpoint number for a block, or undefined if the block is not in any checkpoint. */
557
655
  private findCheckpointNumberForBlock(blockNumber: BlockNumber): CheckpointNumber | undefined {
558
656
  const checkpoint = this.checkpointList.find(c => c.blocks.some(b => b.number === blockNumber));