@aztec/archiver 0.0.1-commit.d1f2d6c → 0.0.1-commit.d20b825a7

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 (120) hide show
  1. package/README.md +12 -6
  2. package/dest/archiver.d.ts +16 -10
  3. package/dest/archiver.d.ts.map +1 -1
  4. package/dest/archiver.js +110 -122
  5. package/dest/config.d.ts +5 -3
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +15 -3
  8. package/dest/errors.d.ts +55 -10
  9. package/dest/errors.d.ts.map +1 -1
  10. package/dest/errors.js +74 -15
  11. package/dest/factory.d.ts +5 -4
  12. package/dest/factory.d.ts.map +1 -1
  13. package/dest/factory.js +34 -29
  14. package/dest/index.d.ts +4 -2
  15. package/dest/index.d.ts.map +1 -1
  16. package/dest/index.js +3 -1
  17. package/dest/l1/bin/retrieve-calldata.js +36 -33
  18. package/dest/l1/calldata_retriever.d.ts +73 -50
  19. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  20. package/dest/l1/calldata_retriever.js +191 -259
  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 +43 -48
  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/validate_historical_logs.d.ts +23 -0
  28. package/dest/l1/validate_historical_logs.d.ts.map +1 -0
  29. package/dest/l1/validate_historical_logs.js +108 -0
  30. package/dest/l1/validate_trace.d.ts +6 -3
  31. package/dest/l1/validate_trace.d.ts.map +1 -1
  32. package/dest/l1/validate_trace.js +13 -9
  33. package/dest/modules/data_source_base.d.ts +17 -10
  34. package/dest/modules/data_source_base.d.ts.map +1 -1
  35. package/dest/modules/data_source_base.js +39 -77
  36. package/dest/modules/data_store_updater.d.ts +50 -26
  37. package/dest/modules/data_store_updater.d.ts.map +1 -1
  38. package/dest/modules/data_store_updater.js +169 -130
  39. package/dest/modules/instrumentation.d.ts +21 -3
  40. package/dest/modules/instrumentation.d.ts.map +1 -1
  41. package/dest/modules/instrumentation.js +58 -18
  42. package/dest/modules/l1_synchronizer.d.ts +10 -9
  43. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  44. package/dest/modules/l1_synchronizer.js +285 -157
  45. package/dest/modules/validation.d.ts +1 -1
  46. package/dest/modules/validation.d.ts.map +1 -1
  47. package/dest/modules/validation.js +2 -2
  48. package/dest/store/block_store.d.ts +85 -36
  49. package/dest/store/block_store.d.ts.map +1 -1
  50. package/dest/store/block_store.js +433 -162
  51. package/dest/store/contract_class_store.d.ts +2 -3
  52. package/dest/store/contract_class_store.d.ts.map +1 -1
  53. package/dest/store/contract_class_store.js +16 -72
  54. package/dest/store/contract_instance_store.d.ts +1 -1
  55. package/dest/store/contract_instance_store.d.ts.map +1 -1
  56. package/dest/store/contract_instance_store.js +6 -2
  57. package/dest/store/kv_archiver_store.d.ts +76 -32
  58. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  59. package/dest/store/kv_archiver_store.js +92 -37
  60. package/dest/store/l2_tips_cache.d.ts +20 -0
  61. package/dest/store/l2_tips_cache.d.ts.map +1 -0
  62. package/dest/store/l2_tips_cache.js +109 -0
  63. package/dest/store/log_store.d.ts +6 -3
  64. package/dest/store/log_store.d.ts.map +1 -1
  65. package/dest/store/log_store.js +151 -56
  66. package/dest/store/message_store.d.ts +5 -1
  67. package/dest/store/message_store.d.ts.map +1 -1
  68. package/dest/store/message_store.js +21 -9
  69. package/dest/test/fake_l1_state.d.ts +24 -1
  70. package/dest/test/fake_l1_state.d.ts.map +1 -1
  71. package/dest/test/fake_l1_state.js +145 -28
  72. package/dest/test/index.js +3 -1
  73. package/dest/test/mock_archiver.d.ts +1 -1
  74. package/dest/test/mock_archiver.d.ts.map +1 -1
  75. package/dest/test/mock_archiver.js +3 -2
  76. package/dest/test/mock_l1_to_l2_message_source.d.ts +1 -1
  77. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  78. package/dest/test/mock_l1_to_l2_message_source.js +2 -1
  79. package/dest/test/mock_l2_block_source.d.ts +31 -10
  80. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  81. package/dest/test/mock_l2_block_source.js +163 -92
  82. package/dest/test/mock_structs.d.ts +6 -2
  83. package/dest/test/mock_structs.d.ts.map +1 -1
  84. package/dest/test/mock_structs.js +20 -6
  85. package/dest/test/noop_l1_archiver.d.ts +26 -0
  86. package/dest/test/noop_l1_archiver.d.ts.map +1 -0
  87. package/dest/test/noop_l1_archiver.js +74 -0
  88. package/package.json +14 -13
  89. package/src/archiver.ts +150 -146
  90. package/src/config.ts +22 -2
  91. package/src/errors.ts +116 -26
  92. package/src/factory.ts +49 -26
  93. package/src/index.ts +3 -1
  94. package/src/l1/README.md +25 -68
  95. package/src/l1/bin/retrieve-calldata.ts +46 -39
  96. package/src/l1/calldata_retriever.ts +250 -379
  97. package/src/l1/data_retrieval.ts +59 -70
  98. package/src/l1/spire_proposer.ts +7 -15
  99. package/src/l1/validate_historical_logs.ts +140 -0
  100. package/src/l1/validate_trace.ts +24 -6
  101. package/src/modules/data_source_base.ts +81 -101
  102. package/src/modules/data_store_updater.ts +202 -160
  103. package/src/modules/instrumentation.ts +71 -19
  104. package/src/modules/l1_synchronizer.ts +365 -197
  105. package/src/modules/validation.ts +2 -2
  106. package/src/store/block_store.ts +546 -206
  107. package/src/store/contract_class_store.ts +16 -110
  108. package/src/store/contract_instance_store.ts +8 -5
  109. package/src/store/kv_archiver_store.ts +143 -53
  110. package/src/store/l2_tips_cache.ts +134 -0
  111. package/src/store/log_store.ts +225 -67
  112. package/src/store/message_store.ts +27 -10
  113. package/src/structs/inbox_message.ts +1 -1
  114. package/src/test/fake_l1_state.ts +193 -32
  115. package/src/test/index.ts +3 -0
  116. package/src/test/mock_archiver.ts +3 -2
  117. package/src/test/mock_l1_to_l2_message_source.ts +1 -0
  118. package/src/test/mock_l2_block_source.ts +217 -90
  119. package/src/test/mock_structs.ts +42 -12
  120. package/src/test/noop_l1_archiver.ts +117 -0
@@ -14,6 +14,7 @@ import {
14
14
  } from '@aztec/kv-store';
15
15
  import { InboxLeaf } from '@aztec/stdlib/messaging';
16
16
 
17
+ import { L1ToL2MessagesNotReadyError } from '../errors.js';
17
18
  import {
18
19
  type InboxMessage,
19
20
  deserializeInboxMessage,
@@ -40,6 +41,8 @@ export class MessageStore {
40
41
  #lastSynchedL1Block: AztecAsyncSingleton<Buffer>;
41
42
  /** Stores total messages stored */
42
43
  #totalMessageCount: AztecAsyncSingleton<bigint>;
44
+ /** Stores the checkpoint number whose message tree is currently being filled on L1. */
45
+ #inboxTreeInProgress: AztecAsyncSingleton<bigint>;
43
46
 
44
47
  #log = createLogger('archiver:message_store');
45
48
 
@@ -48,6 +51,7 @@ export class MessageStore {
48
51
  this.#l1ToL2MessageIndices = db.openMap('archiver_l1_to_l2_message_indices');
49
52
  this.#lastSynchedL1Block = db.openSingleton('archiver_last_l1_block_id');
50
53
  this.#totalMessageCount = db.openSingleton('archiver_l1_to_l2_message_count');
54
+ this.#inboxTreeInProgress = db.openSingleton('archiver_inbox_tree_in_progress');
51
55
  }
52
56
 
53
57
  public async getTotalL1ToL2MessageCount(): Promise<bigint> {
@@ -137,7 +141,7 @@ export class MessageStore {
137
141
  );
138
142
  }
139
143
 
140
- // Check the first message in a block has the correct index.
144
+ // Check the first message in a checkpoint has the correct index.
141
145
  if (
142
146
  (!lastMessage || message.checkpointNumber > lastMessage.checkpointNumber) &&
143
147
  message.index !== expectedStart
@@ -157,15 +161,6 @@ export class MessageStore {
157
161
  lastMessage = message;
158
162
  }
159
163
 
160
- // Update the L1 sync point to that of the last message added.
161
- const currentSyncPoint = await this.getSynchedL1Block();
162
- if (!currentSyncPoint || currentSyncPoint.l1BlockNumber < lastMessage!.l1BlockNumber) {
163
- await this.setSynchedL1Block({
164
- l1BlockNumber: lastMessage!.l1BlockNumber,
165
- l1BlockHash: lastMessage!.l1BlockHash,
166
- });
167
- }
168
-
169
164
  // Update total message count with the number of inserted messages.
170
165
  await this.increaseTotalMessageCount(messageCount);
171
166
  });
@@ -185,7 +180,29 @@ export class MessageStore {
185
180
  return msg ? deserializeInboxMessage(msg) : undefined;
186
181
  }
187
182
 
183
+ /** Returns the inbox tree-in-progress checkpoint number from L1, or undefined if not yet set. */
184
+ public getInboxTreeInProgress(): Promise<bigint | undefined> {
185
+ return this.#inboxTreeInProgress.getAsync();
186
+ }
187
+
188
+ /** Atomically updates the message sync state: the L1 sync point and the inbox tree-in-progress marker. */
189
+ public setMessageSyncState(l1Block: L1BlockId, treeInProgress: bigint | undefined): Promise<void> {
190
+ return this.db.transactionAsync(async () => {
191
+ await this.setSynchedL1Block(l1Block);
192
+ if (treeInProgress !== undefined) {
193
+ await this.#inboxTreeInProgress.set(treeInProgress);
194
+ } else {
195
+ await this.#inboxTreeInProgress.delete();
196
+ }
197
+ });
198
+ }
199
+
188
200
  public async getL1ToL2Messages(checkpointNumber: CheckpointNumber): Promise<Fr[]> {
201
+ const treeInProgress = await this.#inboxTreeInProgress.getAsync();
202
+ if (treeInProgress !== undefined && BigInt(checkpointNumber) >= treeInProgress) {
203
+ throw new L1ToL2MessagesNotReadyError(checkpointNumber, treeInProgress);
204
+ }
205
+
189
206
  const messages: Fr[] = [];
190
207
 
191
208
  const [startIndex, endIndex] = InboxLeaf.indexRangeForCheckpoint(checkpointNumber);
@@ -8,7 +8,7 @@ export type InboxMessage = {
8
8
  index: bigint;
9
9
  leaf: Fr;
10
10
  checkpointNumber: CheckpointNumber;
11
- l1BlockNumber: bigint; // L1 block number - NOT Aztec L2
11
+ l1BlockNumber: bigint;
12
12
  l1BlockHash: Buffer32;
13
13
  rollingHash: Buffer16;
14
14
  };
@@ -1,5 +1,6 @@
1
1
  import type { BlobClientInterface } from '@aztec/blob-client/client';
2
2
  import { type Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
3
+ import { INITIAL_CHECKPOINT_NUMBER } from '@aztec/constants';
3
4
  import type { CheckpointProposedLog, InboxContract, MessageSentLog, RollupContract } from '@aztec/ethereum/contracts';
4
5
  import { MULTI_CALL_3_ADDRESS } from '@aztec/ethereum/contracts';
5
6
  import type { ViemPublicClient } from '@aztec/ethereum/types';
@@ -14,6 +15,7 @@ import { CommitteeAttestation, CommitteeAttestationsAndSigners, L2Block } from '
14
15
  import { Checkpoint } from '@aztec/stdlib/checkpoint';
15
16
  import { getSlotAtTimestamp } from '@aztec/stdlib/epoch-helpers';
16
17
  import { InboxLeaf } from '@aztec/stdlib/messaging';
18
+ import { ConsensusPayload, SignatureDomainSeparator } from '@aztec/stdlib/p2p';
17
19
  import {
18
20
  makeAndSignCommitteeAttestationsAndSigners,
19
21
  makeCheckpointAttestationFromCheckpoint,
@@ -22,7 +24,16 @@ import {
22
24
  import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
23
25
 
24
26
  import { type MockProxy, mock } from 'jest-mock-extended';
25
- import { type FormattedBlock, type Transaction, encodeFunctionData, multicall3Abi, toHex } from 'viem';
27
+ import {
28
+ type AbiParameter,
29
+ type FormattedBlock,
30
+ type Transaction,
31
+ encodeAbiParameters,
32
+ encodeFunctionData,
33
+ keccak256,
34
+ multicall3Abi,
35
+ toHex,
36
+ } from 'viem';
26
37
 
27
38
  import { updateRollingHash } from '../structs/inbox_message.js';
28
39
 
@@ -87,6 +98,10 @@ type CheckpointData = {
87
98
  blobHashes: `0x${string}`[];
88
99
  blobs: Blob[];
89
100
  signers: Secp256k1Signer[];
101
+ /** Hash of the packed attestations, matching what the L1 event emits. */
102
+ attestationsHash: Buffer32;
103
+ /** Payload digest, matching what the L1 event emits. */
104
+ payloadDigest: Buffer32;
90
105
  /** If true, archiveAt will ignore it */
91
106
  pruned?: boolean;
92
107
  };
@@ -131,12 +146,19 @@ export class FakeL1State {
131
146
  private provenCheckpointNumber: CheckpointNumber = CheckpointNumber(0);
132
147
  private targetCommitteeSize: number = 0;
133
148
  private version: bigint = 1n;
149
+ private canPruneResult: boolean = false;
134
150
 
135
151
  // Computed from checkpoints based on L1 block visibility
136
152
  private pendingCheckpointNumber: CheckpointNumber = CheckpointNumber(0);
137
153
 
154
+ // The L1 block number reported as "finalized" (defaults to the start block).
155
+ // `undefined` simulates the startup window on a fresh devnet where
156
+ // `getBlock({ blockTag: 'finalized' })` fails with "finalized block not found".
157
+ private finalizedL1BlockNumber: bigint | undefined;
158
+
138
159
  constructor(private readonly config: FakeL1StateConfig) {
139
160
  this.l1BlockNumber = config.l1StartBlock;
161
+ this.finalizedL1BlockNumber = config.l1StartBlock;
140
162
  this.lastArchive = new AppendOnlyTreeSnapshot(config.genesisArchiveRoot, 1);
141
163
  }
142
164
 
@@ -193,10 +215,10 @@ export class FakeL1State {
193
215
  // Store the messages internally so they match the checkpoint's inHash
194
216
  this.addMessages(checkpointNumber, messagesL1BlockNumber, messages);
195
217
 
196
- // Create the transaction and blobs
197
- const tx = this.makeRollupTx(checkpoint, signers);
198
- const blobHashes = this.makeVersionedBlobHashes(checkpoint);
199
- const blobs = this.makeBlobsFromCheckpoint(checkpoint);
218
+ // Create the transaction, blobs, and event hashes
219
+ const { tx, attestationsHash, payloadDigest } = await this.makeRollupTx(checkpoint, signers);
220
+ const blobHashes = await this.makeVersionedBlobHashes(checkpoint);
221
+ const blobs = await this.makeBlobsFromCheckpoint(checkpoint);
200
222
 
201
223
  // Store the checkpoint data
202
224
  this.checkpoints.push({
@@ -207,6 +229,8 @@ export class FakeL1State {
207
229
  blobHashes,
208
230
  blobs,
209
231
  signers,
232
+ attestationsHash,
233
+ payloadDigest,
210
234
  });
211
235
 
212
236
  // Update last archive for auto-chaining
@@ -266,16 +290,43 @@ export class FakeL1State {
266
290
  this.updatePendingCheckpointNumber();
267
291
  }
268
292
 
293
+ /**
294
+ * Sets the L1 block number that will be reported as "finalized". Pass `undefined` to
295
+ * simulate a chain that does not yet have a finalized block (devnet startup).
296
+ */
297
+ setFinalizedL1BlockNumber(blockNumber: bigint | undefined): void {
298
+ this.finalizedL1BlockNumber = blockNumber;
299
+ }
300
+
269
301
  /** Marks a checkpoint as proven. Updates provenCheckpointNumber. */
270
302
  markCheckpointAsProven(checkpointNumber: CheckpointNumber): void {
271
303
  this.provenCheckpointNumber = checkpointNumber;
272
304
  }
273
305
 
306
+ /**
307
+ * Simulates what `rollup.getProvenCheckpointNumber({ blockNumber: atL1Block })` would return.
308
+ */
309
+ getProvenCheckpointNumberAtL1Block(atL1Block: bigint): CheckpointNumber {
310
+ if (this.provenCheckpointNumber === 0) {
311
+ return CheckpointNumber(0);
312
+ }
313
+ const checkpoint = this.checkpoints.find(cp => cp.checkpointNumber === this.provenCheckpointNumber);
314
+ if (checkpoint && checkpoint.l1BlockNumber <= atL1Block) {
315
+ return this.provenCheckpointNumber;
316
+ }
317
+ return CheckpointNumber(0);
318
+ }
319
+
274
320
  /** Sets the target committee size for attestation validation. */
275
321
  setTargetCommitteeSize(size: number): void {
276
322
  this.targetCommitteeSize = size;
277
323
  }
278
324
 
325
+ /** Sets whether the rollup contract would allow pruning at the next block. */
326
+ setCanPrune(value: boolean): void {
327
+ this.canPruneResult = value;
328
+ }
329
+
279
330
  /**
280
331
  * Removes all entries for a checkpoint number (simulates L1 reorg or prune).
281
332
  * Note: Does NOT remove messages for this checkpoint (use numL1ToL2Messages: 0 when re-adding).
@@ -286,6 +337,21 @@ export class FakeL1State {
286
337
  this.updatePendingCheckpointNumber();
287
338
  }
288
339
 
340
+ /**
341
+ * Moves a checkpoint to a different L1 block number (simulates L1 reorg that
342
+ * re-includes the same checkpoint transaction in a different block).
343
+ * The checkpoint content stays the same — only the L1 metadata changes.
344
+ * Auto-updates pending status.
345
+ */
346
+ moveCheckpointToL1Block(checkpointNumber: CheckpointNumber, newL1BlockNumber: bigint): void {
347
+ for (const cpData of this.checkpoints) {
348
+ if (cpData.checkpointNumber === checkpointNumber) {
349
+ cpData.l1BlockNumber = newL1BlockNumber;
350
+ }
351
+ }
352
+ this.updatePendingCheckpointNumber();
353
+ }
354
+
289
355
  /**
290
356
  * Removes messages after a given total index (simulates L1 reorg).
291
357
  * Auto-updates rolling hash.
@@ -384,6 +450,13 @@ export class FakeL1State {
384
450
  });
385
451
  });
386
452
 
453
+ mockRollup.getProvenCheckpointNumber.mockImplementation((options?: { blockNumber?: bigint }) => {
454
+ const atBlock = options?.blockNumber ?? this.l1BlockNumber;
455
+ return Promise.resolve(this.getProvenCheckpointNumberAtL1Block(atBlock));
456
+ });
457
+
458
+ mockRollup.canPruneAtTime.mockImplementation(() => Promise.resolve(this.canPruneResult));
459
+
387
460
  // Mock the wrapper method for fetching checkpoint events
388
461
  mockRollup.getCheckpointProposedEvents.mockImplementation((fromBlock: bigint, toBlock: bigint) =>
389
462
  Promise.resolve(this.getCheckpointProposedLogs(fromBlock, toBlock)),
@@ -398,21 +471,44 @@ export class FakeL1State {
398
471
  createMockInboxContract(_publicClient: MockProxy<ViemPublicClient>): MockProxy<InboxContract> {
399
472
  const mockInbox = mock<InboxContract>();
400
473
 
401
- mockInbox.getState.mockImplementation(() =>
402
- Promise.resolve({
403
- messagesRollingHash: this.messagesRollingHash,
404
- totalMessagesInserted: BigInt(this.messages.length),
405
- treeInProgress: 0n,
406
- }),
407
- );
474
+ mockInbox.getState.mockImplementation((opts: { blockTag?: string; blockNumber?: bigint } = {}) => {
475
+ // Filter messages visible at the given block number (or all if not specified)
476
+ const blockNumber = opts.blockNumber ?? this.l1BlockNumber;
477
+ const visibleMessages = this.messages.filter(m => m.l1BlockNumber <= blockNumber);
478
+
479
+ // treeInProgress must be > any sealed checkpoint. On L1, a checkpoint can only be proposed
480
+ // after its messages are sealed, so treeInProgress > checkpointNumber for all published checkpoints.
481
+ const maxFromMessages =
482
+ visibleMessages.length > 0 ? Math.max(...visibleMessages.map(m => Number(m.checkpointNumber))) + 1 : 0;
483
+ const maxFromCheckpoints =
484
+ this.checkpoints.length > 0
485
+ ? Math.max(
486
+ ...this.checkpoints
487
+ .filter(cp => !cp.pruned && cp.l1BlockNumber <= blockNumber)
488
+ .map(cp => Number(cp.checkpointNumber)),
489
+ 0,
490
+ ) + 1
491
+ : 0;
492
+ const treeInProgress = Math.max(maxFromMessages, maxFromCheckpoints, INITIAL_CHECKPOINT_NUMBER);
493
+
494
+ // Compute rolling hash only for visible messages
495
+ const rollingHash =
496
+ visibleMessages.length > 0 ? visibleMessages[visibleMessages.length - 1].rollingHash : Buffer16.ZERO;
497
+
498
+ return Promise.resolve({
499
+ messagesRollingHash: rollingHash,
500
+ totalMessagesInserted: BigInt(visibleMessages.length),
501
+ treeInProgress: BigInt(treeInProgress),
502
+ });
503
+ });
408
504
 
409
505
  // Mock the wrapper methods for fetching message events
410
506
  mockInbox.getMessageSentEvents.mockImplementation((fromBlock: bigint, toBlock: bigint) =>
411
507
  Promise.resolve(this.getMessageSentLogs(fromBlock, toBlock)),
412
508
  );
413
509
 
414
- mockInbox.getMessageSentEventByHash.mockImplementation((hash: string, fromBlock: bigint, toBlock: bigint) =>
415
- Promise.resolve(this.getMessageSentLogs(fromBlock, toBlock, hash)),
510
+ mockInbox.getMessageSentEventByHash.mockImplementation((msgHash: string, aroundL1BlockNumber: bigint) =>
511
+ Promise.resolve(this.getMessageSentLogByHash(msgHash, aroundL1BlockNumber) as MessageSentLog),
416
512
  );
417
513
 
418
514
  return mockInbox;
@@ -425,10 +521,18 @@ export class FakeL1State {
425
521
  publicClient.getChainId.mockResolvedValue(1);
426
522
  publicClient.getBlockNumber.mockImplementation(() => Promise.resolve(this.l1BlockNumber));
427
523
 
428
- // Use async function pattern that existing test uses for getBlock
429
-
430
- publicClient.getBlock.mockImplementation((async (args: { blockNumber?: bigint } = {}) => {
431
- const blockNum = args.blockNumber ?? (await publicClient.getBlockNumber());
524
+ publicClient.getBlock.mockImplementation((async (args: { blockNumber?: bigint; blockTag?: string } = {}) => {
525
+ let blockNum: bigint;
526
+ if (args.blockTag === 'finalized') {
527
+ if (this.finalizedL1BlockNumber === undefined) {
528
+ throw Object.assign(new Error('finalized block not found'), {
529
+ details: 'finalized block not found',
530
+ });
531
+ }
532
+ blockNum = this.finalizedL1BlockNumber;
533
+ } else {
534
+ blockNum = args.blockNumber ?? (await publicClient.getBlockNumber());
535
+ }
432
536
  return {
433
537
  number: blockNum,
434
538
  timestamp: BigInt(blockNum) * BigInt(this.config.ethereumSlotDuration) + this.config.l1GenesisTime,
@@ -450,10 +554,10 @@ export class FakeL1State {
450
554
  createMockBlobClient(): MockProxy<BlobClientInterface> {
451
555
  const blobClient = mock<BlobClientInterface>();
452
556
 
453
- // The blockId is the transaction's blockHash, which we set to the checkpoint's archive root
557
+ // The blockId is the L1 block hash, which we derive from the L1 block number
454
558
  blobClient.getBlobSidecar.mockImplementation((blockId: `0x${string}`) =>
455
559
  Promise.resolve(
456
- this.checkpoints.find(cpData => cpData.checkpoint.archive.root.toString() === blockId)?.blobs ?? [],
560
+ this.checkpoints.find(cpData => Buffer32.fromBigInt(cpData.l1BlockNumber).toString() === blockId)?.blobs ?? [],
457
561
  ),
458
562
  );
459
563
 
@@ -502,10 +606,8 @@ export class FakeL1State {
502
606
  checkpointNumber: cpData.checkpointNumber,
503
607
  archive: cpData.checkpoint.archive.root,
504
608
  versionedBlobHashes: cpData.blobHashes.map(h => Buffer.from(h.slice(2), 'hex')),
505
- // These are intentionally undefined to skip hash validation in the archiver
506
- // (validation is skipped when these fields are falsy)
507
- payloadDigest: undefined,
508
- attestationsHash: undefined,
609
+ attestationsHash: cpData.attestationsHash,
610
+ payloadDigest: cpData.payloadDigest,
509
611
  },
510
612
  }));
511
613
  }
@@ -531,14 +633,40 @@ export class FakeL1State {
531
633
  }));
532
634
  }
533
635
 
534
- private makeRollupTx(checkpoint: Checkpoint, signers: Secp256k1Signer[]): Transaction {
636
+ private getMessageSentLogByHash(msgHash: string, aroundL1BlockNumber: bigint): MessageSentLog | undefined {
637
+ const msg = this.messages.find(
638
+ msg =>
639
+ msg.leaf.toString() === msgHash &&
640
+ msg.l1BlockNumber >= aroundL1BlockNumber - 5n &&
641
+ msg.l1BlockNumber <= aroundL1BlockNumber + 5n,
642
+ );
643
+ if (!msg) {
644
+ return undefined;
645
+ }
646
+ return {
647
+ l1BlockNumber: msg.l1BlockNumber,
648
+ l1BlockHash: Buffer32.fromBigInt(msg.l1BlockNumber),
649
+ l1TransactionHash: `0x${msg.l1BlockNumber.toString(16)}` as `0x${string}`,
650
+ args: {
651
+ checkpointNumber: msg.checkpointNumber,
652
+ index: msg.index,
653
+ leaf: msg.leaf,
654
+ rollingHash: msg.rollingHash,
655
+ },
656
+ };
657
+ }
658
+
659
+ private async makeRollupTx(
660
+ checkpoint: Checkpoint,
661
+ signers: Secp256k1Signer[],
662
+ ): Promise<{ tx: Transaction; attestationsHash: Buffer32; payloadDigest: Buffer32 }> {
535
663
  const attestations = signers
536
664
  .map(signer => makeCheckpointAttestationFromCheckpoint(checkpoint, signer))
537
665
  .map(attestation => CommitteeAttestation.fromSignature(attestation.signature))
538
666
  .map(committeeAttestation => committeeAttestation.toViem());
539
667
 
540
668
  const header = checkpoint.header.toViem();
541
- const blobInput = getPrefixedEthBlobCommitments(getBlobsPerL1Block(checkpoint.toBlobFields()));
669
+ const blobInput = getPrefixedEthBlobCommitments(await getBlobsPerL1Block(checkpoint.toBlobFields()));
542
670
  const archive = toHex(checkpoint.archive.root.toBuffer());
543
671
  const attestationsAndSigners = new CommitteeAttestationsAndSigners(
544
672
  attestations.map(attestation => CommitteeAttestation.fromViem(attestation)),
@@ -549,6 +677,8 @@ export class FakeL1State {
549
677
  signers[0],
550
678
  );
551
679
 
680
+ const packedAttestations = attestationsAndSigners.getPackedAttestations();
681
+
552
682
  const rollupInput = encodeFunctionData({
553
683
  abi: RollupAbi,
554
684
  functionName: 'propose',
@@ -558,7 +688,7 @@ export class FakeL1State {
558
688
  archive,
559
689
  oracleInput: { feeAssetPriceModifier: 0n },
560
690
  },
561
- attestationsAndSigners.getPackedAttestations(),
691
+ packedAttestations,
562
692
  attestationsAndSigners.getSigners().map(signer => signer.toString()),
563
693
  attestationsAndSignersSignature.toViemSignature(),
564
694
  blobInput,
@@ -579,21 +709,52 @@ export class FakeL1State {
579
709
  ],
580
710
  });
581
711
 
582
- return {
712
+ // Compute attestationsHash (same logic as CalldataRetriever)
713
+ const attestationsHash = Buffer32.fromString(
714
+ keccak256(encodeAbiParameters([this.getCommitteeAttestationsStructDef()], [packedAttestations])),
715
+ );
716
+
717
+ // Compute payloadDigest (same logic as CalldataRetriever)
718
+ const consensusPayload = ConsensusPayload.fromCheckpoint(checkpoint);
719
+ const payloadToSign = consensusPayload.getPayloadToSign(SignatureDomainSeparator.checkpointAttestation);
720
+ const payloadDigest = Buffer32.fromString(keccak256(payloadToSign));
721
+
722
+ const tx = {
583
723
  input: multiCallInput,
584
724
  hash: archive,
585
725
  blockHash: archive,
586
726
  to: MULTI_CALL_3_ADDRESS as `0x${string}`,
587
727
  } as Transaction<bigint, number>;
728
+
729
+ return { tx, attestationsHash, payloadDigest };
730
+ }
731
+
732
+ /** Extracts the CommitteeAttestations struct definition from RollupAbi for hash computation. */
733
+ private getCommitteeAttestationsStructDef(): AbiParameter {
734
+ const proposeFunction = RollupAbi.find(item => item.type === 'function' && item.name === 'propose') as
735
+ | { type: 'function'; name: string; inputs: readonly AbiParameter[] }
736
+ | undefined;
737
+
738
+ if (!proposeFunction) {
739
+ throw new Error('propose function not found in RollupAbi');
740
+ }
741
+
742
+ const attestationsParam = proposeFunction.inputs.find(param => param.name === '_attestations');
743
+ if (!attestationsParam) {
744
+ throw new Error('_attestations parameter not found in propose function');
745
+ }
746
+
747
+ const tupleParam = attestationsParam as unknown as { type: 'tuple'; components?: readonly AbiParameter[] };
748
+ return { type: 'tuple', components: tupleParam.components || [] } as AbiParameter;
588
749
  }
589
750
 
590
- private makeVersionedBlobHashes(checkpoint: Checkpoint): `0x${string}`[] {
591
- return getBlobsPerL1Block(checkpoint.toBlobFields()).map(
751
+ private async makeVersionedBlobHashes(checkpoint: Checkpoint): Promise<`0x${string}`[]> {
752
+ return (await getBlobsPerL1Block(checkpoint.toBlobFields())).map(
592
753
  b => `0x${b.getEthVersionedBlobHash().toString('hex')}` as `0x${string}`,
593
754
  );
594
755
  }
595
756
 
596
- private makeBlobsFromCheckpoint(checkpoint: Checkpoint): Blob[] {
597
- return getBlobsPerL1Block(checkpoint.toBlobFields());
757
+ private async makeBlobsFromCheckpoint(checkpoint: Checkpoint): Promise<Blob[]> {
758
+ return await getBlobsPerL1Block(checkpoint.toBlobFields());
598
759
  }
599
760
  }
package/src/test/index.ts CHANGED
@@ -2,3 +2,6 @@ export * from './mock_structs.js';
2
2
  export * from './mock_l2_block_source.js';
3
3
  export * from './mock_l1_to_l2_message_source.js';
4
4
  export * from './mock_archiver.js';
5
+ // NOTE: noop_l1_archiver.js is intentionally NOT exported here because it imports
6
+ // jest-mock-extended, which depends on @jest/globals and can only run inside Jest.
7
+ // Import it directly: import { NoopL1Archiver } from '@aztec/archiver/test/noop-l1';
@@ -56,8 +56,9 @@ export class MockPrefilledArchiver extends MockArchiver {
56
56
  }
57
57
 
58
58
  const fromBlock = this.l2Blocks.length;
59
- // TODO: Add L2 blocks and checkpoints separately once archiver has the apis for that.
60
- this.addBlocks(this.prefilled.slice(fromBlock, fromBlock + numBlocks).flatMap(c => c.blocks));
59
+ const checkpointsToAdd = this.prefilled.slice(fromBlock, fromBlock + numBlocks);
60
+ this.addProposedBlocks(checkpointsToAdd.flatMap(c => c.blocks));
61
+ this.checkpointList.push(...checkpointsToAdd);
61
62
  return Promise.resolve();
62
63
  }
63
64
  }
@@ -44,6 +44,7 @@ export class MockL1ToL2MessageSource implements L1ToL2MessageSource {
44
44
  checkpointed: tip,
45
45
  proven: tip,
46
46
  finalized: tip,
47
+ proposedCheckpoint: tip,
47
48
  });
48
49
  }
49
50
  }