@aztec/archiver 0.0.1-commit.e588bc7e5 → 0.0.1-commit.e5a3663dd
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 +19 -11
- package/dest/archiver.d.ts.map +1 -1
- package/dest/archiver.js +96 -53
- package/dest/config.d.ts +3 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +14 -3
- package/dest/errors.d.ts +32 -5
- package/dest/errors.d.ts.map +1 -1
- package/dest/errors.js +51 -6
- package/dest/factory.d.ts +4 -4
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +13 -10
- package/dest/index.d.ts +10 -3
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +9 -2
- package/dest/l1/calldata_retriever.d.ts +2 -1
- package/dest/l1/calldata_retriever.d.ts.map +1 -1
- package/dest/l1/calldata_retriever.js +9 -4
- package/dest/l1/data_retrieval.d.ts +18 -9
- package/dest/l1/data_retrieval.d.ts.map +1 -1
- package/dest/l1/data_retrieval.js +13 -19
- package/dest/l1/validate_historical_logs.d.ts +23 -0
- package/dest/l1/validate_historical_logs.d.ts.map +1 -0
- package/dest/l1/validate_historical_logs.js +108 -0
- package/dest/modules/contract_data_source_adapter.d.ts +25 -0
- package/dest/modules/contract_data_source_adapter.d.ts.map +1 -0
- package/dest/modules/contract_data_source_adapter.js +42 -0
- package/dest/modules/data_source_base.d.ts +16 -10
- package/dest/modules/data_source_base.d.ts.map +1 -1
- package/dest/modules/data_source_base.js +71 -60
- package/dest/modules/data_store_updater.d.ts +16 -9
- package/dest/modules/data_store_updater.d.ts.map +1 -1
- package/dest/modules/data_store_updater.js +52 -40
- package/dest/modules/instrumentation.d.ts +7 -2
- package/dest/modules/instrumentation.d.ts.map +1 -1
- package/dest/modules/instrumentation.js +22 -6
- package/dest/modules/l1_synchronizer.d.ts +8 -4
- package/dest/modules/l1_synchronizer.d.ts.map +1 -1
- package/dest/modules/l1_synchronizer.js +212 -79
- package/dest/modules/validation.d.ts +4 -3
- package/dest/modules/validation.d.ts.map +1 -1
- package/dest/modules/validation.js +4 -4
- package/dest/store/block_store.d.ts +60 -21
- package/dest/store/block_store.d.ts.map +1 -1
- package/dest/store/block_store.js +229 -70
- package/dest/store/contract_class_store.d.ts +17 -3
- package/dest/store/contract_class_store.d.ts.map +1 -1
- package/dest/store/contract_class_store.js +17 -1
- package/dest/store/contract_instance_store.d.ts +28 -1
- package/dest/store/contract_instance_store.d.ts.map +1 -1
- package/dest/store/contract_instance_store.js +31 -0
- package/dest/store/data_stores.d.ts +68 -0
- package/dest/store/data_stores.d.ts.map +1 -0
- package/dest/store/data_stores.js +50 -0
- package/dest/store/function_names_cache.d.ts +17 -0
- package/dest/store/function_names_cache.d.ts.map +1 -0
- package/dest/store/function_names_cache.js +30 -0
- package/dest/store/l2_tips_cache.d.ts +1 -1
- package/dest/store/l2_tips_cache.d.ts.map +1 -1
- package/dest/store/l2_tips_cache.js +3 -3
- package/dest/store/log_store.d.ts +1 -1
- package/dest/store/log_store.d.ts.map +1 -1
- package/dest/store/log_store.js +2 -4
- package/dest/store/message_store.d.ts +9 -3
- package/dest/store/message_store.d.ts.map +1 -1
- package/dest/store/message_store.js +31 -1
- package/dest/test/fake_l1_state.d.ts +14 -3
- package/dest/test/fake_l1_state.d.ts.map +1 -1
- package/dest/test/fake_l1_state.js +55 -15
- package/dest/test/mock_l2_block_source.d.ts +12 -3
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +24 -2
- package/dest/test/noop_l1_archiver.d.ts +4 -4
- package/dest/test/noop_l1_archiver.d.ts.map +1 -1
- package/dest/test/noop_l1_archiver.js +9 -7
- package/package.json +13 -13
- package/src/archiver.ts +113 -52
- package/src/config.ts +15 -1
- package/src/errors.ts +75 -8
- package/src/factory.ts +11 -10
- package/src/index.ts +17 -2
- package/src/l1/calldata_retriever.ts +15 -4
- package/src/l1/data_retrieval.ts +30 -35
- package/src/l1/validate_historical_logs.ts +140 -0
- package/src/modules/contract_data_source_adapter.ts +59 -0
- package/src/modules/data_source_base.ts +75 -57
- package/src/modules/data_store_updater.ts +71 -39
- package/src/modules/instrumentation.ts +27 -7
- package/src/modules/l1_synchronizer.ts +301 -83
- package/src/modules/validation.ts +8 -7
- package/src/store/block_store.ts +264 -77
- package/src/store/contract_class_store.ts +28 -2
- package/src/store/contract_instance_store.ts +43 -0
- package/src/store/data_stores.ts +108 -0
- package/src/store/function_names_cache.ts +37 -0
- package/src/store/l2_tips_cache.ts +9 -3
- package/src/store/log_store.ts +2 -5
- package/src/store/message_store.ts +35 -2
- package/src/test/fake_l1_state.ts +62 -24
- package/src/test/mock_l2_block_source.ts +23 -2
- package/src/test/noop_l1_archiver.ts +9 -7
- package/dest/store/kv_archiver_store.d.ts +0 -377
- package/dest/store/kv_archiver_store.d.ts.map +0 -1
- package/dest/store/kv_archiver_store.js +0 -494
- package/src/store/kv_archiver_store.ts +0 -713
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from '@aztec/stdlib/block';
|
|
11
11
|
import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
12
12
|
import { type L1RollupConstants, computeQuorum, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
13
|
-
import { ConsensusPayload } from '@aztec/stdlib/p2p';
|
|
13
|
+
import { ConsensusPayload, type CoordinationSignatureContext } from '@aztec/stdlib/p2p';
|
|
14
14
|
|
|
15
15
|
export type { ValidateCheckpointResult };
|
|
16
16
|
|
|
@@ -18,11 +18,11 @@ export type { ValidateCheckpointResult };
|
|
|
18
18
|
* Extracts attestation information from a published checkpoint.
|
|
19
19
|
* Returns info for each attestation, preserving array indices.
|
|
20
20
|
*/
|
|
21
|
-
export function getAttestationInfoFromPublishedCheckpoint(
|
|
22
|
-
checkpoint,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const payload = ConsensusPayload.fromCheckpoint(checkpoint);
|
|
21
|
+
export function getAttestationInfoFromPublishedCheckpoint(
|
|
22
|
+
{ checkpoint, attestations }: PublishedCheckpoint,
|
|
23
|
+
signatureContext: CoordinationSignatureContext,
|
|
24
|
+
): AttestationInfo[] {
|
|
25
|
+
const payload = ConsensusPayload.fromCheckpoint(checkpoint, signatureContext);
|
|
26
26
|
return getAttestationInfoFromPayload(payload, attestations);
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -34,9 +34,10 @@ export async function validateCheckpointAttestations(
|
|
|
34
34
|
publishedCheckpoint: PublishedCheckpoint,
|
|
35
35
|
epochCache: EpochCache,
|
|
36
36
|
constants: Pick<L1RollupConstants, 'epochDuration'>,
|
|
37
|
+
signatureContext: CoordinationSignatureContext,
|
|
37
38
|
logger?: Logger,
|
|
38
39
|
): Promise<ValidateCheckpointResult> {
|
|
39
|
-
const attestorInfos = getAttestationInfoFromPublishedCheckpoint(publishedCheckpoint);
|
|
40
|
+
const attestorInfos = getAttestationInfoFromPublishedCheckpoint(publishedCheckpoint, signatureContext);
|
|
40
41
|
const attestors = compactArray(attestorInfos.map(info => ('address' in info ? info.address : undefined)));
|
|
41
42
|
const { checkpoint, attestations } = publishedCheckpoint;
|
|
42
43
|
const headerHash = checkpoint.header.hash();
|
package/src/store/block_store.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncSingleton, Range } fro
|
|
|
10
10
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
11
11
|
import {
|
|
12
12
|
type BlockData,
|
|
13
|
+
type BlockDataWithCheckpointContext,
|
|
13
14
|
BlockHash,
|
|
14
15
|
Body,
|
|
15
16
|
CheckpointedL2Block,
|
|
@@ -45,6 +46,7 @@ import {
|
|
|
45
46
|
import {
|
|
46
47
|
BlockAlreadyCheckpointedError,
|
|
47
48
|
BlockArchiveNotConsistentError,
|
|
49
|
+
BlockCheckpointNumberNotSequentialError,
|
|
48
50
|
BlockIndexNotSequentialError,
|
|
49
51
|
BlockNotFoundError,
|
|
50
52
|
BlockNumberNotSequentialError,
|
|
@@ -52,8 +54,10 @@ import {
|
|
|
52
54
|
CheckpointNotFoundError,
|
|
53
55
|
CheckpointNumberNotSequentialError,
|
|
54
56
|
InitialCheckpointNumberNotSequentialError,
|
|
57
|
+
NoProposedCheckpointToPromoteError,
|
|
58
|
+
ProposedCheckpointArchiveRootMismatchError,
|
|
55
59
|
ProposedCheckpointNotSequentialError,
|
|
56
|
-
|
|
60
|
+
ProposedCheckpointPromotionNotSequentialError,
|
|
57
61
|
} from '../errors.js';
|
|
58
62
|
|
|
59
63
|
export { TxReceipt, type TxEffect, type TxHash } from '@aztec/stdlib/tx';
|
|
@@ -81,6 +85,7 @@ type CommonCheckpointStorage = {
|
|
|
81
85
|
type CheckpointStorage = CommonCheckpointStorage & {
|
|
82
86
|
l1: Buffer;
|
|
83
87
|
attestations: Buffer[];
|
|
88
|
+
feeAssetPriceModifier: string;
|
|
84
89
|
};
|
|
85
90
|
|
|
86
91
|
/** Storage format for a proposed checkpoint (attested but not yet L1-confirmed). */
|
|
@@ -98,7 +103,10 @@ export class BlockStore {
|
|
|
98
103
|
/** Map block number to block data */
|
|
99
104
|
#blocks: AztecAsyncMap<number, BlockStorage>;
|
|
100
105
|
|
|
101
|
-
/** Map checkpoint number
|
|
106
|
+
/** Map keyed by checkpoint number holding proposed (locally-validated, not yet L1-confirmed) checkpoints. */
|
|
107
|
+
#proposedCheckpoints: AztecAsyncMap<number, ProposedCheckpointStorage>;
|
|
108
|
+
|
|
109
|
+
/** Map checkpoint number to checkpoint data for mined checkpoints only */
|
|
102
110
|
#checkpoints: AztecAsyncMap<number, CheckpointStorage>;
|
|
103
111
|
|
|
104
112
|
/** Map slot number to checkpoint number, for looking up checkpoints by slot range. */
|
|
@@ -131,9 +139,6 @@ export class BlockStore {
|
|
|
131
139
|
/** Index mapping block archive to block number */
|
|
132
140
|
#blockArchiveIndex: AztecAsyncMap<string, number>;
|
|
133
141
|
|
|
134
|
-
/** Singleton: assumes max 1-deep pipeline. For deeper pipelining, replace with a map keyed by checkpoint number. */
|
|
135
|
-
#proposedCheckpoint: AztecAsyncSingleton<ProposedCheckpointStorage>;
|
|
136
|
-
|
|
137
142
|
#log = createLogger('archiver:block_store');
|
|
138
143
|
|
|
139
144
|
constructor(private db: AztecAsyncKVStore) {
|
|
@@ -149,7 +154,7 @@ export class BlockStore {
|
|
|
149
154
|
this.#pendingChainValidationStatus = db.openSingleton('archiver_pending_chain_validation_status');
|
|
150
155
|
this.#checkpoints = db.openMap('archiver_checkpoints');
|
|
151
156
|
this.#slotToCheckpoint = db.openMap('archiver_slot_to_checkpoint');
|
|
152
|
-
this.#
|
|
157
|
+
this.#proposedCheckpoints = db.openMap('archiver_proposed_checkpoints');
|
|
153
158
|
}
|
|
154
159
|
|
|
155
160
|
/**
|
|
@@ -185,8 +190,7 @@ export class BlockStore {
|
|
|
185
190
|
|
|
186
191
|
// Extract the latest block and checkpoint numbers
|
|
187
192
|
const previousBlockNumber = await this.getLatestL2BlockNumber();
|
|
188
|
-
const
|
|
189
|
-
const previousCheckpointNumber = await this.getLatestCheckpointNumber();
|
|
193
|
+
const latestCheckpointNumber = await this.getLatestCheckpointNumber();
|
|
190
194
|
|
|
191
195
|
// Verify we're not overwriting checkpointed blocks
|
|
192
196
|
const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber();
|
|
@@ -204,19 +208,14 @@ export class BlockStore {
|
|
|
204
208
|
throw new BlockNumberNotSequentialError(blockNumber, previousBlockNumber);
|
|
205
209
|
}
|
|
206
210
|
|
|
207
|
-
//
|
|
208
|
-
//
|
|
211
|
+
// Accept the block if either the confirmed checkpoint or a pending checkpoint matches
|
|
212
|
+
// the expected predecessor. We look for a pending entry at exactly blockCheckpointNumber - 1.
|
|
209
213
|
const expectedCheckpointNumber = blockCheckpointNumber - 1;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const [reported, source]: [CheckpointNumber, 'confirmed' | 'proposed'] =
|
|
216
|
-
proposedCheckpointNumber > previousCheckpointNumber
|
|
217
|
-
? [proposedCheckpointNumber, 'proposed']
|
|
218
|
-
: [previousCheckpointNumber, 'confirmed'];
|
|
219
|
-
throw new CheckpointNumberNotSequentialError(blockCheckpointNumber, reported, source);
|
|
214
|
+
const hasPendingAtExpected = await this.#proposedCheckpoints.hasAsync(expectedCheckpointNumber);
|
|
215
|
+
if (!opts.force && latestCheckpointNumber !== expectedCheckpointNumber && !hasPendingAtExpected) {
|
|
216
|
+
const [latestPendingKey] = await toArray(this.#proposedCheckpoints.keysAsync({ reverse: true, limit: 1 }));
|
|
217
|
+
const previous = CheckpointNumber(Math.max(latestCheckpointNumber, latestPendingKey ?? 0));
|
|
218
|
+
throw new BlockCheckpointNumberNotSequentialError(blockNumber, blockCheckpointNumber, previous);
|
|
220
219
|
}
|
|
221
220
|
|
|
222
221
|
// Extract the previous block if there is one and see if it is for the same checkpoint or not
|
|
@@ -262,16 +261,28 @@ export class BlockStore {
|
|
|
262
261
|
}
|
|
263
262
|
|
|
264
263
|
return await this.db.transactionAsync(async () => {
|
|
265
|
-
// Check that the checkpoint immediately before the first block to be added is present in the store.
|
|
266
264
|
const firstCheckpointNumber = checkpoints[0].checkpoint.number;
|
|
267
265
|
const previousCheckpointNumber = await this.getLatestCheckpointNumber();
|
|
268
266
|
|
|
269
|
-
|
|
267
|
+
// Handle already-stored checkpoints at the start of the batch.
|
|
268
|
+
// This can happen after an L1 reorg re-includes a checkpoint in a different L1 block.
|
|
269
|
+
// We accept them if archives match (same content) and update their L1 metadata.
|
|
270
|
+
if (!opts.force && firstCheckpointNumber <= previousCheckpointNumber) {
|
|
271
|
+
checkpoints = await this.skipOrUpdateAlreadyStoredCheckpoints(checkpoints, previousCheckpointNumber);
|
|
272
|
+
if (checkpoints.length === 0) {
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
// Re-check sequentiality after skipping
|
|
276
|
+
const newFirstNumber = checkpoints[0].checkpoint.number;
|
|
277
|
+
if (previousCheckpointNumber !== newFirstNumber - 1) {
|
|
278
|
+
throw new InitialCheckpointNumberNotSequentialError(newFirstNumber, previousCheckpointNumber);
|
|
279
|
+
}
|
|
280
|
+
} else if (previousCheckpointNumber !== firstCheckpointNumber - 1 && !opts.force) {
|
|
270
281
|
throw new InitialCheckpointNumberNotSequentialError(firstCheckpointNumber, previousCheckpointNumber);
|
|
271
282
|
}
|
|
272
283
|
|
|
273
284
|
// Get the last block of the previous checkpoint for archive chaining
|
|
274
|
-
let previousBlock = await this.getPreviousCheckpointBlock(
|
|
285
|
+
let previousBlock = await this.getPreviousCheckpointBlock(checkpoints[0].checkpoint.number);
|
|
275
286
|
|
|
276
287
|
// Iterate over checkpoints array and insert them, checking that the block numbers are sequential.
|
|
277
288
|
let previousCheckpoint: PublishedCheckpoint | undefined = undefined;
|
|
@@ -307,21 +318,66 @@ export class BlockStore {
|
|
|
307
318
|
checkpointNumber: checkpoint.checkpoint.number,
|
|
308
319
|
startBlock: checkpoint.checkpoint.blocks[0].number,
|
|
309
320
|
blockCount: checkpoint.checkpoint.blocks.length,
|
|
321
|
+
feeAssetPriceModifier: checkpoint.checkpoint.feeAssetPriceModifier.toString(),
|
|
310
322
|
});
|
|
311
323
|
|
|
312
324
|
// Update slot-to-checkpoint index
|
|
313
325
|
await this.#slotToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, checkpoint.checkpoint.number);
|
|
314
|
-
}
|
|
315
326
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
327
|
+
// Remove proposed checkpoint if it exists, since L1 is authoritative
|
|
328
|
+
await this.#proposedCheckpoints.delete(checkpoint.checkpoint.number);
|
|
329
|
+
}
|
|
319
330
|
|
|
320
331
|
await this.#lastSynchedL1Block.set(checkpoints[checkpoints.length - 1].l1.blockNumber);
|
|
321
332
|
return true;
|
|
322
333
|
});
|
|
323
334
|
}
|
|
324
335
|
|
|
336
|
+
/**
|
|
337
|
+
* Handles checkpoints at the start of a batch that are already stored (e.g. due to L1 reorg).
|
|
338
|
+
* Verifies the archive root matches, updates L1 metadata, and returns only the new checkpoints.
|
|
339
|
+
*/
|
|
340
|
+
private async skipOrUpdateAlreadyStoredCheckpoints(
|
|
341
|
+
checkpoints: PublishedCheckpoint[],
|
|
342
|
+
latestStored: CheckpointNumber,
|
|
343
|
+
): Promise<PublishedCheckpoint[]> {
|
|
344
|
+
let i = 0;
|
|
345
|
+
for (; i < checkpoints.length && checkpoints[i].checkpoint.number <= latestStored; i++) {
|
|
346
|
+
const incoming = checkpoints[i];
|
|
347
|
+
const stored = await this.getCheckpointData(incoming.checkpoint.number);
|
|
348
|
+
if (!stored) {
|
|
349
|
+
// Should not happen if latestStored is correct, but be safe
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
// Verify the checkpoint content matches (archive root)
|
|
353
|
+
if (!stored.archive.root.equals(incoming.checkpoint.archive.root)) {
|
|
354
|
+
throw new Error(
|
|
355
|
+
`Checkpoint ${incoming.checkpoint.number} already exists in store but with a different archive root. ` +
|
|
356
|
+
`Stored: ${stored.archive.root}, incoming: ${incoming.checkpoint.archive.root}`,
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
// Update L1 metadata and attestations for the already-stored checkpoint
|
|
360
|
+
this.#log.warn(
|
|
361
|
+
`Checkpoint ${incoming.checkpoint.number} already stored, updating L1 info ` +
|
|
362
|
+
`(L1 block ${stored.l1.blockNumber} -> ${incoming.l1.blockNumber})`,
|
|
363
|
+
);
|
|
364
|
+
await this.#checkpoints.set(incoming.checkpoint.number, {
|
|
365
|
+
header: incoming.checkpoint.header.toBuffer(),
|
|
366
|
+
archive: incoming.checkpoint.archive.toBuffer(),
|
|
367
|
+
checkpointOutHash: incoming.checkpoint.getCheckpointOutHash().toBuffer(),
|
|
368
|
+
l1: incoming.l1.toBuffer(),
|
|
369
|
+
attestations: incoming.attestations.map(a => a.toBuffer()),
|
|
370
|
+
checkpointNumber: incoming.checkpoint.number,
|
|
371
|
+
startBlock: incoming.checkpoint.blocks[0].number,
|
|
372
|
+
blockCount: incoming.checkpoint.blocks.length,
|
|
373
|
+
feeAssetPriceModifier: incoming.checkpoint.feeAssetPriceModifier.toString(),
|
|
374
|
+
});
|
|
375
|
+
// Update the sync point to reflect the new L1 block
|
|
376
|
+
await this.#lastSynchedL1Block.set(incoming.l1.blockNumber);
|
|
377
|
+
}
|
|
378
|
+
return checkpoints.slice(i);
|
|
379
|
+
}
|
|
380
|
+
|
|
325
381
|
/**
|
|
326
382
|
* Gets the last block of the checkpoint before the given one.
|
|
327
383
|
* Returns undefined if there is no previous checkpoint (i.e. genesis).
|
|
@@ -332,24 +388,27 @@ export class BlockStore {
|
|
|
332
388
|
return undefined;
|
|
333
389
|
}
|
|
334
390
|
|
|
335
|
-
|
|
336
|
-
|
|
391
|
+
// Check across both proposed and mined checkpoints
|
|
392
|
+
const predecessor =
|
|
393
|
+
(await this.getProposedCheckpointByNumber(previousCheckpointNumber)) ??
|
|
394
|
+
(await this.getCheckpointData(previousCheckpointNumber));
|
|
395
|
+
|
|
396
|
+
if (!predecessor) {
|
|
337
397
|
throw new CheckpointNotFoundError(previousCheckpointNumber);
|
|
338
398
|
}
|
|
339
399
|
|
|
340
|
-
const previousBlockNumber = BlockNumber(
|
|
400
|
+
const previousBlockNumber = BlockNumber(predecessor.startBlock + predecessor.blockCount - 1);
|
|
341
401
|
const previousBlock = await this.getBlock(previousBlockNumber);
|
|
342
402
|
if (previousBlock === undefined) {
|
|
343
403
|
throw new BlockNotFoundError(previousBlockNumber);
|
|
344
404
|
}
|
|
345
|
-
|
|
346
405
|
return previousBlock;
|
|
347
406
|
}
|
|
348
407
|
|
|
349
408
|
/**
|
|
350
409
|
* Validates that blocks are sequential, have correct indexes, and chain via archive roots.
|
|
351
410
|
* This is the same validation used for both confirmed checkpoints (addCheckpoints) and
|
|
352
|
-
* proposed checkpoints (
|
|
411
|
+
* proposed checkpoints (addProposedCheckpoint).
|
|
353
412
|
*/
|
|
354
413
|
private validateCheckpointBlocks(blocks: L2Block[], previousBlock: L2Block | undefined): void {
|
|
355
414
|
for (const block of blocks) {
|
|
@@ -476,11 +535,8 @@ export class BlockStore {
|
|
|
476
535
|
this.#log.debug(`Removed checkpoint ${c}`);
|
|
477
536
|
}
|
|
478
537
|
|
|
479
|
-
//
|
|
480
|
-
|
|
481
|
-
if (proposedCheckpointNumber > checkpointNumber) {
|
|
482
|
-
await this.#proposedCheckpoint.delete();
|
|
483
|
-
}
|
|
538
|
+
// Evict all pending checkpoints > checkpointNumber (their base chain no longer exists)
|
|
539
|
+
await this.evictProposedCheckpointsFrom(CheckpointNumber(checkpointNumber + 1));
|
|
484
540
|
|
|
485
541
|
return { blocksRemoved };
|
|
486
542
|
});
|
|
@@ -529,6 +585,7 @@ export class BlockStore {
|
|
|
529
585
|
checkpointNumber: CheckpointNumber(checkpointStorage.checkpointNumber),
|
|
530
586
|
startBlock: BlockNumber(checkpointStorage.startBlock),
|
|
531
587
|
blockCount: checkpointStorage.blockCount,
|
|
588
|
+
feeAssetPriceModifier: BigInt(checkpointStorage.feeAssetPriceModifier),
|
|
532
589
|
l1: L1PublishedData.fromBuffer(checkpointStorage.l1),
|
|
533
590
|
attestations: checkpointStorage.attestations.map(buf => CommitteeAttestation.fromBuffer(buf)),
|
|
534
591
|
};
|
|
@@ -629,44 +686,127 @@ export class BlockStore {
|
|
|
629
686
|
}
|
|
630
687
|
|
|
631
688
|
async hasProposedCheckpoint(): Promise<boolean> {
|
|
632
|
-
const
|
|
633
|
-
return
|
|
689
|
+
const [key] = await toArray(this.#proposedCheckpoints.keysAsync({ limit: 1 }));
|
|
690
|
+
return key !== undefined;
|
|
634
691
|
}
|
|
635
692
|
|
|
636
|
-
/** Deletes
|
|
637
|
-
async
|
|
638
|
-
await this.#
|
|
693
|
+
/** Deletes all pending proposed checkpoints from storage. */
|
|
694
|
+
async deleteProposedCheckpoints(): Promise<void> {
|
|
695
|
+
for await (const key of this.#proposedCheckpoints.keysAsync()) {
|
|
696
|
+
await this.#proposedCheckpoints.delete(key);
|
|
697
|
+
}
|
|
639
698
|
}
|
|
640
699
|
|
|
641
|
-
/**
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
700
|
+
/**
|
|
701
|
+
* Promotes a specific pending checkpoint to a confirmed checkpoint entry.
|
|
702
|
+
* This persists the checkpoint to the store, removes only that pending entry, and updates the L1 sync point.
|
|
703
|
+
* Remaining pending entries (e.g. N+1, N+2) are left intact — they chain off the just-promoted one.
|
|
704
|
+
* @param checkpointNumber - The checkpoint number to promote.
|
|
705
|
+
* @param l1 - L1 published data for the checkpoint.
|
|
706
|
+
* @param attestations - Committee attestations.
|
|
707
|
+
* @param expectedArchiveRoot - Archive root guard against races.
|
|
708
|
+
*/
|
|
709
|
+
async promoteProposedToCheckpointed(
|
|
710
|
+
checkpointNumber: CheckpointNumber,
|
|
711
|
+
l1: L1PublishedData,
|
|
712
|
+
attestations: CommitteeAttestation[],
|
|
713
|
+
expectedArchiveRoot: Fr,
|
|
714
|
+
): Promise<void> {
|
|
715
|
+
return await this.db.transactionAsync(async () => {
|
|
716
|
+
const proposed = await this.getProposedCheckpointByNumber(checkpointNumber);
|
|
717
|
+
if (!proposed) {
|
|
718
|
+
throw new NoProposedCheckpointToPromoteError();
|
|
719
|
+
}
|
|
720
|
+
if (!proposed.archive.root.equals(expectedArchiveRoot)) {
|
|
721
|
+
throw new ProposedCheckpointArchiveRootMismatchError(expectedArchiveRoot, proposed.archive.root);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Verify sequentiality: promoted checkpoint must follow the latest confirmed one
|
|
725
|
+
const latestCheckpointNumber = await this.getLatestCheckpointNumber();
|
|
726
|
+
if (latestCheckpointNumber !== proposed.checkpointNumber - 1) {
|
|
727
|
+
throw new ProposedCheckpointPromotionNotSequentialError(proposed.checkpointNumber, latestCheckpointNumber);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Write the checkpoint entry
|
|
731
|
+
await this.#checkpoints.set(proposed.checkpointNumber, {
|
|
732
|
+
header: proposed.header.toBuffer(),
|
|
733
|
+
archive: proposed.archive.toBuffer(),
|
|
734
|
+
checkpointOutHash: proposed.checkpointOutHash.toBuffer(),
|
|
735
|
+
l1: l1.toBuffer(),
|
|
736
|
+
attestations: attestations.map(attestation => attestation.toBuffer()),
|
|
737
|
+
checkpointNumber: proposed.checkpointNumber,
|
|
738
|
+
startBlock: proposed.startBlock,
|
|
739
|
+
blockCount: proposed.blockCount,
|
|
740
|
+
feeAssetPriceModifier: proposed.feeAssetPriceModifier.toString(),
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
// Update the slot-to-checkpoint index
|
|
744
|
+
await this.#slotToCheckpoint.set(proposed.header.slotNumber, proposed.checkpointNumber);
|
|
745
|
+
|
|
746
|
+
// Remove only this pending entry — remaining entries N+1, N+2, ... stay valid
|
|
747
|
+
await this.#proposedCheckpoints.delete(proposed.checkpointNumber);
|
|
748
|
+
|
|
749
|
+
// Update the last synced L1 block
|
|
750
|
+
await this.#lastSynchedL1Block.set(l1.blockNumber);
|
|
751
|
+
});
|
|
647
752
|
}
|
|
648
753
|
|
|
649
|
-
/**
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
754
|
+
/**
|
|
755
|
+
* Returns the latest pending checkpoint (highest-numbered entry), or undefined if none.
|
|
756
|
+
* No fallback to confirmed.
|
|
757
|
+
*/
|
|
758
|
+
async getLastProposedCheckpoint(): Promise<ProposedCheckpointData | undefined> {
|
|
759
|
+
const [key] = await toArray(this.#proposedCheckpoints.keysAsync({ reverse: true, limit: 1 }));
|
|
760
|
+
if (key === undefined) {
|
|
653
761
|
return undefined;
|
|
654
762
|
}
|
|
655
|
-
|
|
763
|
+
const stored = await this.#proposedCheckpoints.getAsync(key);
|
|
764
|
+
return stored ? this.convertToProposedCheckpointData(stored) : undefined;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/** Returns the pending checkpoint for a specific checkpoint number, or undefined if not found. */
|
|
768
|
+
async getProposedCheckpointByNumber(n: CheckpointNumber): Promise<ProposedCheckpointData | undefined> {
|
|
769
|
+
const stored = await this.#proposedCheckpoints.getAsync(n);
|
|
770
|
+
return stored ? this.convertToProposedCheckpointData(stored) : undefined;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/** Returns all pending checkpoints in ascending checkpoint-number order. */
|
|
774
|
+
async getProposedCheckpoints(): Promise<ProposedCheckpointData[]> {
|
|
775
|
+
const results: ProposedCheckpointData[] = [];
|
|
776
|
+
for await (const [, stored] of this.#proposedCheckpoints.entriesAsync()) {
|
|
777
|
+
results.push(this.convertToProposedCheckpointData(stored));
|
|
778
|
+
}
|
|
779
|
+
return results;
|
|
656
780
|
}
|
|
657
781
|
|
|
658
782
|
/**
|
|
659
|
-
*
|
|
660
|
-
* -
|
|
783
|
+
* Evicts all pending checkpoints with checkpoint number >= fromNumber.
|
|
784
|
+
* Used for divergent-mined-checkpoint cleanup: when L1 mines checkpoint N with a different archive,
|
|
785
|
+
* all pending >= N must be evicted since they chain off the now-invalid pending N.
|
|
786
|
+
*/
|
|
787
|
+
async evictProposedCheckpointsFrom(fromNumber: CheckpointNumber): Promise<void> {
|
|
788
|
+
const keysToDelete: number[] = [];
|
|
789
|
+
for await (const key of this.#proposedCheckpoints.keysAsync()) {
|
|
790
|
+
if (key >= fromNumber) {
|
|
791
|
+
keysToDelete.push(key);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
for (const key of keysToDelete) {
|
|
795
|
+
await this.#proposedCheckpoints.delete(key);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Gets the checkpoint at the proposed tip:
|
|
801
|
+
* - latest pending checkpoint if any exist
|
|
661
802
|
* - fallsback to latest confirmed checkpoint otherwise
|
|
662
|
-
* @returns CommonCheckpointData
|
|
663
803
|
*/
|
|
664
|
-
async
|
|
665
|
-
const
|
|
666
|
-
if (!
|
|
804
|
+
async getLastCheckpoint(): Promise<CommonCheckpointData | undefined> {
|
|
805
|
+
const latest = await this.getLastProposedCheckpoint();
|
|
806
|
+
if (!latest) {
|
|
667
807
|
return this.getCheckpointData(await this.getLatestCheckpointNumber());
|
|
668
808
|
}
|
|
669
|
-
return
|
|
809
|
+
return latest;
|
|
670
810
|
}
|
|
671
811
|
|
|
672
812
|
private convertToProposedCheckpointData(stored: ProposedCheckpointStorage): ProposedCheckpointData {
|
|
@@ -687,7 +827,7 @@ export class BlockStore {
|
|
|
687
827
|
* @returns CheckpointNumber
|
|
688
828
|
*/
|
|
689
829
|
async getProposedCheckpointNumber(): Promise<CheckpointNumber> {
|
|
690
|
-
const proposed = await this.
|
|
830
|
+
const proposed = await this.getLastCheckpoint();
|
|
691
831
|
if (!proposed) {
|
|
692
832
|
return await this.getLatestCheckpointNumber();
|
|
693
833
|
}
|
|
@@ -699,7 +839,7 @@ export class BlockStore {
|
|
|
699
839
|
* @returns BlockNumber
|
|
700
840
|
*/
|
|
701
841
|
async getProposedCheckpointL2BlockNumber(): Promise<BlockNumber> {
|
|
702
|
-
const proposed = await this.
|
|
842
|
+
const proposed = await this.getLastCheckpoint();
|
|
703
843
|
if (!proposed) {
|
|
704
844
|
return await this.getCheckpointedL2BlockNumber();
|
|
705
845
|
}
|
|
@@ -733,7 +873,12 @@ export class BlockStore {
|
|
|
733
873
|
* @param limit - The number of blocks to return.
|
|
734
874
|
* @returns The requested L2 blocks
|
|
735
875
|
*/
|
|
736
|
-
|
|
876
|
+
getCheckpointedBlocks(start: BlockNumber, limit: number): Promise<CheckpointedL2Block[]> {
|
|
877
|
+
return toArray(this.iterateCheckpointedBlocks(start, limit));
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/** Async iterator variant of {@link getCheckpointedBlocks}. */
|
|
881
|
+
async *iterateCheckpointedBlocks(start: BlockNumber, limit: number): AsyncIterableIterator<CheckpointedL2Block> {
|
|
737
882
|
const checkpointCache = new Map<CheckpointNumber, CheckpointStorage>();
|
|
738
883
|
for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
|
|
739
884
|
const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage);
|
|
@@ -777,7 +922,12 @@ export class BlockStore {
|
|
|
777
922
|
* @param limit - The number of blocks to return.
|
|
778
923
|
* @returns The requested L2 blocks
|
|
779
924
|
*/
|
|
780
|
-
|
|
925
|
+
getBlocks(start: BlockNumber, limit: number): Promise<L2Block[]> {
|
|
926
|
+
return toArray(this.iterateBlocks(start, limit));
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/** Async iterator variant of {@link getBlocks}. */
|
|
930
|
+
async *iterateBlocks(start: BlockNumber, limit: number): AsyncIterableIterator<L2Block> {
|
|
781
931
|
for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
|
|
782
932
|
const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage);
|
|
783
933
|
if (block) {
|
|
@@ -799,6 +949,33 @@ export class BlockStore {
|
|
|
799
949
|
return this.getBlockDataFromBlockStorage(blockStorage);
|
|
800
950
|
}
|
|
801
951
|
|
|
952
|
+
/**
|
|
953
|
+
* Gets block metadata plus checkpoint-derived context (L1 publish info, attestations) without
|
|
954
|
+
* deserializing tx bodies. When the block's containing checkpoint has not yet been L1-confirmed,
|
|
955
|
+
* `checkpoint` and `l1` are `undefined` and `attestations` is empty.
|
|
956
|
+
*/
|
|
957
|
+
async getBlockDataWithCheckpointContext(
|
|
958
|
+
blockNumber: BlockNumber,
|
|
959
|
+
): Promise<BlockDataWithCheckpointContext | undefined> {
|
|
960
|
+
const blockStorage = await this.#blocks.getAsync(blockNumber);
|
|
961
|
+
if (!blockStorage || !blockStorage.header) {
|
|
962
|
+
return undefined;
|
|
963
|
+
}
|
|
964
|
+
const data = this.getBlockDataFromBlockStorage(blockStorage);
|
|
965
|
+
const checkpointStorage = await this.#checkpoints.getAsync(blockStorage.checkpointNumber);
|
|
966
|
+
if (!checkpointStorage) {
|
|
967
|
+
return { data, checkpoint: undefined, l1: undefined, attestations: [] };
|
|
968
|
+
}
|
|
969
|
+
const checkpoint = this.checkpointDataFromCheckpointStorage(checkpointStorage);
|
|
970
|
+
return { data, checkpoint, l1: checkpoint.l1, attestations: checkpoint.attestations };
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/** Returns the checkpoint number that contains the given slot (or undefined if not found). */
|
|
974
|
+
async getCheckpointNumberBySlot(slot: SlotNumber): Promise<CheckpointNumber | undefined> {
|
|
975
|
+
const checkpointNumber = await this.#slotToCheckpoint.getAsync(slot);
|
|
976
|
+
return checkpointNumber === undefined ? undefined : CheckpointNumber(checkpointNumber);
|
|
977
|
+
}
|
|
978
|
+
|
|
802
979
|
/**
|
|
803
980
|
* Gets block metadata (without tx data) by archive root.
|
|
804
981
|
* @param archive - The archive root of the block to return.
|
|
@@ -891,7 +1068,12 @@ export class BlockStore {
|
|
|
891
1068
|
* @param limit - The number of blocks to return.
|
|
892
1069
|
* @returns The requested L2 block headers
|
|
893
1070
|
*/
|
|
894
|
-
|
|
1071
|
+
getBlockHeaders(start: BlockNumber, limit: number): Promise<BlockHeader[]> {
|
|
1072
|
+
return toArray(this.iterateBlockHeaders(start, limit));
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/** Async iterator variant of {@link getBlockHeaders}. */
|
|
1076
|
+
async *iterateBlockHeaders(start: BlockNumber, limit: number): AsyncIterableIterator<BlockHeader> {
|
|
895
1077
|
for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
|
|
896
1078
|
const header = BlockHeader.fromBuffer(blockStorage.header);
|
|
897
1079
|
if (header.getBlockNumber() !== blockNumber) {
|
|
@@ -920,7 +1102,7 @@ export class BlockStore {
|
|
|
920
1102
|
return {
|
|
921
1103
|
header: BlockHeader.fromBuffer(blockStorage.header),
|
|
922
1104
|
archive: AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive),
|
|
923
|
-
blockHash:
|
|
1105
|
+
blockHash: BlockHash.fromBuffer(blockStorage.blockHash),
|
|
924
1106
|
checkpointNumber: CheckpointNumber(blockStorage.checkpointNumber),
|
|
925
1107
|
indexWithinCheckpoint: IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
|
|
926
1108
|
};
|
|
@@ -1080,20 +1262,25 @@ export class BlockStore {
|
|
|
1080
1262
|
return this.#lastSynchedL1Block.set(l1BlockNumber);
|
|
1081
1263
|
}
|
|
1082
1264
|
|
|
1083
|
-
/**
|
|
1084
|
-
*
|
|
1085
|
-
|
|
1265
|
+
/**
|
|
1266
|
+
* Adds a proposed checkpoint to the pending queue.
|
|
1267
|
+
* Accepts proposed.checkpointNumber === latestTip + 1, where latestTip is the highest of
|
|
1268
|
+
* confirmed and the highest pending checkpoint number.
|
|
1269
|
+
* Computes archive and checkpointOutHash from the stored blocks.
|
|
1270
|
+
*/
|
|
1271
|
+
async addProposedCheckpoint(proposed: ProposedCheckpointInput) {
|
|
1086
1272
|
return await this.db.transactionAsync(async () => {
|
|
1087
|
-
const current = await this.getProposedCheckpointNumber();
|
|
1088
|
-
if (proposed.checkpointNumber <= current) {
|
|
1089
|
-
throw new ProposedCheckpointStaleError(proposed.checkpointNumber, current);
|
|
1090
|
-
}
|
|
1091
1273
|
const confirmed = await this.getLatestCheckpointNumber();
|
|
1092
|
-
|
|
1093
|
-
|
|
1274
|
+
const [latestPendingKey] = await toArray(this.#proposedCheckpoints.keysAsync({ reverse: true, limit: 1 }));
|
|
1275
|
+
const latestTip = CheckpointNumber(
|
|
1276
|
+
latestPendingKey !== undefined ? Math.max(latestPendingKey, confirmed) : confirmed,
|
|
1277
|
+
);
|
|
1278
|
+
|
|
1279
|
+
if (proposed.checkpointNumber !== latestTip + 1) {
|
|
1280
|
+
throw new ProposedCheckpointNotSequentialError(proposed.checkpointNumber, latestTip);
|
|
1094
1281
|
}
|
|
1095
1282
|
|
|
1096
|
-
// Ensure the
|
|
1283
|
+
// Ensure the predecessor block (from pending or confirmed chain) exists
|
|
1097
1284
|
const previousBlock = await this.getPreviousCheckpointBlock(proposed.checkpointNumber);
|
|
1098
1285
|
const blocks: L2Block[] = [];
|
|
1099
1286
|
for (let i = 0; i < proposed.blockCount; i++) {
|
|
@@ -1108,7 +1295,7 @@ export class BlockStore {
|
|
|
1108
1295
|
const archive = blocks[blocks.length - 1].archive;
|
|
1109
1296
|
const checkpointOutHash = Checkpoint.getCheckpointOutHash(blocks);
|
|
1110
1297
|
|
|
1111
|
-
await this.#
|
|
1298
|
+
await this.#proposedCheckpoints.set(proposed.checkpointNumber, {
|
|
1112
1299
|
header: proposed.header.toBuffer(),
|
|
1113
1300
|
archive: archive.toBuffer(),
|
|
1114
1301
|
checkpointOutHash: checkpointOutHash.toBuffer(),
|
|
@@ -2,7 +2,11 @@ import { Fr } from '@aztec/foundation/curves/bn254';
|
|
|
2
2
|
import { toArray } from '@aztec/foundation/iterable';
|
|
3
3
|
import { BufferReader, numToUInt8, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
4
4
|
import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
ContractClassPublic,
|
|
7
|
+
ContractClassPublicWithBlockNumber,
|
|
8
|
+
ContractClassPublicWithCommitment,
|
|
9
|
+
} from '@aztec/stdlib/contract';
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* LMDB-based contract class storage for the archiver.
|
|
@@ -16,6 +20,28 @@ export class ContractClassStore {
|
|
|
16
20
|
this.#bytecodeCommitments = db.openMap('archiver_bytecode_commitments');
|
|
17
21
|
}
|
|
18
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Adds multiple contract classes to the store.
|
|
25
|
+
* @param data - Contract classes (with bytecode commitments) to add.
|
|
26
|
+
* @param blockNumber - L2 block number where the classes were registered.
|
|
27
|
+
* @returns True if every insert succeeded.
|
|
28
|
+
*/
|
|
29
|
+
async addContractClasses(data: ContractClassPublicWithCommitment[], blockNumber: number): Promise<boolean> {
|
|
30
|
+
return (await Promise.all(data.map(c => this.addContractClass(c, c.publicBytecodeCommitment, blockNumber)))).every(
|
|
31
|
+
Boolean,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Removes multiple contract classes from the store, but only if they were registered at or after the given block.
|
|
37
|
+
* @param data - Contract classes to delete.
|
|
38
|
+
* @param blockNumber - Lower bound on the block number at which the classes were registered.
|
|
39
|
+
* @returns True if every delete succeeded.
|
|
40
|
+
*/
|
|
41
|
+
async deleteContractClasses(data: ContractClassPublic[], blockNumber: number): Promise<boolean> {
|
|
42
|
+
return (await Promise.all(data.map(c => this.deleteContractClass(c, blockNumber)))).every(Boolean);
|
|
43
|
+
}
|
|
44
|
+
|
|
19
45
|
async addContractClass(
|
|
20
46
|
contractClass: ContractClassPublic,
|
|
21
47
|
bytecodeCommitment: Fr,
|
|
@@ -34,7 +60,7 @@ export class ContractClassStore {
|
|
|
34
60
|
});
|
|
35
61
|
}
|
|
36
62
|
|
|
37
|
-
async
|
|
63
|
+
async deleteContractClass(contractClass: ContractClassPublic, blockNumber: number): Promise<void> {
|
|
38
64
|
const restoredContractClass = await this.#contractClasses.getAsync(contractClass.id.toString());
|
|
39
65
|
if (restoredContractClass && deserializeContractClassPublic(restoredContractClass).l2BlockNumber >= blockNumber) {
|
|
40
66
|
await this.db.transactionAsync(async () => {
|