@aztec/world-state 0.0.0-test.1 → 0.0.1-commit.5daedc8
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 +6 -4
- package/dest/instrumentation/instrumentation.d.ts.map +1 -1
- package/dest/instrumentation/instrumentation.js +16 -8
- package/dest/native/bench_metrics.d.ts +23 -0
- package/dest/native/bench_metrics.d.ts.map +1 -0
- package/dest/native/bench_metrics.js +81 -0
- 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 +10 -4
- package/dest/native/merkle_trees_facade.d.ts.map +1 -1
- package/dest/native/merkle_trees_facade.js +39 -7
- package/dest/native/message.d.ts +65 -45
- package/dest/native/message.d.ts.map +1 -1
- package/dest/native/message.js +55 -56
- package/dest/native/native_world_state.d.ts +20 -15
- package/dest/native/native_world_state.d.ts.map +1 -1
- package/dest/native/native_world_state.js +90 -33
- package/dest/native/native_world_state_instance.d.ts +20 -4
- package/dest/native/native_world_state_instance.d.ts.map +1 -1
- package/dest/native/native_world_state_instance.js +42 -3
- 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/native/world_state_ops_queue.js +1 -1
- package/dest/synchronizer/config.d.ts +12 -2
- package/dest/synchronizer/config.d.ts.map +1 -1
- package/dest/synchronizer/config.js +26 -1
- package/dest/synchronizer/errors.d.ts +4 -0
- package/dest/synchronizer/errors.d.ts.map +1 -0
- package/dest/synchronizer/errors.js +5 -0
- package/dest/synchronizer/factory.d.ts +9 -2
- package/dest/synchronizer/factory.d.ts.map +1 -1
- package/dest/synchronizer/factory.js +9 -4
- package/dest/synchronizer/index.d.ts +1 -1
- package/dest/synchronizer/server_world_state_synchronizer.d.ts +14 -18
- package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
- package/dest/synchronizer/server_world_state_synchronizer.js +78 -38
- package/dest/test/index.d.ts +1 -1
- package/dest/test/utils.d.ts +2 -2
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +21 -18
- package/dest/testing.d.ts +2 -2
- package/dest/testing.d.ts.map +1 -1
- package/dest/testing.js +6 -10
- package/dest/world-state-db/index.d.ts +1 -1
- package/dest/world-state-db/merkle_tree_db.d.ts +10 -6
- package/dest/world-state-db/merkle_tree_db.d.ts.map +1 -1
- package/package.json +24 -24
- package/src/instrumentation/instrumentation.ts +22 -10
- package/src/native/bench_metrics.ts +91 -0
- package/src/native/merkle_trees_facade.ts +44 -13
- package/src/native/message.ts +81 -63
- package/src/native/native_world_state.ts +99 -43
- package/src/native/native_world_state_instance.ts +59 -8
- package/src/native/world_state_ops_queue.ts +1 -1
- package/src/synchronizer/config.ts +47 -2
- package/src/synchronizer/errors.ts +5 -0
- package/src/synchronizer/factory.ts +31 -8
- package/src/synchronizer/server_world_state_synchronizer.ts +93 -40
- package/src/test/utils.ts +46 -31
- package/src/testing.ts +3 -7
- package/src/world-state-db/merkle_tree_db.ts +14 -5
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
NULLIFIER_TREE_HEIGHT,
|
|
9
9
|
PUBLIC_DATA_TREE_HEIGHT,
|
|
10
10
|
} from '@aztec/constants';
|
|
11
|
-
import { createLogger } from '@aztec/foundation/log';
|
|
11
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
12
12
|
import { NativeWorldState as BaseNativeWorldState, MsgpackChannel } from '@aztec/native';
|
|
13
13
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
14
14
|
import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees';
|
|
@@ -17,6 +17,7 @@ import assert from 'assert';
|
|
|
17
17
|
import { cpus } from 'os';
|
|
18
18
|
|
|
19
19
|
import type { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
|
|
20
|
+
import type { WorldStateTreeMapSizes } from '../synchronizer/factory.js';
|
|
20
21
|
import {
|
|
21
22
|
WorldStateMessageType,
|
|
22
23
|
type WorldStateRequest,
|
|
@@ -35,6 +36,8 @@ export interface NativeWorldStateInstance {
|
|
|
35
36
|
messageType: T,
|
|
36
37
|
body: WorldStateRequest[T] & WorldStateRequestCategories,
|
|
37
38
|
): Promise<WorldStateResponse[T]>;
|
|
39
|
+
// TODO(dbanks12): this returns any type, but we should strongly type it
|
|
40
|
+
getHandle(): any;
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
/**
|
|
@@ -50,15 +53,17 @@ export class NativeWorldState implements NativeWorldStateInstance {
|
|
|
50
53
|
|
|
51
54
|
/** Creates a new native WorldState instance */
|
|
52
55
|
constructor(
|
|
53
|
-
dataDir: string,
|
|
54
|
-
|
|
55
|
-
prefilledPublicData: PublicDataTreeLeaf[] = [],
|
|
56
|
-
private instrumentation: WorldStateInstrumentation,
|
|
57
|
-
private log = createLogger('world-state:database'),
|
|
56
|
+
private readonly dataDir: string,
|
|
57
|
+
private readonly wsTreeMapSizes: WorldStateTreeMapSizes,
|
|
58
|
+
private readonly prefilledPublicData: PublicDataTreeLeaf[] = [],
|
|
59
|
+
private readonly instrumentation: WorldStateInstrumentation,
|
|
60
|
+
private readonly log: Logger = createLogger('world-state:database'),
|
|
58
61
|
) {
|
|
59
62
|
const threads = Math.min(cpus().length, MAX_WORLD_STATE_THREADS);
|
|
60
63
|
log.info(
|
|
61
|
-
`Creating world state data store at directory ${dataDir} with map
|
|
64
|
+
`Creating world state data store at directory ${dataDir} with map sizes ${JSON.stringify(
|
|
65
|
+
wsTreeMapSizes,
|
|
66
|
+
)} and ${threads} threads.`,
|
|
62
67
|
);
|
|
63
68
|
const prefilledPublicDataBufferArray = prefilledPublicData.map(d => [d.slot.toBuffer(), d.value.toBuffer()]);
|
|
64
69
|
const ws = new BaseNativeWorldState(
|
|
@@ -76,7 +81,13 @@ export class NativeWorldState implements NativeWorldStateInstance {
|
|
|
76
81
|
},
|
|
77
82
|
prefilledPublicDataBufferArray,
|
|
78
83
|
GeneratorIndex.BLOCK_HASH,
|
|
79
|
-
|
|
84
|
+
{
|
|
85
|
+
[MerkleTreeId.NULLIFIER_TREE]: wsTreeMapSizes.nullifierTreeMapSizeKb,
|
|
86
|
+
[MerkleTreeId.NOTE_HASH_TREE]: wsTreeMapSizes.noteHashTreeMapSizeKb,
|
|
87
|
+
[MerkleTreeId.PUBLIC_DATA_TREE]: wsTreeMapSizes.publicDataTreeMapSizeKb,
|
|
88
|
+
[MerkleTreeId.L1_TO_L2_MESSAGE_TREE]: wsTreeMapSizes.messageTreeMapSizeKb,
|
|
89
|
+
[MerkleTreeId.ARCHIVE]: wsTreeMapSizes.archiveTreeMapSizeKb,
|
|
90
|
+
},
|
|
80
91
|
threads,
|
|
81
92
|
);
|
|
82
93
|
this.instance = new MsgpackChannel(ws);
|
|
@@ -84,6 +95,46 @@ export class NativeWorldState implements NativeWorldStateInstance {
|
|
|
84
95
|
this.queues.set(0, new WorldStateOpsQueue());
|
|
85
96
|
}
|
|
86
97
|
|
|
98
|
+
public getDataDir() {
|
|
99
|
+
return this.dataDir;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public clone() {
|
|
103
|
+
return new NativeWorldState(
|
|
104
|
+
this.dataDir,
|
|
105
|
+
this.wsTreeMapSizes,
|
|
106
|
+
this.prefilledPublicData,
|
|
107
|
+
this.instrumentation,
|
|
108
|
+
this.log,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Gets the native WorldState handle from the underlying native instance.
|
|
114
|
+
* We call the getHandle() method on the native WorldState to get a NAPI External
|
|
115
|
+
* that wraps the underlying C++ WorldState pointer.
|
|
116
|
+
* @returns The NAPI External handle to the native WorldState instance,since
|
|
117
|
+
* the NAPI external type is opaque, we return any (we could also use an opaque symbol type)
|
|
118
|
+
*/
|
|
119
|
+
public getHandle(): any {
|
|
120
|
+
const worldStateWrapper = (this.instance as any).dest;
|
|
121
|
+
|
|
122
|
+
if (!worldStateWrapper) {
|
|
123
|
+
throw new Error('No WorldStateWrapper found');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (typeof worldStateWrapper.getHandle !== 'function') {
|
|
127
|
+
throw new Error('WorldStateWrapper does not have getHandle method');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Call getHandle() to get the NAPI External
|
|
131
|
+
try {
|
|
132
|
+
return worldStateWrapper.getHandle();
|
|
133
|
+
} catch (error) {
|
|
134
|
+
this.log.error('Failed to get native WorldState handle', error);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
87
138
|
/**
|
|
88
139
|
* Sends a message to the native instance and returns the response.
|
|
89
140
|
* @param messageType - The type of message to send
|
|
@@ -35,7 +35,7 @@ export const MUTATING_MSG_TYPES = new Set([
|
|
|
35
35
|
WorldStateMessageType.SYNC_BLOCK,
|
|
36
36
|
WorldStateMessageType.CREATE_FORK,
|
|
37
37
|
WorldStateMessageType.DELETE_FORK,
|
|
38
|
-
WorldStateMessageType.
|
|
38
|
+
WorldStateMessageType.FINALIZE_BLOCKS,
|
|
39
39
|
WorldStateMessageType.UNWIND_BLOCKS,
|
|
40
40
|
WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS,
|
|
41
41
|
WorldStateMessageType.CREATE_CHECKPOINT,
|
|
@@ -16,9 +16,24 @@ export interface WorldStateConfig {
|
|
|
16
16
|
/** Size of the batch for each get-blocks request from the synchronizer to the archiver. */
|
|
17
17
|
worldStateBlockRequestBatchSize?: number;
|
|
18
18
|
|
|
19
|
-
/** The map size to be provided to LMDB for each world state tree DB, optional, will inherit from the general
|
|
19
|
+
/** The map size to be provided to LMDB for each world state tree DB, optional, will inherit from the general dataStoreMapSizeKb if not specified*/
|
|
20
20
|
worldStateDbMapSizeKb?: number;
|
|
21
21
|
|
|
22
|
+
/** The map size to be provided to LMDB for each world state archive tree, optional, will inherit from the general worldStateDbMapSizeKb if not specified*/
|
|
23
|
+
archiveTreeMapSizeKb?: number;
|
|
24
|
+
|
|
25
|
+
/** The map size to be provided to LMDB for each world state nullifier tree, optional, will inherit from the general worldStateDbMapSizeKb if not specified*/
|
|
26
|
+
nullifierTreeMapSizeKb?: number;
|
|
27
|
+
|
|
28
|
+
/** The map size to be provided to LMDB for each world state note hash tree, optional, will inherit from the general worldStateDbMapSizeKb if not specified*/
|
|
29
|
+
noteHashTreeMapSizeKb?: number;
|
|
30
|
+
|
|
31
|
+
/** The map size to be provided to LMDB for each world state message tree, optional, will inherit from the general worldStateDbMapSizeKb if not specified*/
|
|
32
|
+
messageTreeMapSizeKb?: number;
|
|
33
|
+
|
|
34
|
+
/** The map size to be provided to LMDB for each world state public data tree, optional, will inherit from the general worldStateDbMapSizeKb if not specified*/
|
|
35
|
+
publicDataTreeMapSizeKb?: number;
|
|
36
|
+
|
|
22
37
|
/** Optional directory for the world state DB, if unspecified will default to the general data directory */
|
|
23
38
|
worldStateDataDirectory?: string;
|
|
24
39
|
|
|
@@ -46,7 +61,37 @@ export const worldStateConfigMappings: ConfigMappingsType<WorldStateConfig> = {
|
|
|
46
61
|
worldStateDbMapSizeKb: {
|
|
47
62
|
env: 'WS_DB_MAP_SIZE_KB',
|
|
48
63
|
parseEnv: (val: string | undefined) => (val ? +val : undefined),
|
|
49
|
-
description: 'The maximum possible size of the world state DB',
|
|
64
|
+
description: 'The maximum possible size of the world state DB in KB. Overwrites the general dataStoreMapSizeKb.',
|
|
65
|
+
},
|
|
66
|
+
archiveTreeMapSizeKb: {
|
|
67
|
+
env: 'ARCHIVE_TREE_MAP_SIZE_KB',
|
|
68
|
+
parseEnv: (val: string | undefined) => (val ? +val : undefined),
|
|
69
|
+
description:
|
|
70
|
+
'The maximum possible size of the world state archive tree in KB. Overwrites the general worldStateDbMapSizeKb.',
|
|
71
|
+
},
|
|
72
|
+
nullifierTreeMapSizeKb: {
|
|
73
|
+
env: 'NULLIFIER_TREE_MAP_SIZE_KB',
|
|
74
|
+
parseEnv: (val: string | undefined) => (val ? +val : undefined),
|
|
75
|
+
description:
|
|
76
|
+
'The maximum possible size of the world state nullifier tree in KB. Overwrites the general worldStateDbMapSizeKb.',
|
|
77
|
+
},
|
|
78
|
+
noteHashTreeMapSizeKb: {
|
|
79
|
+
env: 'NOTE_HASH_TREE_MAP_SIZE_KB',
|
|
80
|
+
parseEnv: (val: string | undefined) => (val ? +val : undefined),
|
|
81
|
+
description:
|
|
82
|
+
'The maximum possible size of the world state note hash tree in KB. Overwrites the general worldStateDbMapSizeKb.',
|
|
83
|
+
},
|
|
84
|
+
messageTreeMapSizeKb: {
|
|
85
|
+
env: 'MESSAGE_TREE_MAP_SIZE_KB',
|
|
86
|
+
parseEnv: (val: string | undefined) => (val ? +val : undefined),
|
|
87
|
+
description:
|
|
88
|
+
'The maximum possible size of the world state message tree in KB. Overwrites the general worldStateDbMapSizeKb.',
|
|
89
|
+
},
|
|
90
|
+
publicDataTreeMapSizeKb: {
|
|
91
|
+
env: 'PUBLIC_DATA_TREE_MAP_SIZE_KB',
|
|
92
|
+
parseEnv: (val: string | undefined) => (val ? +val : undefined),
|
|
93
|
+
description:
|
|
94
|
+
'The maximum possible size of the world state public data tree in KB. Overwrites the general worldStateDbMapSizeKb.',
|
|
50
95
|
},
|
|
51
96
|
worldStateDataDirectory: {
|
|
52
97
|
env: 'WS_DATA_DIRECTORY',
|
|
@@ -9,6 +9,14 @@ import { NativeWorldStateService } from '../native/native_world_state.js';
|
|
|
9
9
|
import type { WorldStateConfig } from './config.js';
|
|
10
10
|
import { ServerWorldStateSynchronizer } from './server_world_state_synchronizer.js';
|
|
11
11
|
|
|
12
|
+
export interface WorldStateTreeMapSizes {
|
|
13
|
+
archiveTreeMapSizeKb: number;
|
|
14
|
+
nullifierTreeMapSizeKb: number;
|
|
15
|
+
noteHashTreeMapSizeKb: number;
|
|
16
|
+
messageTreeMapSizeKb: number;
|
|
17
|
+
publicDataTreeMapSizeKb: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
export async function createWorldStateSynchronizer(
|
|
13
21
|
config: WorldStateConfig & DataStoreConfig,
|
|
14
22
|
l2BlockSource: L2BlockSource & L1ToL2MessageSource,
|
|
@@ -21,25 +29,40 @@ export async function createWorldStateSynchronizer(
|
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
export async function createWorldState(
|
|
24
|
-
config:
|
|
32
|
+
config: Pick<
|
|
33
|
+
WorldStateConfig,
|
|
34
|
+
| 'worldStateDataDirectory'
|
|
35
|
+
| 'worldStateDbMapSizeKb'
|
|
36
|
+
| 'archiveTreeMapSizeKb'
|
|
37
|
+
| 'nullifierTreeMapSizeKb'
|
|
38
|
+
| 'noteHashTreeMapSizeKb'
|
|
39
|
+
| 'messageTreeMapSizeKb'
|
|
40
|
+
| 'publicDataTreeMapSizeKb'
|
|
41
|
+
> &
|
|
42
|
+
Pick<DataStoreConfig, 'dataDirectory' | 'dataStoreMapSizeKb' | 'l1Contracts'>,
|
|
25
43
|
prefilledPublicData: PublicDataTreeLeaf[] = [],
|
|
26
44
|
instrumentation: WorldStateInstrumentation = new WorldStateInstrumentation(getTelemetryClient()),
|
|
27
45
|
) {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
const dataDirectory = config.worldStateDataDirectory ?? config.dataDirectory;
|
|
47
|
+
const dataStoreMapSizeKb = config.worldStateDbMapSizeKb ?? config.dataStoreMapSizeKb;
|
|
48
|
+
const wsTreeMapSizes: WorldStateTreeMapSizes = {
|
|
49
|
+
archiveTreeMapSizeKb: config.archiveTreeMapSizeKb ?? dataStoreMapSizeKb,
|
|
50
|
+
nullifierTreeMapSizeKb: config.nullifierTreeMapSizeKb ?? dataStoreMapSizeKb,
|
|
51
|
+
noteHashTreeMapSizeKb: config.noteHashTreeMapSizeKb ?? dataStoreMapSizeKb,
|
|
52
|
+
messageTreeMapSizeKb: config.messageTreeMapSizeKb ?? dataStoreMapSizeKb,
|
|
53
|
+
publicDataTreeMapSizeKb: config.publicDataTreeMapSizeKb ?? dataStoreMapSizeKb,
|
|
54
|
+
};
|
|
32
55
|
|
|
33
56
|
if (!config.l1Contracts?.rollupAddress) {
|
|
34
57
|
throw new Error('Rollup address is required to create a world state synchronizer.');
|
|
35
58
|
}
|
|
36
59
|
|
|
37
60
|
// If a data directory is provided in config, then create a persistent store.
|
|
38
|
-
const merkleTrees =
|
|
61
|
+
const merkleTrees = dataDirectory
|
|
39
62
|
? await NativeWorldStateService.new(
|
|
40
63
|
config.l1Contracts.rollupAddress,
|
|
41
|
-
|
|
42
|
-
|
|
64
|
+
dataDirectory,
|
|
65
|
+
wsTreeMapSizes,
|
|
43
66
|
prefilledPublicData,
|
|
44
67
|
instrumentation,
|
|
45
68
|
)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { L1_TO_L2_MSG_SUBTREE_HEIGHT } from '@aztec/constants';
|
|
2
|
+
import { SHA256Trunc } from '@aztec/foundation/crypto';
|
|
2
3
|
import type { Fr } from '@aztec/foundation/fields';
|
|
3
|
-
import { createLogger } from '@aztec/foundation/log';
|
|
4
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
4
5
|
import { promiseWithResolvers } from '@aztec/foundation/promise';
|
|
5
6
|
import { elapsed } from '@aztec/foundation/timer';
|
|
6
7
|
import { MerkleTreeCalculator } from '@aztec/foundation/trees';
|
|
7
|
-
import { SHA256Trunc } from '@aztec/merkle-tree';
|
|
8
8
|
import type {
|
|
9
9
|
L2Block,
|
|
10
10
|
L2BlockId,
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
type WorldStateSynchronizerStatus,
|
|
23
23
|
} from '@aztec/stdlib/interfaces/server';
|
|
24
24
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
25
|
+
import type { SnapshotDataKeys } from '@aztec/stdlib/snapshots';
|
|
25
26
|
import type { L2BlockHandledStats } from '@aztec/stdlib/stats';
|
|
26
27
|
import { MerkleTreeId, type MerkleTreeReadOperations, type MerkleTreeWriteOperations } from '@aztec/stdlib/trees';
|
|
27
28
|
import { TraceableL2BlockStream, getTelemetryClient } from '@aztec/telemetry-client';
|
|
@@ -30,6 +31,9 @@ import { WorldStateInstrumentation } from '../instrumentation/instrumentation.js
|
|
|
30
31
|
import type { WorldStateStatusFull } from '../native/message.js';
|
|
31
32
|
import type { MerkleTreeAdminDatabase } from '../world-state-db/merkle_tree_db.js';
|
|
32
33
|
import type { WorldStateConfig } from './config.js';
|
|
34
|
+
import { WorldStateSynchronizerError } from './errors.js';
|
|
35
|
+
|
|
36
|
+
export type { SnapshotDataKeys };
|
|
33
37
|
|
|
34
38
|
/**
|
|
35
39
|
* Synchronizes the world state with the L2 blocks from a L2BlockSource via a block stream.
|
|
@@ -49,12 +53,16 @@ export class ServerWorldStateSynchronizer
|
|
|
49
53
|
private syncPromise = promiseWithResolvers<void>();
|
|
50
54
|
protected blockStream: L2BlockStream | undefined;
|
|
51
55
|
|
|
56
|
+
// WorldState doesn't track the proven block number, it only tracks the latest tips of the pending chain and the finalized chain
|
|
57
|
+
// store the proven block number here, in the synchronizer, so that we don't end up spamming the logs with 'chain-proved' events
|
|
58
|
+
private provenBlockNumber: bigint | undefined;
|
|
59
|
+
|
|
52
60
|
constructor(
|
|
53
61
|
private readonly merkleTreeDb: MerkleTreeAdminDatabase,
|
|
54
62
|
private readonly l2BlockSource: L2BlockSource & L1ToL2MessageSource,
|
|
55
63
|
private readonly config: WorldStateConfig,
|
|
56
64
|
private instrumentation = new WorldStateInstrumentation(getTelemetryClient()),
|
|
57
|
-
private readonly log = createLogger('world_state'),
|
|
65
|
+
private readonly log: Logger = createLogger('world_state'),
|
|
58
66
|
) {
|
|
59
67
|
this.merkleTreeCommitted = this.merkleTreeDb.getCommitted();
|
|
60
68
|
this.historyToKeep = config.worldStateBlockHistory < 1 ? undefined : config.worldStateBlockHistory;
|
|
@@ -77,6 +85,14 @@ export class ServerWorldStateSynchronizer
|
|
|
77
85
|
return this.merkleTreeDb.fork(blockNumber);
|
|
78
86
|
}
|
|
79
87
|
|
|
88
|
+
public backupTo(dstPath: string, compact?: boolean): Promise<Record<Exclude<SnapshotDataKeys, 'archiver'>, string>> {
|
|
89
|
+
return this.merkleTreeDb.backupTo(dstPath, compact);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public clear(): Promise<void> {
|
|
93
|
+
return this.merkleTreeDb.clear();
|
|
94
|
+
}
|
|
95
|
+
|
|
80
96
|
public async start() {
|
|
81
97
|
if (this.currentState === WorldStateRunningState.STOPPED) {
|
|
82
98
|
throw new Error('Synchronizer already stopped');
|
|
@@ -131,9 +147,9 @@ export class ServerWorldStateSynchronizer
|
|
|
131
147
|
public async status(): Promise<WorldStateSynchronizerStatus> {
|
|
132
148
|
const summary = await this.merkleTreeDb.getStatusSummary();
|
|
133
149
|
const status: WorldStateSyncStatus = {
|
|
134
|
-
latestBlockNumber: Number(summary.
|
|
135
|
-
latestBlockHash: (await this.getL2BlockHash(Number(summary.
|
|
136
|
-
|
|
150
|
+
latestBlockNumber: Number(summary.unfinalizedBlockNumber),
|
|
151
|
+
latestBlockHash: (await this.getL2BlockHash(Number(summary.unfinalizedBlockNumber))) ?? '',
|
|
152
|
+
finalizedBlockNumber: Number(summary.finalizedBlockNumber),
|
|
137
153
|
oldestHistoricBlockNumber: Number(summary.oldestHistoricalBlock),
|
|
138
154
|
treesAreSynched: summary.treesAreSynched,
|
|
139
155
|
};
|
|
@@ -147,16 +163,36 @@ export class ServerWorldStateSynchronizer
|
|
|
147
163
|
return (await this.getL2Tips()).latest.number;
|
|
148
164
|
}
|
|
149
165
|
|
|
166
|
+
public async stopSync() {
|
|
167
|
+
this.log.debug('Stopping sync...');
|
|
168
|
+
await this.blockStream?.stop();
|
|
169
|
+
this.log.info('Stopped sync');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
public resumeSync() {
|
|
173
|
+
if (!this.blockStream) {
|
|
174
|
+
throw new Error('Cannot resume sync as block stream is not initialized');
|
|
175
|
+
}
|
|
176
|
+
this.log.debug('Resuming sync...');
|
|
177
|
+
this.blockStream.start();
|
|
178
|
+
this.log.info('Resumed sync');
|
|
179
|
+
}
|
|
180
|
+
|
|
150
181
|
/**
|
|
151
182
|
* Forces an immediate sync.
|
|
152
|
-
* @param targetBlockNumber - The target block number that we must sync to. Will download unproven blocks if needed to reach it.
|
|
183
|
+
* @param targetBlockNumber - The target block number that we must sync to. Will download unproven blocks if needed to reach it.
|
|
184
|
+
* @param skipThrowIfTargetNotReached - Whether to skip throwing if the target block number is not reached.
|
|
153
185
|
* @returns A promise that resolves with the block number the world state was synced to
|
|
154
186
|
*/
|
|
155
|
-
public async syncImmediate(targetBlockNumber?: number): Promise<number> {
|
|
156
|
-
if (this.currentState !== WorldStateRunningState.RUNNING
|
|
187
|
+
public async syncImmediate(targetBlockNumber?: number, skipThrowIfTargetNotReached?: boolean): Promise<number> {
|
|
188
|
+
if (this.currentState !== WorldStateRunningState.RUNNING) {
|
|
157
189
|
throw new Error(`World State is not running. Unable to perform sync.`);
|
|
158
190
|
}
|
|
159
191
|
|
|
192
|
+
if (this.blockStream === undefined) {
|
|
193
|
+
throw new Error('Block stream is not initialized. Unable to perform sync.');
|
|
194
|
+
}
|
|
195
|
+
|
|
160
196
|
// If we have been given a block number to sync to and we have reached that number then return
|
|
161
197
|
const currentBlockNumber = await this.getLatestBlockNumber();
|
|
162
198
|
if (targetBlockNumber !== undefined && targetBlockNumber <= currentBlockNumber) {
|
|
@@ -164,13 +200,32 @@ export class ServerWorldStateSynchronizer
|
|
|
164
200
|
}
|
|
165
201
|
this.log.debug(`World State at ${currentBlockNumber} told to sync to ${targetBlockNumber ?? 'latest'}`);
|
|
166
202
|
|
|
203
|
+
// If the archiver is behind the target block, force an archiver sync
|
|
204
|
+
if (targetBlockNumber) {
|
|
205
|
+
const archiverLatestBlock = await this.l2BlockSource.getBlockNumber();
|
|
206
|
+
if (archiverLatestBlock < targetBlockNumber) {
|
|
207
|
+
this.log.debug(`Archiver is at ${archiverLatestBlock} behind target block ${targetBlockNumber}.`);
|
|
208
|
+
await this.l2BlockSource.syncImmediate();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
167
212
|
// Force the block stream to sync against the archiver now
|
|
168
213
|
await this.blockStream.sync();
|
|
169
214
|
|
|
170
215
|
// If we have been given a block number to sync to and we have not reached that number then fail
|
|
171
216
|
const updatedBlockNumber = await this.getLatestBlockNumber();
|
|
172
|
-
if (targetBlockNumber !== undefined && targetBlockNumber > updatedBlockNumber) {
|
|
173
|
-
throw new
|
|
217
|
+
if (!skipThrowIfTargetNotReached && targetBlockNumber !== undefined && targetBlockNumber > updatedBlockNumber) {
|
|
218
|
+
throw new WorldStateSynchronizerError(
|
|
219
|
+
`Unable to sync to block number ${targetBlockNumber} (last synced is ${updatedBlockNumber})`,
|
|
220
|
+
{
|
|
221
|
+
cause: {
|
|
222
|
+
reason: 'block_not_available',
|
|
223
|
+
previousBlockNumber: currentBlockNumber,
|
|
224
|
+
updatedBlockNumber,
|
|
225
|
+
targetBlockNumber,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
);
|
|
174
229
|
}
|
|
175
230
|
|
|
176
231
|
return updatedBlockNumber;
|
|
@@ -195,35 +250,31 @@ export class ServerWorldStateSynchronizer
|
|
|
195
250
|
/** Returns the latest L2 block number for each tip of the chain (latest, proven, finalized). */
|
|
196
251
|
public async getL2Tips(): Promise<L2Tips> {
|
|
197
252
|
const status = await this.merkleTreeDb.getStatusSummary();
|
|
198
|
-
const
|
|
199
|
-
const latestBlockId: L2BlockId = { number: Number(status.
|
|
253
|
+
const unfinalizedBlockHash = await this.getL2BlockHash(Number(status.unfinalizedBlockNumber));
|
|
254
|
+
const latestBlockId: L2BlockId = { number: Number(status.unfinalizedBlockNumber), hash: unfinalizedBlockHash! };
|
|
200
255
|
|
|
201
256
|
return {
|
|
202
257
|
latest: latestBlockId,
|
|
203
|
-
finalized: { number: Number(status.
|
|
204
|
-
proven: { number: Number(status.
|
|
258
|
+
finalized: { number: Number(status.finalizedBlockNumber), hash: '' },
|
|
259
|
+
proven: { number: Number(this.provenBlockNumber ?? status.finalizedBlockNumber), hash: '' }, // TODO(palla/reorg): Using finalized as proven for now
|
|
205
260
|
};
|
|
206
261
|
}
|
|
207
262
|
|
|
208
263
|
/** Handles an event emitted by the block stream. */
|
|
209
264
|
public async handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void> {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
break;
|
|
224
|
-
}
|
|
225
|
-
} catch (err) {
|
|
226
|
-
this.log.error('Error processing block stream', err);
|
|
265
|
+
switch (event.type) {
|
|
266
|
+
case 'blocks-added':
|
|
267
|
+
await this.handleL2Blocks(event.blocks.map(b => b.block));
|
|
268
|
+
break;
|
|
269
|
+
case 'chain-pruned':
|
|
270
|
+
await this.handleChainPruned(event.block.number);
|
|
271
|
+
break;
|
|
272
|
+
case 'chain-proven':
|
|
273
|
+
await this.handleChainProven(event.block.number);
|
|
274
|
+
break;
|
|
275
|
+
case 'chain-finalized':
|
|
276
|
+
await this.handleChainFinalized(event.block.number);
|
|
277
|
+
break;
|
|
227
278
|
}
|
|
228
279
|
}
|
|
229
280
|
|
|
@@ -235,17 +286,17 @@ export class ServerWorldStateSynchronizer
|
|
|
235
286
|
private async handleL2Blocks(l2Blocks: L2Block[]) {
|
|
236
287
|
this.log.trace(`Handling L2 blocks ${l2Blocks[0].number} to ${l2Blocks.at(-1)!.number}`);
|
|
237
288
|
|
|
238
|
-
const messagePromises = l2Blocks.map(block => this.l2BlockSource.getL1ToL2Messages(
|
|
289
|
+
const messagePromises = l2Blocks.map(block => this.l2BlockSource.getL1ToL2Messages(block.number));
|
|
239
290
|
const l1ToL2Messages: Fr[][] = await Promise.all(messagePromises);
|
|
240
291
|
let updateStatus: WorldStateStatusFull | undefined = undefined;
|
|
241
292
|
|
|
242
293
|
for (let i = 0; i < l2Blocks.length; i++) {
|
|
243
294
|
const [duration, result] = await elapsed(() => this.handleL2Block(l2Blocks[i], l1ToL2Messages[i]));
|
|
244
|
-
this.log.
|
|
295
|
+
this.log.info(`World state updated with L2 block ${l2Blocks[i].number}`, {
|
|
245
296
|
eventName: 'l2-block-handled',
|
|
246
297
|
duration,
|
|
247
|
-
|
|
248
|
-
|
|
298
|
+
unfinalizedBlockNumber: result.summary.unfinalizedBlockNumber,
|
|
299
|
+
finalizedBlockNumber: result.summary.finalizedBlockNumber,
|
|
249
300
|
oldestHistoricBlock: result.summary.oldestHistoricalBlock,
|
|
250
301
|
...l2Blocks[i].getStats(),
|
|
251
302
|
} satisfies L2BlockHandledStats);
|
|
@@ -288,11 +339,11 @@ export class ServerWorldStateSynchronizer
|
|
|
288
339
|
|
|
289
340
|
private async handleChainFinalized(blockNumber: number) {
|
|
290
341
|
this.log.verbose(`Finalized chain is now at block ${blockNumber}`);
|
|
291
|
-
const summary = await this.merkleTreeDb.
|
|
342
|
+
const summary = await this.merkleTreeDb.setFinalized(BigInt(blockNumber));
|
|
292
343
|
if (this.historyToKeep === undefined) {
|
|
293
344
|
return;
|
|
294
345
|
}
|
|
295
|
-
const newHistoricBlock = summary.
|
|
346
|
+
const newHistoricBlock = summary.finalizedBlockNumber - BigInt(this.historyToKeep) + 1n;
|
|
296
347
|
if (newHistoricBlock <= 1) {
|
|
297
348
|
return;
|
|
298
349
|
}
|
|
@@ -302,6 +353,7 @@ export class ServerWorldStateSynchronizer
|
|
|
302
353
|
}
|
|
303
354
|
|
|
304
355
|
private handleChainProven(blockNumber: number) {
|
|
356
|
+
this.provenBlockNumber = BigInt(blockNumber);
|
|
305
357
|
this.log.debug(`Proven chain is now at block ${blockNumber}`);
|
|
306
358
|
return Promise.resolve();
|
|
307
359
|
}
|
|
@@ -310,6 +362,7 @@ export class ServerWorldStateSynchronizer
|
|
|
310
362
|
this.log.warn(`Chain pruned to block ${blockNumber}`);
|
|
311
363
|
const status = await this.merkleTreeDb.unwindBlocks(BigInt(blockNumber));
|
|
312
364
|
this.latestBlockHashQuery = undefined;
|
|
365
|
+
this.provenBlockNumber = undefined;
|
|
313
366
|
this.instrumentation.updateWorldStateMetrics(status);
|
|
314
367
|
}
|
|
315
368
|
|
|
@@ -328,7 +381,7 @@ export class ServerWorldStateSynchronizer
|
|
|
328
381
|
* @param inHash - The inHash of the block.
|
|
329
382
|
* @throws If the L1 to L2 messages do not hash to the block inHash.
|
|
330
383
|
*/
|
|
331
|
-
protected async verifyMessagesHashToInHash(l1ToL2Messages: Fr[], inHash:
|
|
384
|
+
protected async verifyMessagesHashToInHash(l1ToL2Messages: Fr[], inHash: Fr) {
|
|
332
385
|
const treeCalculator = await MerkleTreeCalculator.create(
|
|
333
386
|
L1_TO_L2_MSG_SUBTREE_HEIGHT,
|
|
334
387
|
Buffer.alloc(32),
|
|
@@ -337,7 +390,7 @@ export class ServerWorldStateSynchronizer
|
|
|
337
390
|
|
|
338
391
|
const root = await treeCalculator.computeTreeRoot(l1ToL2Messages.map(msg => msg.toBuffer()));
|
|
339
392
|
|
|
340
|
-
if (!root.equals(inHash)) {
|
|
393
|
+
if (!root.equals(inHash.toBuffer())) {
|
|
341
394
|
throw new Error('Obtained L1 to L2 messages failed to be hashed to the block inHash');
|
|
342
395
|
}
|
|
343
396
|
}
|
package/src/test/utils.ts
CHANGED
|
@@ -7,49 +7,64 @@ import {
|
|
|
7
7
|
import { padArrayEnd } from '@aztec/foundation/collection';
|
|
8
8
|
import { Fr } from '@aztec/foundation/fields';
|
|
9
9
|
import { L2Block } from '@aztec/stdlib/block';
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
IndexedTreeId,
|
|
12
|
+
MerkleTreeReadOperations,
|
|
13
|
+
MerkleTreeWriteOperations,
|
|
14
|
+
} from '@aztec/stdlib/interfaces/server';
|
|
11
15
|
import { AppendOnlyTreeSnapshot, MerkleTreeId } from '@aztec/stdlib/trees';
|
|
12
16
|
|
|
13
17
|
import type { NativeWorldStateService } from '../native/native_world_state.js';
|
|
14
18
|
|
|
15
|
-
export async function mockBlock(
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
export async function mockBlock(
|
|
20
|
+
blockNum: number,
|
|
21
|
+
size: number,
|
|
22
|
+
fork: MerkleTreeWriteOperations,
|
|
23
|
+
maxEffects: number | undefined = 1000, // Defaults to the maximum tx effects.
|
|
24
|
+
) {
|
|
25
|
+
const l2Block = await L2Block.random(blockNum, size, undefined, undefined, undefined, undefined, maxEffects);
|
|
26
|
+
const l1ToL2Messages = Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(0).map(Fr.random);
|
|
18
27
|
|
|
19
|
-
// Sync the append only trees
|
|
20
28
|
{
|
|
29
|
+
const insertData = async (
|
|
30
|
+
treeId: IndexedTreeId,
|
|
31
|
+
data: Buffer[][],
|
|
32
|
+
subTreeHeight: number,
|
|
33
|
+
fork: MerkleTreeWriteOperations,
|
|
34
|
+
) => {
|
|
35
|
+
for (const dataBatch of data) {
|
|
36
|
+
await fork.batchInsert(treeId, dataBatch, subTreeHeight);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const publicDataInsert = insertData(
|
|
41
|
+
MerkleTreeId.PUBLIC_DATA_TREE,
|
|
42
|
+
l2Block.body.txEffects.map(txEffect => txEffect.publicDataWrites.map(write => write.toBuffer())),
|
|
43
|
+
0,
|
|
44
|
+
fork,
|
|
45
|
+
);
|
|
46
|
+
const nullifierInsert = insertData(
|
|
47
|
+
MerkleTreeId.NULLIFIER_TREE,
|
|
48
|
+
l2Block.body.txEffects.map(txEffect =>
|
|
49
|
+
padArrayEnd(txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX).map(nullifier => nullifier.toBuffer()),
|
|
50
|
+
),
|
|
51
|
+
NULLIFIER_SUBTREE_HEIGHT,
|
|
52
|
+
fork,
|
|
53
|
+
);
|
|
21
54
|
const noteHashesPadded = l2Block.body.txEffects.flatMap(txEffect =>
|
|
22
55
|
padArrayEnd(txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX),
|
|
23
56
|
);
|
|
24
|
-
await fork.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, noteHashesPadded);
|
|
25
|
-
|
|
26
|
-
const l1ToL2MessagesPadded = padArrayEnd(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
27
|
-
await fork.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2MessagesPadded);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Sync the indexed trees
|
|
31
|
-
{
|
|
32
|
-
// We insert the public data tree leaves with one batch per tx to avoid updating the same key twice
|
|
33
|
-
for (const txEffect of l2Block.body.txEffects) {
|
|
34
|
-
await fork.batchInsert(
|
|
35
|
-
MerkleTreeId.PUBLIC_DATA_TREE,
|
|
36
|
-
txEffect.publicDataWrites.map(write => write.toBuffer()),
|
|
37
|
-
0,
|
|
38
|
-
);
|
|
39
57
|
|
|
40
|
-
|
|
58
|
+
const l1ToL2MessagesPadded = padArrayEnd<Fr, number>(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
41
59
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
NULLIFIER_SUBTREE_HEIGHT,
|
|
46
|
-
);
|
|
47
|
-
}
|
|
60
|
+
const noteHashInsert = fork.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, noteHashesPadded);
|
|
61
|
+
const messageInsert = fork.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2MessagesPadded);
|
|
62
|
+
await Promise.all([publicDataInsert, nullifierInsert, noteHashInsert, messageInsert]);
|
|
48
63
|
}
|
|
49
64
|
|
|
50
65
|
const state = await fork.getStateReference();
|
|
51
66
|
l2Block.header.state = state;
|
|
52
|
-
await fork.updateArchive(l2Block.
|
|
67
|
+
await fork.updateArchive(l2Block.getBlockHeader());
|
|
53
68
|
|
|
54
69
|
const archiveState = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
55
70
|
|
|
@@ -65,7 +80,7 @@ export async function mockEmptyBlock(blockNum: number, fork: MerkleTreeWriteOper
|
|
|
65
80
|
const l2Block = L2Block.empty();
|
|
66
81
|
const l1ToL2Messages = Array(16).fill(0).map(Fr.zero);
|
|
67
82
|
|
|
68
|
-
l2Block.header.globalVariables.blockNumber =
|
|
83
|
+
l2Block.header.globalVariables.blockNumber = blockNum;
|
|
69
84
|
|
|
70
85
|
// Sync the append only trees
|
|
71
86
|
{
|
|
@@ -74,7 +89,7 @@ export async function mockEmptyBlock(blockNum: number, fork: MerkleTreeWriteOper
|
|
|
74
89
|
);
|
|
75
90
|
await fork.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, noteHashesPadded);
|
|
76
91
|
|
|
77
|
-
const l1ToL2MessagesPadded = padArrayEnd(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
92
|
+
const l1ToL2MessagesPadded = padArrayEnd<Fr, number>(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
78
93
|
await fork.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2MessagesPadded);
|
|
79
94
|
}
|
|
80
95
|
|
|
@@ -100,7 +115,7 @@ export async function mockEmptyBlock(blockNum: number, fork: MerkleTreeWriteOper
|
|
|
100
115
|
|
|
101
116
|
const state = await fork.getStateReference();
|
|
102
117
|
l2Block.header.state = state;
|
|
103
|
-
await fork.updateArchive(l2Block.
|
|
118
|
+
await fork.updateArchive(l2Block.getBlockHeader());
|
|
104
119
|
|
|
105
120
|
const archiveState = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
106
121
|
|