@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.
- package/dest/archiver.d.ts +5 -7
- package/dest/archiver.d.ts.map +1 -1
- package/dest/archiver.js +54 -18
- package/dest/config.d.ts +3 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +2 -1
- package/dest/errors.d.ts +34 -10
- package/dest/errors.d.ts.map +1 -1
- package/dest/errors.js +45 -16
- package/dest/factory.d.ts +3 -4
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +19 -18
- package/dest/l1/calldata_retriever.d.ts +1 -1
- package/dest/l1/calldata_retriever.d.ts.map +1 -1
- package/dest/l1/calldata_retriever.js +2 -1
- package/dest/modules/data_source_base.d.ts +8 -6
- package/dest/modules/data_source_base.d.ts.map +1 -1
- package/dest/modules/data_source_base.js +11 -5
- package/dest/modules/data_store_updater.d.ts +14 -11
- package/dest/modules/data_store_updater.d.ts.map +1 -1
- package/dest/modules/data_store_updater.js +78 -76
- package/dest/modules/l1_synchronizer.d.ts +2 -2
- package/dest/modules/l1_synchronizer.d.ts.map +1 -1
- package/dest/modules/l1_synchronizer.js +57 -24
- package/dest/modules/validation.d.ts +1 -1
- package/dest/modules/validation.d.ts.map +1 -1
- package/dest/modules/validation.js +2 -2
- package/dest/store/block_store.d.ts +49 -16
- package/dest/store/block_store.d.ts.map +1 -1
- package/dest/store/block_store.js +243 -118
- package/dest/store/contract_class_store.d.ts +2 -3
- package/dest/store/contract_class_store.d.ts.map +1 -1
- package/dest/store/contract_class_store.js +7 -67
- package/dest/store/contract_instance_store.d.ts +1 -1
- package/dest/store/contract_instance_store.d.ts.map +1 -1
- package/dest/store/contract_instance_store.js +6 -2
- package/dest/store/kv_archiver_store.d.ts +46 -19
- package/dest/store/kv_archiver_store.d.ts.map +1 -1
- package/dest/store/kv_archiver_store.js +57 -22
- package/dest/store/l2_tips_cache.d.ts +2 -1
- package/dest/store/l2_tips_cache.d.ts.map +1 -1
- package/dest/store/l2_tips_cache.js +25 -5
- package/dest/store/log_store.d.ts +6 -3
- package/dest/store/log_store.d.ts.map +1 -1
- package/dest/store/log_store.js +93 -16
- package/dest/store/message_store.d.ts +5 -1
- package/dest/store/message_store.d.ts.map +1 -1
- package/dest/store/message_store.js +13 -0
- package/dest/test/fake_l1_state.d.ts +8 -1
- package/dest/test/fake_l1_state.d.ts.map +1 -1
- package/dest/test/fake_l1_state.js +39 -5
- package/dest/test/mock_l1_to_l2_message_source.d.ts +1 -1
- package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
- package/dest/test/mock_l1_to_l2_message_source.js +2 -1
- package/dest/test/mock_l2_block_source.d.ts +9 -4
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +32 -7
- package/dest/test/noop_l1_archiver.d.ts +4 -1
- package/dest/test/noop_l1_archiver.d.ts.map +1 -1
- package/dest/test/noop_l1_archiver.js +5 -1
- package/package.json +13 -13
- package/src/archiver.ts +58 -20
- package/src/config.ts +8 -1
- package/src/errors.ts +70 -26
- package/src/factory.ts +19 -14
- package/src/l1/calldata_retriever.ts +2 -1
- package/src/modules/data_source_base.ts +26 -7
- package/src/modules/data_store_updater.ts +91 -107
- package/src/modules/l1_synchronizer.ts +65 -31
- package/src/modules/validation.ts +2 -2
- package/src/store/block_store.ts +312 -138
- package/src/store/contract_class_store.ts +8 -106
- package/src/store/contract_instance_store.ts +8 -5
- package/src/store/kv_archiver_store.ts +83 -34
- package/src/store/l2_tips_cache.ts +50 -11
- package/src/store/log_store.ts +126 -27
- package/src/store/message_store.ts +19 -0
- package/src/test/fake_l1_state.ts +50 -9
- package/src/test/mock_l1_to_l2_message_source.ts +1 -0
- package/src/test/mock_l2_block_source.ts +46 -5
- 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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
|
55
|
+
abstract getSyncedL2SlotNumber(): Promise<SlotNumber | undefined>;
|
|
50
56
|
|
|
51
|
-
abstract
|
|
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(
|
|
169
|
-
|
|
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 {
|
|
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
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
56
|
-
*
|
|
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
|
|
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
|
|
65
|
-
|
|
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.
|
|
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
|
|
75
|
-
this.store.addLogs(
|
|
76
|
-
// Unroll all logs emitted during the retrieved
|
|
77
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
contractClasses.
|
|
327
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
254
|
-
|
|
283
|
+
if (firstUncheckpointedBlockSlot === undefined || firstUncheckpointedBlockSlot >= slotAtNextL1Block) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
255
286
|
|
|
256
|
-
// Prune provisional blocks from slots that have ended without being checkpointed
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
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 =
|
|
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 ??
|
|
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.
|
|
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
|
);
|