@aztec/world-state 0.0.1-commit.b655e406 → 0.0.1-commit.d3ec352c
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/index.d.ts +1 -1
- package/dest/instrumentation/instrumentation.d.ts +1 -1
- package/dest/instrumentation/instrumentation.d.ts.map +1 -1
- package/dest/native/bench_metrics.d.ts +1 -1
- package/dest/native/bench_metrics.d.ts.map +1 -1
- package/dest/native/fork_checkpoint.d.ts +1 -1
- package/dest/native/fork_checkpoint.d.ts.map +1 -1
- package/dest/native/index.d.ts +1 -1
- package/dest/native/merkle_trees_facade.d.ts +5 -4
- package/dest/native/merkle_trees_facade.d.ts.map +1 -1
- package/dest/native/merkle_trees_facade.js +4 -2
- package/dest/native/message.d.ts +11 -10
- package/dest/native/message.d.ts.map +1 -1
- package/dest/native/message.js +13 -12
- package/dest/native/native_world_state.d.ts +9 -8
- package/dest/native/native_world_state.d.ts.map +1 -1
- package/dest/native/native_world_state.js +11 -8
- package/dest/native/native_world_state_instance.d.ts +10 -1
- package/dest/native/native_world_state_instance.d.ts.map +1 -1
- package/dest/native/native_world_state_instance.js +21 -0
- package/dest/native/world_state_ops_queue.d.ts +1 -1
- package/dest/native/world_state_ops_queue.d.ts.map +1 -1
- package/dest/synchronizer/config.d.ts +1 -1
- package/dest/synchronizer/errors.d.ts +1 -1
- package/dest/synchronizer/errors.d.ts.map +1 -1
- package/dest/synchronizer/factory.d.ts +1 -1
- package/dest/synchronizer/index.d.ts +1 -1
- package/dest/synchronizer/server_world_state_synchronizer.d.ts +7 -25
- package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
- package/dest/synchronizer/server_world_state_synchronizer.js +35 -52
- package/dest/synchronizer/utils.d.ts +11 -0
- package/dest/synchronizer/utils.d.ts.map +1 -0
- package/dest/synchronizer/utils.js +59 -0
- package/dest/test/index.d.ts +1 -1
- package/dest/test/utils.d.ts +21 -8
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +51 -10
- package/dest/testing.d.ts +1 -1
- package/dest/world-state-db/index.d.ts +1 -1
- package/dest/world-state-db/merkle_tree_db.d.ts +9 -8
- package/dest/world-state-db/merkle_tree_db.d.ts.map +1 -1
- package/package.json +13 -12
- package/src/native/merkle_trees_facade.ts +6 -5
- package/src/native/message.ts +22 -21
- package/src/native/native_world_state.ts +28 -17
- package/src/native/native_world_state_instance.ts +28 -0
- package/src/synchronizer/server_world_state_synchronizer.ts +58 -75
- package/src/synchronizer/utils.ts +83 -0
- package/src/test/utils.ts +66 -13
- package/src/world-state-db/merkle_tree_db.ts +12 -7
|
@@ -16,4 +16,4 @@ export interface WorldStateTreeMapSizes {
|
|
|
16
16
|
}
|
|
17
17
|
export declare function createWorldStateSynchronizer(config: WorldStateConfig & DataStoreConfig, l2BlockSource: L2BlockSource & L1ToL2MessageSource, prefilledPublicData?: PublicDataTreeLeaf[], client?: TelemetryClient): Promise<ServerWorldStateSynchronizer>;
|
|
18
18
|
export declare function createWorldState(config: Pick<WorldStateConfig, 'worldStateDataDirectory' | 'worldStateDbMapSizeKb' | 'archiveTreeMapSizeKb' | 'nullifierTreeMapSizeKb' | 'noteHashTreeMapSizeKb' | 'messageTreeMapSizeKb' | 'publicDataTreeMapSizeKb'> & Pick<DataStoreConfig, 'dataDirectory' | 'dataStoreMapSizeKb' | 'l1Contracts'>, prefilledPublicData?: PublicDataTreeLeaf[], instrumentation?: WorldStateInstrumentation): Promise<NativeWorldStateService>;
|
|
19
|
-
//# sourceMappingURL=
|
|
19
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3N5bmNocm9uaXplci9mYWN0b3J5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzlELE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxFQUFFLG1CQUFtQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDbkUsT0FBTyxLQUFLLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUM5RCxPQUFPLEVBQUUsS0FBSyxlQUFlLEVBQXNCLE1BQU0seUJBQXlCLENBQUM7QUFFbkYsT0FBTyxFQUFFLHlCQUF5QixFQUFFLE1BQU0sdUNBQXVDLENBQUM7QUFDbEYsT0FBTyxFQUFFLHVCQUF1QixFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDMUUsT0FBTyxLQUFLLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDcEQsT0FBTyxFQUFFLDRCQUE0QixFQUFFLE1BQU0sc0NBQXNDLENBQUM7QUFFcEYsTUFBTSxXQUFXLHNCQUFzQjtJQUNyQyxvQkFBb0IsRUFBRSxNQUFNLENBQUM7SUFDN0Isc0JBQXNCLEVBQUUsTUFBTSxDQUFDO0lBQy9CLHFCQUFxQixFQUFFLE1BQU0sQ0FBQztJQUM5QixvQkFBb0IsRUFBRSxNQUFNLENBQUM7SUFDN0IsdUJBQXVCLEVBQUUsTUFBTSxDQUFDO0NBQ2pDO0FBRUQsd0JBQXNCLDRCQUE0QixDQUNoRCxNQUFNLEVBQUUsZ0JBQWdCLEdBQUcsZUFBZSxFQUMxQyxhQUFhLEVBQUUsYUFBYSxHQUFHLG1CQUFtQixFQUNsRCxtQkFBbUIsR0FBRSxrQkFBa0IsRUFBTyxFQUM5QyxNQUFNLEdBQUUsZUFBc0MseUNBSy9DO0FBRUQsd0JBQXNCLGdCQUFnQixDQUNwQyxNQUFNLEVBQUUsSUFBSSxDQUNWLGdCQUFnQixFQUNkLHlCQUF5QixHQUN6Qix1QkFBdUIsR0FDdkIsc0JBQXNCLEdBQ3RCLHdCQUF3QixHQUN4Qix1QkFBdUIsR0FDdkIsc0JBQXNCLEdBQ3RCLHlCQUF5QixDQUM1QixHQUNDLElBQUksQ0FBQyxlQUFlLEVBQUUsZUFBZSxHQUFHLG9CQUFvQixHQUFHLGFBQWEsQ0FBQyxFQUMvRSxtQkFBbUIsR0FBRSxrQkFBa0IsRUFBTyxFQUM5QyxlQUFlLEdBQUUseUJBQStFLG9DQWdDakcifQ==
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export * from './server_world_state_synchronizer.js';
|
|
2
2
|
export * from './factory.js';
|
|
3
|
-
//# sourceMappingURL=
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zeW5jaHJvbml6ZXIvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxzQ0FBc0MsQ0FBQztBQUNyRCxjQUFjLGNBQWMsQ0FBQyJ9
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
2
2
|
import { type Logger } from '@aztec/foundation/log';
|
|
3
3
|
import type { L2BlockSource, L2BlockStream, L2BlockStreamEvent, L2BlockStreamEventHandler, L2BlockStreamLocalDataProvider, L2Tips } from '@aztec/stdlib/block';
|
|
4
4
|
import { type WorldStateSynchronizer, type WorldStateSynchronizerStatus } from '@aztec/stdlib/interfaces/server';
|
|
@@ -30,15 +30,15 @@ export declare class ServerWorldStateSynchronizer implements WorldStateSynchroni
|
|
|
30
30
|
private provenBlockNumber;
|
|
31
31
|
constructor(merkleTreeDb: MerkleTreeAdminDatabase, l2BlockSource: L2BlockSource & L1ToL2MessageSource, config: WorldStateConfig, instrumentation?: WorldStateInstrumentation, log?: Logger);
|
|
32
32
|
getCommitted(): MerkleTreeReadOperations;
|
|
33
|
-
getSnapshot(blockNumber:
|
|
34
|
-
fork(blockNumber?:
|
|
33
|
+
getSnapshot(blockNumber: BlockNumber): MerkleTreeReadOperations;
|
|
34
|
+
fork(blockNumber?: BlockNumber): Promise<MerkleTreeWriteOperations>;
|
|
35
35
|
backupTo(dstPath: string, compact?: boolean): Promise<Record<Exclude<SnapshotDataKeys, 'archiver'>, string>>;
|
|
36
36
|
clear(): Promise<void>;
|
|
37
37
|
start(): Promise<void | import("@aztec/foundation/promise").PromiseWithResolvers<void>>;
|
|
38
38
|
protected createBlockStream(): L2BlockStream;
|
|
39
39
|
stop(): Promise<void>;
|
|
40
40
|
status(): Promise<WorldStateSynchronizerStatus>;
|
|
41
|
-
getLatestBlockNumber(): Promise<
|
|
41
|
+
getLatestBlockNumber(): Promise<BlockNumber>;
|
|
42
42
|
stopSync(): Promise<void>;
|
|
43
43
|
resumeSync(): void;
|
|
44
44
|
/**
|
|
@@ -47,25 +47,14 @@ export declare class ServerWorldStateSynchronizer implements WorldStateSynchroni
|
|
|
47
47
|
* @param skipThrowIfTargetNotReached - Whether to skip throwing if the target block number is not reached.
|
|
48
48
|
* @returns A promise that resolves with the block number the world state was synced to
|
|
49
49
|
*/
|
|
50
|
-
syncImmediate(targetBlockNumber?:
|
|
50
|
+
syncImmediate(targetBlockNumber?: BlockNumber, skipThrowIfTargetNotReached?: boolean): Promise<BlockNumber>;
|
|
51
51
|
/** Returns the L2 block hash for a given number. Used by the L2BlockStream for detecting reorgs. */
|
|
52
|
-
getL2BlockHash(number:
|
|
52
|
+
getL2BlockHash(number: BlockNumber): Promise<string | undefined>;
|
|
53
53
|
/** Returns the latest L2 block number for each tip of the chain (latest, proven, finalized). */
|
|
54
54
|
getL2Tips(): Promise<L2Tips>;
|
|
55
55
|
/** Handles an event emitted by the block stream. */
|
|
56
56
|
handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void>;
|
|
57
|
-
/**
|
|
58
|
-
* Handles a list of L2 blocks (i.e. Inserts the new note hashes into the merkle tree).
|
|
59
|
-
* @param l2Blocks - The L2 blocks to handle.
|
|
60
|
-
* @returns Whether the block handled was produced by this same node.
|
|
61
|
-
*/
|
|
62
57
|
private handleL2Blocks;
|
|
63
|
-
/**
|
|
64
|
-
* Handles a single L2 block (i.e. Inserts the new note hashes into the merkle tree).
|
|
65
|
-
* @param l2Block - The L2 block to handle.
|
|
66
|
-
* @param l1ToL2Messages - The L1 to L2 messages for the block.
|
|
67
|
-
* @returns Whether the block handled was produced by this same node.
|
|
68
|
-
*/
|
|
69
58
|
private handleL2Block;
|
|
70
59
|
private handleChainFinalized;
|
|
71
60
|
private handleChainProven;
|
|
@@ -75,12 +64,5 @@ export declare class ServerWorldStateSynchronizer implements WorldStateSynchroni
|
|
|
75
64
|
* @param newState - New state value.
|
|
76
65
|
*/
|
|
77
66
|
private setCurrentState;
|
|
78
|
-
/**
|
|
79
|
-
* Verifies that the L1 to L2 messages hash to the block inHash.
|
|
80
|
-
* @param l1ToL2Messages - The L1 to L2 messages for the block.
|
|
81
|
-
* @param inHash - The inHash of the block.
|
|
82
|
-
* @throws If the L1 to L2 messages do not hash to the block inHash.
|
|
83
|
-
*/
|
|
84
|
-
protected verifyMessagesHashToInHash(l1ToL2Messages: Fr[], inHash: Fr): Promise<void>;
|
|
85
67
|
}
|
|
86
|
-
//# sourceMappingURL=
|
|
68
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmVyX3dvcmxkX3N0YXRlX3N5bmNocm9uaXplci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3N5bmNocm9uaXplci9zZXJ2ZXJfd29ybGRfc3RhdGVfc3luY2hyb25pemVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUU5RCxPQUFPLEVBQUUsS0FBSyxNQUFNLEVBQWdCLE1BQU0sdUJBQXVCLENBQUM7QUFHbEUsT0FBTyxLQUFLLEVBR1YsYUFBYSxFQUNiLGFBQWEsRUFDYixrQkFBa0IsRUFDbEIseUJBQXlCLEVBQ3pCLDhCQUE4QixFQUM5QixNQUFNLEVBQ1AsTUFBTSxxQkFBcUIsQ0FBQztBQUM3QixPQUFPLEVBR0wsS0FBSyxzQkFBc0IsRUFDM0IsS0FBSyw0QkFBNEIsRUFDbEMsTUFBTSxpQ0FBaUMsQ0FBQztBQUN6QyxPQUFPLEtBQUssRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ25FLE9BQU8sS0FBSyxFQUFFLGdCQUFnQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFFaEUsT0FBTyxFQUFnQixLQUFLLHdCQUF3QixFQUFFLEtBQUsseUJBQXlCLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUdsSCxPQUFPLEVBQUUseUJBQXlCLEVBQUUsTUFBTSx1Q0FBdUMsQ0FBQztBQUVsRixPQUFPLEtBQUssRUFBRSx1QkFBdUIsRUFBRSxNQUFNLHFDQUFxQyxDQUFDO0FBQ25GLE9BQU8sS0FBSyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sYUFBYSxDQUFDO0FBSXBELFlBQVksRUFBRSxnQkFBZ0IsRUFBRSxDQUFDO0FBRWpDOzs7O0dBSUc7QUFDSCxxQkFBYSw0QkFDWCxZQUFXLHNCQUFzQixFQUFFLDhCQUE4QixFQUFFLHlCQUF5QjtJQWlCMUYsT0FBTyxDQUFDLFFBQVEsQ0FBQyxZQUFZO0lBQzdCLE9BQU8sQ0FBQyxRQUFRLENBQUMsYUFBYTtJQUM5QixPQUFPLENBQUMsUUFBUSxDQUFDLE1BQU07SUFDdkIsT0FBTyxDQUFDLGVBQWU7SUFDdkIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHO0lBbkJ0QixPQUFPLENBQUMsUUFBUSxDQUFDLG1CQUFtQixDQUEyQjtJQUUvRCxPQUFPLENBQUMsd0JBQXdCLENBQW9CO0lBQ3BELE9BQU8sQ0FBQyxhQUFhLENBQXFCO0lBQzFDLE9BQU8sQ0FBQyxZQUFZLENBQXVEO0lBQzNFLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBaUY7SUFFN0csT0FBTyxDQUFDLFdBQVcsQ0FBZ0M7SUFDbkQsU0FBUyxDQUFDLFdBQVcsRUFBRSxhQUFhLEdBQUcsU0FBUyxDQUFDO0lBSWpELE9BQU8sQ0FBQyxpQkFBaUIsQ0FBMEI7SUFFbkQsWUFDbUIsWUFBWSxFQUFFLHVCQUF1QixFQUNyQyxhQUFhLEVBQUUsYUFBYSxHQUFHLG1CQUFtQixFQUNsRCxNQUFNLEVBQUUsZ0JBQWdCLEVBQ2pDLGVBQWUsNEJBQXNELEVBQzVELEdBQUcsR0FBRSxNQUFvQyxFQVMzRDtJQUVNLFlBQVksSUFBSSx3QkFBd0IsQ0FFOUM7SUFFTSxXQUFXLENBQUMsV0FBVyxFQUFFLFdBQVcsR0FBRyx3QkFBd0IsQ0FFckU7SUFFTSxJQUFJLENBQUMsV0FBVyxDQUFDLEVBQUUsV0FBVyxHQUFHLE9BQU8sQ0FBQyx5QkFBeUIsQ0FBQyxDQUV6RTtJQUVNLFFBQVEsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sQ0FBQyxFQUFFLE9BQU8sR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxnQkFBZ0IsRUFBRSxVQUFVLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUVsSDtJQUVNLEtBQUssSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLENBRTVCO0lBRVksS0FBSyxtRkFnQ2pCO0lBRUQsU0FBUyxDQUFDLGlCQUFpQixJQUFJLGFBQWEsQ0FRM0M7SUFFWSxJQUFJLGtCQU9oQjtJQUVZLE1BQU0sSUFBSSxPQUFPLENBQUMsNEJBQTRCLENBQUMsQ0FhM0Q7SUFFWSxvQkFBb0IseUJBRWhDO0lBRVksUUFBUSxrQkFJcEI7SUFFTSxVQUFVLFNBT2hCO0lBRUQ7Ozs7O09BS0c7SUFDVSxhQUFhLENBQ3hCLGlCQUFpQixDQUFDLEVBQUUsV0FBVyxFQUMvQiwyQkFBMkIsQ0FBQyxFQUFFLE9BQU8sR0FDcEMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxDQTZDdEI7SUFFRCxvR0FBb0c7SUFDdkYsY0FBYyxDQUFDLE1BQU0sRUFBRSxXQUFXLEdBQUcsT0FBTyxDQUFDLE1BQU0sR0FBRyxTQUFTLENBQUMsQ0FhNUU7SUFFRCxnR0FBZ0c7SUFDbkYsU0FBUyxJQUFJLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FVeEM7SUFFRCxvREFBb0Q7SUFDdkMsc0JBQXNCLENBQUMsS0FBSyxFQUFFLGtCQUFrQixHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FlNUU7WUFPYSxjQUFjO1lBZ0NkLGFBQWE7WUFxQmIsb0JBQW9CO0lBZWxDLE9BQU8sQ0FBQyxpQkFBaUI7WUFNWCxpQkFBaUI7SUFRL0I7OztPQUdHO0lBQ0gsT0FBTyxDQUFDLGVBQWU7Q0FJeEIifQ==
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server_world_state_synchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/server_world_state_synchronizer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server_world_state_synchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/server_world_state_synchronizer.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { SHA256Trunc } from '@aztec/foundation/crypto';
|
|
1
|
+
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
3
2
|
import { createLogger } from '@aztec/foundation/log';
|
|
4
3
|
import { promiseWithResolvers } from '@aztec/foundation/promise';
|
|
5
4
|
import { elapsed } from '@aztec/foundation/timer';
|
|
6
|
-
import { MerkleTreeCalculator } from '@aztec/foundation/trees';
|
|
7
5
|
import { WorldStateRunningState } from '@aztec/stdlib/interfaces/server';
|
|
8
6
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
9
7
|
import { TraceableL2BlockStream, getTelemetryClient } from '@aztec/telemetry-client';
|
|
10
8
|
import { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
|
|
11
9
|
import { WorldStateSynchronizerError } from './errors.js';
|
|
10
|
+
import { findFirstBlocksInCheckpoints } from './utils.js';
|
|
12
11
|
/**
|
|
13
12
|
* Synchronizes the world state with the L2 blocks from a L2BlockSource via a block stream.
|
|
14
13
|
* The synchronizer will download the L2 blocks from the L2BlockSource and update the merkle trees.
|
|
@@ -35,7 +34,7 @@ import { WorldStateSynchronizerError } from './errors.js';
|
|
|
35
34
|
this.config = config;
|
|
36
35
|
this.instrumentation = instrumentation;
|
|
37
36
|
this.log = log;
|
|
38
|
-
this.latestBlockNumberAtStart =
|
|
37
|
+
this.latestBlockNumberAtStart = BlockNumber.ZERO;
|
|
39
38
|
this.currentState = WorldStateRunningState.IDLE;
|
|
40
39
|
this.latestBlockHashQuery = undefined;
|
|
41
40
|
this.syncPromise = promiseWithResolvers();
|
|
@@ -66,7 +65,7 @@ import { WorldStateSynchronizerError } from './errors.js';
|
|
|
66
65
|
return this.syncPromise;
|
|
67
66
|
}
|
|
68
67
|
// Get the current latest block number
|
|
69
|
-
this.latestBlockNumberAtStart = await (this.config.worldStateProvenBlocksOnly ? this.l2BlockSource.getProvenBlockNumber() : this.l2BlockSource.getBlockNumber());
|
|
68
|
+
this.latestBlockNumberAtStart = BlockNumber(await (this.config.worldStateProvenBlocksOnly ? this.l2BlockSource.getProvenBlockNumber() : this.l2BlockSource.getBlockNumber()));
|
|
70
69
|
const blockToDownloadFrom = await this.getLatestBlockNumber() + 1;
|
|
71
70
|
if (blockToDownloadFrom <= this.latestBlockNumberAtStart) {
|
|
72
71
|
// If there are blocks to be retrieved, go to a synching state
|
|
@@ -103,10 +102,10 @@ import { WorldStateSynchronizerError } from './errors.js';
|
|
|
103
102
|
async status() {
|
|
104
103
|
const summary = await this.merkleTreeDb.getStatusSummary();
|
|
105
104
|
const status = {
|
|
106
|
-
latestBlockNumber:
|
|
107
|
-
latestBlockHash: await this.getL2BlockHash(
|
|
108
|
-
finalizedBlockNumber:
|
|
109
|
-
oldestHistoricBlockNumber:
|
|
105
|
+
latestBlockNumber: summary.unfinalizedBlockNumber,
|
|
106
|
+
latestBlockHash: await this.getL2BlockHash(summary.unfinalizedBlockNumber) ?? '',
|
|
107
|
+
finalizedBlockNumber: summary.finalizedBlockNumber,
|
|
108
|
+
oldestHistoricBlockNumber: summary.oldestHistoricalBlock,
|
|
110
109
|
treesAreSynched: summary.treesAreSynched
|
|
111
110
|
};
|
|
112
111
|
return {
|
|
@@ -150,7 +149,7 @@ import { WorldStateSynchronizerError } from './errors.js';
|
|
|
150
149
|
this.log.debug(`World State at ${currentBlockNumber} told to sync to ${targetBlockNumber ?? 'latest'}`);
|
|
151
150
|
// If the archiver is behind the target block, force an archiver sync
|
|
152
151
|
if (targetBlockNumber) {
|
|
153
|
-
const archiverLatestBlock = await this.l2BlockSource.getBlockNumber();
|
|
152
|
+
const archiverLatestBlock = BlockNumber(await this.l2BlockSource.getBlockNumber());
|
|
154
153
|
if (archiverLatestBlock < targetBlockNumber) {
|
|
155
154
|
this.log.debug(`Archiver is at ${archiverLatestBlock} behind target block ${targetBlockNumber}.`);
|
|
156
155
|
await this.l2BlockSource.syncImmediate();
|
|
@@ -173,7 +172,7 @@ import { WorldStateSynchronizerError } from './errors.js';
|
|
|
173
172
|
return updatedBlockNumber;
|
|
174
173
|
}
|
|
175
174
|
/** Returns the L2 block hash for a given number. Used by the L2BlockStream for detecting reorgs. */ async getL2BlockHash(number) {
|
|
176
|
-
if (number ===
|
|
175
|
+
if (number === BlockNumber.ZERO) {
|
|
177
176
|
return (await this.merkleTreeCommitted.getInitialHeader().hash()).toString();
|
|
178
177
|
}
|
|
179
178
|
if (this.latestBlockHashQuery?.hash === undefined || number !== this.latestBlockHashQuery.blockNumber) {
|
|
@@ -186,19 +185,19 @@ import { WorldStateSynchronizerError } from './errors.js';
|
|
|
186
185
|
}
|
|
187
186
|
/** Returns the latest L2 block number for each tip of the chain (latest, proven, finalized). */ async getL2Tips() {
|
|
188
187
|
const status = await this.merkleTreeDb.getStatusSummary();
|
|
189
|
-
const unfinalizedBlockHash = await this.getL2BlockHash(
|
|
188
|
+
const unfinalizedBlockHash = await this.getL2BlockHash(status.unfinalizedBlockNumber);
|
|
190
189
|
const latestBlockId = {
|
|
191
|
-
number:
|
|
190
|
+
number: status.unfinalizedBlockNumber,
|
|
192
191
|
hash: unfinalizedBlockHash
|
|
193
192
|
};
|
|
194
193
|
return {
|
|
195
194
|
latest: latestBlockId,
|
|
196
195
|
finalized: {
|
|
197
|
-
number:
|
|
196
|
+
number: status.finalizedBlockNumber,
|
|
198
197
|
hash: ''
|
|
199
198
|
},
|
|
200
199
|
proven: {
|
|
201
|
-
number:
|
|
200
|
+
number: this.provenBlockNumber ?? status.finalizedBlockNumber,
|
|
202
201
|
hash: ''
|
|
203
202
|
}
|
|
204
203
|
};
|
|
@@ -206,16 +205,16 @@ import { WorldStateSynchronizerError } from './errors.js';
|
|
|
206
205
|
/** Handles an event emitted by the block stream. */ async handleBlockStreamEvent(event) {
|
|
207
206
|
switch(event.type){
|
|
208
207
|
case 'blocks-added':
|
|
209
|
-
await this.handleL2Blocks(event.blocks.map((b)=>b.block));
|
|
208
|
+
await this.handleL2Blocks(event.blocks.map((b)=>b.block.toL2Block()));
|
|
210
209
|
break;
|
|
211
210
|
case 'chain-pruned':
|
|
212
|
-
await this.handleChainPruned(event.block.number);
|
|
211
|
+
await this.handleChainPruned(BlockNumber(event.block.number));
|
|
213
212
|
break;
|
|
214
213
|
case 'chain-proven':
|
|
215
|
-
await this.handleChainProven(event.block.number);
|
|
214
|
+
await this.handleChainProven(BlockNumber(event.block.number));
|
|
216
215
|
break;
|
|
217
216
|
case 'chain-finalized':
|
|
218
|
-
await this.handleChainFinalized(event.block.number);
|
|
217
|
+
await this.handleChainFinalized(BlockNumber(event.block.number));
|
|
219
218
|
break;
|
|
220
219
|
}
|
|
221
220
|
}
|
|
@@ -225,18 +224,19 @@ import { WorldStateSynchronizerError } from './errors.js';
|
|
|
225
224
|
* @returns Whether the block handled was produced by this same node.
|
|
226
225
|
*/ async handleL2Blocks(l2Blocks) {
|
|
227
226
|
this.log.trace(`Handling L2 blocks ${l2Blocks[0].number} to ${l2Blocks.at(-1).number}`);
|
|
228
|
-
const
|
|
229
|
-
const l1ToL2Messages = await Promise.all(messagePromises);
|
|
227
|
+
const firstBlocksInCheckpoints = await findFirstBlocksInCheckpoints(l2Blocks, this.l2BlockSource);
|
|
230
228
|
let updateStatus = undefined;
|
|
231
|
-
for(
|
|
232
|
-
const
|
|
233
|
-
|
|
229
|
+
for (const block of l2Blocks){
|
|
230
|
+
const l1ToL2Messages = firstBlocksInCheckpoints.get(block.number) ?? [];
|
|
231
|
+
const isFirstBlock = firstBlocksInCheckpoints.has(block.number);
|
|
232
|
+
const [duration, result] = await elapsed(()=>this.handleL2Block(block, l1ToL2Messages, isFirstBlock));
|
|
233
|
+
this.log.info(`World state updated with L2 block ${block.number}`, {
|
|
234
234
|
eventName: 'l2-block-handled',
|
|
235
235
|
duration,
|
|
236
|
-
unfinalizedBlockNumber: result.summary.unfinalizedBlockNumber,
|
|
237
|
-
finalizedBlockNumber: result.summary.finalizedBlockNumber,
|
|
238
|
-
oldestHistoricBlock: result.summary.oldestHistoricalBlock,
|
|
239
|
-
...
|
|
236
|
+
unfinalizedBlockNumber: BigInt(result.summary.unfinalizedBlockNumber),
|
|
237
|
+
finalizedBlockNumber: BigInt(result.summary.finalizedBlockNumber),
|
|
238
|
+
oldestHistoricBlock: BigInt(result.summary.oldestHistoricalBlock),
|
|
239
|
+
...block.getStats()
|
|
240
240
|
});
|
|
241
241
|
updateStatus = result;
|
|
242
242
|
}
|
|
@@ -250,19 +250,14 @@ import { WorldStateSynchronizerError } from './errors.js';
|
|
|
250
250
|
* @param l2Block - The L2 block to handle.
|
|
251
251
|
* @param l1ToL2Messages - The L1 to L2 messages for the block.
|
|
252
252
|
* @returns Whether the block handled was produced by this same node.
|
|
253
|
-
*/ async handleL2Block(l2Block, l1ToL2Messages) {
|
|
254
|
-
// First we check that the L1 to L2 messages hash to the block inHash.
|
|
255
|
-
// Note that we cannot optimize this check by checking the root of the subtree after inserting the messages
|
|
256
|
-
// to the real L1_TO_L2_MESSAGE_TREE (like we do in merkleTreeDb.handleL2BlockAndMessages(...)) because that
|
|
257
|
-
// tree uses pedersen and we don't have access to the converted root.
|
|
258
|
-
await this.verifyMessagesHashToInHash(l1ToL2Messages, l2Block.header.contentCommitment.inHash);
|
|
253
|
+
*/ async handleL2Block(l2Block, l1ToL2Messages, isFirstBlock) {
|
|
259
254
|
// If the above check succeeds, we can proceed to handle the block.
|
|
260
255
|
this.log.trace(`Pushing L2 block ${l2Block.number} to merkle tree db `, {
|
|
261
256
|
blockNumber: l2Block.number,
|
|
262
257
|
blockHash: await l2Block.hash().then((h)=>h.toString()),
|
|
263
258
|
l1ToL2Messages: l1ToL2Messages.map((msg)=>msg.toString())
|
|
264
259
|
});
|
|
265
|
-
const result = await this.merkleTreeDb.handleL2BlockAndMessages(l2Block, l1ToL2Messages);
|
|
260
|
+
const result = await this.merkleTreeDb.handleL2BlockAndMessages(l2Block, l1ToL2Messages, isFirstBlock);
|
|
266
261
|
if (this.currentState === WorldStateRunningState.SYNCHING && l2Block.number >= this.latestBlockNumberAtStart) {
|
|
267
262
|
this.setCurrentState(WorldStateRunningState.RUNNING);
|
|
268
263
|
this.syncPromise.resolve();
|
|
@@ -271,12 +266,12 @@ import { WorldStateSynchronizerError } from './errors.js';
|
|
|
271
266
|
}
|
|
272
267
|
async handleChainFinalized(blockNumber) {
|
|
273
268
|
this.log.verbose(`Finalized chain is now at block ${blockNumber}`);
|
|
274
|
-
const summary = await this.merkleTreeDb.setFinalized(
|
|
269
|
+
const summary = await this.merkleTreeDb.setFinalized(blockNumber);
|
|
275
270
|
if (this.historyToKeep === undefined) {
|
|
276
271
|
return;
|
|
277
272
|
}
|
|
278
|
-
const newHistoricBlock = summary.finalizedBlockNumber -
|
|
279
|
-
if (newHistoricBlock <= 1) {
|
|
273
|
+
const newHistoricBlock = BlockNumber(summary.finalizedBlockNumber - this.historyToKeep + 1);
|
|
274
|
+
if (newHistoricBlock <= BlockNumber(1)) {
|
|
280
275
|
return;
|
|
281
276
|
}
|
|
282
277
|
this.log.verbose(`Pruning historic blocks to ${newHistoricBlock}`);
|
|
@@ -284,13 +279,13 @@ import { WorldStateSynchronizerError } from './errors.js';
|
|
|
284
279
|
this.log.debug(`World state summary `, status.summary);
|
|
285
280
|
}
|
|
286
281
|
handleChainProven(blockNumber) {
|
|
287
|
-
this.provenBlockNumber =
|
|
282
|
+
this.provenBlockNumber = blockNumber;
|
|
288
283
|
this.log.debug(`Proven chain is now at block ${blockNumber}`);
|
|
289
284
|
return Promise.resolve();
|
|
290
285
|
}
|
|
291
286
|
async handleChainPruned(blockNumber) {
|
|
292
287
|
this.log.warn(`Chain pruned to block ${blockNumber}`);
|
|
293
|
-
const status = await this.merkleTreeDb.unwindBlocks(
|
|
288
|
+
const status = await this.merkleTreeDb.unwindBlocks(blockNumber);
|
|
294
289
|
this.latestBlockHashQuery = undefined;
|
|
295
290
|
this.provenBlockNumber = undefined;
|
|
296
291
|
this.instrumentation.updateWorldStateMetrics(status);
|
|
@@ -302,16 +297,4 @@ import { WorldStateSynchronizerError } from './errors.js';
|
|
|
302
297
|
this.currentState = newState;
|
|
303
298
|
this.log.debug(`Moved to state ${WorldStateRunningState[this.currentState]}`);
|
|
304
299
|
}
|
|
305
|
-
/**
|
|
306
|
-
* Verifies that the L1 to L2 messages hash to the block inHash.
|
|
307
|
-
* @param l1ToL2Messages - The L1 to L2 messages for the block.
|
|
308
|
-
* @param inHash - The inHash of the block.
|
|
309
|
-
* @throws If the L1 to L2 messages do not hash to the block inHash.
|
|
310
|
-
*/ async verifyMessagesHashToInHash(l1ToL2Messages, inHash) {
|
|
311
|
-
const treeCalculator = await MerkleTreeCalculator.create(L1_TO_L2_MSG_SUBTREE_HEIGHT, Buffer.alloc(32), (lhs, rhs)=>Promise.resolve(new SHA256Trunc().hash(lhs, rhs)));
|
|
312
|
-
const root = await treeCalculator.computeTreeRoot(l1ToL2Messages.map((msg)=>msg.toBuffer()));
|
|
313
|
-
if (!root.equals(inHash.toBuffer())) {
|
|
314
|
-
throw new Error('Obtained L1 to L2 messages failed to be hashed to the block inHash');
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
300
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Fr } from '@aztec/foundation/fields';
|
|
2
|
+
import type { L2BlockNew, L2BlockSource } from '@aztec/stdlib/block';
|
|
3
|
+
import { type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
4
|
+
/**
|
|
5
|
+
* Determine which blocks in the given array are the first block in a checkpoint.
|
|
6
|
+
* @param blocks - The candidate blocks, sorted by block number in ascending order.
|
|
7
|
+
* @param l2BlockSource - The L2 block source to use to fetch the checkpoints, block headers and L1->L2 messages.
|
|
8
|
+
* @returns A map of block numbers that begin a checkpoint to the L1->L2 messages for that checkpoint.
|
|
9
|
+
*/
|
|
10
|
+
export declare function findFirstBlocksInCheckpoints(blocks: L2BlockNew[], l2BlockSource: L2BlockSource & L1ToL2MessageSource): Promise<Map<number, Fr[]>>;
|
|
11
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zeW5jaHJvbml6ZXIvdXRpbHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxLQUFLLEVBQUUsRUFBRSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDbkQsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLGFBQWEsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRXJFLE9BQU8sRUFBRSxLQUFLLG1CQUFtQixFQUFtQyxNQUFNLHlCQUF5QixDQUFDO0FBRXBHOzs7OztHQUtHO0FBQ0gsd0JBQXNCLDRCQUE0QixDQUNoRCxNQUFNLEVBQUUsVUFBVSxFQUFFLEVBQ3BCLGFBQWEsRUFBRSxhQUFhLEdBQUcsbUJBQW1CLEdBQ2pELE9BQU8sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FtRTVCIn0=
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/synchronizer/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAErE,OAAO,EAAE,KAAK,mBAAmB,EAAmC,MAAM,yBAAyB,CAAC;AAEpG;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,UAAU,EAAE,EACpB,aAAa,EAAE,aAAa,GAAG,mBAAmB,GACjD,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,CAmE5B"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
2
|
+
import { computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
|
|
3
|
+
/**
|
|
4
|
+
* Determine which blocks in the given array are the first block in a checkpoint.
|
|
5
|
+
* @param blocks - The candidate blocks, sorted by block number in ascending order.
|
|
6
|
+
* @param l2BlockSource - The L2 block source to use to fetch the checkpoints, block headers and L1->L2 messages.
|
|
7
|
+
* @returns A map of block numbers that begin a checkpoint to the L1->L2 messages for that checkpoint.
|
|
8
|
+
*/ export async function findFirstBlocksInCheckpoints(blocks, l2BlockSource) {
|
|
9
|
+
// Select the blocks that are the final block within each group of identical slot numbers.
|
|
10
|
+
let seenSlot;
|
|
11
|
+
const maybeLastBlocks = [
|
|
12
|
+
...blocks
|
|
13
|
+
].reverse().filter((b)=>{
|
|
14
|
+
if (b.header.globalVariables.slotNumber !== seenSlot) {
|
|
15
|
+
seenSlot = b.header.globalVariables.slotNumber;
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}).reverse();
|
|
20
|
+
// Try to fetch the checkpoints for those blocks. If undefined (which should only occur for blocks.at(-1)),
|
|
21
|
+
// then the block is not the last one in a checkpoint.
|
|
22
|
+
// If we are not checking the inHashes below, only blocks.at(-1) would need its checkpoint header fetched.
|
|
23
|
+
const checkpointedBlocks = (await Promise.all(maybeLastBlocks.map(async (b)=>({
|
|
24
|
+
blockNumber: b.number,
|
|
25
|
+
// A checkpoint's archive root is the archive root of its last block.
|
|
26
|
+
checkpoint: await l2BlockSource.getCheckpointByArchive(b.archive.root)
|
|
27
|
+
})))).filter((b)=>b.checkpoint !== undefined);
|
|
28
|
+
// Verify that the L1->L2 messages hash to the checkpoint's inHash.
|
|
29
|
+
const checkpointedL1ToL2Messages = await Promise.all(checkpointedBlocks.map((b)=>l2BlockSource.getL1ToL2MessagesForCheckpoint(b.checkpoint.number)));
|
|
30
|
+
checkpointedBlocks.forEach((b, i)=>{
|
|
31
|
+
const computedInHash = computeInHashFromL1ToL2Messages(checkpointedL1ToL2Messages[i]);
|
|
32
|
+
const inHash = b.checkpoint.header.contentCommitment.inHash;
|
|
33
|
+
if (!computedInHash.equals(inHash)) {
|
|
34
|
+
throw new Error('Obtained L1 to L2 messages failed to be hashed to the checkpoint inHash');
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
// Compute the first block numbers, which should be right after each checkpointed block. Exclude blocks that haven't
|
|
38
|
+
// been added yet.
|
|
39
|
+
const firstBlockNumbers = checkpointedBlocks.map((b)=>BlockNumber(b.blockNumber + 1)).filter((n)=>n <= blocks.at(-1).number);
|
|
40
|
+
// Check if blocks[0] is the first block in a checkpoint.
|
|
41
|
+
if (blocks[0].number === 1) {
|
|
42
|
+
firstBlockNumbers.push(blocks[0].number);
|
|
43
|
+
} else {
|
|
44
|
+
const lastBlockHeader = await l2BlockSource.getBlockHeader(BlockNumber(blocks[0].number - 1));
|
|
45
|
+
if (!lastBlockHeader) {
|
|
46
|
+
throw new Error(`Failed to get block ${blocks[0].number - 1}`);
|
|
47
|
+
}
|
|
48
|
+
if (lastBlockHeader.globalVariables.slotNumber !== blocks[0].header.globalVariables.slotNumber) {
|
|
49
|
+
firstBlockNumbers.push(blocks[0].number);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Fetch the L1->L2 messages for the first blocks and assign them to the map.
|
|
53
|
+
const messagesByBlockNumber = new Map();
|
|
54
|
+
await Promise.all(firstBlockNumbers.map(async (blockNumber)=>{
|
|
55
|
+
const l1ToL2Messages = await l2BlockSource.getL1ToL2Messages(blockNumber);
|
|
56
|
+
messagesByBlockNumber.set(blockNumber, l1ToL2Messages);
|
|
57
|
+
}));
|
|
58
|
+
return messagesByBlockNumber;
|
|
59
|
+
}
|
package/dest/test/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from './utils.js';
|
|
2
|
-
//# sourceMappingURL=
|
|
2
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZXN0L2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsWUFBWSxDQUFDIn0=
|
package/dest/test/utils.d.ts
CHANGED
|
@@ -1,19 +1,32 @@
|
|
|
1
|
+
import { BlockNumber, type CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
1
2
|
import { Fr } from '@aztec/foundation/fields';
|
|
2
|
-
import {
|
|
3
|
+
import { L2BlockNew } from '@aztec/stdlib/block';
|
|
4
|
+
import { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
3
5
|
import type { MerkleTreeReadOperations, MerkleTreeWriteOperations } from '@aztec/stdlib/interfaces/server';
|
|
4
6
|
import type { NativeWorldStateService } from '../native/native_world_state.js';
|
|
5
|
-
export declare function mockBlock(blockNum:
|
|
6
|
-
block:
|
|
7
|
+
export declare function mockBlock(blockNum: BlockNumber, size: number, fork: MerkleTreeWriteOperations, maxEffects?: number | undefined, numL1ToL2Messages?: number, isFirstBlock?: boolean): Promise<{
|
|
8
|
+
block: L2BlockNew;
|
|
7
9
|
messages: Fr[];
|
|
8
10
|
}>;
|
|
9
|
-
export declare function mockEmptyBlock(blockNum:
|
|
10
|
-
block:
|
|
11
|
+
export declare function mockEmptyBlock(blockNum: BlockNumber, fork: MerkleTreeWriteOperations): Promise<{
|
|
12
|
+
block: L2BlockNew;
|
|
11
13
|
messages: Fr[];
|
|
12
14
|
}>;
|
|
13
|
-
export declare function mockBlocks(from:
|
|
14
|
-
blocks:
|
|
15
|
+
export declare function mockBlocks(from: BlockNumber, count: number, numTxs: number, worldState: NativeWorldStateService): Promise<{
|
|
16
|
+
blocks: L2BlockNew[];
|
|
15
17
|
messages: Fr[][];
|
|
16
18
|
}>;
|
|
19
|
+
export declare function mockL1ToL2Messages(numL1ToL2Messages: number): Fr[];
|
|
20
|
+
export declare function mockCheckpoint(checkpointNumber: CheckpointNumber, { startBlockNumber, numBlocks, numTxsPerBlock, numL1ToL2Messages, fork }?: {
|
|
21
|
+
startBlockNumber?: BlockNumber;
|
|
22
|
+
numBlocks?: number;
|
|
23
|
+
numTxsPerBlock?: number;
|
|
24
|
+
numL1ToL2Messages?: number;
|
|
25
|
+
fork?: MerkleTreeWriteOperations;
|
|
26
|
+
}): Promise<{
|
|
27
|
+
checkpoint: Checkpoint;
|
|
28
|
+
messages: Fr[];
|
|
29
|
+
}>;
|
|
17
30
|
export declare function assertSameState(forkA: MerkleTreeReadOperations, forkB: MerkleTreeReadOperations): Promise<void>;
|
|
18
31
|
export declare function compareChains(left: MerkleTreeReadOperations, right: MerkleTreeReadOperations): Promise<void>;
|
|
19
|
-
//# sourceMappingURL=
|
|
32
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZXN0L3V0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQU1BLE9BQU8sRUFBRSxXQUFXLEVBQUUsS0FBSyxnQkFBZ0IsRUFBYyxNQUFNLGlDQUFpQyxDQUFDO0FBRWpHLE9BQU8sRUFBRSxFQUFFLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUM5QyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDakQsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ3RELE9BQU8sS0FBSyxFQUVWLHdCQUF3QixFQUN4Qix5QkFBeUIsRUFDMUIsTUFBTSxpQ0FBaUMsQ0FBQztBQUl6QyxPQUFPLEtBQUssRUFBRSx1QkFBdUIsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRS9FLHdCQUFzQixTQUFTLENBQzdCLFFBQVEsRUFBRSxXQUFXLEVBQ3JCLElBQUksRUFBRSxNQUFNLEVBQ1osSUFBSSxFQUFFLHlCQUF5QixFQUMvQixVQUFVLEdBQUUsTUFBTSxHQUFHLFNBQWdCLEVBQ3JDLGlCQUFpQixHQUFFLE1BQTRDLEVBQy9ELFlBQVksR0FBRSxPQUFjOzs7R0F3RDdCO0FBRUQsd0JBQXNCLGNBQWMsQ0FBQyxRQUFRLEVBQUUsV0FBVyxFQUFFLElBQUksRUFBRSx5QkFBeUI7OztHQWlEMUY7QUFFRCx3QkFBc0IsVUFBVSxDQUM5QixJQUFJLEVBQUUsV0FBVyxFQUNqQixLQUFLLEVBQUUsTUFBTSxFQUNiLE1BQU0sRUFBRSxNQUFNLEVBQ2QsVUFBVSxFQUFFLHVCQUF1Qjs7O0dBZXBDO0FBRUQsd0JBQWdCLGtCQUFrQixDQUFDLGlCQUFpQixFQUFFLE1BQU0sUUFFM0Q7QUFFRCx3QkFBc0IsY0FBYyxDQUNsQyxnQkFBZ0IsRUFBRSxnQkFBZ0IsRUFDbEMsRUFDRSxnQkFBaUMsRUFDakMsU0FBYSxFQUNiLGNBQWtCLEVBQ2xCLGlCQUFxQixFQUNyQixJQUFJLEVBQ0wsR0FBRTtJQUNELGdCQUFnQixDQUFDLEVBQUUsV0FBVyxDQUFDO0lBQy9CLFNBQVMsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUNuQixjQUFjLENBQUMsRUFBRSxNQUFNLENBQUM7SUFDeEIsaUJBQWlCLENBQUMsRUFBRSxNQUFNLENBQUM7SUFDM0IsSUFBSSxDQUFDLEVBQUUseUJBQXlCLENBQUM7Q0FDN0I7OztHQXFCUDtBQUVELHdCQUFzQixlQUFlLENBQUMsS0FBSyxFQUFFLHdCQUF3QixFQUFFLEtBQUssRUFBRSx3QkFBd0IsaUJBUXJHO0FBRUQsd0JBQXNCLGFBQWEsQ0FBQyxJQUFJLEVBQUUsd0JBQXdCLEVBQUUsS0FBSyxFQUFFLHdCQUF3QixpQkFZbEcifQ==
|
package/dest/test/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/test/utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/test/utils.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAc,MAAM,iCAAiC,CAAC;AAEjG,OAAO,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,KAAK,EAEV,wBAAwB,EACxB,yBAAyB,EAC1B,MAAM,iCAAiC,CAAC;AAIzC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAE/E,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,WAAW,EACrB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,yBAAyB,EAC/B,UAAU,GAAE,MAAM,GAAG,SAAgB,EACrC,iBAAiB,GAAE,MAA4C,EAC/D,YAAY,GAAE,OAAc;;;GAwD7B;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,yBAAyB;;;GAiD1F;AAED,wBAAsB,UAAU,CAC9B,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,uBAAuB;;;GAepC;AAED,wBAAgB,kBAAkB,CAAC,iBAAiB,EAAE,MAAM,QAE3D;AAED,wBAAsB,cAAc,CAClC,gBAAgB,EAAE,gBAAgB,EAClC,EACE,gBAAiC,EACjC,SAAa,EACb,cAAkB,EAClB,iBAAqB,EACrB,IAAI,EACL,GAAE;IACD,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,IAAI,CAAC,EAAE,yBAAyB,CAAC;CAC7B;;;GAqBP;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,wBAAwB,EAAE,KAAK,EAAE,wBAAwB,iBAQrG;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,wBAAwB,EAAE,KAAK,EAAE,wBAAwB,iBAYlG"}
|
package/dest/test/utils.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, NULLIFIER_SUBTREE_HEIGHT, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/constants';
|
|
2
|
+
import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
2
3
|
import { padArrayEnd } from '@aztec/foundation/collection';
|
|
3
4
|
import { Fr } from '@aztec/foundation/fields';
|
|
4
|
-
import {
|
|
5
|
+
import { L2BlockNew } from '@aztec/stdlib/block';
|
|
6
|
+
import { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
7
|
+
import { computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
|
|
5
8
|
import { AppendOnlyTreeSnapshot, MerkleTreeId } from '@aztec/stdlib/trees';
|
|
6
|
-
export async function mockBlock(blockNum, size, fork, maxEffects =
|
|
7
|
-
const l2Block = await
|
|
8
|
-
|
|
9
|
+
export async function mockBlock(blockNum, size, fork, maxEffects = 1000, numL1ToL2Messages = NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, isFirstBlock = true) {
|
|
10
|
+
const l2Block = await L2BlockNew.random(blockNum, {
|
|
11
|
+
txsPerBlock: size,
|
|
12
|
+
txOptions: {
|
|
13
|
+
maxEffects
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
const l1ToL2Messages = mockL1ToL2Messages(numL1ToL2Messages);
|
|
9
17
|
{
|
|
10
18
|
const insertData = async (treeId, data, subTreeHeight, fork)=>{
|
|
11
19
|
for (const dataBatch of data){
|
|
@@ -15,7 +23,7 @@ export async function mockBlock(blockNum, size, fork, maxEffects = undefined) {
|
|
|
15
23
|
const publicDataInsert = insertData(MerkleTreeId.PUBLIC_DATA_TREE, l2Block.body.txEffects.map((txEffect)=>txEffect.publicDataWrites.map((write)=>write.toBuffer())), 0, fork);
|
|
16
24
|
const nullifierInsert = insertData(MerkleTreeId.NULLIFIER_TREE, l2Block.body.txEffects.map((txEffect)=>padArrayEnd(txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX).map((nullifier)=>nullifier.toBuffer())), NULLIFIER_SUBTREE_HEIGHT, fork);
|
|
17
25
|
const noteHashesPadded = l2Block.body.txEffects.flatMap((txEffect)=>padArrayEnd(txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX));
|
|
18
|
-
const l1ToL2MessagesPadded = padArrayEnd(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
26
|
+
const l1ToL2MessagesPadded = isFirstBlock ? padArrayEnd(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP) : l1ToL2Messages;
|
|
19
27
|
const noteHashInsert = fork.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, noteHashesPadded);
|
|
20
28
|
const messageInsert = fork.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2MessagesPadded);
|
|
21
29
|
await Promise.all([
|
|
@@ -27,7 +35,7 @@ export async function mockBlock(blockNum, size, fork, maxEffects = undefined) {
|
|
|
27
35
|
}
|
|
28
36
|
const state = await fork.getStateReference();
|
|
29
37
|
l2Block.header.state = state;
|
|
30
|
-
await fork.updateArchive(l2Block.
|
|
38
|
+
await fork.updateArchive(l2Block.header);
|
|
31
39
|
const archiveState = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
32
40
|
l2Block.archive = new AppendOnlyTreeSnapshot(Fr.fromBuffer(archiveState.root), Number(archiveState.size));
|
|
33
41
|
return {
|
|
@@ -36,7 +44,7 @@ export async function mockBlock(blockNum, size, fork, maxEffects = undefined) {
|
|
|
36
44
|
};
|
|
37
45
|
}
|
|
38
46
|
export async function mockEmptyBlock(blockNum, fork) {
|
|
39
|
-
const l2Block =
|
|
47
|
+
const l2Block = L2BlockNew.empty();
|
|
40
48
|
const l1ToL2Messages = Array(16).fill(0).map(Fr.zero);
|
|
41
49
|
l2Block.header.globalVariables.blockNumber = blockNum;
|
|
42
50
|
// Sync the append only trees
|
|
@@ -57,7 +65,7 @@ export async function mockEmptyBlock(blockNum, fork) {
|
|
|
57
65
|
}
|
|
58
66
|
const state = await fork.getStateReference();
|
|
59
67
|
l2Block.header.state = state;
|
|
60
|
-
await fork.updateArchive(l2Block.
|
|
68
|
+
await fork.updateArchive(l2Block.header);
|
|
61
69
|
const archiveState = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
62
70
|
l2Block.archive = new AppendOnlyTreeSnapshot(Fr.fromBuffer(archiveState.root), Number(archiveState.size));
|
|
63
71
|
return {
|
|
@@ -66,11 +74,11 @@ export async function mockEmptyBlock(blockNum, fork) {
|
|
|
66
74
|
};
|
|
67
75
|
}
|
|
68
76
|
export async function mockBlocks(from, count, numTxs, worldState) {
|
|
69
|
-
const tempFork = await worldState.fork(from - 1);
|
|
77
|
+
const tempFork = await worldState.fork(BlockNumber(from - 1));
|
|
70
78
|
const blocks = [];
|
|
71
79
|
const messagesArray = [];
|
|
72
80
|
for(let blockNumber = from; blockNumber < from + count; blockNumber++){
|
|
73
|
-
const { block, messages } = await mockBlock(blockNumber, numTxs, tempFork);
|
|
81
|
+
const { block, messages } = await mockBlock(BlockNumber(blockNumber), numTxs, tempFork);
|
|
74
82
|
blocks.push(block);
|
|
75
83
|
messagesArray.push(messages);
|
|
76
84
|
}
|
|
@@ -80,6 +88,39 @@ export async function mockBlocks(from, count, numTxs, worldState) {
|
|
|
80
88
|
messages: messagesArray
|
|
81
89
|
};
|
|
82
90
|
}
|
|
91
|
+
export function mockL1ToL2Messages(numL1ToL2Messages) {
|
|
92
|
+
return Array(numL1ToL2Messages).fill(0).map(Fr.random);
|
|
93
|
+
}
|
|
94
|
+
export async function mockCheckpoint(checkpointNumber, { startBlockNumber = BlockNumber(1), numBlocks = 1, numTxsPerBlock = 1, numL1ToL2Messages = 1, fork } = {}) {
|
|
95
|
+
const slotNumber = SlotNumber(checkpointNumber * 10);
|
|
96
|
+
const blocksAndMessages = [];
|
|
97
|
+
for(let i = 0; i < numBlocks; i++){
|
|
98
|
+
const blockNumber = BlockNumber(startBlockNumber + i);
|
|
99
|
+
const { block, messages } = fork ? await mockBlock(blockNumber, numTxsPerBlock, fork, blockNumber === startBlockNumber ? numL1ToL2Messages : 0) : {
|
|
100
|
+
block: await L2BlockNew.random(blockNumber, {
|
|
101
|
+
txsPerBlock: numTxsPerBlock,
|
|
102
|
+
slotNumber
|
|
103
|
+
}),
|
|
104
|
+
messages: mockL1ToL2Messages(numL1ToL2Messages)
|
|
105
|
+
};
|
|
106
|
+
blocksAndMessages.push({
|
|
107
|
+
block,
|
|
108
|
+
messages
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const messages = blocksAndMessages[0].messages;
|
|
112
|
+
const inHash = computeInHashFromL1ToL2Messages(messages);
|
|
113
|
+
const checkpoint = await Checkpoint.random(checkpointNumber, {
|
|
114
|
+
numBlocks: 0,
|
|
115
|
+
slotNumber,
|
|
116
|
+
inHash
|
|
117
|
+
});
|
|
118
|
+
checkpoint.blocks = blocksAndMessages.map(({ block })=>block);
|
|
119
|
+
return {
|
|
120
|
+
checkpoint,
|
|
121
|
+
messages
|
|
122
|
+
};
|
|
123
|
+
}
|
|
83
124
|
export async function assertSameState(forkA, forkB) {
|
|
84
125
|
const nativeStateRef = await forkA.getStateReference();
|
|
85
126
|
const nativeArchive = await forkA.getTreeInfo(MerkleTreeId.ARCHIVE);
|
package/dest/testing.d.ts
CHANGED
|
@@ -7,4 +7,4 @@ export declare function getGenesisValues(initialAccounts: AztecAddress[], initia
|
|
|
7
7
|
prefilledPublicData: PublicDataTreeLeaf[];
|
|
8
8
|
fundingNeeded: bigint;
|
|
9
9
|
}>;
|
|
10
|
-
//# sourceMappingURL=
|
|
10
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdGluZy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3Rlc3RpbmcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFFLEVBQUUsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBRTlDLE9BQU8sS0FBSyxFQUFFLFlBQVksRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQ2hFLE9BQU8sRUFBZ0Isa0JBQWtCLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQXlCdkUsZUFBTyxNQUFNLDZCQUE2QixJQUFxQixDQUFDO0FBRWhFLHdCQUFzQixnQkFBZ0IsQ0FDcEMsZUFBZSxFQUFFLFlBQVksRUFBRSxFQUMvQixzQkFBc0IsS0FBZ0MsRUFDdEQsaUJBQWlCLEdBQUUsa0JBQWtCLEVBQU87Ozs7R0FxQjdDIn0=
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export * from './merkle_tree_db.js';
|
|
2
2
|
export type { MerkleTreeReadOperations } from '@aztec/stdlib/interfaces/server';
|
|
3
|
-
//# sourceMappingURL=
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy93b3JsZC1zdGF0ZS1kYi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLHFCQUFxQixDQUFDO0FBRXBDLFlBQVksRUFBRSx3QkFBd0IsRUFBRSxNQUFNLGlDQUFpQyxDQUFDIn0=
|