@aztec/world-state 0.57.0 → 0.59.0
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/README.md +1 -1
- package/dest/index.d.ts +1 -0
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +2 -1
- package/dest/native/merkle_trees_facade.d.ts +34 -0
- package/dest/native/merkle_trees_facade.d.ts.map +1 -0
- package/dest/native/merkle_trees_facade.js +193 -0
- package/dest/native/message.d.ts +78 -19
- package/dest/native/message.d.ts.map +1 -1
- package/dest/native/message.js +27 -26
- package/dest/native/native_world_state.d.ts +39 -38
- package/dest/native/native_world_state.d.ts.map +1 -1
- package/dest/native/native_world_state.js +108 -254
- package/dest/native/native_world_state_instance.d.ts +40 -0
- package/dest/native/native_world_state_instance.d.ts.map +1 -0
- package/dest/native/native_world_state_instance.js +183 -0
- package/dest/synchronizer/config.d.ts +2 -2
- package/dest/synchronizer/config.d.ts.map +1 -1
- package/dest/synchronizer/config.js +6 -7
- package/dest/synchronizer/factory.d.ts +3 -0
- package/dest/synchronizer/factory.d.ts.map +1 -1
- package/dest/synchronizer/factory.js +13 -4
- package/dest/synchronizer/server_world_state_synchronizer.d.ts +41 -41
- package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
- package/dest/synchronizer/server_world_state_synchronizer.js +126 -151
- package/dest/test/utils.d.ts +14 -0
- package/dest/test/utils.d.ts.map +1 -0
- package/dest/test/utils.js +67 -0
- package/dest/world-state-db/index.d.ts +1 -1
- package/dest/world-state-db/index.d.ts.map +1 -1
- package/dest/world-state-db/merkle_tree_db.d.ts +42 -32
- package/dest/world-state-db/merkle_tree_db.d.ts.map +1 -1
- package/dest/world-state-db/merkle_tree_db.js +1 -1
- package/dest/world-state-db/merkle_tree_operations_facade.d.ts +8 -37
- package/dest/world-state-db/merkle_tree_operations_facade.d.ts.map +1 -1
- package/dest/world-state-db/merkle_tree_operations_facade.js +6 -45
- package/dest/world-state-db/merkle_tree_snapshot_operations_facade.d.ts +4 -13
- package/dest/world-state-db/merkle_tree_snapshot_operations_facade.d.ts.map +1 -1
- package/dest/world-state-db/merkle_tree_snapshot_operations_facade.js +2 -29
- package/dest/world-state-db/merkle_trees.d.ts +17 -19
- package/dest/world-state-db/merkle_trees.d.ts.map +1 -1
- package/dest/world-state-db/merkle_trees.js +39 -36
- package/package.json +15 -12
- package/src/index.ts +1 -0
- package/src/native/merkle_trees_facade.ts +279 -0
- package/src/native/message.ts +97 -20
- package/src/native/native_world_state.ts +125 -346
- package/src/native/native_world_state_instance.ts +262 -0
- package/src/synchronizer/config.ts +8 -9
- package/src/synchronizer/factory.ts +20 -3
- package/src/synchronizer/server_world_state_synchronizer.ts +149 -178
- package/src/test/utils.ts +123 -0
- package/src/world-state-db/index.ts +1 -1
- package/src/world-state-db/merkle_tree_db.ts +55 -49
- package/src/world-state-db/merkle_tree_operations_facade.ts +10 -55
- package/src/world-state-db/merkle_tree_snapshot_operations_facade.ts +7 -46
- package/src/world-state-db/merkle_trees.ts +50 -45
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { MerkleTreeId } from '@aztec/circuit-types';
|
|
2
|
+
import {
|
|
3
|
+
ARCHIVE_HEIGHT,
|
|
4
|
+
Fr,
|
|
5
|
+
GeneratorIndex,
|
|
6
|
+
L1_TO_L2_MSG_TREE_HEIGHT,
|
|
7
|
+
MAX_NULLIFIERS_PER_TX,
|
|
8
|
+
MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
|
|
9
|
+
NOTE_HASH_TREE_HEIGHT,
|
|
10
|
+
NULLIFIER_TREE_HEIGHT,
|
|
11
|
+
PUBLIC_DATA_TREE_HEIGHT,
|
|
12
|
+
} from '@aztec/circuits.js';
|
|
13
|
+
import { createDebugLogger, fmtLogData } from '@aztec/foundation/log';
|
|
14
|
+
import { SerialQueue } from '@aztec/foundation/queue';
|
|
15
|
+
import { Timer } from '@aztec/foundation/timer';
|
|
16
|
+
|
|
17
|
+
import assert from 'assert';
|
|
18
|
+
import bindings from 'bindings';
|
|
19
|
+
import { Decoder, Encoder, addExtension } from 'msgpackr';
|
|
20
|
+
import { cpus } from 'os';
|
|
21
|
+
import { isAnyArrayBuffer } from 'util/types';
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
MessageHeader,
|
|
25
|
+
TypedMessage,
|
|
26
|
+
WorldStateMessageType,
|
|
27
|
+
type WorldStateRequest,
|
|
28
|
+
type WorldStateResponse,
|
|
29
|
+
} from './message.js';
|
|
30
|
+
|
|
31
|
+
// small extension to pack an NodeJS Fr instance to a representation that the C++ code can understand
|
|
32
|
+
// this only works for writes. Unpacking from C++ can't create Fr instances because the data is passed
|
|
33
|
+
// as raw, untagged, buffers. On the NodeJS side we don't know what the buffer represents
|
|
34
|
+
// Adding a tag would be a solution, but it would have to be done on both sides and it's unclear where else
|
|
35
|
+
// C++ fr instances are sent/received/stored.
|
|
36
|
+
addExtension({
|
|
37
|
+
Class: Fr,
|
|
38
|
+
write: fr => fr.toBuffer(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export interface NativeInstance {
|
|
42
|
+
call(msg: Buffer | Uint8Array): Promise<any>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const NATIVE_LIBRARY_NAME = 'world_state_napi';
|
|
46
|
+
const NATIVE_CLASS_NAME = 'WorldState';
|
|
47
|
+
|
|
48
|
+
const NATIVE_MODULE = bindings(NATIVE_LIBRARY_NAME);
|
|
49
|
+
const MAX_WORLD_STATE_THREADS = 16;
|
|
50
|
+
|
|
51
|
+
export interface NativeWorldStateInstance {
|
|
52
|
+
call<T extends WorldStateMessageType>(messageType: T, body: WorldStateRequest[T]): Promise<WorldStateResponse[T]>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Strongly-typed interface to access the WorldState class in the native world_state_napi module.
|
|
57
|
+
*/
|
|
58
|
+
export class NativeWorldState implements NativeWorldStateInstance {
|
|
59
|
+
private open = true;
|
|
60
|
+
|
|
61
|
+
/** Each message needs a unique ID */
|
|
62
|
+
private nextMessageId = 0;
|
|
63
|
+
|
|
64
|
+
/** A long-lived msgpack encoder */
|
|
65
|
+
private encoder = new Encoder({
|
|
66
|
+
// always encode JS objects as MessagePack maps
|
|
67
|
+
// this makes it compatible with other MessagePack decoders
|
|
68
|
+
useRecords: false,
|
|
69
|
+
int64AsType: 'bigint',
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/** A long-lived msgpack decoder */
|
|
73
|
+
private decoder = new Decoder({
|
|
74
|
+
useRecords: false,
|
|
75
|
+
int64AsType: 'bigint',
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
/** The actual native instance */
|
|
79
|
+
private instance: any;
|
|
80
|
+
|
|
81
|
+
/** Calls to the same instance are serialized */
|
|
82
|
+
private queue = new SerialQueue();
|
|
83
|
+
|
|
84
|
+
/** Creates a new native WorldState instance */
|
|
85
|
+
constructor(dataDir: string, private log = createDebugLogger('aztec:world-state:database')) {
|
|
86
|
+
this.instance = new NATIVE_MODULE[NATIVE_CLASS_NAME](
|
|
87
|
+
dataDir,
|
|
88
|
+
{
|
|
89
|
+
[MerkleTreeId.NULLIFIER_TREE]: NULLIFIER_TREE_HEIGHT,
|
|
90
|
+
[MerkleTreeId.NOTE_HASH_TREE]: NOTE_HASH_TREE_HEIGHT,
|
|
91
|
+
[MerkleTreeId.PUBLIC_DATA_TREE]: PUBLIC_DATA_TREE_HEIGHT,
|
|
92
|
+
[MerkleTreeId.L1_TO_L2_MESSAGE_TREE]: L1_TO_L2_MSG_TREE_HEIGHT,
|
|
93
|
+
[MerkleTreeId.ARCHIVE]: ARCHIVE_HEIGHT,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
[MerkleTreeId.NULLIFIER_TREE]: 2 * MAX_NULLIFIERS_PER_TX,
|
|
97
|
+
[MerkleTreeId.PUBLIC_DATA_TREE]: 2 * MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
|
|
98
|
+
},
|
|
99
|
+
GeneratorIndex.BLOCK_HASH,
|
|
100
|
+
10 * 1024 * 1024, // 10 GB per tree (in KB)
|
|
101
|
+
Math.min(cpus().length, MAX_WORLD_STATE_THREADS),
|
|
102
|
+
);
|
|
103
|
+
this.queue.start();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Sends a message to the native instance and returns the response.
|
|
108
|
+
* @param messageType - The type of message to send
|
|
109
|
+
* @param body - The message body
|
|
110
|
+
* @returns The response to the message
|
|
111
|
+
*/
|
|
112
|
+
public call<T extends WorldStateMessageType>(
|
|
113
|
+
messageType: T,
|
|
114
|
+
body: WorldStateRequest[T],
|
|
115
|
+
): Promise<WorldStateResponse[T]> {
|
|
116
|
+
return this.queue.put(() => {
|
|
117
|
+
assert.notEqual(messageType, WorldStateMessageType.CLOSE, 'Use close() to close the native instance');
|
|
118
|
+
assert.equal(this.open, true, 'Native instance is closed');
|
|
119
|
+
return this._sendMessage(messageType, body);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Stops the native instance.
|
|
125
|
+
*/
|
|
126
|
+
public async close(): Promise<void> {
|
|
127
|
+
if (!this.open) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
this.open = false;
|
|
131
|
+
await this._sendMessage(WorldStateMessageType.CLOSE, undefined);
|
|
132
|
+
await this.queue.end();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private async _sendMessage<T extends WorldStateMessageType>(
|
|
136
|
+
messageType: T,
|
|
137
|
+
body: WorldStateRequest[T],
|
|
138
|
+
): Promise<WorldStateResponse[T]> {
|
|
139
|
+
const messageId = this.nextMessageId++;
|
|
140
|
+
if (body) {
|
|
141
|
+
let data: Record<string, any> = {};
|
|
142
|
+
if ('treeId' in body) {
|
|
143
|
+
data['treeId'] = MerkleTreeId[body.treeId];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if ('revision' in body) {
|
|
147
|
+
data = { ...data, ...body.revision };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if ('forkId' in body) {
|
|
151
|
+
data['forkId'] = body.forkId;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if ('blockNumber' in body) {
|
|
155
|
+
data['blockNumber'] = body.blockNumber;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if ('toBlockNumber' in body) {
|
|
159
|
+
data['toBlockNumber'] = body.toBlockNumber;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if ('leafIndex' in body) {
|
|
163
|
+
data['leafIndex'] = body.leafIndex;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if ('blockHeaderHash' in body) {
|
|
167
|
+
data['blockHeaderHash'] = '0x' + body.blockHeaderHash.toString('hex');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if ('leaf' in body) {
|
|
171
|
+
if (Buffer.isBuffer(body.leaf)) {
|
|
172
|
+
data['leaf'] = '0x' + body.leaf.toString('hex');
|
|
173
|
+
} else if ('slot' in body.leaf) {
|
|
174
|
+
data['slot'] = '0x' + body.leaf.slot.toString('hex');
|
|
175
|
+
data['value'] = '0x' + body.leaf.value.toString('hex');
|
|
176
|
+
} else {
|
|
177
|
+
data['nullifier'] = '0x' + body.leaf.value.toString('hex');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if ('leaves' in body) {
|
|
182
|
+
data['leavesCount'] = body.leaves.length;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// sync operation
|
|
186
|
+
if ('paddedNoteHashes' in body) {
|
|
187
|
+
data['notesCount'] = body.paddedNoteHashes.length;
|
|
188
|
+
data['nullifiersCount'] = body.paddedNullifiers.length;
|
|
189
|
+
data['l1ToL2MessagesCount'] = body.paddedL1ToL2Messages.length;
|
|
190
|
+
data['publicDataWritesCount'] = body.batchesOfPaddedPublicDataWrites.reduce(
|
|
191
|
+
(acc, batch) => acc + batch.length,
|
|
192
|
+
0,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
this.log.debug(`Calling messageId=${messageId} ${WorldStateMessageType[messageType]} with ${fmtLogData(data)}`);
|
|
197
|
+
} else {
|
|
198
|
+
this.log.debug(`Calling messageId=${messageId} ${WorldStateMessageType[messageType]}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const timer = new Timer();
|
|
202
|
+
|
|
203
|
+
const request = new TypedMessage(messageType, new MessageHeader({ messageId }), body);
|
|
204
|
+
const encodedRequest = this.encoder.encode(request);
|
|
205
|
+
const encodingDuration = timer.ms();
|
|
206
|
+
|
|
207
|
+
let encodedResponse: any;
|
|
208
|
+
try {
|
|
209
|
+
encodedResponse = await this.instance.call(encodedRequest);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
this.log.error(`Call messageId=${messageId} ${WorldStateMessageType[messageType]} failed: ${error}`);
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const callDuration = timer.ms() - encodingDuration;
|
|
216
|
+
|
|
217
|
+
const buf = Buffer.isBuffer(encodedResponse)
|
|
218
|
+
? encodedResponse
|
|
219
|
+
: isAnyArrayBuffer(encodedResponse)
|
|
220
|
+
? Buffer.from(encodedResponse)
|
|
221
|
+
: encodedResponse;
|
|
222
|
+
|
|
223
|
+
if (!Buffer.isBuffer(buf)) {
|
|
224
|
+
throw new TypeError(
|
|
225
|
+
'Invalid encoded response: expected Buffer or ArrayBuffer, got ' +
|
|
226
|
+
(encodedResponse === null ? 'null' : typeof encodedResponse),
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const decodedResponse = this.decoder.unpack(buf);
|
|
231
|
+
if (!TypedMessage.isTypedMessageLike(decodedResponse)) {
|
|
232
|
+
throw new TypeError(
|
|
233
|
+
'Invalid response: expected TypedMessageLike, got ' +
|
|
234
|
+
(decodedResponse === null ? 'null' : typeof decodedResponse),
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const response = TypedMessage.fromMessagePack<T, WorldStateResponse[T]>(decodedResponse);
|
|
239
|
+
const decodingDuration = timer.ms() - callDuration;
|
|
240
|
+
const totalDuration = timer.ms();
|
|
241
|
+
this.log.debug(
|
|
242
|
+
`Call messageId=${messageId} ${WorldStateMessageType[messageType]} took (ms) ${fmtLogData({
|
|
243
|
+
totalDuration,
|
|
244
|
+
encodingDuration,
|
|
245
|
+
callDuration,
|
|
246
|
+
decodingDuration,
|
|
247
|
+
})}`,
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
if (response.header.requestId !== request.header.messageId) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
'Response ID does not match request: ' + response.header.requestId + ' != ' + request.header.messageId,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (response.msgType !== messageType) {
|
|
257
|
+
throw new Error('Invalid response message type: ' + response.msgType + ' != ' + messageType);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return response.value;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
@@ -5,11 +5,11 @@ export interface WorldStateConfig {
|
|
|
5
5
|
/** The frequency in which to check. */
|
|
6
6
|
worldStateBlockCheckIntervalMS: number;
|
|
7
7
|
|
|
8
|
-
/** Size of queue of L2 blocks to store. */
|
|
9
|
-
l2QueueSize: number;
|
|
10
|
-
|
|
11
8
|
/** Whether to follow only the proven chain. */
|
|
12
9
|
worldStateProvenBlocksOnly: boolean;
|
|
10
|
+
|
|
11
|
+
/** Size of the batch for each get-blocks request from the synchronizer to the archiver. */
|
|
12
|
+
worldStateBlockRequestBatchSize?: number;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export const worldStateConfigMappings: ConfigMappingsType<WorldStateConfig> = {
|
|
@@ -19,17 +19,16 @@ export const worldStateConfigMappings: ConfigMappingsType<WorldStateConfig> = {
|
|
|
19
19
|
defaultValue: 100,
|
|
20
20
|
description: 'The frequency in which to check.',
|
|
21
21
|
},
|
|
22
|
-
l2QueueSize: {
|
|
23
|
-
env: 'WS_L2_BLOCK_QUEUE_SIZE',
|
|
24
|
-
parseEnv: (val: string) => +val,
|
|
25
|
-
defaultValue: 1000,
|
|
26
|
-
description: 'Size of queue of L2 blocks to store.',
|
|
27
|
-
},
|
|
28
22
|
worldStateProvenBlocksOnly: {
|
|
29
23
|
env: 'WS_PROVEN_BLOCKS_ONLY',
|
|
30
24
|
description: 'Whether to follow only the proven chain.',
|
|
31
25
|
...booleanConfigHelper(),
|
|
32
26
|
},
|
|
27
|
+
worldStateBlockRequestBatchSize: {
|
|
28
|
+
env: 'WS_BLOCK_REQUEST_BATCH_SIZE',
|
|
29
|
+
parseEnv: (val: string | undefined) => (val ? +val : undefined),
|
|
30
|
+
description: 'Size of the batch for each get-blocks request from the synchronizer to the archiver.',
|
|
31
|
+
},
|
|
33
32
|
};
|
|
34
33
|
|
|
35
34
|
/**
|
|
@@ -2,7 +2,9 @@ import { type L1ToL2MessageSource, type L2BlockSource } from '@aztec/circuit-typ
|
|
|
2
2
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
3
3
|
import { type DataStoreConfig, createStore } from '@aztec/kv-store/utils';
|
|
4
4
|
import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
5
|
+
import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
|
|
5
6
|
|
|
7
|
+
import { NativeWorldStateService } from '../native/native_world_state.js';
|
|
6
8
|
import { MerkleTrees } from '../world-state-db/merkle_trees.js';
|
|
7
9
|
import { type WorldStateConfig } from './config.js';
|
|
8
10
|
import { ServerWorldStateSynchronizer } from './server_world_state_synchronizer.js';
|
|
@@ -12,7 +14,22 @@ export async function createWorldStateSynchronizer(
|
|
|
12
14
|
l2BlockSource: L2BlockSource & L1ToL2MessageSource,
|
|
13
15
|
client: TelemetryClient,
|
|
14
16
|
) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
const merkleTrees = await createWorldState(config, client);
|
|
18
|
+
return new ServerWorldStateSynchronizer(merkleTrees, l2BlockSource, config);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function createWorldState(config: DataStoreConfig, client: TelemetryClient = new NoopTelemetryClient()) {
|
|
22
|
+
const merkleTrees = ['true', '1'].includes(process.env.USE_LEGACY_WORLD_STATE ?? '')
|
|
23
|
+
? await MerkleTrees.new(
|
|
24
|
+
await createStore('world-state', config, createDebugLogger('aztec:world-state:lmdb')),
|
|
25
|
+
client,
|
|
26
|
+
)
|
|
27
|
+
: config.dataDirectory
|
|
28
|
+
? await NativeWorldStateService.new(config.l1Contracts.rollupAddress, config.dataDirectory)
|
|
29
|
+
: await NativeWorldStateService.tmp(
|
|
30
|
+
config.l1Contracts.rollupAddress,
|
|
31
|
+
!['true', '1'].includes(process.env.DEBUG_WORLD_STATE!),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
return merkleTrees;
|
|
18
35
|
}
|