@aztec/archiver 0.0.1-commit.181e2d196 → 0.0.1-commit.1a421b1a1
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 +3 -3
- package/dest/archiver.d.ts.map +1 -1
- package/dest/archiver.js +43 -18
- package/dest/errors.d.ts +7 -9
- package/dest/errors.d.ts.map +1 -1
- package/dest/errors.js +9 -14
- package/dest/factory.d.ts +3 -4
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +8 -8
- package/dest/modules/data_source_base.d.ts +3 -3
- package/dest/modules/data_source_base.d.ts.map +1 -1
- package/dest/modules/data_store_updater.d.ts +15 -7
- package/dest/modules/data_store_updater.d.ts.map +1 -1
- package/dest/modules/data_store_updater.js +31 -12
- package/dest/modules/l1_synchronizer.d.ts +2 -1
- package/dest/modules/l1_synchronizer.d.ts.map +1 -1
- package/dest/modules/l1_synchronizer.js +28 -2
- package/dest/store/block_store.d.ts +10 -12
- package/dest/store/block_store.d.ts.map +1 -1
- package/dest/store/block_store.js +54 -57
- package/dest/store/kv_archiver_store.d.ts +16 -8
- package/dest/store/kv_archiver_store.d.ts.map +1 -1
- package/dest/store/kv_archiver_store.js +19 -7
- package/dest/store/message_store.js +1 -1
- 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 +28 -2
- package/dest/test/mock_l2_block_source.d.ts +4 -3
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +5 -2
- package/dest/test/mock_structs.d.ts +4 -1
- package/dest/test/mock_structs.d.ts.map +1 -1
- package/dest/test/mock_structs.js +13 -1
- 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 +46 -19
- package/src/errors.ts +10 -24
- package/src/factory.ts +6 -5
- package/src/modules/data_source_base.ts +2 -2
- package/src/modules/data_store_updater.ts +29 -13
- package/src/modules/l1_synchronizer.ts +32 -3
- package/src/store/block_store.ts +61 -67
- package/src/store/kv_archiver_store.ts +22 -8
- package/src/store/message_store.ts +1 -1
- package/src/test/fake_l1_state.ts +35 -4
- package/src/test/mock_l2_block_source.ts +8 -2
- package/src/test/mock_structs.ts +20 -6
- package/src/test/noop_l1_archiver.ts +7 -1
package/src/factory.ts
CHANGED
|
@@ -7,14 +7,13 @@ import { Buffer32 } from '@aztec/foundation/buffer';
|
|
|
7
7
|
import { merge } from '@aztec/foundation/collection';
|
|
8
8
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
9
9
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
10
|
-
import type { DataStoreConfig } from '@aztec/kv-store/config';
|
|
11
10
|
import { createStore } from '@aztec/kv-store/lmdb-v2';
|
|
12
11
|
import { protocolContractNames } from '@aztec/protocol-contracts';
|
|
13
12
|
import { BundledProtocolContractsProvider } from '@aztec/protocol-contracts/providers/bundle';
|
|
14
13
|
import { FunctionType, decodeFunctionSignature } from '@aztec/stdlib/abi';
|
|
15
14
|
import type { ArchiverEmitter } from '@aztec/stdlib/block';
|
|
16
15
|
import { type ContractClassPublic, computePublicBytecodeCommitment } from '@aztec/stdlib/contract';
|
|
17
|
-
import type {
|
|
16
|
+
import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
|
|
18
17
|
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
19
18
|
|
|
20
19
|
import { EventEmitter } from 'events';
|
|
@@ -32,14 +31,13 @@ export const ARCHIVER_STORE_NAME = 'archiver';
|
|
|
32
31
|
/** Creates an archiver store. */
|
|
33
32
|
export async function createArchiverStore(
|
|
34
33
|
userConfig: Pick<ArchiverConfig, 'archiverStoreMapSizeKb' | 'maxLogs'> & DataStoreConfig,
|
|
35
|
-
l1Constants: Pick<L1RollupConstants, 'epochDuration'>,
|
|
36
34
|
) {
|
|
37
35
|
const config = {
|
|
38
36
|
...userConfig,
|
|
39
37
|
dataStoreMapSizeKb: userConfig.archiverStoreMapSizeKb ?? userConfig.dataStoreMapSizeKb,
|
|
40
38
|
};
|
|
41
39
|
const store = await createStore(ARCHIVER_STORE_NAME, ARCHIVER_DB_VERSION, config);
|
|
42
|
-
return new KVArchiverDataStore(store, config.maxLogs
|
|
40
|
+
return new KVArchiverDataStore(store, config.maxLogs);
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
/**
|
|
@@ -54,7 +52,7 @@ export async function createArchiver(
|
|
|
54
52
|
deps: ArchiverDeps,
|
|
55
53
|
opts: { blockUntilSync: boolean } = { blockUntilSync: true },
|
|
56
54
|
): Promise<Archiver> {
|
|
57
|
-
const archiverStore = await createArchiverStore(config
|
|
55
|
+
const archiverStore = await createArchiverStore(config);
|
|
58
56
|
await registerProtocolContracts(archiverStore);
|
|
59
57
|
|
|
60
58
|
// Create Ethereum clients
|
|
@@ -85,6 +83,7 @@ export async function createArchiver(
|
|
|
85
83
|
genesisArchiveRoot,
|
|
86
84
|
slashingProposerAddress,
|
|
87
85
|
targetCommitteeSize,
|
|
86
|
+
rollupManaLimit,
|
|
88
87
|
] = await Promise.all([
|
|
89
88
|
rollup.getL1StartBlock(),
|
|
90
89
|
rollup.getL1GenesisTime(),
|
|
@@ -92,6 +91,7 @@ export async function createArchiver(
|
|
|
92
91
|
rollup.getGenesisArchiveTreeRoot(),
|
|
93
92
|
rollup.getSlashingProposerAddress(),
|
|
94
93
|
rollup.getTargetCommitteeSize(),
|
|
94
|
+
rollup.getManaLimit(),
|
|
95
95
|
] as const);
|
|
96
96
|
|
|
97
97
|
const l1StartBlockHash = await publicClient
|
|
@@ -110,6 +110,7 @@ export async function createArchiver(
|
|
|
110
110
|
proofSubmissionEpochs: Number(proofSubmissionEpochs),
|
|
111
111
|
targetCommitteeSize,
|
|
112
112
|
genesisArchiveRoot: Fr.fromString(genesisArchiveRoot.toString()),
|
|
113
|
+
rollupManaLimit: Number(rollupManaLimit),
|
|
113
114
|
};
|
|
114
115
|
|
|
115
116
|
const archiverConfig = merge(
|
|
@@ -46,9 +46,9 @@ export abstract class ArchiverDataSourceBase
|
|
|
46
46
|
|
|
47
47
|
abstract getL2Tips(): Promise<L2Tips>;
|
|
48
48
|
|
|
49
|
-
abstract
|
|
49
|
+
abstract getSyncedL2SlotNumber(): Promise<SlotNumber | undefined>;
|
|
50
50
|
|
|
51
|
-
abstract
|
|
51
|
+
abstract getSyncedL2EpochNumber(): Promise<EpochNumber | undefined>;
|
|
52
52
|
|
|
53
53
|
abstract isEpochComplete(epochNumber: EpochNumber): Promise<boolean>;
|
|
54
54
|
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
ContractInstanceUpdatedEvent,
|
|
12
12
|
} from '@aztec/protocol-contracts/instance-registry';
|
|
13
13
|
import type { L2Block, ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
14
|
-
import type
|
|
14
|
+
import { type PublishedCheckpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
15
15
|
import {
|
|
16
16
|
type ExecutablePrivateFunctionWithMembershipProof,
|
|
17
17
|
type UtilityFunctionWithMembershipProof,
|
|
@@ -48,32 +48,33 @@ export class ArchiverDataStoreUpdater {
|
|
|
48
48
|
constructor(
|
|
49
49
|
private store: KVArchiverDataStore,
|
|
50
50
|
private l2TipsCache?: L2TipsCache,
|
|
51
|
+
private opts: { rollupManaLimit?: number } = {},
|
|
51
52
|
) {}
|
|
52
53
|
|
|
53
54
|
/**
|
|
54
|
-
* Adds proposed
|
|
55
|
-
*
|
|
55
|
+
* Adds a proposed block to the store with contract class/instance extraction from logs.
|
|
56
|
+
* This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
|
|
56
57
|
* Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events,
|
|
57
58
|
* and individually broadcasted functions from the block logs.
|
|
58
59
|
*
|
|
59
|
-
* @param
|
|
60
|
+
* @param block - The proposed L2 block to add.
|
|
60
61
|
* @param pendingChainValidationStatus - Optional validation status to set.
|
|
61
62
|
* @returns True if the operation is successful.
|
|
62
63
|
*/
|
|
63
|
-
public async
|
|
64
|
-
|
|
64
|
+
public async addProposedBlock(
|
|
65
|
+
block: L2Block,
|
|
65
66
|
pendingChainValidationStatus?: ValidateCheckpointResult,
|
|
66
67
|
): Promise<boolean> {
|
|
67
68
|
const result = await this.store.transactionAsync(async () => {
|
|
68
|
-
await this.store.
|
|
69
|
+
await this.store.addProposedBlock(block);
|
|
69
70
|
|
|
70
71
|
const opResults = await Promise.all([
|
|
71
72
|
// Update the pending chain validation status if provided
|
|
72
73
|
pendingChainValidationStatus && this.store.setPendingChainValidationStatus(pendingChainValidationStatus),
|
|
73
|
-
// Add any logs emitted during the retrieved
|
|
74
|
-
this.store.addLogs(
|
|
75
|
-
// Unroll all logs emitted during the retrieved
|
|
76
|
-
|
|
74
|
+
// Add any logs emitted during the retrieved block
|
|
75
|
+
this.store.addLogs([block]),
|
|
76
|
+
// Unroll all logs emitted during the retrieved block and extract any contract classes and instances from it
|
|
77
|
+
this.addContractDataToDb(block),
|
|
77
78
|
]);
|
|
78
79
|
|
|
79
80
|
await this.l2TipsCache?.refresh();
|
|
@@ -97,13 +98,17 @@ export class ArchiverDataStoreUpdater {
|
|
|
97
98
|
checkpoints: PublishedCheckpoint[],
|
|
98
99
|
pendingChainValidationStatus?: ValidateCheckpointResult,
|
|
99
100
|
): Promise<ReconcileCheckpointsResult> {
|
|
101
|
+
for (const checkpoint of checkpoints) {
|
|
102
|
+
validateCheckpoint(checkpoint.checkpoint, { rollupManaLimit: this.opts?.rollupManaLimit });
|
|
103
|
+
}
|
|
104
|
+
|
|
100
105
|
const result = await this.store.transactionAsync(async () => {
|
|
101
106
|
// Before adding checkpoints, check for conflicts with local blocks if any
|
|
102
107
|
const { prunedBlocks, lastAlreadyInsertedBlockNumber } = await this.pruneMismatchingLocalBlocks(checkpoints);
|
|
103
108
|
|
|
104
109
|
await this.store.addCheckpoints(checkpoints);
|
|
105
110
|
|
|
106
|
-
// Filter out blocks that were already inserted via
|
|
111
|
+
// Filter out blocks that were already inserted via addProposedBlock() to avoid duplicating logs/contract data
|
|
107
112
|
const newBlocks = checkpoints
|
|
108
113
|
.flatMap((ch: PublishedCheckpoint) => ch.checkpoint.blocks)
|
|
109
114
|
.filter(b => lastAlreadyInsertedBlockNumber === undefined || b.number > lastAlreadyInsertedBlockNumber);
|
|
@@ -173,7 +178,7 @@ export class ArchiverDataStoreUpdater {
|
|
|
173
178
|
this.log.verbose(`Block number ${blockNumber} already inserted and matches checkpoint`, blockInfos);
|
|
174
179
|
lastAlreadyInsertedBlockNumber = blockNumber;
|
|
175
180
|
} else {
|
|
176
|
-
this.log.
|
|
181
|
+
this.log.info(`Conflict detected at block ${blockNumber} between checkpointed and local block`, blockInfos);
|
|
177
182
|
const prunedBlocks = await this.removeBlocksAfter(BlockNumber(blockNumber - 1));
|
|
178
183
|
return { prunedBlocks, lastAlreadyInsertedBlockNumber };
|
|
179
184
|
}
|
|
@@ -276,6 +281,17 @@ export class ArchiverDataStoreUpdater {
|
|
|
276
281
|
});
|
|
277
282
|
}
|
|
278
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Updates the finalized checkpoint number and refreshes the L2 tips cache.
|
|
286
|
+
* @param checkpointNumber - The checkpoint number to set as finalized.
|
|
287
|
+
*/
|
|
288
|
+
public async setFinalizedCheckpointNumber(checkpointNumber: CheckpointNumber): Promise<void> {
|
|
289
|
+
await this.store.transactionAsync(async () => {
|
|
290
|
+
await this.store.setFinalizedCheckpointNumber(checkpointNumber);
|
|
291
|
+
await this.l2TipsCache?.refresh();
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
279
295
|
/** Extracts and stores contract data from a single block. */
|
|
280
296
|
private addContractDataToDb(block: L2Block): Promise<boolean> {
|
|
281
297
|
return this.updateContractDataOnDb(block, Operation.Store);
|
|
@@ -69,13 +69,18 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
69
69
|
private readonly epochCache: EpochCache,
|
|
70
70
|
private readonly dateProvider: DateProvider,
|
|
71
71
|
private readonly instrumentation: ArchiverInstrumentation,
|
|
72
|
-
private readonly l1Constants: L1RollupConstants & {
|
|
72
|
+
private readonly l1Constants: L1RollupConstants & {
|
|
73
|
+
l1StartBlockHash: Buffer32;
|
|
74
|
+
genesisArchiveRoot: Fr;
|
|
75
|
+
},
|
|
73
76
|
private readonly events: ArchiverEmitter,
|
|
74
77
|
tracer: Tracer,
|
|
75
78
|
l2TipsCache?: L2TipsCache,
|
|
76
79
|
private readonly log: Logger = createLogger('archiver:l1-sync'),
|
|
77
80
|
) {
|
|
78
|
-
this.updater = new ArchiverDataStoreUpdater(this.store, l2TipsCache
|
|
81
|
+
this.updater = new ArchiverDataStoreUpdater(this.store, l2TipsCache, {
|
|
82
|
+
rollupManaLimit: l1Constants.rollupManaLimit,
|
|
83
|
+
});
|
|
79
84
|
this.tracer = tracer;
|
|
80
85
|
}
|
|
81
86
|
|
|
@@ -211,6 +216,9 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
211
216
|
this.instrumentation.updateL1BlockHeight(currentL1BlockNumber);
|
|
212
217
|
}
|
|
213
218
|
|
|
219
|
+
// Update the finalized L2 checkpoint based on L1 finality.
|
|
220
|
+
await this.updateFinalizedCheckpoint();
|
|
221
|
+
|
|
214
222
|
// After syncing has completed, update the current l1 block number and timestamp,
|
|
215
223
|
// otherwise we risk announcing to the world that we've synced to a given point,
|
|
216
224
|
// but the corresponding blocks have not been processed (see #12631).
|
|
@@ -226,6 +234,27 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
226
234
|
});
|
|
227
235
|
}
|
|
228
236
|
|
|
237
|
+
/** Query L1 for its finalized block and update the finalized checkpoint accordingly. */
|
|
238
|
+
private async updateFinalizedCheckpoint(): Promise<void> {
|
|
239
|
+
try {
|
|
240
|
+
const finalizedL1Block = await this.publicClient.getBlock({ blockTag: 'finalized', includeTransactions: false });
|
|
241
|
+
const finalizedL1BlockNumber = finalizedL1Block.number;
|
|
242
|
+
const finalizedCheckpointNumber = await this.rollup.getProvenCheckpointNumber({
|
|
243
|
+
blockNumber: finalizedL1BlockNumber,
|
|
244
|
+
});
|
|
245
|
+
const localFinalizedCheckpointNumber = await this.store.getFinalizedCheckpointNumber();
|
|
246
|
+
if (localFinalizedCheckpointNumber !== finalizedCheckpointNumber) {
|
|
247
|
+
await this.updater.setFinalizedCheckpointNumber(finalizedCheckpointNumber);
|
|
248
|
+
this.log.info(`Updated finalized chain to checkpoint ${finalizedCheckpointNumber}`, {
|
|
249
|
+
finalizedCheckpointNumber,
|
|
250
|
+
finalizedL1BlockNumber,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
} catch (err) {
|
|
254
|
+
this.log.warn(`Failed to update finalized checkpoint: ${err}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
229
258
|
/** Prune all proposed local blocks that should have been checkpointed by now. */
|
|
230
259
|
private async pruneUncheckpointedBlocks(currentL1Timestamp: bigint) {
|
|
231
260
|
const [lastCheckpointedBlockNumber, lastProposedBlockNumber] = await Promise.all([
|
|
@@ -822,7 +851,7 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
822
851
|
const prunedCheckpointNumber = result.prunedBlocks[0].checkpointNumber;
|
|
823
852
|
const prunedSlotNumber = result.prunedBlocks[0].header.globalVariables.slotNumber;
|
|
824
853
|
|
|
825
|
-
this.log.
|
|
854
|
+
this.log.info(
|
|
826
855
|
`Pruned ${result.prunedBlocks.length} mismatching blocks for checkpoint ${prunedCheckpointNumber}`,
|
|
827
856
|
{ prunedBlocks: result.prunedBlocks.map(b => b.toBlockInfo()), prunedSlotNumber, prunedCheckpointNumber },
|
|
828
857
|
);
|
package/src/store/block_store.ts
CHANGED
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
serializeValidateCheckpointResult,
|
|
21
21
|
} from '@aztec/stdlib/block';
|
|
22
22
|
import { type CheckpointData, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
23
|
-
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
24
23
|
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
25
24
|
import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
|
|
26
25
|
import {
|
|
@@ -35,15 +34,14 @@ import {
|
|
|
35
34
|
} from '@aztec/stdlib/tx';
|
|
36
35
|
|
|
37
36
|
import {
|
|
37
|
+
BlockAlreadyCheckpointedError,
|
|
38
38
|
BlockArchiveNotConsistentError,
|
|
39
39
|
BlockIndexNotSequentialError,
|
|
40
40
|
BlockNotFoundError,
|
|
41
41
|
BlockNumberNotSequentialError,
|
|
42
42
|
CannotOverwriteCheckpointedBlockError,
|
|
43
43
|
CheckpointNotFoundError,
|
|
44
|
-
CheckpointNumberNotConsistentError,
|
|
45
44
|
CheckpointNumberNotSequentialError,
|
|
46
|
-
InitialBlockNumberNotSequentialError,
|
|
47
45
|
InitialCheckpointNumberNotSequentialError,
|
|
48
46
|
} from '../errors.js';
|
|
49
47
|
|
|
@@ -97,6 +95,9 @@ export class BlockStore {
|
|
|
97
95
|
/** Stores last proven checkpoint */
|
|
98
96
|
#lastProvenCheckpoint: AztecAsyncSingleton<number>;
|
|
99
97
|
|
|
98
|
+
/** Stores last finalized checkpoint (proven at or before the finalized L1 block) */
|
|
99
|
+
#lastFinalizedCheckpoint: AztecAsyncSingleton<number>;
|
|
100
|
+
|
|
100
101
|
/** Stores the pending chain validation status */
|
|
101
102
|
#pendingChainValidationStatus: AztecAsyncSingleton<Buffer>;
|
|
102
103
|
|
|
@@ -111,10 +112,7 @@ export class BlockStore {
|
|
|
111
112
|
|
|
112
113
|
#log = createLogger('archiver:block_store');
|
|
113
114
|
|
|
114
|
-
constructor(
|
|
115
|
-
private db: AztecAsyncKVStore,
|
|
116
|
-
private l1Constants: Pick<L1RollupConstants, 'epochDuration'>,
|
|
117
|
-
) {
|
|
115
|
+
constructor(private db: AztecAsyncKVStore) {
|
|
118
116
|
this.#blocks = db.openMap('archiver_blocks');
|
|
119
117
|
this.#blockTxs = db.openMap('archiver_block_txs');
|
|
120
118
|
this.#txEffects = db.openMap('archiver_tx_effects');
|
|
@@ -123,41 +121,42 @@ export class BlockStore {
|
|
|
123
121
|
this.#blockArchiveIndex = db.openMap('archiver_block_archive_index');
|
|
124
122
|
this.#lastSynchedL1Block = db.openSingleton('archiver_last_synched_l1_block');
|
|
125
123
|
this.#lastProvenCheckpoint = db.openSingleton('archiver_last_proven_l2_checkpoint');
|
|
124
|
+
this.#lastFinalizedCheckpoint = db.openSingleton('archiver_last_finalized_l2_checkpoint');
|
|
126
125
|
this.#pendingChainValidationStatus = db.openSingleton('archiver_pending_chain_validation_status');
|
|
127
126
|
this.#checkpoints = db.openMap('archiver_checkpoints');
|
|
128
127
|
this.#slotToCheckpoint = db.openMap('archiver_slot_to_checkpoint');
|
|
129
128
|
}
|
|
130
129
|
|
|
131
130
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
* TODO(#13569): Compute proper finalized block number based on L1 finalized block.
|
|
135
|
-
* TODO(palla/mbps): Even the provisional computation is wrong, since it should subtract checkpoints, not blocks
|
|
131
|
+
* Returns the finalized L2 block number. An L2 block is finalized when it was proven
|
|
132
|
+
* in an L1 block that has itself been finalized on Ethereum.
|
|
136
133
|
* @returns The finalized block number.
|
|
137
134
|
*/
|
|
138
135
|
async getFinalizedL2BlockNumber(): Promise<BlockNumber> {
|
|
139
|
-
const
|
|
140
|
-
|
|
136
|
+
const finalizedCheckpointNumber = await this.getFinalizedCheckpointNumber();
|
|
137
|
+
if (finalizedCheckpointNumber === INITIAL_CHECKPOINT_NUMBER - 1) {
|
|
138
|
+
return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
|
|
139
|
+
}
|
|
140
|
+
const checkpointStorage = await this.#checkpoints.getAsync(finalizedCheckpointNumber);
|
|
141
|
+
if (!checkpointStorage) {
|
|
142
|
+
throw new CheckpointNotFoundError(finalizedCheckpointNumber);
|
|
143
|
+
}
|
|
144
|
+
return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
|
|
141
145
|
}
|
|
142
146
|
|
|
143
147
|
/**
|
|
144
|
-
* Append new proposed
|
|
145
|
-
*
|
|
148
|
+
* Append a new proposed block to the store.
|
|
149
|
+
* This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
|
|
146
150
|
* For checkpointed blocks (already published to L1), use addCheckpoints() instead.
|
|
147
|
-
* @param
|
|
151
|
+
* @param block - The proposed L2 block to be added to the store.
|
|
148
152
|
* @returns True if the operation is successful.
|
|
149
153
|
*/
|
|
150
|
-
async
|
|
151
|
-
if (blocks.length === 0) {
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
154
|
-
|
|
154
|
+
async addProposedBlock(block: L2Block, opts: { force?: boolean } = {}): Promise<boolean> {
|
|
155
155
|
return await this.db.transactionAsync(async () => {
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
const firstBlockLastArchive = blocks[0].header.lastArchive.root;
|
|
156
|
+
const blockNumber = block.number;
|
|
157
|
+
const blockCheckpointNumber = block.checkpointNumber;
|
|
158
|
+
const blockIndex = block.indexWithinCheckpoint;
|
|
159
|
+
const blockLastArchive = block.header.lastArchive.root;
|
|
161
160
|
|
|
162
161
|
// Extract the latest block and checkpoint numbers
|
|
163
162
|
const previousBlockNumber = await this.getLatestBlockNumber();
|
|
@@ -165,71 +164,52 @@ export class BlockStore {
|
|
|
165
164
|
|
|
166
165
|
// Verify we're not overwriting checkpointed blocks
|
|
167
166
|
const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber();
|
|
168
|
-
if (!opts.force &&
|
|
169
|
-
|
|
167
|
+
if (!opts.force && blockNumber <= lastCheckpointedBlockNumber) {
|
|
168
|
+
// Check if the proposed block matches the already-checkpointed one
|
|
169
|
+
const existingBlock = await this.getBlock(BlockNumber(blockNumber));
|
|
170
|
+
if (existingBlock && existingBlock.archive.root.equals(block.archive.root)) {
|
|
171
|
+
throw new BlockAlreadyCheckpointedError(blockNumber);
|
|
172
|
+
}
|
|
173
|
+
throw new CannotOverwriteCheckpointedBlockError(blockNumber, lastCheckpointedBlockNumber);
|
|
170
174
|
}
|
|
171
175
|
|
|
172
|
-
// Check that the
|
|
173
|
-
if (!opts.force && previousBlockNumber !==
|
|
174
|
-
throw new
|
|
176
|
+
// Check that the block number is the expected one
|
|
177
|
+
if (!opts.force && previousBlockNumber !== blockNumber - 1) {
|
|
178
|
+
throw new BlockNumberNotSequentialError(blockNumber, previousBlockNumber);
|
|
175
179
|
}
|
|
176
180
|
|
|
177
181
|
// The same check as above but for checkpoints
|
|
178
|
-
if (!opts.force && previousCheckpointNumber !==
|
|
179
|
-
throw new
|
|
182
|
+
if (!opts.force && previousCheckpointNumber !== blockCheckpointNumber - 1) {
|
|
183
|
+
throw new CheckpointNumberNotSequentialError(blockCheckpointNumber, previousCheckpointNumber);
|
|
180
184
|
}
|
|
181
185
|
|
|
182
186
|
// Extract the previous block if there is one and see if it is for the same checkpoint or not
|
|
183
187
|
const previousBlockResult = await this.getBlock(previousBlockNumber);
|
|
184
188
|
|
|
185
|
-
let
|
|
189
|
+
let expectedBlockIndex = 0;
|
|
186
190
|
let previousBlockIndex: number | undefined = undefined;
|
|
187
191
|
if (previousBlockResult !== undefined) {
|
|
188
|
-
if (previousBlockResult.checkpointNumber ===
|
|
192
|
+
if (previousBlockResult.checkpointNumber === blockCheckpointNumber) {
|
|
189
193
|
// The previous block is for the same checkpoint, therefore our index should follow it
|
|
190
194
|
previousBlockIndex = previousBlockResult.indexWithinCheckpoint;
|
|
191
|
-
|
|
195
|
+
expectedBlockIndex = previousBlockIndex + 1;
|
|
192
196
|
}
|
|
193
|
-
if (!previousBlockResult.archive.root.equals(
|
|
197
|
+
if (!previousBlockResult.archive.root.equals(blockLastArchive)) {
|
|
194
198
|
throw new BlockArchiveNotConsistentError(
|
|
195
|
-
|
|
199
|
+
blockNumber,
|
|
196
200
|
previousBlockResult.number,
|
|
197
|
-
|
|
201
|
+
blockLastArchive,
|
|
198
202
|
previousBlockResult.archive.root,
|
|
199
203
|
);
|
|
200
204
|
}
|
|
201
205
|
}
|
|
202
206
|
|
|
203
|
-
// Now check that the
|
|
204
|
-
if (!opts.force &&
|
|
205
|
-
throw new BlockIndexNotSequentialError(
|
|
207
|
+
// Now check that the block has the expected index value
|
|
208
|
+
if (!opts.force && expectedBlockIndex !== blockIndex) {
|
|
209
|
+
throw new BlockIndexNotSequentialError(blockIndex, previousBlockIndex);
|
|
206
210
|
}
|
|
207
211
|
|
|
208
|
-
|
|
209
|
-
let previousBlock: L2Block | undefined = undefined;
|
|
210
|
-
for (const block of blocks) {
|
|
211
|
-
if (!opts.force && previousBlock) {
|
|
212
|
-
if (previousBlock.number + 1 !== block.number) {
|
|
213
|
-
throw new BlockNumberNotSequentialError(block.number, previousBlock.number);
|
|
214
|
-
}
|
|
215
|
-
if (previousBlock.indexWithinCheckpoint + 1 !== block.indexWithinCheckpoint) {
|
|
216
|
-
throw new BlockIndexNotSequentialError(block.indexWithinCheckpoint, previousBlock.indexWithinCheckpoint);
|
|
217
|
-
}
|
|
218
|
-
if (!previousBlock.archive.root.equals(block.header.lastArchive.root)) {
|
|
219
|
-
throw new BlockArchiveNotConsistentError(
|
|
220
|
-
block.number,
|
|
221
|
-
previousBlock.number,
|
|
222
|
-
block.header.lastArchive.root,
|
|
223
|
-
previousBlock.archive.root,
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
if (!opts.force && firstBlockCheckpointNumber !== block.checkpointNumber) {
|
|
228
|
-
throw new CheckpointNumberNotConsistentError(block.checkpointNumber, firstBlockCheckpointNumber);
|
|
229
|
-
}
|
|
230
|
-
previousBlock = block;
|
|
231
|
-
await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
|
|
232
|
-
}
|
|
212
|
+
await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
|
|
233
213
|
|
|
234
214
|
return true;
|
|
235
215
|
});
|
|
@@ -976,6 +956,20 @@ export class BlockStore {
|
|
|
976
956
|
return result;
|
|
977
957
|
}
|
|
978
958
|
|
|
959
|
+
async getFinalizedCheckpointNumber(): Promise<CheckpointNumber> {
|
|
960
|
+
const [latestCheckpointNumber, finalizedCheckpointNumber] = await Promise.all([
|
|
961
|
+
this.getLatestCheckpointNumber(),
|
|
962
|
+
this.#lastFinalizedCheckpoint.getAsync(),
|
|
963
|
+
]);
|
|
964
|
+
return (finalizedCheckpointNumber ?? 0) > latestCheckpointNumber
|
|
965
|
+
? latestCheckpointNumber
|
|
966
|
+
: CheckpointNumber(finalizedCheckpointNumber ?? 0);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
setFinalizedCheckpointNumber(checkpointNumber: CheckpointNumber) {
|
|
970
|
+
return this.#lastFinalizedCheckpoint.set(checkpointNumber);
|
|
971
|
+
}
|
|
972
|
+
|
|
979
973
|
#computeBlockRange(start: BlockNumber, limit: number): Required<Pick<Range<number>, 'start' | 'limit'>> {
|
|
980
974
|
if (limit < 1) {
|
|
981
975
|
throw new Error(`Invalid limit: ${limit}`);
|
|
@@ -22,7 +22,6 @@ import type {
|
|
|
22
22
|
ExecutablePrivateFunctionWithMembershipProof,
|
|
23
23
|
UtilityFunctionWithMembershipProof,
|
|
24
24
|
} from '@aztec/stdlib/contract';
|
|
25
|
-
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
26
25
|
import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
|
|
27
26
|
import type { LogFilter, SiloedTag, Tag, TxScopedL2Log } from '@aztec/stdlib/logs';
|
|
28
27
|
import type { BlockHeader, TxHash, TxReceipt } from '@aztec/stdlib/tx';
|
|
@@ -71,9 +70,8 @@ export class KVArchiverDataStore implements ContractDataSource {
|
|
|
71
70
|
constructor(
|
|
72
71
|
private db: AztecAsyncKVStore,
|
|
73
72
|
logsMaxPageSize: number = 1000,
|
|
74
|
-
l1Constants: Pick<L1RollupConstants, 'epochDuration'>,
|
|
75
73
|
) {
|
|
76
|
-
this.#blockStore = new BlockStore(db
|
|
74
|
+
this.#blockStore = new BlockStore(db);
|
|
77
75
|
this.#logStore = new LogStore(db, this.#blockStore, logsMaxPageSize);
|
|
78
76
|
this.#messageStore = new MessageStore(db);
|
|
79
77
|
this.#contractClassStore = new ContractClassStore(db);
|
|
@@ -246,14 +244,14 @@ export class KVArchiverDataStore implements ContractDataSource {
|
|
|
246
244
|
}
|
|
247
245
|
|
|
248
246
|
/**
|
|
249
|
-
* Append new proposed
|
|
250
|
-
*
|
|
247
|
+
* Append a new proposed block to the store.
|
|
248
|
+
* This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
|
|
251
249
|
* For checkpointed blocks (already published to L1), use addCheckpoints() instead.
|
|
252
|
-
* @param
|
|
250
|
+
* @param block - The proposed L2 block to be added to the store.
|
|
253
251
|
* @returns True if the operation is successful.
|
|
254
252
|
*/
|
|
255
|
-
|
|
256
|
-
return this.#blockStore.
|
|
253
|
+
addProposedBlock(block: L2Block, opts: { force?: boolean } = {}): Promise<boolean> {
|
|
254
|
+
return this.#blockStore.addProposedBlock(block, opts);
|
|
257
255
|
}
|
|
258
256
|
|
|
259
257
|
/**
|
|
@@ -542,6 +540,22 @@ export class KVArchiverDataStore implements ContractDataSource {
|
|
|
542
540
|
await this.#blockStore.setProvenCheckpointNumber(checkpointNumber);
|
|
543
541
|
}
|
|
544
542
|
|
|
543
|
+
/**
|
|
544
|
+
* Gets the number of the latest finalized checkpoint processed.
|
|
545
|
+
* @returns The number of the latest finalized checkpoint processed.
|
|
546
|
+
*/
|
|
547
|
+
getFinalizedCheckpointNumber(): Promise<CheckpointNumber> {
|
|
548
|
+
return this.#blockStore.getFinalizedCheckpointNumber();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Stores the number of the latest finalized checkpoint processed.
|
|
553
|
+
* @param checkpointNumber - The number of the latest finalized checkpoint processed.
|
|
554
|
+
*/
|
|
555
|
+
async setFinalizedCheckpointNumber(checkpointNumber: CheckpointNumber) {
|
|
556
|
+
await this.#blockStore.setFinalizedCheckpointNumber(checkpointNumber);
|
|
557
|
+
}
|
|
558
|
+
|
|
545
559
|
async setBlockSynchedL1BlockNumber(l1BlockNumber: bigint) {
|
|
546
560
|
await this.#blockStore.setSynchedL1BlockNumber(l1BlockNumber);
|
|
547
561
|
}
|
|
@@ -137,7 +137,7 @@ export class MessageStore {
|
|
|
137
137
|
);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
// Check the first message in a
|
|
140
|
+
// Check the first message in a checkpoint has the correct index.
|
|
141
141
|
if (
|
|
142
142
|
(!lastMessage || message.checkpointNumber > lastMessage.checkpointNumber) &&
|
|
143
143
|
message.index !== expectedStart
|
|
@@ -150,8 +150,12 @@ export class FakeL1State {
|
|
|
150
150
|
// Computed from checkpoints based on L1 block visibility
|
|
151
151
|
private pendingCheckpointNumber: CheckpointNumber = CheckpointNumber(0);
|
|
152
152
|
|
|
153
|
+
// The L1 block number reported as "finalized" (defaults to the start block)
|
|
154
|
+
private finalizedL1BlockNumber: bigint;
|
|
155
|
+
|
|
153
156
|
constructor(private readonly config: FakeL1StateConfig) {
|
|
154
157
|
this.l1BlockNumber = config.l1StartBlock;
|
|
158
|
+
this.finalizedL1BlockNumber = config.l1StartBlock;
|
|
155
159
|
this.lastArchive = new AppendOnlyTreeSnapshot(config.genesisArchiveRoot, 1);
|
|
156
160
|
}
|
|
157
161
|
|
|
@@ -283,11 +287,30 @@ export class FakeL1State {
|
|
|
283
287
|
this.updatePendingCheckpointNumber();
|
|
284
288
|
}
|
|
285
289
|
|
|
290
|
+
/** Sets the L1 block number that will be reported as "finalized". */
|
|
291
|
+
setFinalizedL1BlockNumber(blockNumber: bigint): void {
|
|
292
|
+
this.finalizedL1BlockNumber = blockNumber;
|
|
293
|
+
}
|
|
294
|
+
|
|
286
295
|
/** Marks a checkpoint as proven. Updates provenCheckpointNumber. */
|
|
287
296
|
markCheckpointAsProven(checkpointNumber: CheckpointNumber): void {
|
|
288
297
|
this.provenCheckpointNumber = checkpointNumber;
|
|
289
298
|
}
|
|
290
299
|
|
|
300
|
+
/**
|
|
301
|
+
* Simulates what `rollup.getProvenCheckpointNumber({ blockNumber: atL1Block })` would return.
|
|
302
|
+
*/
|
|
303
|
+
getProvenCheckpointNumberAtL1Block(atL1Block: bigint): CheckpointNumber {
|
|
304
|
+
if (this.provenCheckpointNumber === 0) {
|
|
305
|
+
return CheckpointNumber(0);
|
|
306
|
+
}
|
|
307
|
+
const checkpoint = this.checkpoints.find(cp => cp.checkpointNumber === this.provenCheckpointNumber);
|
|
308
|
+
if (checkpoint && checkpoint.l1BlockNumber <= atL1Block) {
|
|
309
|
+
return this.provenCheckpointNumber;
|
|
310
|
+
}
|
|
311
|
+
return CheckpointNumber(0);
|
|
312
|
+
}
|
|
313
|
+
|
|
291
314
|
/** Sets the target committee size for attestation validation. */
|
|
292
315
|
setTargetCommitteeSize(size: number): void {
|
|
293
316
|
this.targetCommitteeSize = size;
|
|
@@ -406,6 +429,11 @@ export class FakeL1State {
|
|
|
406
429
|
});
|
|
407
430
|
});
|
|
408
431
|
|
|
432
|
+
mockRollup.getProvenCheckpointNumber.mockImplementation((options?: { blockNumber?: bigint }) => {
|
|
433
|
+
const atBlock = options?.blockNumber ?? this.l1BlockNumber;
|
|
434
|
+
return Promise.resolve(this.getProvenCheckpointNumberAtL1Block(atBlock));
|
|
435
|
+
});
|
|
436
|
+
|
|
409
437
|
mockRollup.canPruneAtTime.mockImplementation(() => Promise.resolve(this.canPruneResult));
|
|
410
438
|
|
|
411
439
|
// Mock the wrapper method for fetching checkpoint events
|
|
@@ -449,10 +477,13 @@ export class FakeL1State {
|
|
|
449
477
|
publicClient.getChainId.mockResolvedValue(1);
|
|
450
478
|
publicClient.getBlockNumber.mockImplementation(() => Promise.resolve(this.l1BlockNumber));
|
|
451
479
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
480
|
+
publicClient.getBlock.mockImplementation((async (args: { blockNumber?: bigint; blockTag?: string } = {}) => {
|
|
481
|
+
let blockNum: bigint;
|
|
482
|
+
if (args.blockTag === 'finalized') {
|
|
483
|
+
blockNum = this.finalizedL1BlockNumber;
|
|
484
|
+
} else {
|
|
485
|
+
blockNum = args.blockNumber ?? (await publicClient.getBlockNumber());
|
|
486
|
+
}
|
|
456
487
|
return {
|
|
457
488
|
number: blockNum,
|
|
458
489
|
timestamp: BigInt(blockNum) * BigInt(this.config.ethereumSlotDuration) + this.config.l1GenesisTime,
|
|
@@ -42,6 +42,12 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
|
|
|
42
42
|
await this.createCheckpoints(numBlocks, 1);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
public getCheckpointNumber(): Promise<CheckpointNumber> {
|
|
46
|
+
return Promise.resolve(
|
|
47
|
+
this.checkpointList.length === 0 ? CheckpointNumber.ZERO : CheckpointNumber(this.checkpointList.length),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
45
51
|
/** Creates checkpoints, each containing `blocksPerCheckpoint` blocks. */
|
|
46
52
|
public async createCheckpoints(numCheckpoints: number, blocksPerCheckpoint: number = 1) {
|
|
47
53
|
for (let c = 0; c < numCheckpoints; c++) {
|
|
@@ -441,11 +447,11 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
|
|
|
441
447
|
};
|
|
442
448
|
}
|
|
443
449
|
|
|
444
|
-
|
|
450
|
+
getSyncedL2EpochNumber(): Promise<EpochNumber> {
|
|
445
451
|
throw new Error('Method not implemented.');
|
|
446
452
|
}
|
|
447
453
|
|
|
448
|
-
|
|
454
|
+
getSyncedL2SlotNumber(): Promise<SlotNumber> {
|
|
449
455
|
throw new Error('Method not implemented.');
|
|
450
456
|
}
|
|
451
457
|
|