@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.
Files changed (57) hide show
  1. package/README.md +1 -1
  2. package/dest/index.d.ts +1 -0
  3. package/dest/index.d.ts.map +1 -1
  4. package/dest/index.js +2 -1
  5. package/dest/native/merkle_trees_facade.d.ts +34 -0
  6. package/dest/native/merkle_trees_facade.d.ts.map +1 -0
  7. package/dest/native/merkle_trees_facade.js +193 -0
  8. package/dest/native/message.d.ts +78 -19
  9. package/dest/native/message.d.ts.map +1 -1
  10. package/dest/native/message.js +27 -26
  11. package/dest/native/native_world_state.d.ts +39 -38
  12. package/dest/native/native_world_state.d.ts.map +1 -1
  13. package/dest/native/native_world_state.js +108 -254
  14. package/dest/native/native_world_state_instance.d.ts +40 -0
  15. package/dest/native/native_world_state_instance.d.ts.map +1 -0
  16. package/dest/native/native_world_state_instance.js +183 -0
  17. package/dest/synchronizer/config.d.ts +2 -2
  18. package/dest/synchronizer/config.d.ts.map +1 -1
  19. package/dest/synchronizer/config.js +6 -7
  20. package/dest/synchronizer/factory.d.ts +3 -0
  21. package/dest/synchronizer/factory.d.ts.map +1 -1
  22. package/dest/synchronizer/factory.js +13 -4
  23. package/dest/synchronizer/server_world_state_synchronizer.d.ts +41 -41
  24. package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
  25. package/dest/synchronizer/server_world_state_synchronizer.js +126 -151
  26. package/dest/test/utils.d.ts +14 -0
  27. package/dest/test/utils.d.ts.map +1 -0
  28. package/dest/test/utils.js +67 -0
  29. package/dest/world-state-db/index.d.ts +1 -1
  30. package/dest/world-state-db/index.d.ts.map +1 -1
  31. package/dest/world-state-db/merkle_tree_db.d.ts +42 -32
  32. package/dest/world-state-db/merkle_tree_db.d.ts.map +1 -1
  33. package/dest/world-state-db/merkle_tree_db.js +1 -1
  34. package/dest/world-state-db/merkle_tree_operations_facade.d.ts +8 -37
  35. package/dest/world-state-db/merkle_tree_operations_facade.d.ts.map +1 -1
  36. package/dest/world-state-db/merkle_tree_operations_facade.js +6 -45
  37. package/dest/world-state-db/merkle_tree_snapshot_operations_facade.d.ts +4 -13
  38. package/dest/world-state-db/merkle_tree_snapshot_operations_facade.d.ts.map +1 -1
  39. package/dest/world-state-db/merkle_tree_snapshot_operations_facade.js +2 -29
  40. package/dest/world-state-db/merkle_trees.d.ts +17 -19
  41. package/dest/world-state-db/merkle_trees.d.ts.map +1 -1
  42. package/dest/world-state-db/merkle_trees.js +39 -36
  43. package/package.json +15 -12
  44. package/src/index.ts +1 -0
  45. package/src/native/merkle_trees_facade.ts +279 -0
  46. package/src/native/message.ts +97 -20
  47. package/src/native/native_world_state.ts +125 -346
  48. package/src/native/native_world_state_instance.ts +262 -0
  49. package/src/synchronizer/config.ts +8 -9
  50. package/src/synchronizer/factory.ts +20 -3
  51. package/src/synchronizer/server_world_state_synchronizer.ts +149 -178
  52. package/src/test/utils.ts +123 -0
  53. package/src/world-state-db/index.ts +1 -1
  54. package/src/world-state-db/merkle_tree_db.ts +55 -49
  55. package/src/world-state-db/merkle_tree_operations_facade.ts +10 -55
  56. package/src/world-state-db/merkle_tree_snapshot_operations_facade.ts +7 -46
  57. 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 store = await createStore('world-state', config, createDebugLogger('aztec:world-state:lmdb'));
16
- const merkleTrees = await MerkleTrees.new(store, client);
17
- return new ServerWorldStateSynchronizer(store, merkleTrees, l2BlockSource, config);
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
  }