@aztec/archiver 0.0.1-commit.ef17749e1 → 0.0.1-commit.f1b29a41e

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 (81) hide show
  1. package/dest/archiver.d.ts +5 -7
  2. package/dest/archiver.d.ts.map +1 -1
  3. package/dest/archiver.js +54 -18
  4. package/dest/config.d.ts +3 -3
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +2 -1
  7. package/dest/errors.d.ts +34 -10
  8. package/dest/errors.d.ts.map +1 -1
  9. package/dest/errors.js +45 -16
  10. package/dest/factory.d.ts +3 -4
  11. package/dest/factory.d.ts.map +1 -1
  12. package/dest/factory.js +19 -18
  13. package/dest/l1/calldata_retriever.d.ts +1 -1
  14. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  15. package/dest/l1/calldata_retriever.js +2 -1
  16. package/dest/modules/data_source_base.d.ts +8 -6
  17. package/dest/modules/data_source_base.d.ts.map +1 -1
  18. package/dest/modules/data_source_base.js +11 -5
  19. package/dest/modules/data_store_updater.d.ts +14 -11
  20. package/dest/modules/data_store_updater.d.ts.map +1 -1
  21. package/dest/modules/data_store_updater.js +78 -76
  22. package/dest/modules/l1_synchronizer.d.ts +2 -2
  23. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  24. package/dest/modules/l1_synchronizer.js +57 -24
  25. package/dest/modules/validation.d.ts +1 -1
  26. package/dest/modules/validation.d.ts.map +1 -1
  27. package/dest/modules/validation.js +2 -2
  28. package/dest/store/block_store.d.ts +49 -16
  29. package/dest/store/block_store.d.ts.map +1 -1
  30. package/dest/store/block_store.js +243 -118
  31. package/dest/store/contract_class_store.d.ts +2 -3
  32. package/dest/store/contract_class_store.d.ts.map +1 -1
  33. package/dest/store/contract_class_store.js +7 -67
  34. package/dest/store/contract_instance_store.d.ts +1 -1
  35. package/dest/store/contract_instance_store.d.ts.map +1 -1
  36. package/dest/store/contract_instance_store.js +6 -2
  37. package/dest/store/kv_archiver_store.d.ts +46 -19
  38. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  39. package/dest/store/kv_archiver_store.js +57 -22
  40. package/dest/store/l2_tips_cache.d.ts +2 -1
  41. package/dest/store/l2_tips_cache.d.ts.map +1 -1
  42. package/dest/store/l2_tips_cache.js +25 -5
  43. package/dest/store/log_store.d.ts +6 -3
  44. package/dest/store/log_store.d.ts.map +1 -1
  45. package/dest/store/log_store.js +93 -16
  46. package/dest/store/message_store.d.ts +5 -1
  47. package/dest/store/message_store.d.ts.map +1 -1
  48. package/dest/store/message_store.js +13 -0
  49. package/dest/test/fake_l1_state.d.ts +8 -1
  50. package/dest/test/fake_l1_state.d.ts.map +1 -1
  51. package/dest/test/fake_l1_state.js +39 -5
  52. package/dest/test/mock_l1_to_l2_message_source.d.ts +1 -1
  53. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  54. package/dest/test/mock_l1_to_l2_message_source.js +2 -1
  55. package/dest/test/mock_l2_block_source.d.ts +9 -4
  56. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  57. package/dest/test/mock_l2_block_source.js +32 -7
  58. package/dest/test/noop_l1_archiver.d.ts +4 -1
  59. package/dest/test/noop_l1_archiver.d.ts.map +1 -1
  60. package/dest/test/noop_l1_archiver.js +5 -1
  61. package/package.json +13 -13
  62. package/src/archiver.ts +58 -20
  63. package/src/config.ts +8 -1
  64. package/src/errors.ts +70 -26
  65. package/src/factory.ts +19 -14
  66. package/src/l1/calldata_retriever.ts +2 -1
  67. package/src/modules/data_source_base.ts +26 -7
  68. package/src/modules/data_store_updater.ts +91 -107
  69. package/src/modules/l1_synchronizer.ts +65 -31
  70. package/src/modules/validation.ts +2 -2
  71. package/src/store/block_store.ts +312 -138
  72. package/src/store/contract_class_store.ts +8 -106
  73. package/src/store/contract_instance_store.ts +8 -5
  74. package/src/store/kv_archiver_store.ts +83 -34
  75. package/src/store/l2_tips_cache.ts +50 -11
  76. package/src/store/log_store.ts +126 -27
  77. package/src/store/message_store.ts +19 -0
  78. package/src/test/fake_l1_state.ts +50 -9
  79. package/src/test/mock_l1_to_l2_message_source.ts +1 -0
  80. package/src/test/mock_l2_block_source.ts +46 -5
  81. package/src/test/noop_l1_archiver.ts +7 -1
package/src/factory.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { EpochCache } from '@aztec/epoch-cache';
2
2
  import { createEthereumChain } from '@aztec/ethereum/chain';
3
+ import { makeL1HttpTransport } from '@aztec/ethereum/client';
3
4
  import { InboxContract, RollupContract } from '@aztec/ethereum/contracts';
4
5
  import type { ViemPublicDebugClient } from '@aztec/ethereum/types';
5
6
  import { BlockNumber } from '@aztec/foundation/branded-types';
@@ -12,13 +13,12 @@ import { protocolContractNames } from '@aztec/protocol-contracts';
12
13
  import { BundledProtocolContractsProvider } from '@aztec/protocol-contracts/providers/bundle';
13
14
  import { FunctionType, decodeFunctionSignature } from '@aztec/stdlib/abi';
14
15
  import type { ArchiverEmitter } from '@aztec/stdlib/block';
15
- import { type ContractClassPublic, computePublicBytecodeCommitment } from '@aztec/stdlib/contract';
16
- import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
16
+ import { type ContractClassPublicWithCommitment, computePublicBytecodeCommitment } from '@aztec/stdlib/contract';
17
17
  import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
18
18
  import { getTelemetryClient } from '@aztec/telemetry-client';
19
19
 
20
20
  import { EventEmitter } from 'events';
21
- import { createPublicClient, fallback, http } from 'viem';
21
+ import { createPublicClient } from 'viem';
22
22
 
23
23
  import { Archiver, type ArchiverDeps } from './archiver.js';
24
24
  import { type ArchiverConfig, mapArchiverConfig } from './config.js';
@@ -32,14 +32,13 @@ export const ARCHIVER_STORE_NAME = 'archiver';
32
32
  /** Creates an archiver store. */
33
33
  export async function createArchiverStore(
34
34
  userConfig: Pick<ArchiverConfig, 'archiverStoreMapSizeKb' | 'maxLogs'> & DataStoreConfig,
35
- l1Constants: Pick<L1RollupConstants, 'epochDuration'>,
36
35
  ) {
37
36
  const config = {
38
37
  ...userConfig,
39
38
  dataStoreMapSizeKb: userConfig.archiverStoreMapSizeKb ?? userConfig.dataStoreMapSizeKb,
40
39
  };
41
40
  const store = await createStore(ARCHIVER_STORE_NAME, ARCHIVER_DB_VERSION, config);
42
- return new KVArchiverDataStore(store, config.maxLogs, l1Constants);
41
+ return new KVArchiverDataStore(store, config.maxLogs);
43
42
  }
44
43
 
45
44
  /**
@@ -54,14 +53,15 @@ export async function createArchiver(
54
53
  deps: ArchiverDeps,
55
54
  opts: { blockUntilSync: boolean } = { blockUntilSync: true },
56
55
  ): Promise<Archiver> {
57
- const archiverStore = await createArchiverStore(config, { epochDuration: config.aztecEpochDuration });
56
+ const archiverStore = await createArchiverStore(config);
58
57
  await registerProtocolContracts(archiverStore);
59
58
 
60
59
  // Create Ethereum clients
61
60
  const chain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
61
+ const httpTimeout = config.l1HttpTimeoutMS;
62
62
  const publicClient = createPublicClient({
63
63
  chain: chain.chainInfo,
64
- transport: fallback(config.l1RpcUrls.map(url => http(url, { batch: false }))),
64
+ transport: makeL1HttpTransport(config.l1RpcUrls, { timeout: httpTimeout }),
65
65
  pollingInterval: config.viemPollingIntervalMS,
66
66
  });
67
67
 
@@ -69,7 +69,7 @@ export async function createArchiver(
69
69
  const debugRpcUrls = config.l1DebugRpcUrls.length > 0 ? config.l1DebugRpcUrls : config.l1RpcUrls;
70
70
  const debugClient = createPublicClient({
71
71
  chain: chain.chainInfo,
72
- transport: fallback(debugRpcUrls.map(url => http(url, { batch: false }))),
72
+ transport: makeL1HttpTransport(debugRpcUrls, { timeout: httpTimeout }),
73
73
  pollingInterval: config.viemPollingIntervalMS,
74
74
  }) as ViemPublicDebugClient;
75
75
 
@@ -173,16 +173,22 @@ export async function createArchiver(
173
173
  return archiver;
174
174
  }
175
175
 
176
- /** Registers protocol contracts in the archiver store. */
176
+ /** Registers protocol contracts in the archiver store. Idempotent — skips contracts that already exist (e.g. on node restart). */
177
177
  export async function registerProtocolContracts(store: KVArchiverDataStore) {
178
178
  const blockNumber = 0;
179
179
  for (const name of protocolContractNames) {
180
180
  const provider = new BundledProtocolContractsProvider();
181
181
  const contract = await provider.getProtocolContractArtifact(name);
182
- const contractClassPublic: ContractClassPublic = {
182
+
183
+ // Skip if already registered (happens on node restart with a persisted store).
184
+ if (await store.getContractClass(contract.contractClass.id)) {
185
+ continue;
186
+ }
187
+
188
+ const publicBytecodeCommitment = await computePublicBytecodeCommitment(contract.contractClass.packedBytecode);
189
+ const contractClassPublic: ContractClassPublicWithCommitment = {
183
190
  ...contract.contractClass,
184
- privateFunctions: [],
185
- utilityFunctions: [],
191
+ publicBytecodeCommitment,
186
192
  };
187
193
 
188
194
  const publicFunctionSignatures = contract.artifact.functions
@@ -190,8 +196,7 @@ export async function registerProtocolContracts(store: KVArchiverDataStore) {
190
196
  .map(fn => decodeFunctionSignature(fn.name, fn.parameters));
191
197
 
192
198
  await store.registerContractFunctionSignatures(publicFunctionSignatures);
193
- const bytecodeCommitment = await computePublicBytecodeCommitment(contractClassPublic.packedBytecode);
194
- await store.addContractClasses([contractClassPublic], [bytecodeCommitment], BlockNumber(blockNumber));
199
+ await store.addContractClasses([contractClassPublic], BlockNumber(blockNumber));
195
200
  await store.addContractInstances([contract.instance], BlockNumber(blockNumber));
196
201
  }
197
202
  }
@@ -1,6 +1,7 @@
1
1
  import { MULTI_CALL_3_ADDRESS, type ViemCommitteeAttestations, type ViemHeader } from '@aztec/ethereum/contracts';
2
2
  import type { ViemPublicClient, ViemPublicDebugClient } from '@aztec/ethereum/types';
3
3
  import { CheckpointNumber } from '@aztec/foundation/branded-types';
4
+ import { LruSet } from '@aztec/foundation/collection';
4
5
  import { Fr } from '@aztec/foundation/curves/bn254';
5
6
  import { EthAddress } from '@aztec/foundation/eth-address';
6
7
  import type { Logger } from '@aztec/foundation/log';
@@ -44,7 +45,7 @@ type CheckpointData = {
44
45
  */
45
46
  export class CalldataRetriever {
46
47
  /** Tx hashes we've already logged for trace+debug failure (log once per tx per process). */
47
- private static readonly traceFailureWarnedTxHashes = new Set<string>();
48
+ private static readonly traceFailureWarnedTxHashes = new LruSet<string>(1000);
48
49
 
49
50
  /** Clears the trace-failure warned set. For testing only. */
50
51
  static resetTraceFailureWarnedForTesting(): void {
@@ -6,7 +6,13 @@ import { isDefined } from '@aztec/foundation/types';
6
6
  import type { FunctionSelector } from '@aztec/stdlib/abi';
7
7
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
8
8
  import { type BlockData, type BlockHash, CheckpointedL2Block, L2Block, type L2Tips } from '@aztec/stdlib/block';
9
- import { Checkpoint, type CheckpointData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
9
+ import {
10
+ Checkpoint,
11
+ type CheckpointData,
12
+ type CommonCheckpointData,
13
+ type ProposedCheckpointData,
14
+ PublishedCheckpoint,
15
+ } from '@aztec/stdlib/checkpoint';
10
16
  import type { ContractClassPublic, ContractDataSource, ContractInstanceWithAddress } from '@aztec/stdlib/contract';
11
17
  import { type L1RollupConstants, getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
12
18
  import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
@@ -46,9 +52,9 @@ export abstract class ArchiverDataSourceBase
46
52
 
47
53
  abstract getL2Tips(): Promise<L2Tips>;
48
54
 
49
- abstract getL2SlotNumber(): Promise<SlotNumber | undefined>;
55
+ abstract getSyncedL2SlotNumber(): Promise<SlotNumber | undefined>;
50
56
 
51
- abstract getL2EpochNumber(): Promise<EpochNumber | undefined>;
57
+ abstract getSyncedL2EpochNumber(): Promise<EpochNumber | undefined>;
52
58
 
53
59
  abstract isEpochComplete(epochNumber: EpochNumber): Promise<boolean>;
54
60
 
@@ -154,7 +160,15 @@ export abstract class ArchiverDataSourceBase
154
160
  }
155
161
 
156
162
  public getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
157
- return this.store.getSettledTxReceipt(txHash);
163
+ return this.store.getSettledTxReceipt(txHash, this.l1Constants);
164
+ }
165
+
166
+ public getProposedCheckpoint(): Promise<CommonCheckpointData | undefined> {
167
+ return this.store.getProposedCheckpoint();
168
+ }
169
+
170
+ public getProposedCheckpointOnly(): Promise<ProposedCheckpointData | undefined> {
171
+ return this.store.getProposedCheckpointOnly();
158
172
  }
159
173
 
160
174
  public isPendingChainInvalid(): Promise<boolean> {
@@ -165,16 +179,21 @@ export abstract class ArchiverDataSourceBase
165
179
  return (await this.store.getPendingChainValidationStatus()) ?? { valid: true };
166
180
  }
167
181
 
168
- public getPrivateLogsByTags(tags: SiloedTag[], page?: number): Promise<TxScopedL2Log[][]> {
169
- return this.store.getPrivateLogsByTags(tags, page);
182
+ public getPrivateLogsByTags(
183
+ tags: SiloedTag[],
184
+ page?: number,
185
+ upToBlockNumber?: BlockNumber,
186
+ ): Promise<TxScopedL2Log[][]> {
187
+ return this.store.getPrivateLogsByTags(tags, page, upToBlockNumber);
170
188
  }
171
189
 
172
190
  public getPublicLogsByTagsFromContract(
173
191
  contractAddress: AztecAddress,
174
192
  tags: Tag[],
175
193
  page?: number,
194
+ upToBlockNumber?: BlockNumber,
176
195
  ): Promise<TxScopedL2Log[][]> {
177
- return this.store.getPublicLogsByTagsFromContract(contractAddress, tags, page);
196
+ return this.store.getPublicLogsByTagsFromContract(contractAddress, tags, page, upToBlockNumber);
178
197
  }
179
198
 
180
199
  public getPublicLogs(filter: LogFilter): Promise<GetPublicLogsResponse> {
@@ -1,29 +1,21 @@
1
1
  import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
2
- import { Fr } from '@aztec/foundation/curves/bn254';
2
+ import { filterAsync } from '@aztec/foundation/collection';
3
3
  import { createLogger } from '@aztec/foundation/log';
4
- import {
5
- ContractClassPublishedEvent,
6
- PrivateFunctionBroadcastedEvent,
7
- UtilityFunctionBroadcastedEvent,
8
- } from '@aztec/protocol-contracts/class-registry';
4
+ import { ContractClassPublishedEvent } from '@aztec/protocol-contracts/class-registry';
9
5
  import {
10
6
  ContractInstancePublishedEvent,
11
7
  ContractInstanceUpdatedEvent,
12
8
  } from '@aztec/protocol-contracts/instance-registry';
13
9
  import type { L2Block, ValidateCheckpointResult } from '@aztec/stdlib/block';
14
- import { type PublishedCheckpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
10
+ import { type ProposedCheckpointInput, type PublishedCheckpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
15
11
  import {
16
- type ExecutablePrivateFunctionWithMembershipProof,
17
- type UtilityFunctionWithMembershipProof,
18
- computePublicBytecodeCommitment,
19
- isValidPrivateFunctionMembershipProof,
20
- isValidUtilityFunctionMembershipProof,
12
+ type ContractClassPublicWithCommitment,
13
+ computeContractAddressFromInstance,
14
+ computeContractClassId,
21
15
  } from '@aztec/stdlib/contract';
22
16
  import type { ContractClassLog, PrivateLog, PublicLog } from '@aztec/stdlib/logs';
23
17
  import type { UInt64 } from '@aztec/stdlib/types';
24
18
 
25
- import groupBy from 'lodash.groupby';
26
-
27
19
  import type { KVArchiverDataStore } from '../store/kv_archiver_store.js';
28
20
  import type { L2TipsCache } from '../store/l2_tips_cache.js';
29
21
 
@@ -52,29 +44,28 @@ export class ArchiverDataStoreUpdater {
52
44
  ) {}
53
45
 
54
46
  /**
55
- * Adds proposed blocks to the store with contract class/instance extraction from logs.
56
- * These are uncheckpointed blocks that have been proposed by the sequencer but not yet included in a checkpoint on L1.
57
- * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events,
58
- * and individually broadcasted functions from the block logs.
47
+ * Adds a proposed block to the store with contract class/instance extraction from logs.
48
+ * This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
49
+ * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events from the block logs.
59
50
  *
60
- * @param blocks - The proposed L2 blocks to add.
51
+ * @param block - The proposed L2 block to add.
61
52
  * @param pendingChainValidationStatus - Optional validation status to set.
62
53
  * @returns True if the operation is successful.
63
54
  */
64
- public async addProposedBlocks(
65
- blocks: L2Block[],
55
+ public async addProposedBlock(
56
+ block: L2Block,
66
57
  pendingChainValidationStatus?: ValidateCheckpointResult,
67
58
  ): Promise<boolean> {
68
59
  const result = await this.store.transactionAsync(async () => {
69
- await this.store.addProposedBlocks(blocks);
60
+ await this.store.addProposedBlock(block);
70
61
 
71
62
  const opResults = await Promise.all([
72
63
  // Update the pending chain validation status if provided
73
64
  pendingChainValidationStatus && this.store.setPendingChainValidationStatus(pendingChainValidationStatus),
74
- // Add any logs emitted during the retrieved blocks
75
- this.store.addLogs(blocks),
76
- // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
77
- ...blocks.map(block => this.addContractDataToDb(block)),
65
+ // Add any logs emitted during the retrieved block
66
+ this.store.addLogs([block]),
67
+ // Unroll all logs emitted during the retrieved block and extract any contract classes and instances from it
68
+ this.addContractDataToDb(block),
78
69
  ]);
79
70
 
80
71
  await this.l2TipsCache?.refresh();
@@ -87,8 +78,7 @@ export class ArchiverDataStoreUpdater {
87
78
  * Reconciles local blocks with incoming checkpoints from L1.
88
79
  * Adds new checkpoints to the store with contract class/instance extraction from logs.
89
80
  * Prunes any local blocks that conflict with checkpoint data (by comparing archive roots).
90
- * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events,
91
- * and individually broadcasted functions from the checkpoint block logs.
81
+ * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events from the checkpoint block logs.
92
82
  *
93
83
  * @param checkpoints - The published checkpoints to add.
94
84
  * @param pendingChainValidationStatus - Optional validation status to set.
@@ -108,7 +98,7 @@ export class ArchiverDataStoreUpdater {
108
98
 
109
99
  await this.store.addCheckpoints(checkpoints);
110
100
 
111
- // Filter out blocks that were already inserted via addProposedBlocks() to avoid duplicating logs/contract data
101
+ // Filter out blocks that were already inserted via addProposedBlock() to avoid duplicating logs/contract data
112
102
  const newBlocks = checkpoints
113
103
  .flatMap((ch: PublishedCheckpoint) => ch.checkpoint.blocks)
114
104
  .filter(b => lastAlreadyInsertedBlockNumber === undefined || b.number > lastAlreadyInsertedBlockNumber);
@@ -128,6 +118,15 @@ export class ArchiverDataStoreUpdater {
128
118
  return result;
129
119
  }
130
120
 
121
+ public async setProposedCheckpoint(proposedCheckpoint: ProposedCheckpointInput) {
122
+ const result = await this.store.transactionAsync(async () => {
123
+ await this.store.setProposedCheckpoint(proposedCheckpoint);
124
+ await this.l2TipsCache?.refresh();
125
+ });
126
+
127
+ return result;
128
+ }
129
+
131
130
  /**
132
131
  * Checks for local proposed blocks that do not match the ones to be checkpointed and prunes them.
133
132
  * This method handles multiple checkpoints but returns after pruning the first conflict found.
@@ -178,7 +177,7 @@ export class ArchiverDataStoreUpdater {
178
177
  this.log.verbose(`Block number ${blockNumber} already inserted and matches checkpoint`, blockInfos);
179
178
  lastAlreadyInsertedBlockNumber = blockNumber;
180
179
  } else {
181
- this.log.warn(`Conflict detected at block ${blockNumber} between checkpointed and local block`, blockInfos);
180
+ this.log.info(`Conflict detected at block ${blockNumber} between checkpointed and local block`, blockInfos);
182
181
  const prunedBlocks = await this.removeBlocksAfter(BlockNumber(blockNumber - 1));
183
182
  return { prunedBlocks, lastAlreadyInsertedBlockNumber };
184
183
  }
@@ -221,6 +220,10 @@ export class ArchiverDataStoreUpdater {
221
220
  }
222
221
 
223
222
  const result = await this.removeBlocksAfter(blockNumber);
223
+
224
+ // Clear the proposed checkpoint if it exists, since its blocks have been pruned
225
+ await this.store.deleteProposedCheckpoint();
226
+
224
227
  await this.l2TipsCache?.refresh();
225
228
  return result;
226
229
  });
@@ -281,6 +284,17 @@ export class ArchiverDataStoreUpdater {
281
284
  });
282
285
  }
283
286
 
287
+ /**
288
+ * Updates the finalized checkpoint number and refreshes the L2 tips cache.
289
+ * @param checkpointNumber - The checkpoint number to set as finalized.
290
+ */
291
+ public async setFinalizedCheckpointNumber(checkpointNumber: CheckpointNumber): Promise<void> {
292
+ await this.store.transactionAsync(async () => {
293
+ await this.store.setFinalizedCheckpointNumber(checkpointNumber);
294
+ await this.l2TipsCache?.refresh();
295
+ });
296
+ }
297
+
284
298
  /** Extracts and stores contract data from a single block. */
285
299
  private addContractDataToDb(block: L2Block): Promise<boolean> {
286
300
  return this.updateContractDataOnDb(block, Operation.Store);
@@ -302,9 +316,6 @@ export class ArchiverDataStoreUpdater {
302
316
  this.updatePublishedContractClasses(contractClassLogs, block.number, operation),
303
317
  this.updateDeployedContractInstances(privateLogs, block.number, operation),
304
318
  this.updateUpdatedContractInstances(publicLogs, block.header.globalVariables.timestamp, operation),
305
- operation === Operation.Store
306
- ? this.storeBroadcastedIndividualFunctions(contractClassLogs, block.number)
307
- : Promise.resolve(true),
308
319
  ])
309
320
  ).every(Boolean);
310
321
  }
@@ -321,18 +332,37 @@ export class ArchiverDataStoreUpdater {
321
332
  .filter(log => ContractClassPublishedEvent.isContractClassPublishedEvent(log))
322
333
  .map(log => ContractClassPublishedEvent.fromLog(log));
323
334
 
324
- const contractClasses = await Promise.all(contractClassPublishedEvents.map(e => e.toContractClassPublic()));
325
- if (contractClasses.length > 0) {
326
- contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
327
- if (operation == Operation.Store) {
328
- // TODO: Will probably want to create some worker threads to compute these bytecode commitments as they are expensive
329
- const commitments = await Promise.all(
330
- contractClasses.map(c => computePublicBytecodeCommitment(c.packedBytecode)),
331
- );
332
- return await this.store.addContractClasses(contractClasses, commitments, blockNum);
333
- } else if (operation == Operation.Delete) {
335
+ if (operation == Operation.Delete) {
336
+ const contractClasses = contractClassPublishedEvents.map(e => e.toContractClassPublic());
337
+ if (contractClasses.length > 0) {
338
+ contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
334
339
  return await this.store.deleteContractClasses(contractClasses, blockNum);
335
340
  }
341
+ return true;
342
+ }
343
+
344
+ // Compute bytecode commitments and validate class IDs in a single pass.
345
+ const contractClasses: ContractClassPublicWithCommitment[] = [];
346
+ for (const event of contractClassPublishedEvents) {
347
+ const contractClass = await event.toContractClassPublicWithBytecodeCommitment();
348
+ const computedClassId = await computeContractClassId({
349
+ artifactHash: contractClass.artifactHash,
350
+ privateFunctionsRoot: contractClass.privateFunctionsRoot,
351
+ publicBytecodeCommitment: contractClass.publicBytecodeCommitment,
352
+ });
353
+ if (!computedClassId.equals(contractClass.id)) {
354
+ this.log.warn(
355
+ `Skipping contract class with mismatched id at block ${blockNum}. Claimed ${contractClass.id}, computed ${computedClassId}`,
356
+ { blockNum, contractClassId: event.contractClassId.toString() },
357
+ );
358
+ continue;
359
+ }
360
+ contractClasses.push(contractClass);
361
+ }
362
+
363
+ if (contractClasses.length > 0) {
364
+ contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
365
+ return await this.store.addContractClasses(contractClasses, blockNum);
336
366
  }
337
367
  return true;
338
368
  }
@@ -345,10 +375,27 @@ export class ArchiverDataStoreUpdater {
345
375
  blockNum: BlockNumber,
346
376
  operation: Operation,
347
377
  ): Promise<boolean> {
348
- const contractInstances = allLogs
378
+ const allInstances = allLogs
349
379
  .filter(log => ContractInstancePublishedEvent.isContractInstancePublishedEvent(log))
350
380
  .map(log => ContractInstancePublishedEvent.fromLog(log))
351
381
  .map(e => e.toContractInstance());
382
+
383
+ // Verify that each instance's address matches the one derived from its fields if we're adding
384
+ const contractInstances =
385
+ operation === Operation.Delete
386
+ ? allInstances
387
+ : await filterAsync(allInstances, async instance => {
388
+ const computedAddress = await computeContractAddressFromInstance(instance);
389
+ if (!computedAddress.equals(instance.address)) {
390
+ this.log.warn(
391
+ `Found contract instance with mismatched address at block ${blockNum}. Claimed ${instance.address} but computed ${computedAddress}.`,
392
+ { instanceAddress: instance.address.toString(), computedAddress: computedAddress.toString(), blockNum },
393
+ );
394
+ return false;
395
+ }
396
+ return true;
397
+ });
398
+
352
399
  if (contractInstances.length > 0) {
353
400
  contractInstances.forEach(c =>
354
401
  this.log.verbose(`${Operation[operation]} contract instance at ${c.address.toString()}`),
@@ -387,67 +434,4 @@ export class ArchiverDataStoreUpdater {
387
434
  }
388
435
  return true;
389
436
  }
390
-
391
- /**
392
- * Stores the functions that were broadcasted individually.
393
- *
394
- * @dev Beware that there is not a delete variant of this, since they are added to contract classes
395
- * and will be deleted as part of the class if needed.
396
- */
397
- private async storeBroadcastedIndividualFunctions(
398
- allLogs: ContractClassLog[],
399
- _blockNum: BlockNumber,
400
- ): Promise<boolean> {
401
- // Filter out private and utility function broadcast events
402
- const privateFnEvents = allLogs
403
- .filter(log => PrivateFunctionBroadcastedEvent.isPrivateFunctionBroadcastedEvent(log))
404
- .map(log => PrivateFunctionBroadcastedEvent.fromLog(log));
405
- const utilityFnEvents = allLogs
406
- .filter(log => UtilityFunctionBroadcastedEvent.isUtilityFunctionBroadcastedEvent(log))
407
- .map(log => UtilityFunctionBroadcastedEvent.fromLog(log));
408
-
409
- // Group all events by contract class id
410
- for (const [classIdString, classEvents] of Object.entries(
411
- groupBy([...privateFnEvents, ...utilityFnEvents], e => e.contractClassId.toString()),
412
- )) {
413
- const contractClassId = Fr.fromHexString(classIdString);
414
- const contractClass = await this.store.getContractClass(contractClassId);
415
- if (!contractClass) {
416
- this.log.warn(`Skipping broadcasted functions as contract class ${contractClassId.toString()} was not found`);
417
- continue;
418
- }
419
-
420
- // Split private and utility functions, and filter out invalid ones
421
- const allFns = classEvents.map(e => e.toFunctionWithMembershipProof());
422
- const privateFns = allFns.filter(
423
- (fn): fn is ExecutablePrivateFunctionWithMembershipProof => 'utilityFunctionsTreeRoot' in fn,
424
- );
425
- const utilityFns = allFns.filter(
426
- (fn): fn is UtilityFunctionWithMembershipProof => 'privateFunctionsArtifactTreeRoot' in fn,
427
- );
428
-
429
- const privateFunctionsWithValidity = await Promise.all(
430
- privateFns.map(async fn => ({ fn, valid: await isValidPrivateFunctionMembershipProof(fn, contractClass) })),
431
- );
432
- const validPrivateFns = privateFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn);
433
- const utilityFunctionsWithValidity = await Promise.all(
434
- utilityFns.map(async fn => ({
435
- fn,
436
- valid: await isValidUtilityFunctionMembershipProof(fn, contractClass),
437
- })),
438
- );
439
- const validUtilityFns = utilityFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn);
440
- const validFnCount = validPrivateFns.length + validUtilityFns.length;
441
- if (validFnCount !== allFns.length) {
442
- this.log.warn(`Skipping ${allFns.length - validFnCount} invalid functions`);
443
- }
444
-
445
- // Store the functions in the contract class in a single operation
446
- if (validFnCount > 0) {
447
- this.log.verbose(`Storing ${validFnCount} functions for contract class ${contractClassId.toString()}`);
448
- }
449
- return await this.store.addFunctions(contractClassId, validPrivateFns, validUtilityFns);
450
- }
451
- return true;
452
- }
453
437
  }
@@ -3,9 +3,10 @@ import { EpochCache } from '@aztec/epoch-cache';
3
3
  import { InboxContract, RollupContract } from '@aztec/ethereum/contracts';
4
4
  import type { L1BlockId } from '@aztec/ethereum/l1-types';
5
5
  import type { ViemPublicClient, ViemPublicDebugClient } from '@aztec/ethereum/types';
6
+ import { asyncPool } from '@aztec/foundation/async-pool';
6
7
  import { maxBigint } from '@aztec/foundation/bigint';
7
8
  import { BlockNumber, CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
8
- import { Buffer32 } from '@aztec/foundation/buffer';
9
+ import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
9
10
  import { pick } from '@aztec/foundation/collection';
10
11
  import { Fr } from '@aztec/foundation/curves/bn254';
11
12
  import { type Logger, createLogger } from '@aztec/foundation/log';
@@ -72,7 +73,6 @@ export class ArchiverL1Synchronizer implements Traceable {
72
73
  private readonly l1Constants: L1RollupConstants & {
73
74
  l1StartBlockHash: Buffer32;
74
75
  genesisArchiveRoot: Fr;
75
- rollupManaLimit?: number;
76
76
  },
77
77
  private readonly events: ArchiverEmitter,
78
78
  tracer: Tracer,
@@ -217,6 +217,9 @@ export class ArchiverL1Synchronizer implements Traceable {
217
217
  this.instrumentation.updateL1BlockHeight(currentL1BlockNumber);
218
218
  }
219
219
 
220
+ // Update the finalized L2 checkpoint based on L1 finality.
221
+ await this.updateFinalizedCheckpoint();
222
+
220
223
  // After syncing has completed, update the current l1 block number and timestamp,
221
224
  // otherwise we risk announcing to the world that we've synced to a given point,
222
225
  // but the corresponding blocks have not been processed (see #12631).
@@ -232,6 +235,30 @@ export class ArchiverL1Synchronizer implements Traceable {
232
235
  });
233
236
  }
234
237
 
238
+ /** Query L1 for its finalized block and update the finalized checkpoint accordingly. */
239
+ private async updateFinalizedCheckpoint(): Promise<void> {
240
+ try {
241
+ const finalizedL1Block = await this.publicClient.getBlock({ blockTag: 'finalized', includeTransactions: false });
242
+ const finalizedL1BlockNumber = finalizedL1Block.number;
243
+ const finalizedCheckpointNumber = await this.rollup.getProvenCheckpointNumber({
244
+ blockNumber: finalizedL1BlockNumber,
245
+ });
246
+ const localFinalizedCheckpointNumber = await this.store.getFinalizedCheckpointNumber();
247
+ if (localFinalizedCheckpointNumber !== finalizedCheckpointNumber) {
248
+ await this.updater.setFinalizedCheckpointNumber(finalizedCheckpointNumber);
249
+ this.log.info(`Updated finalized chain to checkpoint ${finalizedCheckpointNumber}`, {
250
+ finalizedCheckpointNumber,
251
+ finalizedL1BlockNumber,
252
+ });
253
+ }
254
+ } catch (err: any) {
255
+ // The rollup contract may not exist at the finalized L1 block right after deployment.
256
+ if (!err?.message?.includes('returned no data')) {
257
+ this.log.warn(`Failed to update finalized checkpoint: ${err}`);
258
+ }
259
+ }
260
+ }
261
+
235
262
  /** Prune all proposed local blocks that should have been checkpointed by now. */
236
263
  private async pruneUncheckpointedBlocks(currentL1Timestamp: bigint) {
237
264
  const [lastCheckpointedBlockNumber, lastProposedBlockNumber] = await Promise.all([
@@ -245,29 +272,32 @@ export class ArchiverL1Synchronizer implements Traceable {
245
272
  return;
246
273
  }
247
274
 
248
- // What's the slot of the first uncheckpointed block?
275
+ // What's the slot at the next L1 block? All blocks for slots strictly before this one should've been checkpointed by now.
276
+ const slotAtNextL1Block = getSlotAtNextL1Block(currentL1Timestamp, this.l1Constants);
249
277
  const firstUncheckpointedBlockNumber = BlockNumber(lastCheckpointedBlockNumber + 1);
278
+
279
+ // What's the slot of the first uncheckpointed block?
250
280
  const [firstUncheckpointedBlockHeader] = await this.store.getBlockHeaders(firstUncheckpointedBlockNumber, 1);
251
281
  const firstUncheckpointedBlockSlot = firstUncheckpointedBlockHeader?.getSlot();
252
282
 
253
- // What's the slot at the next L1 block? All blocks for slots strictly before this one should've been checkpointed by now.
254
- const slotAtNextL1Block = getSlotAtNextL1Block(currentL1Timestamp, this.l1Constants);
283
+ if (firstUncheckpointedBlockSlot === undefined || firstUncheckpointedBlockSlot >= slotAtNextL1Block) {
284
+ return;
285
+ }
255
286
 
256
- // Prune provisional blocks from slots that have ended without being checkpointed
257
- if (firstUncheckpointedBlockSlot !== undefined && firstUncheckpointedBlockSlot < slotAtNextL1Block) {
258
- this.log.warn(
259
- `Pruning blocks after block ${lastCheckpointedBlockNumber} due to slot ${firstUncheckpointedBlockSlot} not being checkpointed`,
260
- { firstUncheckpointedBlockHeader: firstUncheckpointedBlockHeader.toInspect(), slotAtNextL1Block },
261
- );
262
- const prunedBlocks = await this.updater.removeUncheckpointedBlocksAfter(lastCheckpointedBlockNumber);
287
+ // Prune provisional blocks from slots that have ended without being checkpointed.
288
+ // This also clears any proposed checkpoint whose blocks are being pruned.
289
+ this.log.warn(
290
+ `Pruning blocks after block ${lastCheckpointedBlockNumber} due to slot ${firstUncheckpointedBlockSlot} not being checkpointed`,
291
+ { firstUncheckpointedBlockHeader: firstUncheckpointedBlockHeader.toInspect(), slotAtNextL1Block },
292
+ );
293
+ const prunedBlocks = await this.updater.removeUncheckpointedBlocksAfter(lastCheckpointedBlockNumber);
263
294
 
264
- if (prunedBlocks.length > 0) {
265
- this.events.emit(L2BlockSourceEvents.L2PruneUncheckpointed, {
266
- type: L2BlockSourceEvents.L2PruneUncheckpointed,
267
- slotNumber: firstUncheckpointedBlockSlot,
268
- blocks: prunedBlocks,
269
- });
270
- }
295
+ if (prunedBlocks.length > 0) {
296
+ this.events.emit(L2BlockSourceEvents.L2PruneUncheckpointed, {
297
+ type: L2BlockSourceEvents.L2PruneUncheckpointed,
298
+ slotNumber: firstUncheckpointedBlockSlot,
299
+ blocks: prunedBlocks,
300
+ });
271
301
  }
272
302
  }
273
303
 
@@ -310,17 +340,20 @@ export class ArchiverL1Synchronizer implements Traceable {
310
340
 
311
341
  const checkpointsToUnwind = localPendingCheckpointNumber - provenCheckpointNumber;
312
342
 
313
- const checkpointPromises = Array.from({ length: checkpointsToUnwind })
314
- .fill(0)
315
- .map((_, i) => this.store.getCheckpointData(CheckpointNumber(i + pruneFrom)));
316
- const checkpoints = await Promise.all(checkpointPromises);
317
-
318
- const blockPromises = await Promise.all(
319
- checkpoints
320
- .filter(isDefined)
321
- .map(cp => this.store.getBlocksForCheckpoint(CheckpointNumber(cp.checkpointNumber))),
343
+ // Fetch checkpoints and blocks in bounded batches to avoid unbounded concurrent
344
+ // promises when the gap between local pending and proven checkpoint numbers is large.
345
+ const BATCH_SIZE = 10;
346
+ const indices = Array.from({ length: checkpointsToUnwind }, (_, i) => CheckpointNumber(i + pruneFrom));
347
+ const checkpoints = (await asyncPool(BATCH_SIZE, indices, idx => this.store.getCheckpointData(idx))).filter(
348
+ isDefined,
322
349
  );
323
- const newBlocks = blockPromises.filter(isDefined).flat();
350
+ const newBlocks = (
351
+ await asyncPool(BATCH_SIZE, checkpoints, cp =>
352
+ this.store.getBlocksForCheckpoint(CheckpointNumber(cp.checkpointNumber)),
353
+ )
354
+ )
355
+ .filter(isDefined)
356
+ .flat();
324
357
 
325
358
  // Emit an event for listening services to react to the chain prune
326
359
  this.events.emit(L2BlockSourceEvents.L2PruneUnproven, {
@@ -368,6 +401,7 @@ export class ArchiverL1Synchronizer implements Traceable {
368
401
  const localMessagesInserted = await this.store.getTotalL1ToL2MessageCount();
369
402
  const localLastMessage = await this.store.getLastL1ToL2Message();
370
403
  const remoteMessagesState = await this.inbox.getState({ blockNumber: currentL1BlockNumber });
404
+ await this.store.setInboxTreeInProgress(remoteMessagesState.treeInProgress);
371
405
 
372
406
  this.log.trace(`Retrieved remote inbox state at L1 block ${currentL1BlockNumber}.`, {
373
407
  localMessagesInserted,
@@ -378,7 +412,7 @@ export class ArchiverL1Synchronizer implements Traceable {
378
412
  // Compare message count and rolling hash. If they match, no need to retrieve anything.
379
413
  if (
380
414
  remoteMessagesState.totalMessagesInserted === localMessagesInserted &&
381
- remoteMessagesState.messagesRollingHash.equals(localLastMessage?.rollingHash ?? Buffer32.ZERO)
415
+ remoteMessagesState.messagesRollingHash.equals(localLastMessage?.rollingHash ?? Buffer16.ZERO)
382
416
  ) {
383
417
  this.log.trace(
384
418
  `No L1 to L2 messages to query between L1 blocks ${messagesSyncPoint.l1BlockNumber} and ${currentL1BlockNumber}.`,
@@ -828,7 +862,7 @@ export class ArchiverL1Synchronizer implements Traceable {
828
862
  const prunedCheckpointNumber = result.prunedBlocks[0].checkpointNumber;
829
863
  const prunedSlotNumber = result.prunedBlocks[0].header.globalVariables.slotNumber;
830
864
 
831
- this.log.warn(
865
+ this.log.info(
832
866
  `Pruned ${result.prunedBlocks.length} mismatching blocks for checkpoint ${prunedCheckpointNumber}`,
833
867
  { prunedBlocks: result.prunedBlocks.map(b => b.toBlockInfo()), prunedSlotNumber, prunedCheckpointNumber },
834
868
  );