@aztec/archiver 0.0.1-commit.4d3c002 → 0.0.1-commit.4d9804df

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 (124) hide show
  1. package/README.md +7 -5
  2. package/dest/archiver.d.ts +70 -15
  3. package/dest/archiver.d.ts.map +1 -1
  4. package/dest/archiver.js +274 -71
  5. package/dest/config.d.ts +7 -3
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +39 -16
  8. package/dest/errors.d.ts +35 -13
  9. package/dest/errors.d.ts.map +1 -1
  10. package/dest/errors.js +54 -16
  11. package/dest/factory.d.ts +11 -6
  12. package/dest/factory.d.ts.map +1 -1
  13. package/dest/factory.js +35 -21
  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 +9 -1
  18. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  19. package/dest/l1/calldata_retriever.js +20 -10
  20. package/dest/l1/data_retrieval.d.ts +24 -10
  21. package/dest/l1/data_retrieval.d.ts.map +1 -1
  22. package/dest/l1/data_retrieval.js +13 -19
  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 +32 -0
  31. package/dest/modules/data_source_base.d.ts +71 -50
  32. package/dest/modules/data_source_base.d.ts.map +1 -1
  33. package/dest/modules/data_source_base.js +250 -150
  34. package/dest/modules/data_store_updater.d.ts +28 -9
  35. package/dest/modules/data_store_updater.d.ts.map +1 -1
  36. package/dest/modules/data_store_updater.js +121 -61
  37. package/dest/modules/instrumentation.d.ts +15 -2
  38. package/dest/modules/instrumentation.d.ts.map +1 -1
  39. package/dest/modules/instrumentation.js +48 -9
  40. package/dest/modules/l1_synchronizer.d.ts +11 -7
  41. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  42. package/dest/modules/l1_synchronizer.js +347 -118
  43. package/dest/modules/outbox_trees_resolver.d.ts +62 -0
  44. package/dest/modules/outbox_trees_resolver.d.ts.map +1 -0
  45. package/dest/modules/outbox_trees_resolver.js +162 -0
  46. package/dest/modules/validation.d.ts +30 -7
  47. package/dest/modules/validation.d.ts.map +1 -1
  48. package/dest/modules/validation.js +33 -15
  49. package/dest/store/block_store.d.ts +167 -94
  50. package/dest/store/block_store.d.ts.map +1 -1
  51. package/dest/store/block_store.js +524 -260
  52. package/dest/store/contract_class_store.d.ts +17 -3
  53. package/dest/store/contract_class_store.d.ts.map +1 -1
  54. package/dest/store/contract_class_store.js +36 -2
  55. package/dest/store/contract_instance_store.d.ts +28 -1
  56. package/dest/store/contract_instance_store.d.ts.map +1 -1
  57. package/dest/store/contract_instance_store.js +49 -1
  58. package/dest/store/data_stores.d.ts +68 -0
  59. package/dest/store/data_stores.d.ts.map +1 -0
  60. package/dest/store/data_stores.js +54 -0
  61. package/dest/store/function_names_cache.d.ts +17 -0
  62. package/dest/store/function_names_cache.d.ts.map +1 -0
  63. package/dest/store/function_names_cache.js +30 -0
  64. package/dest/store/l2_tips_cache.d.ts +13 -8
  65. package/dest/store/l2_tips_cache.d.ts.map +1 -1
  66. package/dest/store/l2_tips_cache.js +13 -96
  67. package/dest/store/log_store.d.ts +42 -40
  68. package/dest/store/log_store.d.ts.map +1 -1
  69. package/dest/store/log_store.js +257 -480
  70. package/dest/store/log_store_codec.d.ts +78 -0
  71. package/dest/store/log_store_codec.d.ts.map +1 -0
  72. package/dest/store/log_store_codec.js +110 -0
  73. package/dest/store/message_store.d.ts +9 -3
  74. package/dest/store/message_store.d.ts.map +1 -1
  75. package/dest/store/message_store.js +31 -1
  76. package/dest/test/fake_l1_state.d.ts +7 -3
  77. package/dest/test/fake_l1_state.d.ts.map +1 -1
  78. package/dest/test/fake_l1_state.js +45 -18
  79. package/dest/test/mock_l1_to_l2_message_source.d.ts +1 -1
  80. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  81. package/dest/test/mock_l1_to_l2_message_source.js +1 -2
  82. package/dest/test/mock_l2_block_source.d.ts +47 -54
  83. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  84. package/dest/test/mock_l2_block_source.js +229 -205
  85. package/dest/test/mock_structs.d.ts +1 -1
  86. package/dest/test/mock_structs.d.ts.map +1 -1
  87. package/dest/test/mock_structs.js +2 -2
  88. package/dest/test/noop_l1_archiver.d.ts +14 -6
  89. package/dest/test/noop_l1_archiver.d.ts.map +1 -1
  90. package/dest/test/noop_l1_archiver.js +26 -10
  91. package/package.json +14 -14
  92. package/src/archiver.ts +355 -77
  93. package/src/config.ts +43 -20
  94. package/src/errors.ts +80 -23
  95. package/src/factory.ts +46 -19
  96. package/src/index.ts +18 -2
  97. package/src/l1/calldata_retriever.ts +27 -10
  98. package/src/l1/data_retrieval.ts +38 -36
  99. package/src/l1/validate_historical_logs.ts +140 -0
  100. package/src/modules/contract_data_source_adapter.ts +46 -0
  101. package/src/modules/data_source_base.ts +319 -200
  102. package/src/modules/data_store_updater.ts +146 -64
  103. package/src/modules/instrumentation.ts +43 -10
  104. package/src/modules/l1_synchronizer.ts +499 -136
  105. package/src/modules/outbox_trees_resolver.ts +199 -0
  106. package/src/modules/validation.ts +78 -21
  107. package/src/store/block_store.ts +627 -321
  108. package/src/store/contract_class_store.ts +47 -3
  109. package/src/store/contract_instance_store.ts +61 -1
  110. package/src/store/data_stores.ts +104 -0
  111. package/src/store/function_names_cache.ts +37 -0
  112. package/src/store/l2_tips_cache.ts +16 -109
  113. package/src/store/log_store.ts +296 -652
  114. package/src/store/log_store_codec.ts +143 -0
  115. package/src/store/message_store.ts +35 -2
  116. package/src/test/fake_l1_state.ts +50 -27
  117. package/src/test/mock_l1_to_l2_message_source.ts +0 -1
  118. package/src/test/mock_l2_block_source.ts +276 -258
  119. package/src/test/mock_structs.ts +5 -2
  120. package/src/test/noop_l1_archiver.ts +54 -11
  121. package/dest/store/kv_archiver_store.d.ts +0 -377
  122. package/dest/store/kv_archiver_store.d.ts.map +0 -1
  123. package/dest/store/kv_archiver_store.js +0 -494
  124. package/src/store/kv_archiver_store.ts +0 -713
@@ -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
  /**
@@ -45,10 +55,67 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
45
55
  private provenBlockNumber: number = 0;
46
56
  private finalizedBlockNumber: number = 0;
47
57
  private checkpointedBlockNumber: number = 0;
48
- private proposedCheckpointBlockNumber: number = 0;
58
+
59
+ private initialHeader: BlockHeader = BlockHeader.empty();
60
+ private initialHeaderHash: BlockHash = GENESIS_BLOCK_HEADER_HASH;
61
+ private genesisArchiveRoot?: Fr;
62
+ private genesisBlock?: L2Block;
49
63
 
50
64
  private log = createLogger('archiver:mock_l2_block_source');
51
65
 
66
+ /** Returns the initial header used to synthesize block 0. */
67
+ public getInitialHeader(): BlockHeader {
68
+ return this.initialHeader;
69
+ }
70
+
71
+ /**
72
+ * Sets the initial header used to synthesize block 0. Tests that wire up a real
73
+ * world-state should call this with `worldState.getInitialHeader()` so the L2BlockStream
74
+ * agrees on the genesis hash on both sides. Precomputes and caches the header hash so
75
+ * `getGenesisBlockHash()` can return synchronously.
76
+ */
77
+ public async setInitialHeader(header: BlockHeader): Promise<void> {
78
+ this.initialHeader = header;
79
+ this.initialHeaderHash = await header.hash();
80
+ this.genesisBlock = undefined;
81
+ }
82
+
83
+ /**
84
+ * Returns the precomputed hash of the genesis block header. Defaults to the static
85
+ * {@link GENESIS_BLOCK_HEADER_HASH} unless {@link setInitialHeader} has been called with a
86
+ * custom header.
87
+ */
88
+ public getGenesisBlockHash(): BlockHash {
89
+ return this.initialHeaderHash;
90
+ }
91
+
92
+ /**
93
+ * Sets the post-genesis archive root used to synthesize block 0. Mirrors the real archiver,
94
+ * whose synthetic block 0 carries `new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1)` rather
95
+ * than `AppendOnlyTreeSnapshot.empty()`. Tests wiring up a real world-state should set this so
96
+ * archive-based block lookups against the mock match production semantics.
97
+ */
98
+ public setGenesisArchiveRoot(root: Fr): void {
99
+ this.genesisArchiveRoot = root;
100
+ this.genesisBlock = undefined;
101
+ }
102
+
103
+ private getGenesisBlock(): L2Block {
104
+ if (this.genesisBlock) {
105
+ return this.genesisBlock;
106
+ }
107
+ const archive = this.genesisArchiveRoot
108
+ ? new AppendOnlyTreeSnapshot(this.genesisArchiveRoot, 1)
109
+ : AppendOnlyTreeSnapshot.empty();
110
+ return (this.genesisBlock = new L2Block(
111
+ archive,
112
+ this.initialHeader,
113
+ Body.empty(),
114
+ CheckpointNumber.ZERO,
115
+ IndexWithinCheckpoint(0),
116
+ ));
117
+ }
118
+
52
119
  /** Creates blocks grouped into single-block checkpoints. */
53
120
  public async createBlocks(numBlocks: number) {
54
121
  await this.createCheckpoints(numBlocks, 1);
@@ -96,7 +163,6 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
96
163
  });
97
164
  // Keep tip numbers consistent with remaining blocks.
98
165
  this.checkpointedBlockNumber = Math.min(this.checkpointedBlockNumber, maxBlockNum);
99
- this.proposedCheckpointBlockNumber = Math.min(this.proposedCheckpointBlockNumber, maxBlockNum);
100
166
  this.provenBlockNumber = Math.min(this.provenBlockNumber, maxBlockNum);
101
167
  this.finalizedBlockNumber = Math.min(this.finalizedBlockNumber, maxBlockNum);
102
168
  this.log.verbose(`Removed ${numBlocks} blocks from the mock L2 block source`);
@@ -113,17 +179,9 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
113
179
  this.finalizedBlockNumber = finalizedBlockNumber;
114
180
  }
115
181
 
116
- public setProposedCheckpointBlockNumber(blockNumber: number) {
117
- this.proposedCheckpointBlockNumber = blockNumber;
118
- }
119
-
120
182
  public setCheckpointedBlockNumber(checkpointedBlockNumber: number) {
121
183
  const prevCheckpointed = this.checkpointedBlockNumber;
122
184
  this.checkpointedBlockNumber = checkpointedBlockNumber;
123
- // Proposed checkpoint is always at least as advanced as checkpointed
124
- if (this.proposedCheckpointBlockNumber < checkpointedBlockNumber) {
125
- this.proposedCheckpointBlockNumber = checkpointedBlockNumber;
126
- }
127
185
  // Auto-create single-block checkpoints for newly checkpointed blocks that don't have one yet.
128
186
  // This handles blocks added via addProposedBlocks that are now being marked as checkpointed.
129
187
  const newCheckpoints: Checkpoint[] = [];
@@ -171,84 +229,32 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
171
229
  * Gets the number of the latest L2 block processed by the block source implementation.
172
230
  * @returns In this mock instance, returns the number of L2 blocks that we've mocked.
173
231
  */
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));
188
- }
189
-
190
- public getProposedCheckpointL2BlockNumber() {
191
- return Promise.resolve(BlockNumber(this.proposedCheckpointBlockNumber));
192
- }
193
-
194
- public getCheckpointedBlock(number: BlockNumber): Promise<CheckpointedL2Block | undefined> {
195
- if (number > this.checkpointedBlockNumber) {
196
- return Promise.resolve(undefined);
232
+ public getBlockNumber(): Promise<BlockNumber>;
233
+ public getBlockNumber(query: BlockQuery): Promise<BlockNumber | undefined>;
234
+ public async getBlockNumber(query?: BlockQuery): Promise<BlockNumber | undefined> {
235
+ if (!query) {
236
+ return BlockNumber(this.l2Blocks.length);
197
237
  }
198
- const block = this.l2Blocks[number - 1];
199
- if (!block) {
200
- return Promise.resolve(undefined);
238
+ if ('number' in query) {
239
+ return query.number;
201
240
  }
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
- }
241
+ if ('tag' in query) {
242
+ return BlockNumber(this.resolveBlockTag(query.tag));
216
243
  }
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);
244
+ const block = await this.getBlock(query);
245
+ return block ? block.header.globalVariables.blockNumber : undefined;
228
246
  }
229
247
 
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));
248
+ public getCheckpoint(query: CheckpointQuery): Promise<PublishedCheckpoint | undefined> {
249
+ const checkpoint = this.resolveCheckpointQuery(query);
250
+ if (!checkpoint) {
251
+ return Promise.resolve(undefined);
252
+ }
253
+ return Promise.resolve(new PublishedCheckpoint(checkpoint, this.mockL1DataForCheckpoint(checkpoint), []));
248
254
  }
249
255
 
250
- public getCheckpoints(from: CheckpointNumber, limit: number) {
251
- const checkpoints = this.checkpointList.slice(from - 1, from - 1 + limit);
256
+ public getCheckpoints(query: CheckpointsQuery): Promise<PublishedCheckpoint[]> {
257
+ const checkpoints = this.resolveCheckpointsQuery(query);
252
258
  return Promise.resolve(
253
259
  checkpoints.map(checkpoint => new PublishedCheckpoint(checkpoint, this.mockL1DataForCheckpoint(checkpoint), [])),
254
260
  );
@@ -259,115 +265,72 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
259
265
  return Promise.resolve(checkpoint);
260
266
  }
261
267
 
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) {
268
+ public getCheckpointData(query: CheckpointQuery): Promise<CheckpointData | undefined> {
269
+ const checkpoint = this.resolveCheckpointQuery(query);
270
+ if (!checkpoint) {
275
271
  return Promise.resolve(undefined);
276
272
  }
277
- return Promise.resolve(this.toCheckpointedBlock(block));
273
+ return Promise.resolve(this.checkpointToData(checkpoint));
278
274
  }
279
275
 
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;
276
+ public getCheckpointsData(query: CheckpointsQuery): Promise<CheckpointData[]> {
277
+ const checkpoints = this.resolveCheckpointsQuery(query);
278
+ return Promise.resolve(checkpoints.map(c => this.checkpointToData(c)));
288
279
  }
289
280
 
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);
281
+ private checkpointToData(checkpoint: Checkpoint): CheckpointData {
282
+ return {
283
+ checkpointNumber: checkpoint.number,
284
+ header: checkpoint.header,
285
+ archive: checkpoint.archive,
286
+ checkpointOutHash: computeCheckpointOutHash(
287
+ checkpoint.blocks.map(b => b.body.txEffects.map(tx => tx.l2ToL1Msgs)),
288
+ ),
289
+ startBlock: checkpoint.blocks[0].number,
290
+ blockCount: checkpoint.blocks.length,
291
+ feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
292
+ attestations: [],
293
+ l1: this.mockL1DataForCheckpoint(checkpoint),
294
+ };
293
295
  }
294
296
 
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;
297
+ private resolveCheckpointQuery(query: CheckpointQuery): Checkpoint | undefined {
298
+ if ('number' in query) {
299
+ return this.checkpointList[query.number - 1];
300
+ }
301
+ if ('slot' in query) {
302
+ return this.checkpointList.find(c => c.header.slotNumber === query.slot);
303
+ }
304
+ switch (query.tag) {
305
+ case 'checkpointed':
306
+ return this.checkpointList[this.checkpointList.length - 1];
307
+ case 'proven': {
308
+ const provenCheckpoint = this.checkpointList.filter(c =>
309
+ c.blocks.some(b => b.number <= this.provenBlockNumber),
310
+ );
311
+ return provenCheckpoint.at(-1);
312
+ }
313
+ case 'finalized': {
314
+ const finalizedCheckpoint = this.checkpointList.filter(c =>
315
+ c.blocks.some(b => b.number <= this.finalizedBlockNumber),
316
+ );
317
+ return finalizedCheckpoint.at(-1);
300
318
  }
301
319
  }
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
320
  }
309
321
 
310
- public async getBlockData(number: BlockNumber): Promise<BlockData | undefined> {
311
- const block = this.l2Blocks[number - 1];
312
- if (!block) {
313
- return undefined;
322
+ private resolveCheckpointsQuery(query: CheckpointsQuery): Checkpoint[] {
323
+ if ('from' in query) {
324
+ return this.checkpointList.slice(query.from - 1, query.from - 1 + query.limit);
314
325
  }
315
- return {
316
- header: block.header,
317
- archive: block.archive,
318
- blockHash: await block.hash(),
319
- checkpointNumber: block.checkpointNumber,
320
- indexWithinCheckpoint: block.indexWithinCheckpoint,
321
- };
322
- }
323
-
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;
326
+ if ('fromSlot' in query) {
327
+ const matching = this.checkpointList.filter(c =>
328
+ query.reverse ? c.header.slotNumber <= query.fromSlot : c.header.slotNumber >= query.fromSlot,
329
+ );
330
+ const nearestFirst = query.reverse ? matching.reverse() : matching;
331
+ return nearestFirst.slice(0, query.limit);
328
332
  }
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
- }
365
-
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
- );
333
+ return this.getCheckpointsInEpoch(query.epoch);
371
334
  }
372
335
 
373
336
  getBlocksForSlot(slotNumber: SlotNumber): Promise<L2Block[]> {
@@ -375,11 +338,6 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
375
338
  return Promise.resolve(blocks);
376
339
  }
377
340
 
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
341
  /**
384
342
  * Gets a tx effect.
385
343
  * @param txHash - The hash of the tx corresponding to the tx effect.
@@ -397,86 +355,73 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
397
355
  data: txEffect,
398
356
  l2BlockNumber: block.number,
399
357
  l2BlockHash: await block.hash(),
400
- txIndexInBlock: block.body.txEffects.indexOf(txEffect),
358
+ txIndexInBlock: block.body.txEffects.findIndex(t => t.txHash.equals(txHash)),
359
+ slotNumber: block.header.globalVariables.slotNumber,
401
360
  };
402
361
  }
403
362
 
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;
363
+ public getL2ToL1MembershipWitness(): Promise<undefined> {
364
+ // Mock does not back the L2-to-L1 message witness flow.
365
+ return Promise.resolve(undefined);
428
366
  }
429
367
 
430
368
  async getL2Tips(): Promise<L2Tips> {
431
- const [latest, proven, finalized, checkpointed, proposedCheckpoint] = [
369
+ const [latest, proven, finalized, checkpointed] = [
432
370
  await this.getBlockNumber(),
433
- await this.getProvenBlockNumber(),
371
+ this.provenBlockNumber,
434
372
  this.finalizedBlockNumber,
435
373
  this.checkpointedBlockNumber,
436
- await this.getProposedCheckpointL2BlockNumber(),
437
374
  ] as const;
438
375
 
439
376
  const latestBlock = this.l2Blocks[latest - 1];
440
377
  const provenBlock = this.l2Blocks[proven - 1];
441
378
  const finalizedBlock = this.l2Blocks[finalized - 1];
442
379
  const checkpointedBlock = this.l2Blocks[checkpointed - 1];
443
- const proposedCheckpointBlock = this.l2Blocks[proposedCheckpoint - 1];
380
+
381
+ // For genesis tips (block number 0) report the dynamic initial header hash so consumers
382
+ // running L2BlockStream against this mock agree at block 0 with their local tip store.
383
+ const genesisHash = (await this.initialHeader.hash()).toString();
384
+ const tipHash = async (block: L2Block | undefined, number: number): Promise<string> => {
385
+ if (block) {
386
+ return (await block.hash()).toString();
387
+ }
388
+ return number === 0 ? genesisHash : '';
389
+ };
444
390
 
445
391
  const latestBlockId = {
446
392
  number: BlockNumber(latest),
447
- hash: (await latestBlock?.hash())?.toString(),
393
+ hash: await tipHash(latestBlock, latest),
448
394
  };
449
395
  const provenBlockId = {
450
396
  number: BlockNumber(proven),
451
- hash: (await provenBlock?.hash())?.toString(),
397
+ hash: await tipHash(provenBlock, proven),
452
398
  };
453
399
  const finalizedBlockId = {
454
400
  number: BlockNumber(finalized),
455
- hash: (await finalizedBlock?.hash())?.toString(),
401
+ hash: await tipHash(finalizedBlock, finalized),
456
402
  };
457
403
  const checkpointedBlockId = {
458
404
  number: BlockNumber(checkpointed),
459
- hash: (await checkpointedBlock?.hash())?.toString(),
460
- };
461
- const proposedCheckpointBlockId = {
462
- number: BlockNumber(proposedCheckpoint),
463
- hash: (await proposedCheckpointBlock?.hash())?.toString(),
405
+ hash: await tipHash(checkpointedBlock, checkpointed),
464
406
  };
465
407
 
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
- });
408
+ const makeTipId = (blockId: typeof latestBlockId) => {
409
+ const checkpointNumber = this.findCheckpointNumberForBlock(blockId.number) ?? CheckpointNumber(0);
410
+ // Match production semantics: checkpoint 0 is fully synthetic (no real checkpoint header
411
+ // exists at 0), so its hash stays at the protocol constant `GENESIS_CHECKPOINT_HEADER_HASH`
412
+ // even though the block-0 hash is dynamic. See L2TipsCache for the production path.
413
+ const hash = checkpointNumber === 0 ? GENESIS_CHECKPOINT_HEADER_HASH.toString() : blockId.hash;
414
+ return {
415
+ block: blockId,
416
+ checkpoint: { number: checkpointNumber, hash },
417
+ };
418
+ };
473
419
 
474
420
  return {
475
421
  proposed: latestBlockId,
476
422
  checkpointed: makeTipId(checkpointedBlockId),
477
423
  proven: makeTipId(provenBlockId),
478
424
  finalized: makeTipId(finalizedBlockId),
479
- proposedCheckpoint: makeTipId(proposedCheckpointBlockId),
480
425
  };
481
426
  }
482
427
 
@@ -496,8 +441,12 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
496
441
  return Promise.resolve(EmptyL1RollupConstants);
497
442
  }
498
443
 
444
+ isPruneDueAtSlot(_slot: SlotNumber): Promise<boolean> {
445
+ return Promise.resolve(false);
446
+ }
447
+
499
448
  getGenesisValues(): Promise<{ genesisArchiveRoot: Fr }> {
500
- return Promise.resolve({ genesisArchiveRoot: new Fr(GENESIS_ARCHIVE_ROOT) });
449
+ return Promise.resolve({ genesisArchiveRoot: this.genesisArchiveRoot ?? new Fr(GENESIS_ARCHIVE_ROOT) });
501
450
  }
502
451
 
503
452
  getL1Timestamp(): Promise<bigint> {
@@ -550,6 +499,95 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
550
499
  return Promise.resolve();
551
500
  }
552
501
 
502
+ async getBlock(query: BlockQuery): Promise<L2Block | undefined> {
503
+ if ('number' in query) {
504
+ if (query.number === 0) {
505
+ return this.getGenesisBlock();
506
+ }
507
+ return this.l2Blocks[query.number - 1];
508
+ }
509
+ if ('hash' in query) {
510
+ const genesis = this.getGenesisBlock();
511
+ if ((await genesis.hash()).equals(query.hash)) {
512
+ return genesis;
513
+ }
514
+ for (const b of this.l2Blocks) {
515
+ const hash = await b.hash();
516
+ if (hash.equals(query.hash)) {
517
+ return b;
518
+ }
519
+ }
520
+ return undefined;
521
+ }
522
+ if ('archive' in query) {
523
+ const genesis = this.getGenesisBlock();
524
+ if (genesis.archive.root.equals(query.archive)) {
525
+ return genesis;
526
+ }
527
+ return this.l2Blocks.find(b => b.archive.root.equals(query.archive));
528
+ }
529
+ const number = this.resolveBlockTag(query.tag);
530
+ if (number === 0) {
531
+ return this.getGenesisBlock();
532
+ }
533
+ return this.l2Blocks[number - 1];
534
+ }
535
+
536
+ private resolveBlockTag(tag: BlockTag): number {
537
+ switch (tag) {
538
+ case 'latest':
539
+ case 'proposed':
540
+ return this.l2Blocks.length;
541
+ case 'checkpointed':
542
+ return this.checkpointedBlockNumber;
543
+ case 'proven':
544
+ return this.provenBlockNumber;
545
+ case 'finalized':
546
+ return this.finalizedBlockNumber;
547
+ }
548
+ }
549
+
550
+ getBlocks(query: BlocksQuery): Promise<L2Block[]> {
551
+ let blocks: L2Block[];
552
+ if ('from' in query) {
553
+ blocks = this.l2Blocks.slice(query.from - 1, query.from - 1 + query.limit);
554
+ } else {
555
+ const epochCheckpoints = this.getCheckpointsInEpoch(query.epoch);
556
+ blocks = epochCheckpoints.flatMap(c => c.blocks);
557
+ }
558
+ if (query.onlyCheckpointed) {
559
+ blocks = blocks.filter(b => b.header.globalVariables.blockNumber <= this.checkpointedBlockNumber);
560
+ }
561
+ return Promise.resolve(blocks);
562
+ }
563
+
564
+ async getBlockData(query: BlockQuery): Promise<BlockData | undefined> {
565
+ const block = await this.getBlock(query);
566
+ if (!block) {
567
+ return undefined;
568
+ }
569
+ return {
570
+ header: block.header,
571
+ archive: block.archive,
572
+ blockHash: await block.hash(),
573
+ checkpointNumber: block.checkpointNumber,
574
+ indexWithinCheckpoint: block.indexWithinCheckpoint,
575
+ };
576
+ }
577
+
578
+ async getBlocksData(query: BlocksQuery): Promise<BlockData[]> {
579
+ const blocks = await this.getBlocks(query);
580
+ return Promise.all(
581
+ blocks.map(async block => ({
582
+ header: block.header,
583
+ archive: block.archive,
584
+ blockHash: await block.hash(),
585
+ checkpointNumber: block.checkpointNumber,
586
+ indexWithinCheckpoint: block.indexWithinCheckpoint,
587
+ })),
588
+ );
589
+ }
590
+
553
591
  isPendingChainInvalid(): Promise<boolean> {
554
592
  return Promise.resolve(false);
555
593
  }
@@ -558,11 +596,7 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
558
596
  return Promise.resolve({ valid: true });
559
597
  }
560
598
 
561
- getProposedCheckpoint(): Promise<ProposedCheckpointData | undefined> {
562
- return Promise.resolve(undefined);
563
- }
564
-
565
- getProposedCheckpointOnly(): Promise<ProposedCheckpointData | undefined> {
599
+ getProposedCheckpointData(_query?: ProposedCheckpointQuery): Promise<ProposedCheckpointData | undefined> {
566
600
  return Promise.resolve(undefined);
567
601
  }
568
602
 
@@ -578,22 +612,6 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
578
612
  return new L1PublishedData(BigInt(checkpoint.number), BigInt(checkpoint.number), Buffer32.random().toString());
579
613
  }
580
614
 
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
615
  /** Finds the checkpoint number for a block, or undefined if the block is not in any checkpoint. */
598
616
  private findCheckpointNumberForBlock(blockNumber: BlockNumber): CheckpointNumber | undefined {
599
617
  const checkpoint = this.checkpointList.find(c => c.blocks.some(b => b.number === blockNumber));