@aztec/archiver 0.0.1-commit.2b2662070 → 0.0.1-commit.2c0ee1788
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 +17 -10
- package/dest/archiver.d.ts.map +1 -1
- package/dest/archiver.js +92 -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 +4 -1
- package/dest/modules/instrumentation.d.ts.map +1 -1
- package/dest/modules/instrumentation.js +5 -0
- 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 +182 -70
- 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 +59 -21
- package/dest/store/block_store.d.ts.map +1 -1
- package/dest/store/block_store.js +181 -66
- 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.js +1 -1
- package/dest/test/fake_l1_state.d.ts +7 -3
- package/dest/test/fake_l1_state.d.ts.map +1 -1
- package/dest/test/fake_l1_state.js +42 -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 -6
- package/package.json +13 -13
- package/src/archiver.ts +108 -50
- 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 +8 -0
- package/src/modules/l1_synchronizer.ts +241 -71
- package/src/modules/validation.ts +8 -7
- package/src/store/block_store.ts +204 -73
- 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 +1 -1
- package/src/test/fake_l1_state.ts +47 -24
- package/src/test/mock_l2_block_source.ts +23 -2
- package/src/test/noop_l1_archiver.ts +9 -6
- 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
|
|
@@ -319,15 +318,15 @@ export class BlockStore {
|
|
|
319
318
|
checkpointNumber: checkpoint.checkpoint.number,
|
|
320
319
|
startBlock: checkpoint.checkpoint.blocks[0].number,
|
|
321
320
|
blockCount: checkpoint.checkpoint.blocks.length,
|
|
321
|
+
feeAssetPriceModifier: checkpoint.checkpoint.feeAssetPriceModifier.toString(),
|
|
322
322
|
});
|
|
323
323
|
|
|
324
324
|
// Update slot-to-checkpoint index
|
|
325
325
|
await this.#slotToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, checkpoint.checkpoint.number);
|
|
326
|
-
}
|
|
327
326
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
327
|
+
// Remove proposed checkpoint if it exists, since L1 is authoritative
|
|
328
|
+
await this.#proposedCheckpoints.delete(checkpoint.checkpoint.number);
|
|
329
|
+
}
|
|
331
330
|
|
|
332
331
|
await this.#lastSynchedL1Block.set(checkpoints[checkpoints.length - 1].l1.blockNumber);
|
|
333
332
|
return true;
|
|
@@ -371,6 +370,7 @@ export class BlockStore {
|
|
|
371
370
|
checkpointNumber: incoming.checkpoint.number,
|
|
372
371
|
startBlock: incoming.checkpoint.blocks[0].number,
|
|
373
372
|
blockCount: incoming.checkpoint.blocks.length,
|
|
373
|
+
feeAssetPriceModifier: incoming.checkpoint.feeAssetPriceModifier.toString(),
|
|
374
374
|
});
|
|
375
375
|
// Update the sync point to reflect the new L1 block
|
|
376
376
|
await this.#lastSynchedL1Block.set(incoming.l1.blockNumber);
|
|
@@ -388,24 +388,27 @@ export class BlockStore {
|
|
|
388
388
|
return undefined;
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
-
|
|
392
|
-
|
|
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) {
|
|
393
397
|
throw new CheckpointNotFoundError(previousCheckpointNumber);
|
|
394
398
|
}
|
|
395
399
|
|
|
396
|
-
const previousBlockNumber = BlockNumber(
|
|
400
|
+
const previousBlockNumber = BlockNumber(predecessor.startBlock + predecessor.blockCount - 1);
|
|
397
401
|
const previousBlock = await this.getBlock(previousBlockNumber);
|
|
398
402
|
if (previousBlock === undefined) {
|
|
399
403
|
throw new BlockNotFoundError(previousBlockNumber);
|
|
400
404
|
}
|
|
401
|
-
|
|
402
405
|
return previousBlock;
|
|
403
406
|
}
|
|
404
407
|
|
|
405
408
|
/**
|
|
406
409
|
* Validates that blocks are sequential, have correct indexes, and chain via archive roots.
|
|
407
410
|
* This is the same validation used for both confirmed checkpoints (addCheckpoints) and
|
|
408
|
-
* proposed checkpoints (
|
|
411
|
+
* proposed checkpoints (addProposedCheckpoint).
|
|
409
412
|
*/
|
|
410
413
|
private validateCheckpointBlocks(blocks: L2Block[], previousBlock: L2Block | undefined): void {
|
|
411
414
|
for (const block of blocks) {
|
|
@@ -532,11 +535,8 @@ export class BlockStore {
|
|
|
532
535
|
this.#log.debug(`Removed checkpoint ${c}`);
|
|
533
536
|
}
|
|
534
537
|
|
|
535
|
-
//
|
|
536
|
-
|
|
537
|
-
if (proposedCheckpointNumber > checkpointNumber) {
|
|
538
|
-
await this.#proposedCheckpoint.delete();
|
|
539
|
-
}
|
|
538
|
+
// Evict all pending checkpoints > checkpointNumber (their base chain no longer exists)
|
|
539
|
+
await this.evictProposedCheckpointsFrom(CheckpointNumber(checkpointNumber + 1));
|
|
540
540
|
|
|
541
541
|
return { blocksRemoved };
|
|
542
542
|
});
|
|
@@ -585,6 +585,7 @@ export class BlockStore {
|
|
|
585
585
|
checkpointNumber: CheckpointNumber(checkpointStorage.checkpointNumber),
|
|
586
586
|
startBlock: BlockNumber(checkpointStorage.startBlock),
|
|
587
587
|
blockCount: checkpointStorage.blockCount,
|
|
588
|
+
feeAssetPriceModifier: BigInt(checkpointStorage.feeAssetPriceModifier),
|
|
588
589
|
l1: L1PublishedData.fromBuffer(checkpointStorage.l1),
|
|
589
590
|
attestations: checkpointStorage.attestations.map(buf => CommitteeAttestation.fromBuffer(buf)),
|
|
590
591
|
};
|
|
@@ -685,44 +686,127 @@ export class BlockStore {
|
|
|
685
686
|
}
|
|
686
687
|
|
|
687
688
|
async hasProposedCheckpoint(): Promise<boolean> {
|
|
688
|
-
const
|
|
689
|
-
return
|
|
689
|
+
const [key] = await toArray(this.#proposedCheckpoints.keysAsync({ limit: 1 }));
|
|
690
|
+
return key !== undefined;
|
|
690
691
|
}
|
|
691
692
|
|
|
692
|
-
/** Deletes
|
|
693
|
-
async
|
|
694
|
-
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
|
+
}
|
|
695
698
|
}
|
|
696
699
|
|
|
697
|
-
/**
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
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
|
+
});
|
|
703
752
|
}
|
|
704
753
|
|
|
705
|
-
/**
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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) {
|
|
709
761
|
return undefined;
|
|
710
762
|
}
|
|
711
|
-
|
|
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;
|
|
712
780
|
}
|
|
713
781
|
|
|
714
782
|
/**
|
|
715
|
-
*
|
|
716
|
-
* -
|
|
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
|
|
717
802
|
* - fallsback to latest confirmed checkpoint otherwise
|
|
718
|
-
* @returns CommonCheckpointData
|
|
719
803
|
*/
|
|
720
|
-
async
|
|
721
|
-
const
|
|
722
|
-
if (!
|
|
804
|
+
async getLastCheckpoint(): Promise<CommonCheckpointData | undefined> {
|
|
805
|
+
const latest = await this.getLastProposedCheckpoint();
|
|
806
|
+
if (!latest) {
|
|
723
807
|
return this.getCheckpointData(await this.getLatestCheckpointNumber());
|
|
724
808
|
}
|
|
725
|
-
return
|
|
809
|
+
return latest;
|
|
726
810
|
}
|
|
727
811
|
|
|
728
812
|
private convertToProposedCheckpointData(stored: ProposedCheckpointStorage): ProposedCheckpointData {
|
|
@@ -743,7 +827,7 @@ export class BlockStore {
|
|
|
743
827
|
* @returns CheckpointNumber
|
|
744
828
|
*/
|
|
745
829
|
async getProposedCheckpointNumber(): Promise<CheckpointNumber> {
|
|
746
|
-
const proposed = await this.
|
|
830
|
+
const proposed = await this.getLastCheckpoint();
|
|
747
831
|
if (!proposed) {
|
|
748
832
|
return await this.getLatestCheckpointNumber();
|
|
749
833
|
}
|
|
@@ -755,7 +839,7 @@ export class BlockStore {
|
|
|
755
839
|
* @returns BlockNumber
|
|
756
840
|
*/
|
|
757
841
|
async getProposedCheckpointL2BlockNumber(): Promise<BlockNumber> {
|
|
758
|
-
const proposed = await this.
|
|
842
|
+
const proposed = await this.getLastCheckpoint();
|
|
759
843
|
if (!proposed) {
|
|
760
844
|
return await this.getCheckpointedL2BlockNumber();
|
|
761
845
|
}
|
|
@@ -789,7 +873,12 @@ export class BlockStore {
|
|
|
789
873
|
* @param limit - The number of blocks to return.
|
|
790
874
|
* @returns The requested L2 blocks
|
|
791
875
|
*/
|
|
792
|
-
|
|
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> {
|
|
793
882
|
const checkpointCache = new Map<CheckpointNumber, CheckpointStorage>();
|
|
794
883
|
for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
|
|
795
884
|
const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage);
|
|
@@ -833,7 +922,12 @@ export class BlockStore {
|
|
|
833
922
|
* @param limit - The number of blocks to return.
|
|
834
923
|
* @returns The requested L2 blocks
|
|
835
924
|
*/
|
|
836
|
-
|
|
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> {
|
|
837
931
|
for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
|
|
838
932
|
const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage);
|
|
839
933
|
if (block) {
|
|
@@ -855,6 +949,33 @@ export class BlockStore {
|
|
|
855
949
|
return this.getBlockDataFromBlockStorage(blockStorage);
|
|
856
950
|
}
|
|
857
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
|
+
|
|
858
979
|
/**
|
|
859
980
|
* Gets block metadata (without tx data) by archive root.
|
|
860
981
|
* @param archive - The archive root of the block to return.
|
|
@@ -947,7 +1068,12 @@ export class BlockStore {
|
|
|
947
1068
|
* @param limit - The number of blocks to return.
|
|
948
1069
|
* @returns The requested L2 block headers
|
|
949
1070
|
*/
|
|
950
|
-
|
|
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> {
|
|
951
1077
|
for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
|
|
952
1078
|
const header = BlockHeader.fromBuffer(blockStorage.header);
|
|
953
1079
|
if (header.getBlockNumber() !== blockNumber) {
|
|
@@ -1136,20 +1262,25 @@ export class BlockStore {
|
|
|
1136
1262
|
return this.#lastSynchedL1Block.set(l1BlockNumber);
|
|
1137
1263
|
}
|
|
1138
1264
|
|
|
1139
|
-
/**
|
|
1140
|
-
*
|
|
1141
|
-
|
|
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) {
|
|
1142
1272
|
return await this.db.transactionAsync(async () => {
|
|
1143
|
-
const current = await this.getProposedCheckpointNumber();
|
|
1144
|
-
if (proposed.checkpointNumber <= current) {
|
|
1145
|
-
throw new ProposedCheckpointStaleError(proposed.checkpointNumber, current);
|
|
1146
|
-
}
|
|
1147
1273
|
const confirmed = await this.getLatestCheckpointNumber();
|
|
1148
|
-
|
|
1149
|
-
|
|
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);
|
|
1150
1281
|
}
|
|
1151
1282
|
|
|
1152
|
-
// Ensure the
|
|
1283
|
+
// Ensure the predecessor block (from pending or confirmed chain) exists
|
|
1153
1284
|
const previousBlock = await this.getPreviousCheckpointBlock(proposed.checkpointNumber);
|
|
1154
1285
|
const blocks: L2Block[] = [];
|
|
1155
1286
|
for (let i = 0; i < proposed.blockCount; i++) {
|
|
@@ -1164,7 +1295,7 @@ export class BlockStore {
|
|
|
1164
1295
|
const archive = blocks[blocks.length - 1].archive;
|
|
1165
1296
|
const checkpointOutHash = Checkpoint.getCheckpointOutHash(blocks);
|
|
1166
1297
|
|
|
1167
|
-
await this.#
|
|
1298
|
+
await this.#proposedCheckpoints.set(proposed.checkpointNumber, {
|
|
1168
1299
|
header: proposed.header.toBuffer(),
|
|
1169
1300
|
archive: archive.toBuffer(),
|
|
1170
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 () => {
|
|
@@ -25,6 +25,49 @@ export class ContractInstanceStore {
|
|
|
25
25
|
this.#contractInstanceUpdates = db.openMap('archiver_contract_instance_updates');
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Adds multiple contract instances to the store.
|
|
30
|
+
* @param data - Contract instances to add.
|
|
31
|
+
* @param blockNumber - L2 block number where the instances were deployed.
|
|
32
|
+
* @returns True if every insert succeeded.
|
|
33
|
+
*/
|
|
34
|
+
async addContractInstances(data: ContractInstanceWithAddress[], blockNumber: number): Promise<boolean> {
|
|
35
|
+
return (await Promise.all(data.map(c => this.addContractInstance(c, blockNumber)))).every(Boolean);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Removes multiple contract instances from the store.
|
|
40
|
+
* @param data - Contract instances to delete.
|
|
41
|
+
* @returns True if every delete succeeded.
|
|
42
|
+
*/
|
|
43
|
+
async deleteContractInstances(data: ContractInstanceWithAddress[]): Promise<boolean> {
|
|
44
|
+
return (await Promise.all(data.map(c => this.deleteContractInstance(c)))).every(Boolean);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Adds multiple contract instance updates to the store.
|
|
49
|
+
* @param data - Contract instance updates to add.
|
|
50
|
+
* @param timestamp - Timestamp at which the updates were scheduled.
|
|
51
|
+
* @returns True if every insert succeeded.
|
|
52
|
+
*/
|
|
53
|
+
async addContractInstanceUpdates(data: ContractInstanceUpdateWithAddress[], timestamp: UInt64): Promise<boolean> {
|
|
54
|
+
return (
|
|
55
|
+
await Promise.all(data.map((update, logIndex) => this.addContractInstanceUpdate(update, timestamp, logIndex)))
|
|
56
|
+
).every(Boolean);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Removes multiple contract instance updates from the store.
|
|
61
|
+
* @param data - Contract instance updates to delete.
|
|
62
|
+
* @param timestamp - Timestamp at which the updates were scheduled.
|
|
63
|
+
* @returns True if every delete succeeded.
|
|
64
|
+
*/
|
|
65
|
+
async deleteContractInstanceUpdates(data: ContractInstanceUpdateWithAddress[], timestamp: UInt64): Promise<boolean> {
|
|
66
|
+
return (
|
|
67
|
+
await Promise.all(data.map((update, logIndex) => this.deleteContractInstanceUpdate(update, timestamp, logIndex)))
|
|
68
|
+
).every(Boolean);
|
|
69
|
+
}
|
|
70
|
+
|
|
28
71
|
addContractInstance(contractInstance: ContractInstanceWithAddress, blockNumber: number): Promise<void> {
|
|
29
72
|
return this.db.transactionAsync(async () => {
|
|
30
73
|
const key = contractInstance.address.toString();
|