@aztec/archiver 0.0.1-commit.0dc957cde → 0.0.1-commit.0ec55a70b
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 +15 -9
- package/dest/archiver.d.ts.map +1 -1
- package/dest/archiver.js +85 -53
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +2 -2
- package/dest/errors.d.ts +16 -5
- package/dest/errors.d.ts.map +1 -1
- package/dest/errors.js +29 -6
- package/dest/factory.d.ts +4 -4
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +11 -9
- package/dest/index.d.ts +8 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +7 -1
- 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/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 +6 -6
- package/dest/modules/data_store_updater.d.ts.map +1 -1
- package/dest/modules/data_store_updater.js +42 -40
- package/dest/modules/l1_synchronizer.d.ts +5 -4
- package/dest/modules/l1_synchronizer.d.ts.map +1 -1
- package/dest/modules/l1_synchronizer.js +79 -54
- 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 +58 -27
- package/dest/store/block_store.d.ts.map +1 -1
- package/dest/store/block_store.js +152 -75
- 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 +2 -1
- package/dest/test/fake_l1_state.d.ts.map +1 -1
- package/dest/test/fake_l1_state.js +25 -8
- 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 +5 -5
- package/package.json +13 -13
- package/src/archiver.ts +91 -49
- package/src/config.ts +2 -1
- package/src/errors.ts +41 -8
- package/src/factory.ts +10 -10
- package/src/index.ts +15 -1
- package/src/l1/calldata_retriever.ts +15 -4
- 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 +46 -38
- package/src/modules/l1_synchronizer.ts +92 -60
- package/src/modules/validation.ts +8 -7
- package/src/store/block_store.ts +159 -80
- 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 +24 -14
- package/src/test/mock_l2_block_source.ts +23 -2
- package/src/test/noop_l1_archiver.ts +6 -6
- package/dest/store/kv_archiver_store.d.ts +0 -383
- package/dest/store/kv_archiver_store.d.ts.map +0 -1
- package/dest/store/kv_archiver_store.js +0 -501
- package/src/store/kv_archiver_store.ts +0 -728
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,
|
|
@@ -56,7 +58,6 @@ import {
|
|
|
56
58
|
ProposedCheckpointArchiveRootMismatchError,
|
|
57
59
|
ProposedCheckpointNotSequentialError,
|
|
58
60
|
ProposedCheckpointPromotionNotSequentialError,
|
|
59
|
-
ProposedCheckpointStaleError,
|
|
60
61
|
} from '../errors.js';
|
|
61
62
|
|
|
62
63
|
export { TxReceipt, type TxEffect, type TxHash } from '@aztec/stdlib/tx';
|
|
@@ -84,6 +85,7 @@ type CommonCheckpointStorage = {
|
|
|
84
85
|
type CheckpointStorage = CommonCheckpointStorage & {
|
|
85
86
|
l1: Buffer;
|
|
86
87
|
attestations: Buffer[];
|
|
88
|
+
feeAssetPriceModifier: string;
|
|
87
89
|
};
|
|
88
90
|
|
|
89
91
|
/** Storage format for a proposed checkpoint (attested but not yet L1-confirmed). */
|
|
@@ -101,7 +103,10 @@ export class BlockStore {
|
|
|
101
103
|
/** Map block number to block data */
|
|
102
104
|
#blocks: AztecAsyncMap<number, BlockStorage>;
|
|
103
105
|
|
|
104
|
-
/** 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 */
|
|
105
110
|
#checkpoints: AztecAsyncMap<number, CheckpointStorage>;
|
|
106
111
|
|
|
107
112
|
/** Map slot number to checkpoint number, for looking up checkpoints by slot range. */
|
|
@@ -134,9 +139,6 @@ export class BlockStore {
|
|
|
134
139
|
/** Index mapping block archive to block number */
|
|
135
140
|
#blockArchiveIndex: AztecAsyncMap<string, number>;
|
|
136
141
|
|
|
137
|
-
/** Singleton: assumes max 1-deep pipeline. For deeper pipelining, replace with a map keyed by checkpoint number. */
|
|
138
|
-
#proposedCheckpoint: AztecAsyncSingleton<ProposedCheckpointStorage>;
|
|
139
|
-
|
|
140
142
|
#log = createLogger('archiver:block_store');
|
|
141
143
|
|
|
142
144
|
constructor(private db: AztecAsyncKVStore) {
|
|
@@ -152,7 +154,7 @@ export class BlockStore {
|
|
|
152
154
|
this.#pendingChainValidationStatus = db.openSingleton('archiver_pending_chain_validation_status');
|
|
153
155
|
this.#checkpoints = db.openMap('archiver_checkpoints');
|
|
154
156
|
this.#slotToCheckpoint = db.openMap('archiver_slot_to_checkpoint');
|
|
155
|
-
this.#
|
|
157
|
+
this.#proposedCheckpoints = db.openMap('archiver_proposed_checkpoints');
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
/**
|
|
@@ -188,8 +190,7 @@ export class BlockStore {
|
|
|
188
190
|
|
|
189
191
|
// Extract the latest block and checkpoint numbers
|
|
190
192
|
const previousBlockNumber = await this.getLatestL2BlockNumber();
|
|
191
|
-
const
|
|
192
|
-
const previousCheckpointNumber = await this.getLatestCheckpointNumber();
|
|
193
|
+
const latestCheckpointNumber = await this.getLatestCheckpointNumber();
|
|
193
194
|
|
|
194
195
|
// Verify we're not overwriting checkpointed blocks
|
|
195
196
|
const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber();
|
|
@@ -207,19 +208,14 @@ export class BlockStore {
|
|
|
207
208
|
throw new BlockNumberNotSequentialError(blockNumber, previousBlockNumber);
|
|
208
209
|
}
|
|
209
210
|
|
|
210
|
-
//
|
|
211
|
-
//
|
|
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.
|
|
212
213
|
const expectedCheckpointNumber = blockCheckpointNumber - 1;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const [reported, source]: [CheckpointNumber, 'confirmed' | 'proposed'] =
|
|
219
|
-
proposedCheckpointNumber > previousCheckpointNumber
|
|
220
|
-
? [proposedCheckpointNumber, 'proposed']
|
|
221
|
-
: [previousCheckpointNumber, 'confirmed'];
|
|
222
|
-
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);
|
|
223
219
|
}
|
|
224
220
|
|
|
225
221
|
// Extract the previous block if there is one and see if it is for the same checkpoint or not
|
|
@@ -322,15 +318,15 @@ export class BlockStore {
|
|
|
322
318
|
checkpointNumber: checkpoint.checkpoint.number,
|
|
323
319
|
startBlock: checkpoint.checkpoint.blocks[0].number,
|
|
324
320
|
blockCount: checkpoint.checkpoint.blocks.length,
|
|
321
|
+
feeAssetPriceModifier: checkpoint.checkpoint.feeAssetPriceModifier.toString(),
|
|
325
322
|
});
|
|
326
323
|
|
|
327
324
|
// Update slot-to-checkpoint index
|
|
328
325
|
await this.#slotToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, checkpoint.checkpoint.number);
|
|
329
|
-
}
|
|
330
326
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
327
|
+
// Remove proposed checkpoint if it exists, since L1 is authoritative
|
|
328
|
+
await this.#proposedCheckpoints.delete(checkpoint.checkpoint.number);
|
|
329
|
+
}
|
|
334
330
|
|
|
335
331
|
await this.#lastSynchedL1Block.set(checkpoints[checkpoints.length - 1].l1.blockNumber);
|
|
336
332
|
return true;
|
|
@@ -374,6 +370,7 @@ export class BlockStore {
|
|
|
374
370
|
checkpointNumber: incoming.checkpoint.number,
|
|
375
371
|
startBlock: incoming.checkpoint.blocks[0].number,
|
|
376
372
|
blockCount: incoming.checkpoint.blocks.length,
|
|
373
|
+
feeAssetPriceModifier: incoming.checkpoint.feeAssetPriceModifier.toString(),
|
|
377
374
|
});
|
|
378
375
|
// Update the sync point to reflect the new L1 block
|
|
379
376
|
await this.#lastSynchedL1Block.set(incoming.l1.blockNumber);
|
|
@@ -391,24 +388,27 @@ export class BlockStore {
|
|
|
391
388
|
return undefined;
|
|
392
389
|
}
|
|
393
390
|
|
|
394
|
-
|
|
395
|
-
|
|
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) {
|
|
396
397
|
throw new CheckpointNotFoundError(previousCheckpointNumber);
|
|
397
398
|
}
|
|
398
399
|
|
|
399
|
-
const previousBlockNumber = BlockNumber(
|
|
400
|
+
const previousBlockNumber = BlockNumber(predecessor.startBlock + predecessor.blockCount - 1);
|
|
400
401
|
const previousBlock = await this.getBlock(previousBlockNumber);
|
|
401
402
|
if (previousBlock === undefined) {
|
|
402
403
|
throw new BlockNotFoundError(previousBlockNumber);
|
|
403
404
|
}
|
|
404
|
-
|
|
405
405
|
return previousBlock;
|
|
406
406
|
}
|
|
407
407
|
|
|
408
408
|
/**
|
|
409
409
|
* Validates that blocks are sequential, have correct indexes, and chain via archive roots.
|
|
410
410
|
* This is the same validation used for both confirmed checkpoints (addCheckpoints) and
|
|
411
|
-
* proposed checkpoints (
|
|
411
|
+
* proposed checkpoints (addProposedCheckpoint).
|
|
412
412
|
*/
|
|
413
413
|
private validateCheckpointBlocks(blocks: L2Block[], previousBlock: L2Block | undefined): void {
|
|
414
414
|
for (const block of blocks) {
|
|
@@ -535,11 +535,8 @@ export class BlockStore {
|
|
|
535
535
|
this.#log.debug(`Removed checkpoint ${c}`);
|
|
536
536
|
}
|
|
537
537
|
|
|
538
|
-
//
|
|
539
|
-
|
|
540
|
-
if (proposedCheckpointNumber > checkpointNumber) {
|
|
541
|
-
await this.#proposedCheckpoint.delete();
|
|
542
|
-
}
|
|
538
|
+
// Evict all pending checkpoints > checkpointNumber (their base chain no longer exists)
|
|
539
|
+
await this.evictProposedCheckpointsFrom(CheckpointNumber(checkpointNumber + 1));
|
|
543
540
|
|
|
544
541
|
return { blocksRemoved };
|
|
545
542
|
});
|
|
@@ -588,6 +585,7 @@ export class BlockStore {
|
|
|
588
585
|
checkpointNumber: CheckpointNumber(checkpointStorage.checkpointNumber),
|
|
589
586
|
startBlock: BlockNumber(checkpointStorage.startBlock),
|
|
590
587
|
blockCount: checkpointStorage.blockCount,
|
|
588
|
+
feeAssetPriceModifier: BigInt(checkpointStorage.feeAssetPriceModifier),
|
|
591
589
|
l1: L1PublishedData.fromBuffer(checkpointStorage.l1),
|
|
592
590
|
attestations: checkpointStorage.attestations.map(buf => CommitteeAttestation.fromBuffer(buf)),
|
|
593
591
|
};
|
|
@@ -688,28 +686,34 @@ export class BlockStore {
|
|
|
688
686
|
}
|
|
689
687
|
|
|
690
688
|
async hasProposedCheckpoint(): Promise<boolean> {
|
|
691
|
-
const
|
|
692
|
-
return
|
|
689
|
+
const [key] = await toArray(this.#proposedCheckpoints.keysAsync({ limit: 1 }));
|
|
690
|
+
return key !== undefined;
|
|
693
691
|
}
|
|
694
692
|
|
|
695
|
-
/** Deletes
|
|
696
|
-
async
|
|
697
|
-
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
|
+
}
|
|
698
698
|
}
|
|
699
699
|
|
|
700
700
|
/**
|
|
701
|
-
* Promotes
|
|
702
|
-
* This persists the checkpoint to the store,
|
|
703
|
-
*
|
|
704
|
-
* @param
|
|
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.
|
|
705
708
|
*/
|
|
706
709
|
async promoteProposedToCheckpointed(
|
|
710
|
+
checkpointNumber: CheckpointNumber,
|
|
707
711
|
l1: L1PublishedData,
|
|
708
712
|
attestations: CommitteeAttestation[],
|
|
709
713
|
expectedArchiveRoot: Fr,
|
|
710
714
|
): Promise<void> {
|
|
711
715
|
return await this.db.transactionAsync(async () => {
|
|
712
|
-
const proposed = await this.
|
|
716
|
+
const proposed = await this.getProposedCheckpointByNumber(checkpointNumber);
|
|
713
717
|
if (!proposed) {
|
|
714
718
|
throw new NoProposedCheckpointToPromoteError();
|
|
715
719
|
}
|
|
@@ -733,48 +737,76 @@ export class BlockStore {
|
|
|
733
737
|
checkpointNumber: proposed.checkpointNumber,
|
|
734
738
|
startBlock: proposed.startBlock,
|
|
735
739
|
blockCount: proposed.blockCount,
|
|
740
|
+
feeAssetPriceModifier: proposed.feeAssetPriceModifier.toString(),
|
|
736
741
|
});
|
|
737
742
|
|
|
738
743
|
// Update the slot-to-checkpoint index
|
|
739
744
|
await this.#slotToCheckpoint.set(proposed.header.slotNumber, proposed.checkpointNumber);
|
|
740
745
|
|
|
741
|
-
//
|
|
742
|
-
await this.#
|
|
746
|
+
// Remove only this pending entry — remaining entries N+1, N+2, ... stay valid
|
|
747
|
+
await this.#proposedCheckpoints.delete(proposed.checkpointNumber);
|
|
743
748
|
|
|
744
749
|
// Update the last synced L1 block
|
|
745
750
|
await this.#lastSynchedL1Block.set(l1.blockNumber);
|
|
746
751
|
});
|
|
747
752
|
}
|
|
748
753
|
|
|
749
|
-
/**
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
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) {
|
|
761
|
+
return undefined;
|
|
754
762
|
}
|
|
763
|
+
const stored = await this.#proposedCheckpoints.getAsync(key);
|
|
764
|
+
return stored ? this.convertToProposedCheckpointData(stored) : undefined;
|
|
755
765
|
}
|
|
756
766
|
|
|
757
|
-
/** Returns the
|
|
758
|
-
async
|
|
759
|
-
const stored = await this.#
|
|
760
|
-
|
|
761
|
-
|
|
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;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
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);
|
|
762
796
|
}
|
|
763
|
-
return this.convertToProposedCheckpointData(stored);
|
|
764
797
|
}
|
|
765
798
|
|
|
766
799
|
/**
|
|
767
|
-
* Gets the checkpoint at the proposed tip
|
|
768
|
-
* - pending checkpoint if
|
|
800
|
+
* Gets the checkpoint at the proposed tip:
|
|
801
|
+
* - latest pending checkpoint if any exist
|
|
769
802
|
* - fallsback to latest confirmed checkpoint otherwise
|
|
770
|
-
* @returns CommonCheckpointData
|
|
771
803
|
*/
|
|
772
|
-
async
|
|
773
|
-
const
|
|
774
|
-
if (!
|
|
804
|
+
async getLastCheckpoint(): Promise<CommonCheckpointData | undefined> {
|
|
805
|
+
const latest = await this.getLastProposedCheckpoint();
|
|
806
|
+
if (!latest) {
|
|
775
807
|
return this.getCheckpointData(await this.getLatestCheckpointNumber());
|
|
776
808
|
}
|
|
777
|
-
return
|
|
809
|
+
return latest;
|
|
778
810
|
}
|
|
779
811
|
|
|
780
812
|
private convertToProposedCheckpointData(stored: ProposedCheckpointStorage): ProposedCheckpointData {
|
|
@@ -795,7 +827,7 @@ export class BlockStore {
|
|
|
795
827
|
* @returns CheckpointNumber
|
|
796
828
|
*/
|
|
797
829
|
async getProposedCheckpointNumber(): Promise<CheckpointNumber> {
|
|
798
|
-
const proposed = await this.
|
|
830
|
+
const proposed = await this.getLastCheckpoint();
|
|
799
831
|
if (!proposed) {
|
|
800
832
|
return await this.getLatestCheckpointNumber();
|
|
801
833
|
}
|
|
@@ -807,7 +839,7 @@ export class BlockStore {
|
|
|
807
839
|
* @returns BlockNumber
|
|
808
840
|
*/
|
|
809
841
|
async getProposedCheckpointL2BlockNumber(): Promise<BlockNumber> {
|
|
810
|
-
const proposed = await this.
|
|
842
|
+
const proposed = await this.getLastCheckpoint();
|
|
811
843
|
if (!proposed) {
|
|
812
844
|
return await this.getCheckpointedL2BlockNumber();
|
|
813
845
|
}
|
|
@@ -841,7 +873,12 @@ export class BlockStore {
|
|
|
841
873
|
* @param limit - The number of blocks to return.
|
|
842
874
|
* @returns The requested L2 blocks
|
|
843
875
|
*/
|
|
844
|
-
|
|
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> {
|
|
845
882
|
const checkpointCache = new Map<CheckpointNumber, CheckpointStorage>();
|
|
846
883
|
for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
|
|
847
884
|
const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage);
|
|
@@ -885,7 +922,12 @@ export class BlockStore {
|
|
|
885
922
|
* @param limit - The number of blocks to return.
|
|
886
923
|
* @returns The requested L2 blocks
|
|
887
924
|
*/
|
|
888
|
-
|
|
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> {
|
|
889
931
|
for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
|
|
890
932
|
const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage);
|
|
891
933
|
if (block) {
|
|
@@ -907,6 +949,33 @@ export class BlockStore {
|
|
|
907
949
|
return this.getBlockDataFromBlockStorage(blockStorage);
|
|
908
950
|
}
|
|
909
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
|
+
|
|
910
979
|
/**
|
|
911
980
|
* Gets block metadata (without tx data) by archive root.
|
|
912
981
|
* @param archive - The archive root of the block to return.
|
|
@@ -999,7 +1068,12 @@ export class BlockStore {
|
|
|
999
1068
|
* @param limit - The number of blocks to return.
|
|
1000
1069
|
* @returns The requested L2 block headers
|
|
1001
1070
|
*/
|
|
1002
|
-
|
|
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> {
|
|
1003
1077
|
for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
|
|
1004
1078
|
const header = BlockHeader.fromBuffer(blockStorage.header);
|
|
1005
1079
|
if (header.getBlockNumber() !== blockNumber) {
|
|
@@ -1188,20 +1262,25 @@ export class BlockStore {
|
|
|
1188
1262
|
return this.#lastSynchedL1Block.set(l1BlockNumber);
|
|
1189
1263
|
}
|
|
1190
1264
|
|
|
1191
|
-
/**
|
|
1192
|
-
*
|
|
1193
|
-
|
|
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) {
|
|
1194
1272
|
return await this.db.transactionAsync(async () => {
|
|
1195
|
-
const current = await this.getProposedCheckpointNumber();
|
|
1196
|
-
if (proposed.checkpointNumber <= current) {
|
|
1197
|
-
throw new ProposedCheckpointStaleError(proposed.checkpointNumber, current);
|
|
1198
|
-
}
|
|
1199
1273
|
const confirmed = await this.getLatestCheckpointNumber();
|
|
1200
|
-
|
|
1201
|
-
|
|
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);
|
|
1202
1281
|
}
|
|
1203
1282
|
|
|
1204
|
-
// Ensure the
|
|
1283
|
+
// Ensure the predecessor block (from pending or confirmed chain) exists
|
|
1205
1284
|
const previousBlock = await this.getPreviousCheckpointBlock(proposed.checkpointNumber);
|
|
1206
1285
|
const blocks: L2Block[] = [];
|
|
1207
1286
|
for (let i = 0; i < proposed.blockCount; i++) {
|
|
@@ -1216,7 +1295,7 @@ export class BlockStore {
|
|
|
1216
1295
|
const archive = blocks[blocks.length - 1].archive;
|
|
1217
1296
|
const checkpointOutHash = Checkpoint.getCheckpointOutHash(blocks);
|
|
1218
1297
|
|
|
1219
|
-
await this.#
|
|
1298
|
+
await this.#proposedCheckpoints.set(proposed.checkpointNumber, {
|
|
1220
1299
|
header: proposed.header.toBuffer(),
|
|
1221
1300
|
archive: archive.toBuffer(),
|
|
1222
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();
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { L1BlockId } from '@aztec/ethereum/l1-types';
|
|
2
|
+
import type { AztecAsyncKVStore } from '@aztec/kv-store';
|
|
3
|
+
import type { ContractDataSource } from '@aztec/stdlib/contract';
|
|
4
|
+
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
|
|
7
|
+
import { ArchiverContractDataSourceAdapter } from '../modules/contract_data_source_adapter.js';
|
|
8
|
+
import { BlockStore } from './block_store.js';
|
|
9
|
+
import { ContractClassStore } from './contract_class_store.js';
|
|
10
|
+
import { ContractInstanceStore } from './contract_instance_store.js';
|
|
11
|
+
import { FunctionNamesCache } from './function_names_cache.js';
|
|
12
|
+
import { LogStore } from './log_store.js';
|
|
13
|
+
import { MessageStore } from './message_store.js';
|
|
14
|
+
|
|
15
|
+
export const ARCHIVER_DB_VERSION = 6;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Represents the latest L1 block processed by the archiver for various objects in L2.
|
|
19
|
+
*/
|
|
20
|
+
export type ArchiverL1SynchPoint = {
|
|
21
|
+
/** Number of the last L1 block that added a new L2 checkpoint metadata. */
|
|
22
|
+
blocksSynchedTo?: bigint;
|
|
23
|
+
/** Last L1 block checked for L1 to L2 messages. */
|
|
24
|
+
messagesSynchedTo?: L1BlockId;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Bundle of archiver-owned LMDB substores plus the in-memory caches that span them.
|
|
29
|
+
*
|
|
30
|
+
* Replaces the former `KVArchiverDataStore` pass-through wrapper. Callers reach into
|
|
31
|
+
* the relevant substore directly (e.g. `stores.blocks.getBlock`) and use
|
|
32
|
+
* {@link createArchiverDataStores} to wire them up against a shared KV store.
|
|
33
|
+
*/
|
|
34
|
+
export type ArchiverDataStores = {
|
|
35
|
+
/** The underlying key-value store. Use {@link AztecAsyncKVStore.transactionAsync} to compose updates atomically. */
|
|
36
|
+
db: AztecAsyncKVStore;
|
|
37
|
+
/** Blocks, checkpoints, tx effects, proven/finalized state. */
|
|
38
|
+
blocks: BlockStore;
|
|
39
|
+
/** Public, private and contract class logs. */
|
|
40
|
+
logs: LogStore;
|
|
41
|
+
/** L1 to L2 messages and message sync state. */
|
|
42
|
+
messages: MessageStore;
|
|
43
|
+
/** Contract classes (with bytecode commitments). */
|
|
44
|
+
contractClasses: ContractClassStore;
|
|
45
|
+
/** Contract instances and contract instance updates. */
|
|
46
|
+
contractInstances: ContractInstanceStore;
|
|
47
|
+
/** In-memory cache of public function selectors -> names. */
|
|
48
|
+
functionNames: FunctionNamesCache;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** Options used by {@link createArchiverDataStores}. */
|
|
52
|
+
export type CreateArchiverDataStoresOptions = {
|
|
53
|
+
/** Maximum number of logs returned per page when paginating tagged log queries. */
|
|
54
|
+
logsMaxPageSize?: number;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Wires up the archiver substores against a shared KV store and returns the
|
|
59
|
+
* {@link ArchiverDataStores} bundle.
|
|
60
|
+
*/
|
|
61
|
+
export function createArchiverDataStores(
|
|
62
|
+
db: AztecAsyncKVStore,
|
|
63
|
+
opts: CreateArchiverDataStoresOptions = {},
|
|
64
|
+
): ArchiverDataStores {
|
|
65
|
+
const blocks = new BlockStore(db);
|
|
66
|
+
return {
|
|
67
|
+
db,
|
|
68
|
+
blocks,
|
|
69
|
+
logs: new LogStore(db, blocks, opts.logsMaxPageSize ?? 1000),
|
|
70
|
+
messages: new MessageStore(db),
|
|
71
|
+
contractClasses: new ContractClassStore(db),
|
|
72
|
+
contractInstances: new ContractInstanceStore(db),
|
|
73
|
+
functionNames: new FunctionNamesCache(),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Returns the L1 sync point of the archiver, combining the block sync point from {@link BlockStore}
|
|
79
|
+
* and the message sync point from {@link MessageStore}.
|
|
80
|
+
*/
|
|
81
|
+
export async function getArchiverSynchPoint(stores: ArchiverDataStores): Promise<ArchiverL1SynchPoint> {
|
|
82
|
+
const [blocksSynchedTo, messagesSynchedTo] = await Promise.all([
|
|
83
|
+
stores.blocks.getSynchedL1BlockNumber(),
|
|
84
|
+
stores.messages.getSynchedL1Block(),
|
|
85
|
+
]);
|
|
86
|
+
return { blocksSynchedTo, messagesSynchedTo };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Backs up the underlying KV store to the given folder. Returns the path to the resulting db file.
|
|
91
|
+
*/
|
|
92
|
+
export async function backupArchiverDataStores(
|
|
93
|
+
stores: ArchiverDataStores,
|
|
94
|
+
path: string,
|
|
95
|
+
compress = true,
|
|
96
|
+
): Promise<string> {
|
|
97
|
+
await stores.db.backupTo(path, compress);
|
|
98
|
+
return join(path, 'data.mdb');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Returns a {@link ContractDataSource} adapter over {@link ArchiverDataStores}.
|
|
103
|
+
* Used by contexts (e.g. offline epoch re-prover tools) that need a ContractDataSource
|
|
104
|
+
* but do not need a full archiver instance.
|
|
105
|
+
*/
|
|
106
|
+
export function createContractDataSource(stores: ArchiverDataStores): ContractDataSource {
|
|
107
|
+
return new ArchiverContractDataSourceAdapter(stores);
|
|
108
|
+
}
|