@aztec/archiver 0.0.1-commit.b2a5d0dd1 → 0.0.1-commit.b3d3157a

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