@aztec/archiver 0.0.1-commit.86469d5 → 0.0.1-commit.8655d4a

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 (132) hide show
  1. package/README.md +19 -11
  2. package/dest/archiver.d.ts +39 -17
  3. package/dest/archiver.d.ts.map +1 -1
  4. package/dest/archiver.js +260 -160
  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 +13 -9
  12. package/dest/factory.d.ts.map +1 -1
  13. package/dest/factory.js +51 -37
  14. package/dest/index.d.ts +12 -3
  15. package/dest/index.d.ts.map +1 -1
  16. package/dest/index.js +11 -2
  17. package/dest/l1/bin/retrieve-calldata.js +36 -33
  18. package/dest/l1/calldata_retriever.d.ts +74 -50
  19. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  20. package/dest/l1/calldata_retriever.js +201 -260
  21. package/dest/l1/data_retrieval.d.ts +26 -17
  22. package/dest/l1/data_retrieval.d.ts.map +1 -1
  23. package/dest/l1/data_retrieval.js +42 -47
  24. package/dest/l1/spire_proposer.d.ts +5 -5
  25. package/dest/l1/spire_proposer.d.ts.map +1 -1
  26. package/dest/l1/spire_proposer.js +9 -17
  27. package/dest/l1/trace_tx.d.ts +12 -66
  28. package/dest/l1/trace_tx.d.ts.map +1 -1
  29. package/dest/l1/validate_historical_logs.d.ts +23 -0
  30. package/dest/l1/validate_historical_logs.d.ts.map +1 -0
  31. package/dest/l1/validate_historical_logs.js +108 -0
  32. package/dest/modules/contract_data_source_adapter.d.ts +25 -0
  33. package/dest/modules/contract_data_source_adapter.d.ts.map +1 -0
  34. package/dest/modules/contract_data_source_adapter.js +40 -0
  35. package/dest/modules/data_source_base.d.ts +71 -42
  36. package/dest/modules/data_source_base.d.ts.map +1 -1
  37. package/dest/modules/data_source_base.js +270 -179
  38. package/dest/modules/data_store_updater.d.ts +49 -17
  39. package/dest/modules/data_store_updater.d.ts.map +1 -1
  40. package/dest/modules/data_store_updater.js +211 -121
  41. package/dest/modules/instrumentation.d.ts +21 -3
  42. package/dest/modules/instrumentation.d.ts.map +1 -1
  43. package/dest/modules/instrumentation.js +44 -9
  44. package/dest/modules/l1_synchronizer.d.ts +14 -12
  45. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  46. package/dest/modules/l1_synchronizer.js +443 -211
  47. package/dest/modules/validation.d.ts +4 -3
  48. package/dest/modules/validation.d.ts.map +1 -1
  49. package/dest/modules/validation.js +6 -6
  50. package/dest/store/block_store.d.ts +174 -66
  51. package/dest/store/block_store.d.ts.map +1 -1
  52. package/dest/store/block_store.js +743 -245
  53. package/dest/store/contract_class_store.d.ts +17 -4
  54. package/dest/store/contract_class_store.d.ts.map +1 -1
  55. package/dest/store/contract_class_store.js +24 -68
  56. package/dest/store/contract_instance_store.d.ts +28 -1
  57. package/dest/store/contract_instance_store.d.ts.map +1 -1
  58. package/dest/store/contract_instance_store.js +37 -2
  59. package/dest/store/data_stores.d.ts +68 -0
  60. package/dest/store/data_stores.d.ts.map +1 -0
  61. package/dest/store/data_stores.js +54 -0
  62. package/dest/store/function_names_cache.d.ts +17 -0
  63. package/dest/store/function_names_cache.d.ts.map +1 -0
  64. package/dest/store/function_names_cache.js +30 -0
  65. package/dest/store/l2_tips_cache.d.ts +25 -0
  66. package/dest/store/l2_tips_cache.d.ts.map +1 -0
  67. package/dest/store/l2_tips_cache.js +26 -0
  68. package/dest/store/log_store.d.ts +42 -37
  69. package/dest/store/log_store.d.ts.map +1 -1
  70. package/dest/store/log_store.js +262 -388
  71. package/dest/store/log_store_codec.d.ts +70 -0
  72. package/dest/store/log_store_codec.d.ts.map +1 -0
  73. package/dest/store/log_store_codec.js +101 -0
  74. package/dest/store/message_store.d.ts +11 -1
  75. package/dest/store/message_store.d.ts.map +1 -1
  76. package/dest/store/message_store.js +51 -9
  77. package/dest/test/fake_l1_state.d.ts +25 -1
  78. package/dest/test/fake_l1_state.d.ts.map +1 -1
  79. package/dest/test/fake_l1_state.js +166 -32
  80. package/dest/test/mock_archiver.d.ts +1 -1
  81. package/dest/test/mock_archiver.d.ts.map +1 -1
  82. package/dest/test/mock_archiver.js +3 -2
  83. package/dest/test/mock_l1_to_l2_message_source.d.ts +1 -1
  84. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  85. package/dest/test/mock_l1_to_l2_message_source.js +2 -1
  86. package/dest/test/mock_l2_block_source.d.ts +62 -41
  87. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  88. package/dest/test/mock_l2_block_source.js +321 -202
  89. package/dest/test/mock_structs.d.ts +4 -1
  90. package/dest/test/mock_structs.d.ts.map +1 -1
  91. package/dest/test/mock_structs.js +13 -1
  92. package/dest/test/noop_l1_archiver.d.ts +12 -6
  93. package/dest/test/noop_l1_archiver.d.ts.map +1 -1
  94. package/dest/test/noop_l1_archiver.js +26 -9
  95. package/package.json +14 -14
  96. package/src/archiver.ts +319 -181
  97. package/src/config.ts +32 -12
  98. package/src/errors.ts +122 -21
  99. package/src/factory.ts +75 -36
  100. package/src/index.ts +19 -2
  101. package/src/l1/README.md +25 -68
  102. package/src/l1/bin/retrieve-calldata.ts +46 -39
  103. package/src/l1/calldata_retriever.ts +260 -379
  104. package/src/l1/data_retrieval.ts +58 -69
  105. package/src/l1/spire_proposer.ts +7 -15
  106. package/src/l1/validate_historical_logs.ts +140 -0
  107. package/src/modules/contract_data_source_adapter.ts +55 -0
  108. package/src/modules/data_source_base.ts +347 -221
  109. package/src/modules/data_store_updater.ts +248 -153
  110. package/src/modules/instrumentation.ts +56 -9
  111. package/src/modules/l1_synchronizer.ts +585 -258
  112. package/src/modules/validation.ts +10 -9
  113. package/src/store/block_store.ts +924 -300
  114. package/src/store/contract_class_store.ts +31 -103
  115. package/src/store/contract_instance_store.ts +51 -5
  116. package/src/store/data_stores.ts +104 -0
  117. package/src/store/function_names_cache.ts +37 -0
  118. package/src/store/l2_tips_cache.ts +35 -0
  119. package/src/store/log_store.ts +303 -499
  120. package/src/store/log_store_codec.ts +132 -0
  121. package/src/store/message_store.ts +60 -10
  122. package/src/structs/inbox_message.ts +1 -1
  123. package/src/test/fake_l1_state.ts +213 -42
  124. package/src/test/mock_archiver.ts +3 -2
  125. package/src/test/mock_l1_to_l2_message_source.ts +1 -0
  126. package/src/test/mock_l2_block_source.ts +394 -210
  127. package/src/test/mock_structs.ts +20 -6
  128. package/src/test/noop_l1_archiver.ts +39 -9
  129. package/dest/store/kv_archiver_store.d.ts +0 -340
  130. package/dest/store/kv_archiver_store.d.ts.map +0 -1
  131. package/dest/store/kv_archiver_store.js +0 -446
  132. package/src/store/kv_archiver_store.ts +0 -639
@@ -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';
@@ -8,17 +14,40 @@ import { createLogger } from '@aztec/foundation/log';
8
14
  import type { FunctionSelector } from '@aztec/stdlib/abi';
9
15
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
10
16
  import {
11
- BlockHash,
12
- CheckpointedL2Block,
17
+ type BlockData,
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,
13
27
  L2Block,
14
28
  type L2BlockSource,
15
29
  type L2Tips,
30
+ type ProposedCheckpointQuery,
16
31
  type ValidateCheckpointResult,
17
32
  } from '@aztec/stdlib/block';
18
- import { Checkpoint, 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';
19
40
  import type { ContractClassPublic, ContractDataSource, ContractInstanceWithAddress } from '@aztec/stdlib/contract';
20
- import { EmptyL1RollupConstants, type L1RollupConstants, getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
21
- import { type BlockHeader, TxExecutionResult, TxHash, TxReceipt, TxStatus } from '@aztec/stdlib/tx';
41
+ import {
42
+ EmptyL1RollupConstants,
43
+ type L1RollupConstants,
44
+ getEpochAtSlot,
45
+ getSlotRangeForEpoch,
46
+ } from '@aztec/stdlib/epoch-helpers';
47
+ import { computeCheckpointOutHash } from '@aztec/stdlib/messaging';
48
+ import { CheckpointHeader } from '@aztec/stdlib/rollup';
49
+ import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
50
+ import { BlockHeader, TxExecutionResult, TxHash, TxReceipt, TxStatus } from '@aztec/stdlib/tx';
22
51
  import type { UInt64 } from '@aztec/stdlib/types';
23
52
 
24
53
  /**
@@ -26,21 +55,103 @@ import type { UInt64 } from '@aztec/stdlib/types';
26
55
  */
27
56
  export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
28
57
  protected l2Blocks: L2Block[] = [];
58
+ protected checkpointList: Checkpoint[] = [];
29
59
 
30
60
  private provenBlockNumber: number = 0;
31
61
  private finalizedBlockNumber: number = 0;
32
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;
33
69
 
34
70
  private log = createLogger('archiver:mock_l2_block_source');
35
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
+
125
+ /** Creates blocks grouped into single-block checkpoints. */
36
126
  public async createBlocks(numBlocks: number) {
37
- for (let i = 0; i < numBlocks; i++) {
38
- const blockNum = this.l2Blocks.length + 1;
39
- const block = await L2Block.random(BlockNumber(blockNum), { slotNumber: SlotNumber(blockNum) });
40
- this.l2Blocks.push(block);
127
+ await this.createCheckpoints(numBlocks, 1);
128
+ }
129
+
130
+ public getCheckpointNumber(): Promise<CheckpointNumber> {
131
+ return Promise.resolve(
132
+ this.checkpointList.length === 0 ? CheckpointNumber.ZERO : CheckpointNumber(this.checkpointList.length),
133
+ );
134
+ }
135
+
136
+ /** Creates checkpoints, each containing `blocksPerCheckpoint` blocks. */
137
+ public async createCheckpoints(numCheckpoints: number, blocksPerCheckpoint: number = 1) {
138
+ for (let c = 0; c < numCheckpoints; c++) {
139
+ const checkpointNum = CheckpointNumber(this.checkpointList.length + 1);
140
+ const startBlockNum = this.l2Blocks.length + 1;
141
+ const slotNumber = SlotNumber(Number(checkpointNum));
142
+ const checkpoint = await Checkpoint.random(checkpointNum, {
143
+ numBlocks: blocksPerCheckpoint,
144
+ startBlockNumber: startBlockNum,
145
+ slotNumber,
146
+ checkpointNumber: checkpointNum,
147
+ });
148
+ this.checkpointList.push(checkpoint);
149
+ this.l2Blocks.push(...checkpoint.blocks);
41
150
  }
42
151
 
43
- this.log.verbose(`Created ${numBlocks} blocks in the mock L2 block source`);
152
+ this.log.verbose(
153
+ `Created ${numCheckpoints} checkpoints with ${blocksPerCheckpoint} blocks each in the mock L2 block source`,
154
+ );
44
155
  }
45
156
 
46
157
  public addProposedBlocks(blocks: L2Block[]) {
@@ -50,6 +161,17 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
50
161
 
51
162
  public removeBlocks(numBlocks: number) {
52
163
  this.l2Blocks = this.l2Blocks.slice(0, -numBlocks);
164
+ const maxBlockNum = this.l2Blocks.length;
165
+ // Remove any checkpoint whose last block is beyond the remaining blocks.
166
+ this.checkpointList = this.checkpointList.filter(c => {
167
+ const lastBlockNum = c.blocks[0].number + c.blocks.length - 1;
168
+ return lastBlockNum <= maxBlockNum;
169
+ });
170
+ // Keep tip numbers consistent with remaining blocks.
171
+ this.checkpointedBlockNumber = Math.min(this.checkpointedBlockNumber, maxBlockNum);
172
+ this.proposedCheckpointBlockNumber = Math.min(this.proposedCheckpointBlockNumber, maxBlockNum);
173
+ this.provenBlockNumber = Math.min(this.provenBlockNumber, maxBlockNum);
174
+ this.finalizedBlockNumber = Math.min(this.finalizedBlockNumber, maxBlockNum);
53
175
  this.log.verbose(`Removed ${numBlocks} blocks from the mock L2 block source`);
54
176
  }
55
177
 
@@ -64,8 +186,42 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
64
186
  this.finalizedBlockNumber = finalizedBlockNumber;
65
187
  }
66
188
 
189
+ public setProposedCheckpointBlockNumber(blockNumber: number) {
190
+ this.proposedCheckpointBlockNumber = blockNumber;
191
+ }
192
+
67
193
  public setCheckpointedBlockNumber(checkpointedBlockNumber: number) {
194
+ const prevCheckpointed = this.checkpointedBlockNumber;
68
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
+ }
200
+ // Auto-create single-block checkpoints for newly checkpointed blocks that don't have one yet.
201
+ // This handles blocks added via addProposedBlocks that are now being marked as checkpointed.
202
+ const newCheckpoints: Checkpoint[] = [];
203
+ for (let blockNum = prevCheckpointed + 1; blockNum <= checkpointedBlockNumber; blockNum++) {
204
+ const block = this.l2Blocks[blockNum - 1];
205
+ if (!block) {
206
+ continue;
207
+ }
208
+ if (this.checkpointList.some(c => c.blocks.some(b => b.number === block.number))) {
209
+ continue;
210
+ }
211
+ const checkpointNum = CheckpointNumber(this.checkpointList.length + newCheckpoints.length + 1);
212
+ const checkpoint = new Checkpoint(
213
+ block.archive,
214
+ CheckpointHeader.random({ slotNumber: block.header.globalVariables.slotNumber }),
215
+ [block],
216
+ checkpointNum,
217
+ );
218
+ newCheckpoints.push(checkpoint);
219
+ }
220
+ // Insert new checkpoints in order by number.
221
+ if (newCheckpoints.length > 0) {
222
+ this.checkpointList.push(...newCheckpoints);
223
+ this.checkpointList.sort((a, b) => a.number - b.number);
224
+ }
69
225
  }
70
226
 
71
227
  /**
@@ -88,212 +244,105 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
88
244
  * Gets the number of the latest L2 block processed by the block source implementation.
89
245
  * @returns In this mock instance, returns the number of L2 blocks that we've mocked.
90
246
  */
91
- public getBlockNumber() {
92
- return Promise.resolve(BlockNumber(this.l2Blocks.length));
93
- }
94
-
95
- public getProvenBlockNumber() {
96
- return Promise.resolve(BlockNumber(this.provenBlockNumber));
97
- }
98
-
99
- public getCheckpointedL2BlockNumber() {
100
- return Promise.resolve(BlockNumber(this.checkpointedBlockNumber));
101
- }
102
-
103
- public getFinalizedL2BlockNumber() {
104
- return Promise.resolve(BlockNumber(this.finalizedBlockNumber));
105
- }
106
-
107
- public getCheckpointedBlock(number: BlockNumber): Promise<CheckpointedL2Block | undefined> {
108
- if (number > this.checkpointedBlockNumber) {
109
- 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);
110
252
  }
111
- const block = this.l2Blocks[number - 1];
112
- if (!block) {
113
- return Promise.resolve(undefined);
253
+ if ('number' in query) {
254
+ return query.number;
114
255
  }
115
- const checkpointedBlock = new CheckpointedL2Block(
116
- CheckpointNumber.fromBlockNumber(number),
117
- block,
118
- new L1PublishedData(BigInt(number), BigInt(number), `0x${number.toString(16).padStart(64, '0')}`),
119
- [],
120
- );
121
- return Promise.resolve(checkpointedBlock);
122
- }
123
-
124
- public async getCheckpointedBlocks(from: BlockNumber, limit: number): Promise<CheckpointedL2Block[]> {
125
- const result: CheckpointedL2Block[] = [];
126
- for (let i = 0; i < limit; i++) {
127
- const blockNum = from + i;
128
- if (blockNum > this.checkpointedBlockNumber) {
129
- break;
130
- }
131
- const block = await this.getCheckpointedBlock(BlockNumber(blockNum));
132
- if (block) {
133
- result.push(block);
134
- }
256
+ if ('tag' in query) {
257
+ return BlockNumber(this.resolveBlockTag(query.tag));
135
258
  }
136
- return result;
137
- }
138
-
139
- /**
140
- * Gets an l2 block.
141
- * @param number - The block number to return (inclusive).
142
- * @returns The requested L2 block.
143
- */
144
- public getBlock(number: number): Promise<L2Block | undefined> {
145
- const block = this.l2Blocks[number - 1];
146
- return Promise.resolve(block);
259
+ const block = await this.getBlock(query);
260
+ return block ? block.header.globalVariables.blockNumber : undefined;
147
261
  }
148
262
 
149
- /**
150
- * Gets an L2 block (new format).
151
- * @param number - The block number to return.
152
- * @returns The requested L2 block.
153
- */
154
- public getL2Block(number: BlockNumber): Promise<L2Block | undefined> {
155
- const block = this.l2Blocks[number - 1];
156
- return Promise.resolve(block);
263
+ public getProposedCheckpointL2BlockNumber() {
264
+ return Promise.resolve(BlockNumber(this.proposedCheckpointBlockNumber));
157
265
  }
158
266
 
159
- /**
160
- * Gets up to `limit` amount of L2 blocks starting from `from`.
161
- * @param from - Number of the first block to return (inclusive).
162
- * @param limit - The maximum number of blocks to return.
163
- * @returns The requested mocked L2 blocks.
164
- */
165
- public getBlocks(from: number, limit: number): Promise<L2Block[]> {
166
- 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), []));
167
273
  }
168
274
 
169
- public getCheckpoints(from: CheckpointNumber, limit: number) {
170
- // TODO(mbps): Implement this properly. This only works when we have one block per checkpoint.
171
- const blocks = this.l2Blocks.slice(from - 1, from - 1 + limit);
172
- return Promise.all(
173
- blocks.map(async block => {
174
- // Create a checkpoint from the block - manually construct since L2Block doesn't have toCheckpoint()
175
- const checkpoint = await Checkpoint.random(block.checkpointNumber, { numBlocks: 1 });
176
- checkpoint.blocks = [block];
177
- return new PublishedCheckpoint(
178
- checkpoint,
179
- new L1PublishedData(BigInt(block.number), BigInt(block.number), Buffer32.random().toString()),
180
- [],
181
- );
182
- }),
275
+ public getCheckpoints(query: CheckpointsQuery): Promise<PublishedCheckpoint[]> {
276
+ const checkpoints = this.resolveCheckpointsQuery(query);
277
+ return Promise.resolve(
278
+ checkpoints.map(checkpoint => new PublishedCheckpoint(checkpoint, this.mockL1DataForCheckpoint(checkpoint), [])),
183
279
  );
184
280
  }
185
281
 
186
- public async getCheckpointByArchive(archive: Fr): Promise<Checkpoint | undefined> {
187
- // TODO(mbps): Implement this properly. This only works when we have one block per checkpoint.
188
- const block = this.l2Blocks.find(b => b.archive.root.equals(archive));
189
- if (!block) {
190
- return undefined;
191
- }
192
- // Create a checkpoint from the block - manually construct since L2Block doesn't have toCheckpoint()
193
- const checkpoint = await Checkpoint.random(block.checkpointNumber, { numBlocks: 1 });
194
- checkpoint.blocks = [block];
195
- return checkpoint;
196
- }
197
-
198
- public async getCheckpointedBlockByHash(blockHash: BlockHash): Promise<CheckpointedL2Block | undefined> {
199
- for (const block of this.l2Blocks) {
200
- const hash = await block.hash();
201
- if (hash.equals(blockHash)) {
202
- return CheckpointedL2Block.fromFields({
203
- checkpointNumber: CheckpointNumber.fromBlockNumber(block.number),
204
- block,
205
- l1: new L1PublishedData(BigInt(block.number), BigInt(block.number), Buffer32.random().toString()),
206
- attestations: [],
207
- });
208
- }
209
- }
210
- return undefined;
282
+ public getCheckpointByArchive(archive: Fr): Promise<Checkpoint | undefined> {
283
+ const checkpoint = this.checkpointList.find(c => c.archive.root.equals(archive));
284
+ return Promise.resolve(checkpoint);
211
285
  }
212
286
 
213
- public getCheckpointedBlockByArchive(archive: Fr): Promise<CheckpointedL2Block | undefined> {
214
- const block = this.l2Blocks.find(b => b.archive.root.equals(archive));
215
- if (!block) {
287
+ public getCheckpointData(query: CheckpointQuery): Promise<CheckpointData | undefined> {
288
+ const checkpoint = this.resolveCheckpointQuery(query);
289
+ if (!checkpoint) {
216
290
  return Promise.resolve(undefined);
217
291
  }
218
- return Promise.resolve(
219
- CheckpointedL2Block.fromFields({
220
- checkpointNumber: CheckpointNumber.fromBlockNumber(block.number),
221
- block,
222
- l1: new L1PublishedData(BigInt(block.number), BigInt(block.number), Buffer32.random().toString()),
223
- attestations: [],
224
- }),
225
- );
292
+ return Promise.resolve(this.checkpointToData(checkpoint));
226
293
  }
227
294
 
228
- public async getL2BlockByHash(blockHash: BlockHash): Promise<L2Block | undefined> {
229
- for (const block of this.l2Blocks) {
230
- const hash = await block.hash();
231
- if (hash.equals(blockHash)) {
232
- return block;
233
- }
234
- }
235
- 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)));
236
298
  }
237
299
 
238
- public getL2BlockByArchive(archive: Fr): Promise<L2Block | undefined> {
239
- const block = this.l2Blocks.find(b => b.archive.root.equals(archive));
240
- return Promise.resolve(block);
300
+ private checkpointToData(checkpoint: Checkpoint): CheckpointData {
301
+ return {
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),
313
+ };
241
314
  }
242
315
 
243
- public async getBlockHeaderByHash(blockHash: BlockHash): Promise<BlockHeader | undefined> {
244
- for (const block of this.l2Blocks) {
245
- const hash = await block.hash();
246
- if (hash.equals(blockHash)) {
247
- return block.header;
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);
248
337
  }
249
338
  }
250
- return undefined;
251
- }
252
-
253
- public getBlockHeaderByArchive(archive: Fr): Promise<BlockHeader | undefined> {
254
- const block = this.l2Blocks.find(b => b.archive.root.equals(archive));
255
- return Promise.resolve(block?.header);
256
- }
257
-
258
- getBlockHeader(number: number | 'latest'): Promise<BlockHeader | undefined> {
259
- return Promise.resolve(this.l2Blocks.at(typeof number === 'number' ? number - 1 : -1)?.header);
260
- }
261
-
262
- getCheckpointsForEpoch(epochNumber: EpochNumber): Promise<Checkpoint[]> {
263
- // TODO(mbps): Implement this properly. This only works when we have one block per checkpoint.
264
- const epochDuration = DefaultL1ContractsConfig.aztecEpochDuration;
265
- const [start, end] = getSlotRangeForEpoch(epochNumber, { epochDuration });
266
- const blocks = this.l2Blocks.filter(b => {
267
- const slot = b.header.globalVariables.slotNumber;
268
- return slot >= start && slot <= end;
269
- });
270
- // Create checkpoints from blocks - manually construct since L2Block doesn't have toCheckpoint()
271
- return Promise.all(
272
- blocks.map(async block => {
273
- const checkpoint = await Checkpoint.random(block.checkpointNumber, { numBlocks: 1 });
274
- checkpoint.blocks = [block];
275
- return checkpoint;
276
- }),
277
- );
278
339
  }
279
340
 
280
- getCheckpointedBlocksForEpoch(epochNumber: EpochNumber): Promise<CheckpointedL2Block[]> {
281
- const epochDuration = DefaultL1ContractsConfig.aztecEpochDuration;
282
- const [start, end] = getSlotRangeForEpoch(epochNumber, { epochDuration });
283
- const blocks = this.l2Blocks.filter(b => {
284
- const slot = b.header.globalVariables.slotNumber;
285
- return slot >= start && slot <= end;
286
- });
287
- return Promise.resolve(
288
- blocks.map(block =>
289
- CheckpointedL2Block.fromFields({
290
- checkpointNumber: CheckpointNumber.fromBlockNumber(block.number),
291
- block,
292
- l1: new L1PublishedData(BigInt(block.number), BigInt(block.number), Buffer32.random().toString()),
293
- attestations: [],
294
- }),
295
- ),
296
- );
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);
297
346
  }
298
347
 
299
348
  getBlocksForSlot(slotNumber: SlotNumber): Promise<L2Block[]> {
@@ -301,11 +350,6 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
301
350
  return Promise.resolve(blocks);
302
351
  }
303
352
 
304
- async getCheckpointedBlockHeadersForEpoch(epochNumber: EpochNumber): Promise<BlockHeader[]> {
305
- const checkpointedBlocks = await this.getCheckpointedBlocksForEpoch(epochNumber);
306
- return checkpointedBlocks.map(b => b.block.header);
307
- }
308
-
309
353
  /**
310
354
  * Gets a tx effect.
311
355
  * @param txHash - The hash of the tx corresponding to the tx effect.
@@ -323,7 +367,7 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
323
367
  data: txEffect,
324
368
  l2BlockNumber: block.number,
325
369
  l2BlockHash: await block.hash(),
326
- txIndexInBlock: block.body.txEffects.indexOf(txEffect),
370
+ txIndexInBlock: block.body.txEffects.findIndex(t => t.txHash.equals(txHash)),
327
371
  };
328
372
  }
329
373
 
@@ -345,6 +389,7 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
345
389
  txEffect.transactionFee.toBigInt(),
346
390
  await block.hash(),
347
391
  block.number,
392
+ getEpochAtSlot(block.slot, EmptyL1RollupConstants),
348
393
  );
349
394
  }
350
395
  }
@@ -353,53 +398,77 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
353
398
  }
354
399
 
355
400
  async getL2Tips(): Promise<L2Tips> {
356
- const [latest, proven, finalized, checkpointed] = [
401
+ const [latest, proven, finalized, checkpointed, proposedCheckpoint] = [
357
402
  await this.getBlockNumber(),
358
- await this.getProvenBlockNumber(),
403
+ this.provenBlockNumber,
359
404
  this.finalizedBlockNumber,
360
405
  this.checkpointedBlockNumber,
406
+ await this.getProposedCheckpointL2BlockNumber(),
361
407
  ] as const;
362
408
 
363
409
  const latestBlock = this.l2Blocks[latest - 1];
364
410
  const provenBlock = this.l2Blocks[proven - 1];
365
411
  const finalizedBlock = this.l2Blocks[finalized - 1];
366
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
+ };
367
424
 
368
425
  const latestBlockId = {
369
426
  number: BlockNumber(latest),
370
- hash: (await latestBlock?.hash())?.toString(),
427
+ hash: await tipHash(latestBlock, latest),
371
428
  };
372
429
  const provenBlockId = {
373
430
  number: BlockNumber(proven),
374
- hash: (await provenBlock?.hash())?.toString(),
431
+ hash: await tipHash(provenBlock, proven),
375
432
  };
376
433
  const finalizedBlockId = {
377
434
  number: BlockNumber(finalized),
378
- hash: (await finalizedBlock?.hash())?.toString(),
435
+ hash: await tipHash(finalizedBlock, finalized),
379
436
  };
380
437
  const checkpointedBlockId = {
381
438
  number: BlockNumber(checkpointed),
382
- 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),
383
444
  };
384
445
 
385
- const makeTipId = (blockId: typeof latestBlockId) => ({
386
- block: blockId,
387
- checkpoint: { number: CheckpointNumber.fromBlockNumber(blockId.number), hash: blockId.hash },
388
- });
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
+ };
389
457
 
390
458
  return {
391
459
  proposed: latestBlockId,
392
460
  checkpointed: makeTipId(checkpointedBlockId),
393
461
  proven: makeTipId(provenBlockId),
394
462
  finalized: makeTipId(finalizedBlockId),
463
+ proposedCheckpoint: makeTipId(proposedCheckpointBlockId),
395
464
  };
396
465
  }
397
466
 
398
- getL2EpochNumber(): Promise<EpochNumber> {
467
+ getSyncedL2EpochNumber(): Promise<EpochNumber> {
399
468
  throw new Error('Method not implemented.');
400
469
  }
401
470
 
402
- getL2SlotNumber(): Promise<SlotNumber> {
471
+ getSyncedL2SlotNumber(): Promise<SlotNumber> {
403
472
  throw new Error('Method not implemented.');
404
473
  }
405
474
 
@@ -411,8 +480,12 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
411
480
  return Promise.resolve(EmptyL1RollupConstants);
412
481
  }
413
482
 
483
+ isPruneDueAtSlot(_slot: SlotNumber): Promise<boolean> {
484
+ return Promise.resolve(false);
485
+ }
486
+
414
487
  getGenesisValues(): Promise<{ genesisArchiveRoot: Fr }> {
415
- return Promise.resolve({ genesisArchiveRoot: new Fr(GENESIS_ARCHIVE_ROOT) });
488
+ return Promise.resolve({ genesisArchiveRoot: this.genesisArchiveRoot ?? new Fr(GENESIS_ARCHIVE_ROOT) });
416
489
  }
417
490
 
418
491
  getL1Timestamp(): Promise<bigint> {
@@ -465,6 +538,95 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
465
538
  return Promise.resolve();
466
539
  }
467
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
+
468
630
  isPendingChainInvalid(): Promise<boolean> {
469
631
  return Promise.resolve(false);
470
632
  }
@@ -472,4 +634,26 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
472
634
  getPendingChainValidationStatus(): Promise<ValidateCheckpointResult> {
473
635
  return Promise.resolve({ valid: true });
474
636
  }
637
+
638
+ getProposedCheckpointData(_query?: ProposedCheckpointQuery): Promise<ProposedCheckpointData | undefined> {
639
+ return Promise.resolve(undefined);
640
+ }
641
+
642
+ /** Returns checkpoints whose slot falls within the given epoch. */
643
+ private getCheckpointsInEpoch(epochNumber: EpochNumber): Checkpoint[] {
644
+ const epochDuration = DefaultL1ContractsConfig.aztecEpochDuration;
645
+ const [start, end] = getSlotRangeForEpoch(epochNumber, { epochDuration });
646
+ return this.checkpointList.filter(c => c.header.slotNumber >= start && c.header.slotNumber <= end);
647
+ }
648
+
649
+ /** Creates a mock L1PublishedData for a checkpoint. */
650
+ private mockL1DataForCheckpoint(checkpoint: Checkpoint): L1PublishedData {
651
+ return new L1PublishedData(BigInt(checkpoint.number), BigInt(checkpoint.number), Buffer32.random().toString());
652
+ }
653
+
654
+ /** Finds the checkpoint number for a block, or undefined if the block is not in any checkpoint. */
655
+ private findCheckpointNumberForBlock(blockNumber: BlockNumber): CheckpointNumber | undefined {
656
+ const checkpoint = this.checkpointList.find(c => c.blocks.some(b => b.number === blockNumber));
657
+ return checkpoint?.number;
658
+ }
475
659
  }