@aztec/archiver 0.0.0-test.1 → 0.0.1-commit.5476d83

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 (110) hide show
  1. package/README.md +27 -6
  2. package/dest/archiver/archiver.d.ts +147 -57
  3. package/dest/archiver/archiver.d.ts.map +1 -1
  4. package/dest/archiver/archiver.js +841 -333
  5. package/dest/archiver/archiver_store.d.ts +85 -50
  6. package/dest/archiver/archiver_store.d.ts.map +1 -1
  7. package/dest/archiver/archiver_store_test_suite.d.ts +1 -1
  8. package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
  9. package/dest/archiver/archiver_store_test_suite.js +708 -213
  10. package/dest/archiver/config.d.ts +5 -21
  11. package/dest/archiver/config.d.ts.map +1 -1
  12. package/dest/archiver/config.js +21 -12
  13. package/dest/archiver/data_retrieval.d.ts +32 -27
  14. package/dest/archiver/data_retrieval.d.ts.map +1 -1
  15. package/dest/archiver/data_retrieval.js +197 -94
  16. package/dest/archiver/errors.d.ts +9 -1
  17. package/dest/archiver/errors.d.ts.map +1 -1
  18. package/dest/archiver/errors.js +12 -0
  19. package/dest/archiver/index.d.ts +3 -4
  20. package/dest/archiver/index.d.ts.map +1 -1
  21. package/dest/archiver/index.js +1 -2
  22. package/dest/archiver/instrumentation.d.ts +12 -6
  23. package/dest/archiver/instrumentation.d.ts.map +1 -1
  24. package/dest/archiver/instrumentation.js +58 -17
  25. package/dest/archiver/kv_archiver_store/block_store.d.ts +48 -11
  26. package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
  27. package/dest/archiver/kv_archiver_store/block_store.js +216 -63
  28. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +3 -3
  29. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
  30. package/dest/archiver/kv_archiver_store/contract_class_store.js +12 -18
  31. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +11 -8
  32. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
  33. package/dest/archiver/kv_archiver_store/contract_instance_store.js +30 -16
  34. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +50 -35
  35. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
  36. package/dest/archiver/kv_archiver_store/kv_archiver_store.js +88 -46
  37. package/dest/archiver/kv_archiver_store/log_store.d.ts +2 -2
  38. package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
  39. package/dest/archiver/kv_archiver_store/log_store.js +18 -46
  40. package/dest/archiver/kv_archiver_store/message_store.d.ts +23 -17
  41. package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
  42. package/dest/archiver/kv_archiver_store/message_store.js +150 -48
  43. package/dest/archiver/structs/data_retrieval.d.ts +1 -1
  44. package/dest/archiver/structs/inbox_message.d.ts +15 -0
  45. package/dest/archiver/structs/inbox_message.d.ts.map +1 -0
  46. package/dest/archiver/structs/inbox_message.js +38 -0
  47. package/dest/archiver/structs/published.d.ts +3 -11
  48. package/dest/archiver/structs/published.d.ts.map +1 -1
  49. package/dest/archiver/structs/published.js +1 -1
  50. package/dest/archiver/validation.d.ts +17 -0
  51. package/dest/archiver/validation.d.ts.map +1 -0
  52. package/dest/archiver/validation.js +98 -0
  53. package/dest/factory.d.ts +8 -13
  54. package/dest/factory.d.ts.map +1 -1
  55. package/dest/factory.js +18 -49
  56. package/dest/index.d.ts +2 -2
  57. package/dest/index.d.ts.map +1 -1
  58. package/dest/index.js +1 -1
  59. package/dest/rpc/index.d.ts +2 -3
  60. package/dest/rpc/index.d.ts.map +1 -1
  61. package/dest/rpc/index.js +1 -4
  62. package/dest/test/index.d.ts +1 -1
  63. package/dest/test/mock_archiver.d.ts +2 -2
  64. package/dest/test/mock_archiver.d.ts.map +1 -1
  65. package/dest/test/mock_l1_to_l2_message_source.d.ts +5 -3
  66. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  67. package/dest/test/mock_l1_to_l2_message_source.js +14 -1
  68. package/dest/test/mock_l2_block_source.d.ts +38 -10
  69. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  70. package/dest/test/mock_l2_block_source.js +119 -8
  71. package/dest/test/mock_structs.d.ts +9 -0
  72. package/dest/test/mock_structs.d.ts.map +1 -0
  73. package/dest/test/mock_structs.js +37 -0
  74. package/package.json +28 -30
  75. package/src/archiver/archiver.ts +1087 -410
  76. package/src/archiver/archiver_store.ts +97 -55
  77. package/src/archiver/archiver_store_test_suite.ts +664 -210
  78. package/src/archiver/config.ts +28 -41
  79. package/src/archiver/data_retrieval.ts +279 -125
  80. package/src/archiver/errors.ts +21 -0
  81. package/src/archiver/index.ts +2 -3
  82. package/src/archiver/instrumentation.ts +77 -22
  83. package/src/archiver/kv_archiver_store/block_store.ts +270 -72
  84. package/src/archiver/kv_archiver_store/contract_class_store.ts +13 -23
  85. package/src/archiver/kv_archiver_store/contract_instance_store.ts +35 -27
  86. package/src/archiver/kv_archiver_store/kv_archiver_store.ts +127 -63
  87. package/src/archiver/kv_archiver_store/log_store.ts +24 -62
  88. package/src/archiver/kv_archiver_store/message_store.ts +209 -53
  89. package/src/archiver/structs/inbox_message.ts +41 -0
  90. package/src/archiver/structs/published.ts +2 -11
  91. package/src/archiver/validation.ts +124 -0
  92. package/src/factory.ts +24 -66
  93. package/src/index.ts +1 -1
  94. package/src/rpc/index.ts +1 -5
  95. package/src/test/mock_archiver.ts +1 -1
  96. package/src/test/mock_l1_to_l2_message_source.ts +14 -3
  97. package/src/test/mock_l2_block_source.ts +158 -13
  98. package/src/test/mock_structs.ts +49 -0
  99. package/dest/archiver/kv_archiver_store/nullifier_store.d.ts +0 -12
  100. package/dest/archiver/kv_archiver_store/nullifier_store.d.ts.map +0 -1
  101. package/dest/archiver/kv_archiver_store/nullifier_store.js +0 -73
  102. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts +0 -23
  103. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts.map +0 -1
  104. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.js +0 -49
  105. package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts +0 -175
  106. package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts.map +0 -1
  107. package/dest/archiver/memory_archiver_store/memory_archiver_store.js +0 -636
  108. package/src/archiver/kv_archiver_store/nullifier_store.ts +0 -97
  109. package/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +0 -61
  110. package/src/archiver/memory_archiver_store/memory_archiver_store.ts +0 -801
@@ -1,18 +1,31 @@
1
- import { Blob, BlobDeserializationError } from '@aztec/blob-lib';
1
+ import {
2
+ BlobDeserializationError,
3
+ type CheckpointBlobData,
4
+ SpongeBlob,
5
+ decodeCheckpointBlobDataFromBlobs,
6
+ encodeBlockBlobData,
7
+ } from '@aztec/blob-lib';
2
8
  import type { BlobSinkClientInterface } from '@aztec/blob-sink/client';
3
- import type { EpochProofPublicInputArgs, ViemPublicClient } from '@aztec/ethereum';
9
+ import type {
10
+ EpochProofPublicInputArgs,
11
+ ViemClient,
12
+ ViemCommitteeAttestations,
13
+ ViemHeader,
14
+ ViemPublicClient,
15
+ } from '@aztec/ethereum';
4
16
  import { asyncPool } from '@aztec/foundation/async-pool';
17
+ import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
5
18
  import type { EthAddress } from '@aztec/foundation/eth-address';
6
19
  import type { ViemSignature } from '@aztec/foundation/eth-signature';
7
20
  import { Fr } from '@aztec/foundation/fields';
8
21
  import { type Logger, createLogger } from '@aztec/foundation/log';
9
- import { numToUInt32BE } from '@aztec/foundation/serialize';
10
- import { ForwarderAbi, type InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
11
- import { Body, L2Block } from '@aztec/stdlib/block';
12
- import { InboxLeaf } from '@aztec/stdlib/messaging';
22
+ import { type InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
23
+ import { Body, CommitteeAttestation, L2BlockNew } from '@aztec/stdlib/block';
24
+ import { Checkpoint, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
13
25
  import { Proof } from '@aztec/stdlib/proofs';
26
+ import { CheckpointHeader } from '@aztec/stdlib/rollup';
14
27
  import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
15
- import { BlockHeader } from '@aztec/stdlib/tx';
28
+ import { BlockHeader, GlobalVariables, PartialStateReference, StateReference } from '@aztec/stdlib/tx';
16
29
 
17
30
  import {
18
31
  type GetContractEventsReturnType,
@@ -21,14 +34,114 @@ import {
21
34
  decodeFunctionData,
22
35
  getAbiItem,
23
36
  hexToBytes,
37
+ multicall3Abi,
24
38
  } from 'viem';
25
39
 
26
40
  import { NoBlobBodiesFoundError } from './errors.js';
27
41
  import type { DataRetrieval } from './structs/data_retrieval.js';
28
- import type { L1Published, L1PublishedData } from './structs/published.js';
42
+ import type { InboxMessage } from './structs/inbox_message.js';
43
+ import type { L1PublishedData } from './structs/published.js';
44
+
45
+ export type RetrievedCheckpoint = {
46
+ checkpointNumber: number;
47
+ archiveRoot: Fr;
48
+ header: CheckpointHeader;
49
+ checkpointBlobData: CheckpointBlobData;
50
+ l1: L1PublishedData;
51
+ chainId: Fr;
52
+ version: Fr;
53
+ attestations: CommitteeAttestation[];
54
+ };
55
+
56
+ export async function retrievedToPublishedCheckpoint({
57
+ checkpointNumber,
58
+ archiveRoot,
59
+ header: checkpointHeader,
60
+ checkpointBlobData,
61
+ l1,
62
+ chainId,
63
+ version,
64
+ attestations,
65
+ }: RetrievedCheckpoint): Promise<PublishedCheckpoint> {
66
+ const { blocks: blocksBlobData } = checkpointBlobData;
67
+
68
+ // The lastArchiveRoot of a block is the new archive for the previous block.
69
+ const newArchiveRoots = blocksBlobData
70
+ .map(b => b.lastArchiveRoot)
71
+ .slice(1)
72
+ .concat([archiveRoot]);
73
+
74
+ // `blocksBlobData` is created from `decodeCheckpointBlobDataFromBlobs`. An error will be thrown if it can't read a
75
+ // field for the `l1ToL2MessageRoot` of the first block. So below we can safely assume it exists:
76
+ const l1toL2MessageTreeRoot = blocksBlobData[0].l1ToL2MessageRoot!;
77
+
78
+ const spongeBlob = SpongeBlob.init();
79
+ const l2Blocks: L2BlockNew[] = [];
80
+ for (let i = 0; i < blocksBlobData.length; i++) {
81
+ const blockBlobData = blocksBlobData[i];
82
+ const { blockEndMarker, blockEndStateField, lastArchiveRoot, noteHashRoot, nullifierRoot, publicDataRoot } =
83
+ blockBlobData;
84
+
85
+ const l2BlockNumber = blockEndMarker.blockNumber;
86
+
87
+ const globalVariables = GlobalVariables.from({
88
+ chainId,
89
+ version,
90
+ blockNumber: l2BlockNumber,
91
+ slotNumber: checkpointHeader.slotNumber,
92
+ timestamp: blockEndMarker.timestamp,
93
+ coinbase: checkpointHeader.coinbase,
94
+ feeRecipient: checkpointHeader.feeRecipient,
95
+ gasFees: checkpointHeader.gasFees,
96
+ });
97
+
98
+ const state = StateReference.from({
99
+ l1ToL2MessageTree: new AppendOnlyTreeSnapshot(
100
+ l1toL2MessageTreeRoot,
101
+ blockEndStateField.l1ToL2MessageNextAvailableLeafIndex,
102
+ ),
103
+ partial: PartialStateReference.from({
104
+ noteHashTree: new AppendOnlyTreeSnapshot(noteHashRoot, blockEndStateField.noteHashNextAvailableLeafIndex),
105
+ nullifierTree: new AppendOnlyTreeSnapshot(nullifierRoot, blockEndStateField.nullifierNextAvailableLeafIndex),
106
+ publicDataTree: new AppendOnlyTreeSnapshot(publicDataRoot, blockEndStateField.publicDataNextAvailableLeafIndex),
107
+ }),
108
+ });
109
+
110
+ const body = Body.fromTxBlobData(checkpointBlobData.blocks[0].txs);
111
+
112
+ const blobFields = encodeBlockBlobData(blockBlobData);
113
+ await spongeBlob.absorb(blobFields);
114
+
115
+ const clonedSpongeBlob = spongeBlob.clone();
116
+ const spongeBlobHash = await clonedSpongeBlob.squeeze();
117
+
118
+ const header = BlockHeader.from({
119
+ lastArchive: new AppendOnlyTreeSnapshot(lastArchiveRoot, l2BlockNumber),
120
+ state,
121
+ spongeBlobHash,
122
+ globalVariables,
123
+ totalFees: body.txEffects.reduce((accum, txEffect) => accum.add(txEffect.transactionFee), Fr.ZERO),
124
+ totalManaUsed: new Fr(blockEndStateField.totalManaUsed),
125
+ });
126
+
127
+ const newArchive = new AppendOnlyTreeSnapshot(newArchiveRoots[i], l2BlockNumber + 1);
128
+
129
+ l2Blocks.push(new L2BlockNew(newArchive, header, body));
130
+ }
131
+
132
+ const lastBlock = l2Blocks.at(-1)!;
133
+ const checkpoint = Checkpoint.from({
134
+ archive: new AppendOnlyTreeSnapshot(archiveRoot, lastBlock.number + 1),
135
+ header: checkpointHeader,
136
+ blocks: l2Blocks,
137
+ number: checkpointNumber,
138
+ });
139
+
140
+ return PublishedCheckpoint.from({ checkpoint, l1, attestations });
141
+ }
29
142
 
30
143
  /**
31
- * Fetches new L2 blocks.
144
+ * Fetches new checkpoints.
32
145
  * @param publicClient - The viem public client to use for transaction retrieval.
33
146
  * @param rollupAddress - The address of the rollup contract.
34
147
  * @param searchStartBlock - The block number to use for starting the search.
@@ -36,21 +149,24 @@ import type { L1Published, L1PublishedData } from './structs/published.js';
36
149
  * @param expectedNextL2BlockNum - The next L2 block number that we expect to find.
37
150
  * @returns An array of block; as well as the next eth block to search from.
38
151
  */
39
- export async function retrieveBlocksFromRollup(
152
+ export async function retrieveCheckpointsFromRollup(
40
153
  rollup: GetContractReturnType<typeof RollupAbi, ViemPublicClient>,
41
154
  publicClient: ViemPublicClient,
42
155
  blobSinkClient: BlobSinkClientInterface,
43
156
  searchStartBlock: bigint,
44
157
  searchEndBlock: bigint,
45
158
  logger: Logger = createLogger('archiver'),
46
- ): Promise<L1Published<L2Block>[]> {
47
- const retrievedBlocks: L1Published<L2Block>[] = [];
159
+ ): Promise<RetrievedCheckpoint[]> {
160
+ const retrievedCheckpoints: RetrievedCheckpoint[] = [];
161
+
162
+ let rollupConstants: { chainId: Fr; version: Fr; targetCommitteeSize: number } | undefined;
163
+
48
164
  do {
49
165
  if (searchStartBlock > searchEndBlock) {
50
166
  break;
51
167
  }
52
- const l2BlockProposedLogs = (
53
- await rollup.getEvents.L2BlockProposed(
168
+ const checkpointProposedLogs = (
169
+ await rollup.getEvents.CheckpointProposed(
54
170
  {},
55
171
  {
56
172
  fromBlock: searchStartBlock,
@@ -59,60 +175,76 @@ export async function retrieveBlocksFromRollup(
59
175
  )
60
176
  ).filter(log => log.blockNumber! >= searchStartBlock && log.blockNumber! <= searchEndBlock);
61
177
 
62
- if (l2BlockProposedLogs.length === 0) {
178
+ if (checkpointProposedLogs.length === 0) {
63
179
  break;
64
180
  }
65
181
 
66
- const lastLog = l2BlockProposedLogs[l2BlockProposedLogs.length - 1];
182
+ const lastLog = checkpointProposedLogs.at(-1)!;
67
183
  logger.debug(
68
- `Got ${l2BlockProposedLogs.length} L2 block processed logs for L2 blocks ${l2BlockProposedLogs[0].args.blockNumber}-${lastLog.args.blockNumber} between L1 blocks ${searchStartBlock}-${searchEndBlock}`,
184
+ `Got ${checkpointProposedLogs.length} processed logs for checkpoints ${checkpointProposedLogs[0].args.checkpointNumber}-${lastLog.args.checkpointNumber} between L1 blocks ${searchStartBlock}-${searchEndBlock}`,
69
185
  );
70
186
 
71
- const newBlocks = await processL2BlockProposedLogs(
187
+ if (rollupConstants === undefined) {
188
+ const [chainId, version, targetCommitteeSize] = await Promise.all([
189
+ publicClient.getChainId(),
190
+ rollup.read.getVersion(),
191
+ rollup.read.getTargetCommitteeSize(),
192
+ ]);
193
+ rollupConstants = {
194
+ chainId: new Fr(chainId),
195
+ version: new Fr(version),
196
+ targetCommitteeSize: Number(targetCommitteeSize),
197
+ };
198
+ }
199
+
200
+ const newCheckpoints = await processCheckpointProposedLogs(
72
201
  rollup,
73
202
  publicClient,
74
203
  blobSinkClient,
75
- l2BlockProposedLogs,
204
+ checkpointProposedLogs,
205
+ rollupConstants,
76
206
  logger,
77
207
  );
78
- retrievedBlocks.push(...newBlocks);
208
+ retrievedCheckpoints.push(...newCheckpoints);
79
209
  searchStartBlock = lastLog.blockNumber! + 1n;
80
210
  } while (searchStartBlock <= searchEndBlock);
81
211
 
82
- // The asyncpool from processL2BlockProposedLogs will not necessarily return the blocks in order, so we sort them before returning.
83
- return retrievedBlocks.sort((a, b) => Number(a.l1.blockNumber - b.l1.blockNumber));
212
+ // The asyncPool from processCheckpointProposedLogs will not necessarily return the checkpoints in order, so we sort them before returning.
213
+ return retrievedCheckpoints.sort((a, b) => Number(a.l1.blockNumber - b.l1.blockNumber));
84
214
  }
85
215
 
86
216
  /**
87
- * Processes newly received L2BlockProposed logs.
217
+ * Processes newly received CheckpointProposed logs.
88
218
  * @param rollup - The rollup contract
89
219
  * @param publicClient - The viem public client to use for transaction retrieval.
90
- * @param logs - L2BlockProposed logs.
91
- * @returns - An array blocks.
220
+ * @param logs - CheckpointProposed logs.
221
+ * @returns - An array of checkpoints.
92
222
  */
93
- export async function processL2BlockProposedLogs(
223
+ async function processCheckpointProposedLogs(
94
224
  rollup: GetContractReturnType<typeof RollupAbi, ViemPublicClient>,
95
225
  publicClient: ViemPublicClient,
96
226
  blobSinkClient: BlobSinkClientInterface,
97
- logs: GetContractEventsReturnType<typeof RollupAbi, 'L2BlockProposed'>,
227
+ logs: GetContractEventsReturnType<typeof RollupAbi, 'CheckpointProposed'>,
228
+ { chainId, version, targetCommitteeSize }: { chainId: Fr; version: Fr; targetCommitteeSize: number },
98
229
  logger: Logger,
99
- ): Promise<L1Published<L2Block>[]> {
100
- const retrievedBlocks: L1Published<L2Block>[] = [];
230
+ ): Promise<RetrievedCheckpoint[]> {
231
+ const retrievedCheckpoints: RetrievedCheckpoint[] = [];
101
232
  await asyncPool(10, logs, async log => {
102
- const l2BlockNumber = log.args.blockNumber!;
233
+ const checkpointNumber = Number(log.args.checkpointNumber!);
103
234
  const archive = log.args.archive!;
104
- const archiveFromChain = await rollup.read.archiveAt([l2BlockNumber]);
235
+ const archiveFromChain = await rollup.read.archiveAt([BigInt(checkpointNumber)]);
105
236
  const blobHashes = log.args.versionedBlobHashes!.map(blobHash => Buffer.from(blobHash.slice(2), 'hex'));
106
237
 
107
- // The value from the event and contract will match only if the block is in the chain.
238
+ // The value from the event and contract will match only if the checkpoint is in the chain.
108
239
  if (archive === archiveFromChain) {
109
- const block = await getBlockFromRollupTx(
240
+ const checkpoint = await getCheckpointFromRollupTx(
110
241
  publicClient,
111
242
  blobSinkClient,
112
243
  log.transactionHash!,
113
244
  blobHashes,
114
- l2BlockNumber,
245
+ checkpointNumber,
115
246
  rollup.address,
247
+ targetCommitteeSize,
116
248
  logger,
117
249
  );
118
250
 
@@ -122,16 +254,22 @@ export async function processL2BlockProposedLogs(
122
254
  timestamp: await getL1BlockTime(publicClient, log.blockNumber),
123
255
  };
124
256
 
125
- retrievedBlocks.push({ data: block, l1 });
257
+ retrievedCheckpoints.push({ ...checkpoint, l1, chainId, version });
258
+ logger.trace(`Retrieved checkpoint ${checkpointNumber} from L1 tx ${log.transactionHash}`, {
259
+ l1BlockNumber: log.blockNumber,
260
+ checkpointNumber,
261
+ archive: archive.toString(),
262
+ attestations: checkpoint.attestations,
263
+ });
126
264
  } else {
127
- logger.warn(`Ignoring L2 block ${l2BlockNumber} due to archive root mismatch`, {
265
+ logger.warn(`Ignoring checkpoint ${checkpointNumber} due to archive root mismatch`, {
128
266
  actual: archive,
129
267
  expected: archiveFromChain,
130
268
  });
131
269
  }
132
270
  });
133
271
 
134
- return retrievedBlocks;
272
+ return retrievedCheckpoints;
135
273
  }
136
274
 
137
275
  export async function getL1BlockTime(publicClient: ViemPublicClient, blockNumber: bigint): Promise<bigint> {
@@ -140,37 +278,36 @@ export async function getL1BlockTime(publicClient: ViemPublicClient, blockNumber
140
278
  }
141
279
 
142
280
  /**
143
- * Extracts the first 'propose' method calldata from a forwarder transaction's data.
144
- * @param forwarderData - The forwarder transaction input data
281
+ * Extracts the first 'propose' method calldata from a multicall3 transaction's data.
282
+ * @param multicall3Data - The multicall3 transaction input data
145
283
  * @param rollupAddress - The address of the rollup contract
146
284
  * @returns The calldata for the first 'propose' method call to the rollup contract
147
285
  */
148
- function extractRollupProposeCalldata(forwarderData: Hex, rollupAddress: Hex): Hex {
149
- // TODO(#11451): custom forwarders
150
- const { functionName: forwarderFunctionName, args: forwarderArgs } = decodeFunctionData({
151
- abi: ForwarderAbi,
152
- data: forwarderData,
286
+ function extractRollupProposeCalldata(multicall3Data: Hex, rollupAddress: Hex): Hex {
287
+ const { functionName: multicall3FunctionName, args: multicall3Args } = decodeFunctionData({
288
+ abi: multicall3Abi,
289
+ data: multicall3Data,
153
290
  });
154
291
 
155
- if (forwarderFunctionName !== 'forward') {
156
- throw new Error(`Unexpected forwarder method called ${forwarderFunctionName}`);
292
+ if (multicall3FunctionName !== 'aggregate3') {
293
+ throw new Error(`Unexpected multicall3 method called ${multicall3FunctionName}`);
157
294
  }
158
295
 
159
- if (forwarderArgs.length !== 2) {
160
- throw new Error(`Unexpected number of arguments for forwarder`);
296
+ if (multicall3Args.length !== 1) {
297
+ throw new Error(`Unexpected number of arguments for multicall3`);
161
298
  }
162
299
 
163
- const [to, data] = forwarderArgs;
300
+ const [calls] = multicall3Args;
164
301
 
165
302
  // Find all rollup calls
166
303
  const rollupAddressLower = rollupAddress.toLowerCase();
167
304
 
168
- for (let i = 0; i < to.length; i++) {
169
- const addr = to[i];
305
+ for (let i = 0; i < calls.length; i++) {
306
+ const addr = calls[i].target;
170
307
  if (addr.toLowerCase() !== rollupAddressLower) {
171
308
  continue;
172
309
  }
173
- const callData = data[i];
310
+ const callData = calls[i].callData;
174
311
 
175
312
  try {
176
313
  const { functionName: rollupFunctionName } = decodeFunctionData({
@@ -181,33 +318,35 @@ function extractRollupProposeCalldata(forwarderData: Hex, rollupAddress: Hex): H
181
318
  if (rollupFunctionName === 'propose') {
182
319
  return callData;
183
320
  }
184
- } catch (err) {
321
+ } catch {
185
322
  // Skip invalid function data
186
323
  continue;
187
324
  }
188
325
  }
189
326
 
190
- throw new Error(`Rollup address not found in forwarder args`);
327
+ throw new Error(`Rollup address not found in multicall3 args`);
191
328
  }
192
329
 
193
330
  /**
194
- * Gets block from the calldata of an L1 transaction.
195
- * Assumes that the block was published from an EOA.
331
+ * Gets checkpoint from the calldata of an L1 transaction.
332
+ * Assumes that the checkpoint was published from an EOA.
196
333
  * TODO: Add retries and error management.
197
334
  * @param publicClient - The viem public client to use for transaction retrieval.
198
335
  * @param txHash - Hash of the tx that published it.
199
- * @param l2BlockNum - L2 block number.
200
- * @returns L2 block from the calldata, deserialized
336
+ * @param checkpointNumber - Checkpoint number.
337
+ * @returns Checkpoint from the calldata, deserialized
201
338
  */
202
- async function getBlockFromRollupTx(
339
+ async function getCheckpointFromRollupTx(
203
340
  publicClient: ViemPublicClient,
204
341
  blobSinkClient: BlobSinkClientInterface,
205
342
  txHash: `0x${string}`,
206
- blobHashes: Buffer[], // WORKTODO(md): buffer32?
207
- l2BlockNum: bigint,
343
+ blobHashes: Buffer[], // TODO(md): buffer32?
344
+ checkpointNumber: number,
208
345
  rollupAddress: Hex,
346
+ targetCommitteeSize: number,
209
347
  logger: Logger,
210
- ): Promise<L2Block> {
348
+ ): Promise<Omit<RetrievedCheckpoint, 'l1' | 'chainId' | 'version'>> {
349
+ logger.trace(`Fetching checkpoint ${checkpointNumber} from rollup tx ${txHash}`);
211
350
  const { input: forwarderData, blockHash } = await publicClient.getTransaction({ hash: txHash });
212
351
 
213
352
  const rollupData = extractRollupProposeCalldata(forwarderData, rollupAddress);
@@ -220,29 +359,44 @@ async function getBlockFromRollupTx(
220
359
  throw new Error(`Unexpected rollup method called ${rollupFunctionName}`);
221
360
  }
222
361
 
223
- const [decodedArgs, ,] = rollupArgs! as readonly [
362
+ const [decodedArgs, packedAttestations, _signers, _blobInput] = rollupArgs! as readonly [
224
363
  {
225
- header: Hex;
226
364
  archive: Hex;
227
- blockHash: Hex;
228
365
  oracleInput: {
229
366
  feeAssetPriceModifier: bigint;
230
367
  };
231
- txHashes: Hex[];
368
+ header: ViemHeader;
369
+ txHashes: readonly Hex[];
232
370
  },
233
- ViemSignature[],
371
+ ViemCommitteeAttestations,
372
+ Hex[],
373
+ ViemSignature,
234
374
  Hex,
235
375
  ];
236
376
 
237
- const header = BlockHeader.fromBuffer(Buffer.from(hexToBytes(decodedArgs.header)));
377
+ const attestations = CommitteeAttestation.fromPacked(packedAttestations, targetCommitteeSize);
378
+
379
+ logger.trace(`Recovered propose calldata from tx ${txHash}`, {
380
+ checkpointNumber,
381
+ archive: decodedArgs.archive,
382
+ header: decodedArgs.header,
383
+ l1BlockHash: blockHash,
384
+ blobHashes,
385
+ attestations,
386
+ packedAttestations,
387
+ targetCommitteeSize,
388
+ });
389
+
390
+ const header = CheckpointHeader.fromViem(decodedArgs.header);
238
391
  const blobBodies = await blobSinkClient.getBlobSidecar(blockHash, blobHashes);
239
392
  if (blobBodies.length === 0) {
240
- throw new NoBlobBodiesFoundError(Number(l2BlockNum));
393
+ throw new NoBlobBodiesFoundError(checkpointNumber);
241
394
  }
242
395
 
243
- let blockFields: Fr[];
396
+ let checkpointBlobData: CheckpointBlobData;
244
397
  try {
245
- blockFields = Blob.toEncodedFields(blobBodies);
398
+ // Attempt to decode the checkpoint blob data.
399
+ checkpointBlobData = decodeCheckpointBlobDataFromBlobs(blobBodies.map(b => b.blob));
246
400
  } catch (err: any) {
247
401
  if (err instanceof BlobDeserializationError) {
248
402
  logger.fatal(err.message);
@@ -252,23 +406,28 @@ async function getBlockFromRollupTx(
252
406
  throw err;
253
407
  }
254
408
 
255
- // The blob source gives us blockFields, and we must construct the body from them:
256
- const blockBody = Body.fromBlobFields(blockFields);
257
-
258
- const blockNumberFromHeader = header.globalVariables.blockNumber.toBigInt();
259
-
260
- if (blockNumberFromHeader !== l2BlockNum) {
261
- throw new Error(`Block number mismatch: expected ${l2BlockNum} but got ${blockNumberFromHeader}`);
262
- }
409
+ const archiveRoot = new Fr(Buffer.from(hexToBytes(decodedArgs.archive)));
263
410
 
264
- const archive = AppendOnlyTreeSnapshot.fromBuffer(
265
- Buffer.concat([
266
- Buffer.from(hexToBytes(decodedArgs.archive)), // L2Block.archive.root
267
- numToUInt32BE(Number(l2BlockNum + 1n)), // L2Block.archive.nextAvailableLeafIndex
268
- ]),
269
- );
411
+ return {
412
+ checkpointNumber,
413
+ archiveRoot,
414
+ header,
415
+ checkpointBlobData,
416
+ attestations,
417
+ };
418
+ }
270
419
 
271
- return new L2Block(archive, header, blockBody);
420
+ /** Given an L1 to L2 message, retrieves its corresponding event from the Inbox within a specific block range. */
421
+ export async function retrieveL1ToL2Message(
422
+ inbox: GetContractReturnType<typeof InboxAbi, ViemClient>,
423
+ leaf: Fr,
424
+ fromBlock: bigint,
425
+ toBlock: bigint,
426
+ ): Promise<InboxMessage | undefined> {
427
+ const logs = await inbox.getEvents.MessageSent({ hash: leaf.toString() }, { fromBlock, toBlock });
428
+
429
+ const messages = mapLogsInboxMessage(logs);
430
+ return messages.length > 0 ? messages[0] : undefined;
272
431
  }
273
432
 
274
433
  /**
@@ -281,39 +440,39 @@ async function getBlockFromRollupTx(
281
440
  * @returns An array of InboxLeaf and next eth block to search from.
282
441
  */
283
442
  export async function retrieveL1ToL2Messages(
284
- inbox: GetContractReturnType<typeof InboxAbi, ViemPublicClient>,
443
+ inbox: GetContractReturnType<typeof InboxAbi, ViemClient>,
285
444
  searchStartBlock: bigint,
286
445
  searchEndBlock: bigint,
287
- ): Promise<DataRetrieval<InboxLeaf>> {
288
- const retrievedL1ToL2Messages: InboxLeaf[] = [];
289
- do {
290
- if (searchStartBlock > searchEndBlock) {
291
- break;
292
- }
293
-
446
+ ): Promise<InboxMessage[]> {
447
+ const retrievedL1ToL2Messages: InboxMessage[] = [];
448
+ while (searchStartBlock <= searchEndBlock) {
294
449
  const messageSentLogs = (
295
- await inbox.getEvents.MessageSent(
296
- {},
297
- {
298
- fromBlock: searchStartBlock,
299
- toBlock: searchEndBlock,
300
- },
301
- )
450
+ await inbox.getEvents.MessageSent({}, { fromBlock: searchStartBlock, toBlock: searchEndBlock })
302
451
  ).filter(log => log.blockNumber! >= searchStartBlock && log.blockNumber! <= searchEndBlock);
303
452
 
304
453
  if (messageSentLogs.length === 0) {
305
454
  break;
306
455
  }
307
456
 
308
- for (const log of messageSentLogs) {
309
- const { index, hash } = log.args;
310
- retrievedL1ToL2Messages.push(new InboxLeaf(index!, Fr.fromHexString(hash!)));
311
- }
457
+ retrievedL1ToL2Messages.push(...mapLogsInboxMessage(messageSentLogs));
458
+ searchStartBlock = messageSentLogs.at(-1)!.blockNumber + 1n;
459
+ }
312
460
 
313
- // handles the case when there are no new messages:
314
- searchStartBlock = (messageSentLogs.findLast(msgLog => !!msgLog)?.blockNumber || searchStartBlock) + 1n;
315
- } while (searchStartBlock <= searchEndBlock);
316
- return { lastProcessedL1BlockNumber: searchStartBlock - 1n, retrievedData: retrievedL1ToL2Messages };
461
+ return retrievedL1ToL2Messages;
462
+ }
463
+
464
+ function mapLogsInboxMessage(logs: GetContractEventsReturnType<typeof InboxAbi, 'MessageSent'>): InboxMessage[] {
465
+ return logs.map(log => {
466
+ const { index, hash, checkpointNumber, rollingHash } = log.args;
467
+ return {
468
+ index: index!,
469
+ leaf: Fr.fromHexString(hash!),
470
+ l1BlockNumber: log.blockNumber,
471
+ l1BlockHash: Buffer32.fromString(log.blockHash),
472
+ l2BlockNumber: Number(checkpointNumber!),
473
+ rollingHash: Buffer16.fromString(rollingHash!),
474
+ };
475
+ });
317
476
  }
318
477
 
319
478
  /** Retrieves L2ProofVerified events from the rollup contract. */
@@ -322,7 +481,7 @@ export async function retrieveL2ProofVerifiedEvents(
322
481
  rollupAddress: EthAddress,
323
482
  searchStartBlock: bigint,
324
483
  searchEndBlock?: bigint,
325
- ): Promise<{ l1BlockNumber: bigint; l2BlockNumber: bigint; proverId: Fr; txHash: Hex }[]> {
484
+ ): Promise<{ l1BlockNumber: bigint; checkpointNumber: number; proverId: Fr; txHash: Hex }[]> {
326
485
  const logs = await publicClient.getLogs({
327
486
  address: rollupAddress.toString(),
328
487
  fromBlock: searchStartBlock,
@@ -333,7 +492,7 @@ export async function retrieveL2ProofVerifiedEvents(
333
492
 
334
493
  return logs.map(log => ({
335
494
  l1BlockNumber: log.blockNumber,
336
- l2BlockNumber: log.args.blockNumber,
495
+ checkpointNumber: Number(log.args.checkpointNumber),
337
496
  proverId: Fr.fromHexString(log.args.proverId),
338
497
  txHash: log.transactionHash,
339
498
  }));
@@ -345,14 +504,14 @@ export async function retrieveL2ProofsFromRollup(
345
504
  rollupAddress: EthAddress,
346
505
  searchStartBlock: bigint,
347
506
  searchEndBlock?: bigint,
348
- ): Promise<DataRetrieval<{ proof: Proof; proverId: Fr; l2BlockNumber: bigint; txHash: `0x${string}` }>> {
507
+ ): Promise<DataRetrieval<{ proof: Proof; proverId: Fr; checkpointNumber: number; txHash: `0x${string}` }>> {
349
508
  const logs = await retrieveL2ProofVerifiedEvents(publicClient, rollupAddress, searchStartBlock, searchEndBlock);
350
- const retrievedData: { proof: Proof; proverId: Fr; l2BlockNumber: bigint; txHash: `0x${string}` }[] = [];
509
+ const retrievedData: { proof: Proof; proverId: Fr; checkpointNumber: number; txHash: `0x${string}` }[] = [];
351
510
  const lastProcessedL1BlockNumber = logs.length > 0 ? logs.at(-1)!.l1BlockNumber : searchStartBlock - 1n;
352
511
 
353
- for (const { txHash, proverId, l2BlockNumber } of logs) {
512
+ for (const { txHash, proverId, checkpointNumber } of logs) {
354
513
  const proofData = await getProofFromSubmitProofTx(publicClient, txHash, proverId);
355
- retrievedData.push({ proof: proofData.proof, proverId: proofData.proverId, l2BlockNumber, txHash });
514
+ retrievedData.push({ proof: proofData.proof, proverId: proofData.proverId, checkpointNumber, txHash });
356
515
  }
357
516
  return {
358
517
  retrievedData,
@@ -360,33 +519,31 @@ export async function retrieveL2ProofsFromRollup(
360
519
  };
361
520
  }
362
521
 
363
- export type SubmitBlockProof = {
522
+ export type SubmitEpochProof = {
364
523
  archiveRoot: Fr;
365
524
  proverId: Fr;
366
- aggregationObject: Buffer;
367
525
  proof: Proof;
368
526
  };
369
527
 
370
528
  /**
371
- * Gets block metadata (header and archive snapshot) from the calldata of an L1 transaction.
529
+ * Gets epoch proof metadata (archive root and proof) from the calldata of an L1 transaction.
372
530
  * Assumes that the block was published from an EOA.
373
531
  * TODO: Add retries and error management.
374
532
  * @param publicClient - The viem public client to use for transaction retrieval.
375
533
  * @param txHash - Hash of the tx that published it.
376
- * @param l2BlockNum - L2 block number.
377
- * @returns L2 block metadata (header and archive) from the calldata, deserialized
534
+ * @param expectedProverId - Expected prover ID.
535
+ * @returns Epoch proof metadata from the calldata, deserialized.
378
536
  */
379
537
  export async function getProofFromSubmitProofTx(
380
538
  publicClient: ViemPublicClient,
381
539
  txHash: `0x${string}`,
382
540
  expectedProverId: Fr,
383
- ): Promise<SubmitBlockProof> {
541
+ ): Promise<SubmitEpochProof> {
384
542
  const { input: data } = await publicClient.getTransaction({ hash: txHash });
385
543
  const { functionName, args } = decodeFunctionData({ abi: RollupAbi, data });
386
544
 
387
545
  let proverId: Fr;
388
546
  let archiveRoot: Fr;
389
- let aggregationObject: Buffer;
390
547
  let proof: Proof;
391
548
 
392
549
  if (functionName === 'submitEpochRootProof') {
@@ -396,12 +553,10 @@ export async function getProofFromSubmitProofTx(
396
553
  end: bigint;
397
554
  args: EpochProofPublicInputArgs;
398
555
  fees: readonly Hex[];
399
- aggregationObject: Hex;
400
556
  proof: Hex;
401
557
  },
402
558
  ];
403
559
 
404
- aggregationObject = Buffer.from(hexToBytes(decodedArgs.aggregationObject));
405
560
  proverId = Fr.fromHexString(decodedArgs.args.proverId);
406
561
  archiveRoot = Fr.fromHexString(decodedArgs.args.endArchive);
407
562
  proof = Proof.fromBuffer(Buffer.from(hexToBytes(decodedArgs.proof)));
@@ -415,7 +570,6 @@ export async function getProofFromSubmitProofTx(
415
570
 
416
571
  return {
417
572
  proverId,
418
- aggregationObject,
419
573
  archiveRoot,
420
574
  proof,
421
575
  };