@aztec/archiver 0.0.0-test.1 → 0.0.1-commit.24de95ac

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 (102) hide show
  1. package/README.md +27 -6
  2. package/dest/archiver/archiver.d.ts +126 -46
  3. package/dest/archiver/archiver.d.ts.map +1 -1
  4. package/dest/archiver/archiver.js +683 -261
  5. package/dest/archiver/archiver_store.d.ts +84 -49
  6. package/dest/archiver/archiver_store.d.ts.map +1 -1
  7. package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
  8. package/dest/archiver/archiver_store_test_suite.js +707 -213
  9. package/dest/archiver/config.d.ts +4 -20
  10. package/dest/archiver/config.d.ts.map +1 -1
  11. package/dest/archiver/config.js +16 -12
  12. package/dest/archiver/data_retrieval.d.ts +25 -20
  13. package/dest/archiver/data_retrieval.d.ts.map +1 -1
  14. package/dest/archiver/data_retrieval.js +147 -68
  15. package/dest/archiver/errors.d.ts +8 -0
  16. package/dest/archiver/errors.d.ts.map +1 -1
  17. package/dest/archiver/errors.js +12 -0
  18. package/dest/archiver/index.d.ts +2 -3
  19. package/dest/archiver/index.d.ts.map +1 -1
  20. package/dest/archiver/index.js +1 -2
  21. package/dest/archiver/instrumentation.d.ts +9 -3
  22. package/dest/archiver/instrumentation.d.ts.map +1 -1
  23. package/dest/archiver/instrumentation.js +58 -17
  24. package/dest/archiver/kv_archiver_store/block_store.d.ts +47 -10
  25. package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
  26. package/dest/archiver/kv_archiver_store/block_store.js +216 -63
  27. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +2 -2
  28. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
  29. package/dest/archiver/kv_archiver_store/contract_class_store.js +12 -18
  30. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +10 -7
  31. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
  32. package/dest/archiver/kv_archiver_store/contract_instance_store.js +30 -16
  33. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +49 -34
  34. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
  35. package/dest/archiver/kv_archiver_store/kv_archiver_store.js +88 -46
  36. package/dest/archiver/kv_archiver_store/log_store.d.ts +1 -1
  37. package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
  38. package/dest/archiver/kv_archiver_store/log_store.js +18 -46
  39. package/dest/archiver/kv_archiver_store/message_store.d.ts +22 -16
  40. package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
  41. package/dest/archiver/kv_archiver_store/message_store.js +150 -48
  42. package/dest/archiver/structs/inbox_message.d.ts +15 -0
  43. package/dest/archiver/structs/inbox_message.d.ts.map +1 -0
  44. package/dest/archiver/structs/inbox_message.js +38 -0
  45. package/dest/archiver/structs/published.d.ts +1 -10
  46. package/dest/archiver/structs/published.d.ts.map +1 -1
  47. package/dest/archiver/structs/published.js +1 -1
  48. package/dest/archiver/validation.d.ts +11 -0
  49. package/dest/archiver/validation.d.ts.map +1 -0
  50. package/dest/archiver/validation.js +90 -0
  51. package/dest/factory.d.ts +7 -12
  52. package/dest/factory.d.ts.map +1 -1
  53. package/dest/factory.js +18 -49
  54. package/dest/rpc/index.d.ts +1 -2
  55. package/dest/rpc/index.d.ts.map +1 -1
  56. package/dest/rpc/index.js +1 -4
  57. package/dest/test/mock_archiver.d.ts +1 -1
  58. package/dest/test/mock_l1_to_l2_message_source.d.ts +4 -2
  59. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  60. package/dest/test/mock_l1_to_l2_message_source.js +14 -1
  61. package/dest/test/mock_l2_block_source.d.ts +32 -5
  62. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  63. package/dest/test/mock_l2_block_source.js +118 -7
  64. package/dest/test/mock_structs.d.ts +9 -0
  65. package/dest/test/mock_structs.d.ts.map +1 -0
  66. package/dest/test/mock_structs.js +37 -0
  67. package/package.json +25 -27
  68. package/src/archiver/archiver.ts +858 -317
  69. package/src/archiver/archiver_store.ts +97 -55
  70. package/src/archiver/archiver_store_test_suite.ts +663 -210
  71. package/src/archiver/config.ts +23 -41
  72. package/src/archiver/data_retrieval.ts +215 -92
  73. package/src/archiver/errors.ts +21 -0
  74. package/src/archiver/index.ts +2 -3
  75. package/src/archiver/instrumentation.ts +75 -20
  76. package/src/archiver/kv_archiver_store/block_store.ts +270 -72
  77. package/src/archiver/kv_archiver_store/contract_class_store.ts +13 -23
  78. package/src/archiver/kv_archiver_store/contract_instance_store.ts +35 -27
  79. package/src/archiver/kv_archiver_store/kv_archiver_store.ts +127 -63
  80. package/src/archiver/kv_archiver_store/log_store.ts +24 -62
  81. package/src/archiver/kv_archiver_store/message_store.ts +209 -53
  82. package/src/archiver/structs/inbox_message.ts +41 -0
  83. package/src/archiver/structs/published.ts +1 -11
  84. package/src/archiver/validation.ts +99 -0
  85. package/src/factory.ts +24 -66
  86. package/src/rpc/index.ts +1 -5
  87. package/src/test/mock_archiver.ts +1 -1
  88. package/src/test/mock_l1_to_l2_message_source.ts +14 -3
  89. package/src/test/mock_l2_block_source.ts +152 -8
  90. package/src/test/mock_structs.ts +49 -0
  91. package/dest/archiver/kv_archiver_store/nullifier_store.d.ts +0 -12
  92. package/dest/archiver/kv_archiver_store/nullifier_store.d.ts.map +0 -1
  93. package/dest/archiver/kv_archiver_store/nullifier_store.js +0 -73
  94. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts +0 -23
  95. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts.map +0 -1
  96. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.js +0 -49
  97. package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts +0 -175
  98. package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts.map +0 -1
  99. package/dest/archiver/memory_archiver_store/memory_archiver_store.js +0 -636
  100. package/src/archiver/kv_archiver_store/nullifier_store.ts +0 -97
  101. package/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +0 -61
  102. package/src/archiver/memory_archiver_store/memory_archiver_store.ts +0 -801
@@ -1,62 +1,31 @@
1
1
  import { type BlobSinkConfig, blobSinkConfigMapping } from '@aztec/blob-sink/client';
2
2
  import {
3
- type L1ContractAddresses,
4
3
  type L1ContractsConfig,
5
4
  type L1ReaderConfig,
5
+ l1ContractAddressesMapping,
6
6
  l1ContractsConfigMappings,
7
7
  l1ReaderConfigMappings,
8
8
  } from '@aztec/ethereum';
9
- import { type ConfigMappingsType, getConfigFromMappings, numberConfigHelper } from '@aztec/foundation/config';
9
+ import {
10
+ type ConfigMappingsType,
11
+ booleanConfigHelper,
12
+ getConfigFromMappings,
13
+ numberConfigHelper,
14
+ } from '@aztec/foundation/config';
10
15
  import { type ChainConfig, chainConfigMappings } from '@aztec/stdlib/config';
16
+ import type { ArchiverSpecificConfig } from '@aztec/stdlib/interfaces/server';
11
17
 
12
18
  /**
19
+ * The archiver configuration.
13
20
  * There are 2 polling intervals used in this configuration. The first is the archiver polling interval, archiverPollingIntervalMS.
14
21
  * This is the interval between successive calls to eth_blockNumber via viem.
15
22
  * Results of calls to eth_blockNumber are cached by viem with this cache being updated periodically at the interval specified by viemPollingIntervalMS.
16
23
  * As a result the maximum observed polling time for new blocks will be viemPollingIntervalMS + archiverPollingIntervalMS.
17
24
  */
18
-
19
- /**
20
- * The archiver configuration.
21
- */
22
- export type ArchiverConfig = {
23
- /** URL for an archiver service. If set, will return an archiver client as opposed to starting a new one. */
24
- archiverUrl?: string;
25
-
26
- /** URL for an L1 consensus client */
27
- l1ConsensusHostUrl?: string;
28
-
29
- /** The polling interval in ms for retrieving new L2 blocks and encrypted logs. */
30
- archiverPollingIntervalMS?: number;
31
-
32
- /** The number of L2 blocks the archiver will attempt to download at a time. */
33
- archiverBatchSize?: number;
34
-
35
- /** The polling interval viem uses in ms */
36
- viemPollingIntervalMS?: number;
37
-
38
- /** The deployed L1 contract addresses */
39
- l1Contracts: L1ContractAddresses;
40
-
41
- /** The max number of logs that can be obtained in 1 "getPublicLogs" call. */
42
- maxLogs?: number;
43
- } & L1ReaderConfig &
44
- L1ContractsConfig &
45
- BlobSinkConfig &
46
- ChainConfig;
25
+ export type ArchiverConfig = ArchiverSpecificConfig & L1ReaderConfig & L1ContractsConfig & BlobSinkConfig & ChainConfig;
47
26
 
48
27
  export const archiverConfigMappings: ConfigMappingsType<ArchiverConfig> = {
49
28
  ...blobSinkConfigMapping,
50
- archiverUrl: {
51
- env: 'ARCHIVER_URL',
52
- description:
53
- 'URL for an archiver service. If set, will return an archiver client as opposed to starting a new one.',
54
- },
55
- l1ConsensusHostUrl: {
56
- env: 'L1_CONSENSUS_HOST_URL',
57
- description: 'URL for an L1 consensus client.',
58
- parseEnv: (val: string) => val,
59
- },
60
29
  archiverPollingIntervalMS: {
61
30
  env: 'ARCHIVER_POLLING_INTERVAL_MS',
62
31
  description: 'The polling interval in ms for retrieving new L2 blocks and encrypted logs.',
@@ -72,6 +41,15 @@ export const archiverConfigMappings: ConfigMappingsType<ArchiverConfig> = {
72
41
  description: 'The max number of logs that can be obtained in 1 "getPublicLogs" call.',
73
42
  ...numberConfigHelper(1_000),
74
43
  },
44
+ archiverStoreMapSizeKb: {
45
+ env: 'ARCHIVER_STORE_MAP_SIZE_KB',
46
+ parseEnv: (val: string | undefined) => (val ? +val : undefined),
47
+ description: 'The maximum possible size of the archiver DB in KB. Overwrites the general dataStoreMapSizeKb.',
48
+ },
49
+ skipValidateBlockAttestations: {
50
+ description: 'Whether to skip validating block attestations (use only for testing).',
51
+ ...booleanConfigHelper(false),
52
+ },
75
53
  ...chainConfigMappings,
76
54
  ...l1ReaderConfigMappings,
77
55
  viemPollingIntervalMS: {
@@ -80,6 +58,10 @@ export const archiverConfigMappings: ConfigMappingsType<ArchiverConfig> = {
80
58
  ...numberConfigHelper(1000),
81
59
  },
82
60
  ...l1ContractsConfigMappings,
61
+ l1Contracts: {
62
+ description: 'The deployed L1 contract addresses',
63
+ nested: l1ContractAddressesMapping,
64
+ },
83
65
  };
84
66
 
85
67
  /**
@@ -1,18 +1,25 @@
1
- import { Blob, BlobDeserializationError } from '@aztec/blob-lib';
1
+ import { BlobDeserializationError, SpongeBlob, getBlobFieldsInCheckpoint } from '@aztec/blob-lib';
2
2
  import type { BlobSinkClientInterface } from '@aztec/blob-sink/client';
3
- import type { EpochProofPublicInputArgs, ViemPublicClient } from '@aztec/ethereum';
3
+ import type {
4
+ EpochProofPublicInputArgs,
5
+ ViemClient,
6
+ ViemCommitteeAttestations,
7
+ ViemHeader,
8
+ ViemPublicClient,
9
+ ViemStateReference,
10
+ } from '@aztec/ethereum';
4
11
  import { asyncPool } from '@aztec/foundation/async-pool';
12
+ import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
5
13
  import type { EthAddress } from '@aztec/foundation/eth-address';
6
14
  import type { ViemSignature } from '@aztec/foundation/eth-signature';
7
15
  import { Fr } from '@aztec/foundation/fields';
8
16
  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';
17
+ import { type InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
18
+ import { Body, CommitteeAttestation, L2Block, L2BlockHeader, PublishedL2Block } from '@aztec/stdlib/block';
13
19
  import { Proof } from '@aztec/stdlib/proofs';
20
+ import { CheckpointHeader } from '@aztec/stdlib/rollup';
14
21
  import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
15
- import { BlockHeader } from '@aztec/stdlib/tx';
22
+ import { GlobalVariables, StateReference } from '@aztec/stdlib/tx';
16
23
 
17
24
  import {
18
25
  type GetContractEventsReturnType,
@@ -21,11 +28,80 @@ import {
21
28
  decodeFunctionData,
22
29
  getAbiItem,
23
30
  hexToBytes,
31
+ multicall3Abi,
24
32
  } from 'viem';
25
33
 
26
34
  import { NoBlobBodiesFoundError } from './errors.js';
27
35
  import type { DataRetrieval } from './structs/data_retrieval.js';
28
- import type { L1Published, L1PublishedData } from './structs/published.js';
36
+ import type { InboxMessage } from './structs/inbox_message.js';
37
+ import type { L1PublishedData } from './structs/published.js';
38
+
39
+ export type RetrievedL2Block = {
40
+ l2BlockNumber: number;
41
+ archiveRoot: Fr;
42
+ stateReference: StateReference;
43
+ header: CheckpointHeader;
44
+ blobFields: Fr[];
45
+ l1: L1PublishedData;
46
+ chainId: Fr;
47
+ version: Fr;
48
+ attestations: CommitteeAttestation[];
49
+ };
50
+
51
+ export async function retrievedBlockToPublishedL2Block(retrievedBlock: RetrievedL2Block): Promise<PublishedL2Block> {
52
+ const {
53
+ l2BlockNumber,
54
+ archiveRoot,
55
+ stateReference,
56
+ header: checkpointHeader,
57
+ blobFields,
58
+ l1,
59
+ chainId,
60
+ version,
61
+ attestations,
62
+ } = retrievedBlock;
63
+
64
+ const archive = new AppendOnlyTreeSnapshot(
65
+ archiveRoot,
66
+ l2BlockNumber + 1, // nextAvailableLeafIndex
67
+ );
68
+
69
+ const globalVariables = GlobalVariables.from({
70
+ chainId,
71
+ version,
72
+ blockNumber: l2BlockNumber,
73
+ slotNumber: checkpointHeader.slotNumber,
74
+ timestamp: checkpointHeader.timestamp,
75
+ coinbase: checkpointHeader.coinbase,
76
+ feeRecipient: checkpointHeader.feeRecipient,
77
+ gasFees: checkpointHeader.gasFees,
78
+ });
79
+
80
+ // TODO(#17027)
81
+ // This works when there's only one block in the checkpoint.
82
+ // If there's more than one block, we need to build the spongeBlob from the endSpongeBlob of the previous block.
83
+ const spongeBlob = await SpongeBlob.init(blobFields.length);
84
+ // Skip the first field which is the checkpoint prefix indicating the number of total blob fields in a checkpoint.
85
+ const blockBlobFields = blobFields.slice(1);
86
+ await spongeBlob.absorb(blockBlobFields);
87
+ const spongeBlobHash = await spongeBlob.squeeze();
88
+
89
+ const body = Body.fromBlobFields(blockBlobFields);
90
+
91
+ const header = L2BlockHeader.from({
92
+ lastArchive: new AppendOnlyTreeSnapshot(checkpointHeader.lastArchiveRoot, l2BlockNumber),
93
+ contentCommitment: checkpointHeader.contentCommitment,
94
+ state: stateReference,
95
+ globalVariables,
96
+ totalFees: body.txEffects.reduce((accum, txEffect) => accum.add(txEffect.transactionFee), Fr.ZERO),
97
+ totalManaUsed: checkpointHeader.totalManaUsed,
98
+ spongeBlobHash,
99
+ });
100
+
101
+ const block = new L2Block(archive, header, body);
102
+
103
+ return PublishedL2Block.fromFields({ block, l1, attestations });
104
+ }
29
105
 
30
106
  /**
31
107
  * Fetches new L2 blocks.
@@ -43,8 +119,11 @@ export async function retrieveBlocksFromRollup(
43
119
  searchStartBlock: bigint,
44
120
  searchEndBlock: bigint,
45
121
  logger: Logger = createLogger('archiver'),
46
- ): Promise<L1Published<L2Block>[]> {
47
- const retrievedBlocks: L1Published<L2Block>[] = [];
122
+ ): Promise<RetrievedL2Block[]> {
123
+ const retrievedBlocks: RetrievedL2Block[] = [];
124
+
125
+ let rollupConstants: { chainId: Fr; version: Fr; targetCommitteeSize: number } | undefined;
126
+
48
127
  do {
49
128
  if (searchStartBlock > searchEndBlock) {
50
129
  break;
@@ -68,11 +147,25 @@ export async function retrieveBlocksFromRollup(
68
147
  `Got ${l2BlockProposedLogs.length} L2 block processed logs for L2 blocks ${l2BlockProposedLogs[0].args.blockNumber}-${lastLog.args.blockNumber} between L1 blocks ${searchStartBlock}-${searchEndBlock}`,
69
148
  );
70
149
 
150
+ if (rollupConstants === undefined) {
151
+ const [chainId, version, targetCommitteeSize] = await Promise.all([
152
+ publicClient.getChainId(),
153
+ rollup.read.getVersion(),
154
+ rollup.read.getTargetCommitteeSize(),
155
+ ]);
156
+ rollupConstants = {
157
+ chainId: new Fr(chainId),
158
+ version: new Fr(version),
159
+ targetCommitteeSize: Number(targetCommitteeSize),
160
+ };
161
+ }
162
+
71
163
  const newBlocks = await processL2BlockProposedLogs(
72
164
  rollup,
73
165
  publicClient,
74
166
  blobSinkClient,
75
167
  l2BlockProposedLogs,
168
+ rollupConstants,
76
169
  logger,
77
170
  );
78
171
  retrievedBlocks.push(...newBlocks);
@@ -90,18 +183,19 @@ export async function retrieveBlocksFromRollup(
90
183
  * @param logs - L2BlockProposed logs.
91
184
  * @returns - An array blocks.
92
185
  */
93
- export async function processL2BlockProposedLogs(
186
+ async function processL2BlockProposedLogs(
94
187
  rollup: GetContractReturnType<typeof RollupAbi, ViemPublicClient>,
95
188
  publicClient: ViemPublicClient,
96
189
  blobSinkClient: BlobSinkClientInterface,
97
190
  logs: GetContractEventsReturnType<typeof RollupAbi, 'L2BlockProposed'>,
191
+ { chainId, version, targetCommitteeSize }: { chainId: Fr; version: Fr; targetCommitteeSize: number },
98
192
  logger: Logger,
99
- ): Promise<L1Published<L2Block>[]> {
100
- const retrievedBlocks: L1Published<L2Block>[] = [];
193
+ ): Promise<RetrievedL2Block[]> {
194
+ const retrievedBlocks: RetrievedL2Block[] = [];
101
195
  await asyncPool(10, logs, async log => {
102
- const l2BlockNumber = log.args.blockNumber!;
196
+ const l2BlockNumber = Number(log.args.blockNumber!);
103
197
  const archive = log.args.archive!;
104
- const archiveFromChain = await rollup.read.archiveAt([l2BlockNumber]);
198
+ const archiveFromChain = await rollup.read.archiveAt([BigInt(l2BlockNumber)]);
105
199
  const blobHashes = log.args.versionedBlobHashes!.map(blobHash => Buffer.from(blobHash.slice(2), 'hex'));
106
200
 
107
201
  // The value from the event and contract will match only if the block is in the chain.
@@ -113,6 +207,7 @@ export async function processL2BlockProposedLogs(
113
207
  blobHashes,
114
208
  l2BlockNumber,
115
209
  rollup.address,
210
+ targetCommitteeSize,
116
211
  logger,
117
212
  );
118
213
 
@@ -122,7 +217,13 @@ export async function processL2BlockProposedLogs(
122
217
  timestamp: await getL1BlockTime(publicClient, log.blockNumber),
123
218
  };
124
219
 
125
- retrievedBlocks.push({ data: block, l1 });
220
+ retrievedBlocks.push({ ...block, l1, chainId, version });
221
+ logger.trace(`Retrieved L2 block ${l2BlockNumber} from L1 tx ${log.transactionHash}`, {
222
+ l1BlockNumber: log.blockNumber,
223
+ l2BlockNumber,
224
+ archive: archive.toString(),
225
+ attestations: block.attestations,
226
+ });
126
227
  } else {
127
228
  logger.warn(`Ignoring L2 block ${l2BlockNumber} due to archive root mismatch`, {
128
229
  actual: archive,
@@ -140,37 +241,36 @@ export async function getL1BlockTime(publicClient: ViemPublicClient, blockNumber
140
241
  }
141
242
 
142
243
  /**
143
- * Extracts the first 'propose' method calldata from a forwarder transaction's data.
144
- * @param forwarderData - The forwarder transaction input data
244
+ * Extracts the first 'propose' method calldata from a multicall3 transaction's data.
245
+ * @param multicall3Data - The multicall3 transaction input data
145
246
  * @param rollupAddress - The address of the rollup contract
146
247
  * @returns The calldata for the first 'propose' method call to the rollup contract
147
248
  */
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,
249
+ function extractRollupProposeCalldata(multicall3Data: Hex, rollupAddress: Hex): Hex {
250
+ const { functionName: multicall3FunctionName, args: multicall3Args } = decodeFunctionData({
251
+ abi: multicall3Abi,
252
+ data: multicall3Data,
153
253
  });
154
254
 
155
- if (forwarderFunctionName !== 'forward') {
156
- throw new Error(`Unexpected forwarder method called ${forwarderFunctionName}`);
255
+ if (multicall3FunctionName !== 'aggregate3') {
256
+ throw new Error(`Unexpected multicall3 method called ${multicall3FunctionName}`);
157
257
  }
158
258
 
159
- if (forwarderArgs.length !== 2) {
160
- throw new Error(`Unexpected number of arguments for forwarder`);
259
+ if (multicall3Args.length !== 1) {
260
+ throw new Error(`Unexpected number of arguments for multicall3`);
161
261
  }
162
262
 
163
- const [to, data] = forwarderArgs;
263
+ const [calls] = multicall3Args;
164
264
 
165
265
  // Find all rollup calls
166
266
  const rollupAddressLower = rollupAddress.toLowerCase();
167
267
 
168
- for (let i = 0; i < to.length; i++) {
169
- const addr = to[i];
268
+ for (let i = 0; i < calls.length; i++) {
269
+ const addr = calls[i].target;
170
270
  if (addr.toLowerCase() !== rollupAddressLower) {
171
271
  continue;
172
272
  }
173
- const callData = data[i];
273
+ const callData = calls[i].callData;
174
274
 
175
275
  try {
176
276
  const { functionName: rollupFunctionName } = decodeFunctionData({
@@ -181,13 +281,13 @@ function extractRollupProposeCalldata(forwarderData: Hex, rollupAddress: Hex): H
181
281
  if (rollupFunctionName === 'propose') {
182
282
  return callData;
183
283
  }
184
- } catch (err) {
284
+ } catch {
185
285
  // Skip invalid function data
186
286
  continue;
187
287
  }
188
288
  }
189
289
 
190
- throw new Error(`Rollup address not found in forwarder args`);
290
+ throw new Error(`Rollup address not found in multicall3 args`);
191
291
  }
192
292
 
193
293
  /**
@@ -196,18 +296,19 @@ function extractRollupProposeCalldata(forwarderData: Hex, rollupAddress: Hex): H
196
296
  * TODO: Add retries and error management.
197
297
  * @param publicClient - The viem public client to use for transaction retrieval.
198
298
  * @param txHash - Hash of the tx that published it.
199
- * @param l2BlockNum - L2 block number.
299
+ * @param l2BlockNumber - L2 block number.
200
300
  * @returns L2 block from the calldata, deserialized
201
301
  */
202
302
  async function getBlockFromRollupTx(
203
303
  publicClient: ViemPublicClient,
204
304
  blobSinkClient: BlobSinkClientInterface,
205
305
  txHash: `0x${string}`,
206
- blobHashes: Buffer[], // WORKTODO(md): buffer32?
207
- l2BlockNum: bigint,
306
+ blobHashes: Buffer[], // TODO(md): buffer32?
307
+ l2BlockNumber: number,
208
308
  rollupAddress: Hex,
309
+ targetCommitteeSize: number,
209
310
  logger: Logger,
210
- ): Promise<L2Block> {
311
+ ): Promise<Omit<RetrievedL2Block, 'l1' | 'chainId' | 'version'>> {
211
312
  const { input: forwarderData, blockHash } = await publicClient.getTransaction({ hash: txHash });
212
313
 
213
314
  const rollupData = extractRollupProposeCalldata(forwarderData, rollupAddress);
@@ -220,29 +321,48 @@ async function getBlockFromRollupTx(
220
321
  throw new Error(`Unexpected rollup method called ${rollupFunctionName}`);
221
322
  }
222
323
 
223
- const [decodedArgs, ,] = rollupArgs! as readonly [
324
+ const [decodedArgs, packedAttestations, _signers, _blobInput] = rollupArgs! as readonly [
224
325
  {
225
- header: Hex;
226
326
  archive: Hex;
227
- blockHash: Hex;
327
+ stateReference: ViemStateReference;
228
328
  oracleInput: {
229
329
  feeAssetPriceModifier: bigint;
230
330
  };
231
- txHashes: Hex[];
331
+ header: ViemHeader;
332
+ txHashes: readonly Hex[];
232
333
  },
233
- ViemSignature[],
334
+ ViemCommitteeAttestations,
335
+ Hex[],
336
+ ViemSignature,
234
337
  Hex,
235
338
  ];
236
339
 
237
- const header = BlockHeader.fromBuffer(Buffer.from(hexToBytes(decodedArgs.header)));
340
+ const attestations = CommitteeAttestation.fromPacked(packedAttestations, targetCommitteeSize);
341
+
342
+ logger.trace(`Recovered propose calldata from tx ${txHash}`, {
343
+ l2BlockNumber,
344
+ archive: decodedArgs.archive,
345
+ stateReference: decodedArgs.stateReference,
346
+ header: decodedArgs.header,
347
+ blobHashes,
348
+ attestations,
349
+ packedAttestations,
350
+ targetCommitteeSize,
351
+ });
352
+
353
+ const header = CheckpointHeader.fromViem(decodedArgs.header);
238
354
  const blobBodies = await blobSinkClient.getBlobSidecar(blockHash, blobHashes);
239
355
  if (blobBodies.length === 0) {
240
- throw new NoBlobBodiesFoundError(Number(l2BlockNum));
356
+ throw new NoBlobBodiesFoundError(l2BlockNumber);
241
357
  }
242
358
 
243
- let blockFields: Fr[];
359
+ let blobFields: Fr[];
244
360
  try {
245
- blockFields = Blob.toEncodedFields(blobBodies);
361
+ // Get the fields that were actually added in the checkpoint. And check the encoding of the fields.
362
+ blobFields = getBlobFieldsInCheckpoint(
363
+ blobBodies.map(b => b.blob),
364
+ true /* checkEncoding */,
365
+ );
246
366
  } catch (err: any) {
247
367
  if (err instanceof BlobDeserializationError) {
248
368
  logger.fatal(err.message);
@@ -252,23 +372,31 @@ async function getBlockFromRollupTx(
252
372
  throw err;
253
373
  }
254
374
 
255
- // The blob source gives us blockFields, and we must construct the body from them:
256
- const blockBody = Body.fromBlobFields(blockFields);
375
+ const archiveRoot = new Fr(Buffer.from(hexToBytes(decodedArgs.archive)));
257
376
 
258
- const blockNumberFromHeader = header.globalVariables.blockNumber.toBigInt();
377
+ const stateReference = StateReference.fromViem(decodedArgs.stateReference);
259
378
 
260
- if (blockNumberFromHeader !== l2BlockNum) {
261
- throw new Error(`Block number mismatch: expected ${l2BlockNum} but got ${blockNumberFromHeader}`);
262
- }
263
-
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
- );
379
+ return {
380
+ l2BlockNumber,
381
+ archiveRoot,
382
+ stateReference,
383
+ header,
384
+ blobFields,
385
+ attestations,
386
+ };
387
+ }
270
388
 
271
- return new L2Block(archive, header, blockBody);
389
+ /** Given an L1 to L2 message, retrieves its corresponding event from the Inbox within a specific block range. */
390
+ export async function retrieveL1ToL2Message(
391
+ inbox: GetContractReturnType<typeof InboxAbi, ViemClient>,
392
+ leaf: Fr,
393
+ fromBlock: bigint,
394
+ toBlock: bigint,
395
+ ): Promise<InboxMessage | undefined> {
396
+ const logs = await inbox.getEvents.MessageSent({ hash: leaf.toString() }, { fromBlock, toBlock });
397
+
398
+ const messages = mapLogsInboxMessage(logs);
399
+ return messages.length > 0 ? messages[0] : undefined;
272
400
  }
273
401
 
274
402
  /**
@@ -281,39 +409,39 @@ async function getBlockFromRollupTx(
281
409
  * @returns An array of InboxLeaf and next eth block to search from.
282
410
  */
283
411
  export async function retrieveL1ToL2Messages(
284
- inbox: GetContractReturnType<typeof InboxAbi, ViemPublicClient>,
412
+ inbox: GetContractReturnType<typeof InboxAbi, ViemClient>,
285
413
  searchStartBlock: bigint,
286
414
  searchEndBlock: bigint,
287
- ): Promise<DataRetrieval<InboxLeaf>> {
288
- const retrievedL1ToL2Messages: InboxLeaf[] = [];
289
- do {
290
- if (searchStartBlock > searchEndBlock) {
291
- break;
292
- }
293
-
415
+ ): Promise<InboxMessage[]> {
416
+ const retrievedL1ToL2Messages: InboxMessage[] = [];
417
+ while (searchStartBlock <= searchEndBlock) {
294
418
  const messageSentLogs = (
295
- await inbox.getEvents.MessageSent(
296
- {},
297
- {
298
- fromBlock: searchStartBlock,
299
- toBlock: searchEndBlock,
300
- },
301
- )
419
+ await inbox.getEvents.MessageSent({}, { fromBlock: searchStartBlock, toBlock: searchEndBlock })
302
420
  ).filter(log => log.blockNumber! >= searchStartBlock && log.blockNumber! <= searchEndBlock);
303
421
 
304
422
  if (messageSentLogs.length === 0) {
305
423
  break;
306
424
  }
307
425
 
308
- for (const log of messageSentLogs) {
309
- const { index, hash } = log.args;
310
- retrievedL1ToL2Messages.push(new InboxLeaf(index!, Fr.fromHexString(hash!)));
311
- }
426
+ retrievedL1ToL2Messages.push(...mapLogsInboxMessage(messageSentLogs));
427
+ searchStartBlock = messageSentLogs.at(-1)!.blockNumber + 1n;
428
+ }
312
429
 
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 };
430
+ return retrievedL1ToL2Messages;
431
+ }
432
+
433
+ function mapLogsInboxMessage(logs: GetContractEventsReturnType<typeof InboxAbi, 'MessageSent'>): InboxMessage[] {
434
+ return logs.map(log => {
435
+ const { index, hash, l2BlockNumber, rollingHash } = log.args;
436
+ return {
437
+ index: index!,
438
+ leaf: Fr.fromHexString(hash!),
439
+ l1BlockNumber: log.blockNumber,
440
+ l1BlockHash: Buffer32.fromString(log.blockHash),
441
+ l2BlockNumber: Number(l2BlockNumber!),
442
+ rollingHash: Buffer16.fromString(rollingHash!),
443
+ };
444
+ });
317
445
  }
318
446
 
319
447
  /** Retrieves L2ProofVerified events from the rollup contract. */
@@ -322,7 +450,7 @@ export async function retrieveL2ProofVerifiedEvents(
322
450
  rollupAddress: EthAddress,
323
451
  searchStartBlock: bigint,
324
452
  searchEndBlock?: bigint,
325
- ): Promise<{ l1BlockNumber: bigint; l2BlockNumber: bigint; proverId: Fr; txHash: Hex }[]> {
453
+ ): Promise<{ l1BlockNumber: bigint; l2BlockNumber: number; proverId: Fr; txHash: Hex }[]> {
326
454
  const logs = await publicClient.getLogs({
327
455
  address: rollupAddress.toString(),
328
456
  fromBlock: searchStartBlock,
@@ -333,7 +461,7 @@ export async function retrieveL2ProofVerifiedEvents(
333
461
 
334
462
  return logs.map(log => ({
335
463
  l1BlockNumber: log.blockNumber,
336
- l2BlockNumber: log.args.blockNumber,
464
+ l2BlockNumber: Number(log.args.blockNumber),
337
465
  proverId: Fr.fromHexString(log.args.proverId),
338
466
  txHash: log.transactionHash,
339
467
  }));
@@ -345,9 +473,9 @@ export async function retrieveL2ProofsFromRollup(
345
473
  rollupAddress: EthAddress,
346
474
  searchStartBlock: bigint,
347
475
  searchEndBlock?: bigint,
348
- ): Promise<DataRetrieval<{ proof: Proof; proverId: Fr; l2BlockNumber: bigint; txHash: `0x${string}` }>> {
476
+ ): Promise<DataRetrieval<{ proof: Proof; proverId: Fr; l2BlockNumber: number; txHash: `0x${string}` }>> {
349
477
  const logs = await retrieveL2ProofVerifiedEvents(publicClient, rollupAddress, searchStartBlock, searchEndBlock);
350
- const retrievedData: { proof: Proof; proverId: Fr; l2BlockNumber: bigint; txHash: `0x${string}` }[] = [];
478
+ const retrievedData: { proof: Proof; proverId: Fr; l2BlockNumber: number; txHash: `0x${string}` }[] = [];
351
479
  const lastProcessedL1BlockNumber = logs.length > 0 ? logs.at(-1)!.l1BlockNumber : searchStartBlock - 1n;
352
480
 
353
481
  for (const { txHash, proverId, l2BlockNumber } of logs) {
@@ -363,7 +491,6 @@ export async function retrieveL2ProofsFromRollup(
363
491
  export type SubmitBlockProof = {
364
492
  archiveRoot: Fr;
365
493
  proverId: Fr;
366
- aggregationObject: Buffer;
367
494
  proof: Proof;
368
495
  };
369
496
 
@@ -386,7 +513,6 @@ export async function getProofFromSubmitProofTx(
386
513
 
387
514
  let proverId: Fr;
388
515
  let archiveRoot: Fr;
389
- let aggregationObject: Buffer;
390
516
  let proof: Proof;
391
517
 
392
518
  if (functionName === 'submitEpochRootProof') {
@@ -396,12 +522,10 @@ export async function getProofFromSubmitProofTx(
396
522
  end: bigint;
397
523
  args: EpochProofPublicInputArgs;
398
524
  fees: readonly Hex[];
399
- aggregationObject: Hex;
400
525
  proof: Hex;
401
526
  },
402
527
  ];
403
528
 
404
- aggregationObject = Buffer.from(hexToBytes(decodedArgs.aggregationObject));
405
529
  proverId = Fr.fromHexString(decodedArgs.args.proverId);
406
530
  archiveRoot = Fr.fromHexString(decodedArgs.args.endArchive);
407
531
  proof = Proof.fromBuffer(Buffer.from(hexToBytes(decodedArgs.proof)));
@@ -415,7 +539,6 @@ export async function getProofFromSubmitProofTx(
415
539
 
416
540
  return {
417
541
  proverId,
418
- aggregationObject,
419
542
  archiveRoot,
420
543
  proof,
421
544
  };
@@ -3,3 +3,24 @@ export class NoBlobBodiesFoundError extends Error {
3
3
  super(`No blob bodies found for block ${l2BlockNum}`);
4
4
  }
5
5
  }
6
+
7
+ export class InitialBlockNumberNotSequentialError extends Error {
8
+ constructor(
9
+ public readonly newBlockNumber: number,
10
+ public readonly previousBlockNumber: number | undefined,
11
+ ) {
12
+ super(
13
+ `Cannot insert new block ${newBlockNumber} given previous block number in store is ${
14
+ previousBlockNumber ?? 'undefined'
15
+ }`,
16
+ );
17
+ }
18
+ }
19
+
20
+ export class BlockNumberNotSequentialError extends Error {
21
+ constructor(newBlockNumber: number, previous: number | undefined) {
22
+ super(
23
+ `Cannot insert new block ${newBlockNumber} given previous block number in batch is ${previous ?? 'undefined'}`,
24
+ );
25
+ }
26
+ }
@@ -1,7 +1,6 @@
1
1
  export * from './archiver.js';
2
2
  export * from './config.js';
3
- export { type L1Published, type L1PublishedData } from './structs/published.js';
4
- export { MemoryArchiverStore } from './memory_archiver_store/memory_archiver_store.js';
3
+ export { type PublishedL2Block, type L1PublishedData } from './structs/published.js';
5
4
  export type { ArchiverDataStore } from './archiver_store.js';
6
- export { KVArchiverDataStore } from './kv_archiver_store/kv_archiver_store.js';
5
+ export { KVArchiverDataStore, ARCHIVER_DB_VERSION } from './kv_archiver_store/kv_archiver_store.js';
7
6
  export { ContractInstanceStore } from './kv_archiver_store/contract_instance_store.js';