@aztec/world-state 0.0.1-commit.001888fc → 0.0.1-commit.017a351

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 (38) hide show
  1. package/dest/native/ipc_world_state_instance.d.ts +45 -0
  2. package/dest/native/ipc_world_state_instance.d.ts.map +1 -0
  3. package/dest/native/ipc_world_state_instance.js +750 -0
  4. package/dest/native/merkle_trees_facade.d.ts +8 -4
  5. package/dest/native/merkle_trees_facade.d.ts.map +1 -1
  6. package/dest/native/merkle_trees_facade.js +34 -15
  7. package/dest/native/message.d.ts +7 -8
  8. package/dest/native/message.d.ts.map +1 -1
  9. package/dest/native/native_world_state.d.ts +19 -7
  10. package/dest/native/native_world_state.d.ts.map +1 -1
  11. package/dest/native/native_world_state.js +89 -24
  12. package/dest/native/native_world_state_instance.d.ts +21 -39
  13. package/dest/native/native_world_state_instance.d.ts.map +1 -1
  14. package/dest/native/native_world_state_instance.js +8 -202
  15. package/dest/native/world_state_ops_queue.js +5 -5
  16. package/dest/synchronizer/config.d.ts +1 -1
  17. package/dest/synchronizer/config.d.ts.map +1 -1
  18. package/dest/synchronizer/config.js +9 -10
  19. package/dest/synchronizer/factory.d.ts +4 -4
  20. package/dest/synchronizer/factory.d.ts.map +1 -1
  21. package/dest/synchronizer/factory.js +7 -6
  22. package/dest/synchronizer/server_world_state_synchronizer.d.ts +1 -1
  23. package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
  24. package/dest/synchronizer/server_world_state_synchronizer.js +40 -11
  25. package/dest/testing.d.ts +4 -3
  26. package/dest/testing.d.ts.map +1 -1
  27. package/dest/testing.js +10 -6
  28. package/package.json +13 -10
  29. package/src/native/ipc_world_state_instance.ts +863 -0
  30. package/src/native/merkle_trees_facade.ts +41 -19
  31. package/src/native/message.ts +6 -7
  32. package/src/native/native_world_state.ts +98 -35
  33. package/src/native/native_world_state_instance.ts +28 -276
  34. package/src/native/world_state_ops_queue.ts +5 -5
  35. package/src/synchronizer/config.ts +14 -10
  36. package/src/synchronizer/factory.ts +11 -10
  37. package/src/synchronizer/server_world_state_synchronizer.ts +35 -13
  38. package/src/testing.ts +8 -9
@@ -0,0 +1,863 @@
1
+ import {
2
+ ARCHIVE_HEIGHT,
3
+ DomainSeparator,
4
+ L1_TO_L2_MSG_TREE_HEIGHT,
5
+ MAX_NULLIFIERS_PER_TX,
6
+ MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
7
+ NOTE_HASH_TREE_HEIGHT,
8
+ NULLIFIER_TREE_HEIGHT,
9
+ PUBLIC_DATA_TREE_HEIGHT,
10
+ } from '@aztec/constants';
11
+ import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
12
+ import { MerkleTreeId } from '@aztec/stdlib/trees';
13
+ import type { GenesisData, WorldStateRevision } from '@aztec/stdlib/world-state';
14
+ import { AsyncApi, WsdbService } from '@aztec/wsdb';
15
+ import type {
16
+ WorldStateDBStats as WsdbDBStats,
17
+ DBStats as WsdbDBStatsInner,
18
+ WorldStateMeta as WsdbMeta,
19
+ SiblingPathAndIndex as WsdbSiblingPathAndIndex,
20
+ WorldStateStatusFull as WsdbStatusFull,
21
+ WorldStateStatusSummary as WsdbStatusSummary,
22
+ TreeDBStats as WsdbTreeDBStats,
23
+ TreeMeta as WsdbTreeMeta,
24
+ } from '@aztec/wsdb';
25
+
26
+ import assert from 'assert';
27
+ import { cpus } from 'node:os';
28
+
29
+ import type { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
30
+ import type { WorldStateTreeMapSizes } from '../synchronizer/factory.js';
31
+ import {
32
+ type DBStats,
33
+ type SerializedLeafValue,
34
+ type TreeDBStats,
35
+ type TreeMeta,
36
+ type WorldStateDBStats,
37
+ WorldStateMessageType,
38
+ type WorldStateMeta,
39
+ type WorldStateRequest,
40
+ type WorldStateRequestCategories,
41
+ type WorldStateResponse,
42
+ type WorldStateStatusFull,
43
+ type WorldStateStatusSummary,
44
+ isWithCanonical,
45
+ isWithForkId,
46
+ isWithRevision,
47
+ } from './message.js';
48
+ import type { NativeWorldStateInstance } from './native_world_state_instance.js';
49
+ import { WorldStateOpsQueue } from './world_state_ops_queue.js';
50
+
51
+ // ————— Request conversion helpers —————
52
+
53
+ function toWsdbRevision(rev: WorldStateRevision): { forkId: number; blockNumber: number; includeUncommitted: boolean } {
54
+ return {
55
+ forkId: rev.forkId,
56
+ blockNumber: Number(rev.blockNumber),
57
+ includeUncommitted: rev.includeUncommitted,
58
+ };
59
+ }
60
+
61
+ function blockStateRefToMap(ref: Map<number, readonly [Buffer, number | bigint]>) {
62
+ return [...ref.entries()].map(([treeId, [root, size]]) => ({
63
+ treeId,
64
+ root: new Uint8Array(root),
65
+ size: Number(size),
66
+ }));
67
+ }
68
+
69
+ function toPublicDataLeaf(leaf: SerializedLeafValue): { slot: Uint8Array; value: Uint8Array } {
70
+ if (leaf instanceof Buffer || !('slot' in leaf)) {
71
+ throw new Error('Expected public data leaf');
72
+ }
73
+ return { slot: new Uint8Array(leaf.slot), value: new Uint8Array(leaf.value) };
74
+ }
75
+
76
+ function toNullifierLeaf(leaf: SerializedLeafValue): { nullifier: Uint8Array } {
77
+ if (leaf instanceof Buffer || !('nullifier' in leaf)) {
78
+ throw new Error('Expected nullifier leaf');
79
+ }
80
+ return { nullifier: new Uint8Array(leaf.nullifier) };
81
+ }
82
+
83
+ function fromPublicDataLeaf(leaf: { slot: Uint8Array; value: Uint8Array }): Exclude<SerializedLeafValue, Buffer> {
84
+ return { slot: Buffer.from(leaf.slot), value: Buffer.from(leaf.value) };
85
+ }
86
+
87
+ function fromNullifierLeaf(leaf: { nullifier: Uint8Array }): Exclude<SerializedLeafValue, Buffer> {
88
+ return { nullifier: Buffer.from(leaf.nullifier) };
89
+ }
90
+
91
+ type WireIndexedLeaf<TLeaf> = { leaf: TLeaf; nextIndex: number; nextKey: Uint8Array };
92
+ type WireLeafUpdateWitnessData<TLeaf> = { leaf: WireIndexedLeaf<TLeaf>; index: number; path: Uint8Array[] };
93
+
94
+ function fromIndexedLeaf<TLeaf>(
95
+ leaf: WireIndexedLeaf<TLeaf>,
96
+ convertLeaf: (leaf: TLeaf) => Exclude<SerializedLeafValue, Buffer>,
97
+ ) {
98
+ return {
99
+ leaf: convertLeaf(leaf.leaf),
100
+ nextIndex: leaf.nextIndex,
101
+ nextKey: Buffer.from(leaf.nextKey),
102
+ };
103
+ }
104
+
105
+ function fromLeafUpdateWitnessData<TLeaf>(
106
+ data: WireLeafUpdateWitnessData<TLeaf>,
107
+ convertLeaf: (leaf: TLeaf) => Exclude<SerializedLeafValue, Buffer>,
108
+ ) {
109
+ return {
110
+ leaf: fromIndexedLeaf(data.leaf, convertLeaf),
111
+ index: data.index,
112
+ path: data.path.map(p => Buffer.from(p)),
113
+ };
114
+ }
115
+
116
+ function fromBatchInsertionResult<
117
+ TLeaf,
118
+ TResult extends {
119
+ lowLeafWitnessData: Array<WireLeafUpdateWitnessData<TLeaf>>;
120
+ sortedLeaves: Array<{ leaf: TLeaf; index: number }>;
121
+ subtreePath: Uint8Array[];
122
+ },
123
+ >(result: TResult, convertLeaf: (leaf: TLeaf) => Exclude<SerializedLeafValue, Buffer>) {
124
+ return {
125
+ lowLeafWitnessData: result.lowLeafWitnessData.map(data => fromLeafUpdateWitnessData(data, convertLeaf)),
126
+ sortedLeaves: result.sortedLeaves.map(({ leaf, index }) => [convertLeaf(leaf), index] as const),
127
+ subtreePath: result.subtreePath.map(p => Buffer.from(p)),
128
+ };
129
+ }
130
+
131
+ function fromSequentialInsertionResult<
132
+ TLeaf,
133
+ TResult extends {
134
+ lowLeafWitnessData: Array<WireLeafUpdateWitnessData<TLeaf>>;
135
+ insertionWitnessData: Array<WireLeafUpdateWitnessData<TLeaf>>;
136
+ },
137
+ >(result: TResult, convertLeaf: (leaf: TLeaf) => Exclude<SerializedLeafValue, Buffer>) {
138
+ return {
139
+ lowLeafWitnessData: result.lowLeafWitnessData.map(data => fromLeafUpdateWitnessData(data, convertLeaf)),
140
+ insertionWitnessData: result.insertionWitnessData.map(data => fromLeafUpdateWitnessData(data, convertLeaf)),
141
+ };
142
+ }
143
+
144
+ function toFrLeaf(leaf: SerializedLeafValue): Uint8Array {
145
+ if (!(leaf instanceof Buffer)) {
146
+ throw new Error('Expected field leaf');
147
+ }
148
+ return new Uint8Array(leaf);
149
+ }
150
+
151
+ function formatMap(map: Record<number, number> | undefined): string | undefined {
152
+ if (!map || Object.keys(map).length === 0) {
153
+ return undefined;
154
+ }
155
+ return `{${Object.entries(map)
156
+ .map(([key, value]) => `${key}:${value}`)
157
+ .join(',')}}`;
158
+ }
159
+
160
+ function getWsdbThreadCount(): number {
161
+ return Math.min(16, cpus().length);
162
+ }
163
+
164
+ function getWsdbExtraArgs(
165
+ dataDir: string,
166
+ wsTreeMapSizes: WorldStateTreeMapSizes,
167
+ genesis: GenesisData,
168
+ threads: number,
169
+ ): string[] {
170
+ const options = getWsdbOptions(dataDir, wsTreeMapSizes);
171
+ const args = ['--data-dir', dataDir, '--threads', threads.toString()];
172
+
173
+ const treeHeights = formatMap(options.treeHeights);
174
+ if (treeHeights) {
175
+ args.push('--tree-heights', treeHeights);
176
+ }
177
+
178
+ const treePrefill = formatMap(options.treePrefill);
179
+ if (treePrefill) {
180
+ args.push('--tree-prefill', treePrefill);
181
+ }
182
+
183
+ const mapSizes = formatMap(options.mapSizes);
184
+ if (mapSizes) {
185
+ args.push('--map-sizes', mapSizes);
186
+ }
187
+
188
+ args.push('--initial-header-generator-point', options.initialHeaderGeneratorPoint.toString());
189
+
190
+ if (genesis.prefilledPublicData.length > 0) {
191
+ const pairs = genesis.prefilledPublicData.map(data => [
192
+ data.slot.toBuffer().toString('hex'),
193
+ data.value.toBuffer().toString('hex'),
194
+ ]);
195
+ args.push('--prefilled-public-data', JSON.stringify(pairs));
196
+ }
197
+
198
+ const genesisTimestamp = Number(genesis.genesisTimestamp);
199
+ if (genesisTimestamp !== 0) {
200
+ args.push('--genesis-timestamp', genesisTimestamp.toString());
201
+ }
202
+
203
+ return args;
204
+ }
205
+
206
+ // ————— Response conversion helpers —————
207
+
208
+ /** Convert Uint8Array fields to Buffer recursively (for opaque blob responses). */
209
+ function convertUint8ArraysToBuffers(obj: unknown): unknown {
210
+ if (obj instanceof Uint8Array) {
211
+ return Buffer.from(obj);
212
+ }
213
+ if (Array.isArray(obj)) {
214
+ return obj.map(convertUint8ArraysToBuffers);
215
+ }
216
+ if (obj !== null && typeof obj === 'object') {
217
+ const result: Record<string, unknown> = {};
218
+ for (const [key, value] of Object.entries(obj)) {
219
+ result[key] = convertUint8ArraysToBuffers(value);
220
+ }
221
+ return result;
222
+ }
223
+ return obj;
224
+ }
225
+
226
+ /** Convert Wsdb state reference (Record<number, [Uint8Array, number]>) to NAPI format. */
227
+ function convertStateRef(
228
+ state: Array<{ treeId: number; root: Uint8Array; size: number }>,
229
+ ): Record<number, readonly [Buffer, number | bigint]> {
230
+ const result: Record<number, readonly [Buffer, number | bigint]> = {};
231
+ for (const { treeId, root, size } of state) {
232
+ result[treeId] = [Buffer.from(root), BigInt(size)] as const;
233
+ }
234
+ return result;
235
+ }
236
+
237
+ /** Convert Wsdb WorldStateStatusSummary to the native world-state format. */
238
+ function convertStatusSummary(s: WsdbStatusSummary): WorldStateStatusSummary {
239
+ return {
240
+ unfinalizedBlockNumber: s.unfinalizedBlockNumber,
241
+ finalizedBlockNumber: s.finalizedBlockNumber,
242
+ oldestHistoricalBlock: s.oldestHistoricalBlock,
243
+ treesAreSynched: s.treesAreSynched,
244
+ } as unknown as WorldStateStatusSummary;
245
+ }
246
+
247
+ function convertDBStats(s: WsdbDBStatsInner): DBStats {
248
+ return {
249
+ name: s.name,
250
+ numDataItems: s.numDataItems,
251
+ totalUsedSize: s.totalUsedSize,
252
+ } as unknown as DBStats;
253
+ }
254
+
255
+ function convertTreeDBStats(s: WsdbTreeDBStats): TreeDBStats {
256
+ return {
257
+ mapSize: s.mapSize,
258
+ physicalFileSize: s.physicalFileSize,
259
+ blocksDBStats: convertDBStats(s.blocksDBStats),
260
+ nodesDBStats: convertDBStats(s.nodesDBStats),
261
+ leafPreimagesDBStats: convertDBStats(s.leafPreimagesDBStats),
262
+ leafIndicesDBStats: convertDBStats(s.leafIndicesDBStats),
263
+ blockIndicesDBStats: convertDBStats(s.blockIndicesDBStats),
264
+ } as unknown as TreeDBStats;
265
+ }
266
+
267
+ function convertWorldStateDBStats(s: WsdbDBStats): WorldStateDBStats {
268
+ return {
269
+ noteHashTreeStats: convertTreeDBStats(s.noteHashTreeStats),
270
+ messageTreeStats: convertTreeDBStats(s.messageTreeStats),
271
+ archiveTreeStats: convertTreeDBStats(s.archiveTreeStats),
272
+ publicDataTreeStats: convertTreeDBStats(s.publicDataTreeStats),
273
+ nullifierTreeStats: convertTreeDBStats(s.nullifierTreeStats),
274
+ } as unknown as WorldStateDBStats;
275
+ }
276
+
277
+ function convertTreeMeta(m: WsdbTreeMeta): TreeMeta {
278
+ return {
279
+ name: m.name,
280
+ depth: m.depth,
281
+ size: m.size,
282
+ committedSize: m.committedSize,
283
+ root: m.root,
284
+ initialSize: m.initialSize,
285
+ initialRoot: m.initialRoot,
286
+ oldestHistoricBlock: m.oldestHistoricBlock,
287
+ unfinalizedBlockHeight: m.unfinalizedBlockHeight,
288
+ finalizedBlockHeight: m.finalizedBlockHeight,
289
+ } as unknown as TreeMeta;
290
+ }
291
+
292
+ function convertWorldStateMeta(m: WsdbMeta): WorldStateMeta {
293
+ return {
294
+ noteHashTreeMeta: convertTreeMeta(m.noteHashTreeMeta),
295
+ messageTreeMeta: convertTreeMeta(m.messageTreeMeta),
296
+ archiveTreeMeta: convertTreeMeta(m.archiveTreeMeta),
297
+ publicDataTreeMeta: convertTreeMeta(m.publicDataTreeMeta),
298
+ nullifierTreeMeta: convertTreeMeta(m.nullifierTreeMeta),
299
+ } as unknown as WorldStateMeta;
300
+ }
301
+
302
+ function convertStatusFull(s: WsdbStatusFull): WorldStateStatusFull {
303
+ return {
304
+ summary: convertStatusSummary(s.summary),
305
+ dbStats: convertWorldStateDBStats(s.dbStats),
306
+ meta: convertWorldStateMeta(s.meta),
307
+ } as unknown as WorldStateStatusFull;
308
+ }
309
+
310
+ /** Convert Wsdb SiblingPathAndIndex to NAPI format. */
311
+ function convertSiblingPathAndIndex(
312
+ s: WsdbSiblingPathAndIndex | null | undefined,
313
+ ): { index: bigint; path: Buffer[] } | undefined {
314
+ if (!s) {
315
+ return undefined;
316
+ }
317
+ return {
318
+ index: BigInt(s.index),
319
+ path: s.path.map(p => Buffer.from(p)),
320
+ };
321
+ }
322
+
323
+ // ————— Public API —————
324
+
325
+ /**
326
+ * IPC-backed world state instance.
327
+ * Uses WsdbService (spawns aztec-wsdb binary) and the generated AsyncApi
328
+ * to communicate via the NamedUnion IPC protocol.
329
+ */
330
+ export class IpcWorldState implements NativeWorldStateInstance {
331
+ private open = true;
332
+ private queues = new Map<number, WorldStateOpsQueue>();
333
+ private api: AsyncApi;
334
+ /** Tracks checkpoint depth per fork (WSDB IPC doesn't return depth in response). */
335
+ private checkpointDepths = new Map<number, number>();
336
+
337
+ constructor(
338
+ private readonly wsdb: WsdbService,
339
+ private readonly instrumentation: WorldStateInstrumentation,
340
+ bindings?: LoggerBindings,
341
+ private readonly log: Logger = createLogger('world-state:ipc-database', bindings),
342
+ ) {
343
+ this.api = wsdb;
344
+ this.queues.set(0, new WorldStateOpsQueue());
345
+ this.log.info('Created IPC-backed world state instance');
346
+ }
347
+
348
+ /**
349
+ * Spawn an `aztec-wsdb` subprocess and return an IPC-backed world state wrapping it.
350
+ * Encapsulates wsdb binary discovery, service construction, and readiness wait.
351
+ */
352
+ static async spawn(
353
+ dataDir: string,
354
+ wsTreeMapSizes: WorldStateTreeMapSizes,
355
+ genesis: GenesisData,
356
+ instrumentation: WorldStateInstrumentation,
357
+ bindings?: LoggerBindings,
358
+ ): Promise<IpcWorldState> {
359
+ const threads = getWsdbThreadCount();
360
+ const wsdb = await WsdbService.spawn({
361
+ transport: 'uds',
362
+ extraArgs: getWsdbExtraArgs(dataDir, wsTreeMapSizes, genesis, threads),
363
+ env: { HARDWARE_CONCURRENCY: threads.toString() },
364
+ });
365
+ return new IpcWorldState(wsdb, instrumentation, bindings);
366
+ }
367
+
368
+ /** Returns the socket path of the underlying wsdb server. */
369
+ getSocketPath(): string {
370
+ return this.wsdb.getIpcPath();
371
+ }
372
+
373
+ async call<T extends WorldStateMessageType>(
374
+ messageType: T,
375
+ body: WorldStateRequest[T] & WorldStateRequestCategories,
376
+ responseHandler = (response: WorldStateResponse[T]): WorldStateResponse[T] => response,
377
+ errorHandler = (_: string) => {},
378
+ ): Promise<WorldStateResponse[T]> {
379
+ let forkId = -1;
380
+ let committedOnly = false;
381
+
382
+ if (isWithCanonical(body)) {
383
+ forkId = 0;
384
+ } else if (isWithForkId(body)) {
385
+ forkId = body.forkId;
386
+ } else if (isWithRevision(body)) {
387
+ forkId = body.revision.forkId;
388
+ committedOnly = body.revision.includeUncommitted === false;
389
+ } else {
390
+ const _: never = body;
391
+ throw new Error(`Unable to determine forkId for message=${WorldStateMessageType[messageType]}`);
392
+ }
393
+
394
+ let requestQueue = this.queues.get(forkId);
395
+ if (requestQueue === undefined) {
396
+ requestQueue = new WorldStateOpsQueue();
397
+ this.queues.set(forkId, requestQueue);
398
+ }
399
+
400
+ try {
401
+ return await requestQueue.execute(
402
+ async () => {
403
+ assert.notEqual(messageType, WorldStateMessageType.CLOSE, 'Use close() to close the IPC instance');
404
+ assert.equal(this.open, true, 'IPC instance is closed');
405
+ let response: WorldStateResponse[T];
406
+ try {
407
+ response = await this._sendMessage(messageType, body);
408
+ } catch (error: any) {
409
+ errorHandler(error.message);
410
+ throw error;
411
+ }
412
+ return responseHandler(response);
413
+ },
414
+ messageType,
415
+ committedOnly,
416
+ );
417
+ } finally {
418
+ if (messageType === WorldStateMessageType.DELETE_FORK) {
419
+ await requestQueue.stop();
420
+ this.queues.delete(forkId);
421
+ }
422
+ }
423
+ }
424
+
425
+ async close(): Promise<void> {
426
+ if (!this.open) {
427
+ return;
428
+ }
429
+ this.open = false;
430
+ const queue = this.queues.get(0)!;
431
+
432
+ await queue.stop();
433
+
434
+ await this.wsdb.destroy();
435
+ }
436
+
437
+ private async _sendMessage<T extends WorldStateMessageType>(
438
+ messageType: T,
439
+ body: WorldStateRequest[T] & WorldStateRequestCategories,
440
+ ): Promise<WorldStateResponse[T]> {
441
+ const start = performance.now();
442
+ try {
443
+ const response = await this.dispatch(messageType, body);
444
+ const durationMs = performance.now() - start;
445
+ this.log.trace(`Call ${WorldStateMessageType[messageType]} took (ms)`, { duration: durationMs });
446
+ this.instrumentation.recordRoundTrip(durationMs * 1000, messageType);
447
+ return response;
448
+ } catch (error) {
449
+ this.log.error(`Call ${WorldStateMessageType[messageType]} failed: ${error}`, error);
450
+ throw error;
451
+ }
452
+ }
453
+
454
+ private async dispatch<T extends WorldStateMessageType>(
455
+ messageType: T,
456
+ body: WorldStateRequest[T] & WorldStateRequestCategories,
457
+ ): Promise<WorldStateResponse[T]> {
458
+ switch (messageType) {
459
+ // ——— Tree info & state reference ———
460
+
461
+ case WorldStateMessageType.GET_TREE_INFO: {
462
+ const b = body as WorldStateRequest[WorldStateMessageType.GET_TREE_INFO];
463
+ const resp = await this.api.getTreeInfo({
464
+ treeId: b.treeId,
465
+ revision: toWsdbRevision(b.revision),
466
+ });
467
+ return {
468
+ treeId: resp.treeId,
469
+ root: Buffer.from(resp.root),
470
+ size: resp.size,
471
+ depth: resp.depth,
472
+ } as WorldStateResponse[T];
473
+ }
474
+
475
+ case WorldStateMessageType.GET_STATE_REFERENCE: {
476
+ const b = body as WorldStateRequest[WorldStateMessageType.GET_STATE_REFERENCE];
477
+ const resp = await this.api.getStateReference({
478
+ revision: toWsdbRevision(b.revision),
479
+ });
480
+ return { state: convertStateRef(resp.state) } as WorldStateResponse[T];
481
+ }
482
+
483
+ case WorldStateMessageType.GET_INITIAL_STATE_REFERENCE: {
484
+ const resp = await this.api.getInitialStateReference({});
485
+ return { state: convertStateRef(resp.state) } as WorldStateResponse[T];
486
+ }
487
+
488
+ // ——— Leaf queries ———
489
+
490
+ case WorldStateMessageType.GET_LEAF_VALUE: {
491
+ const b = body as WorldStateRequest[WorldStateMessageType.GET_LEAF_VALUE];
492
+ const revision = toWsdbRevision(b.revision);
493
+ const leafIndex = Number(b.leafIndex);
494
+
495
+ if (b.treeId === MerkleTreeId.PUBLIC_DATA_TREE) {
496
+ const resp = await this.api.getPublicDataLeafValue({ revision, leafIndex });
497
+ return (resp.value ? fromPublicDataLeaf(resp.value) : undefined) as WorldStateResponse[T];
498
+ }
499
+
500
+ if (b.treeId === MerkleTreeId.NULLIFIER_TREE) {
501
+ const resp = await this.api.getNullifierLeafValue({ revision, leafIndex });
502
+ return (resp.value ? fromNullifierLeaf(resp.value) : undefined) as WorldStateResponse[T];
503
+ }
504
+
505
+ const resp = await this.api.getLeafValue({ treeId: b.treeId, revision, leafIndex });
506
+ if (!resp.value) {
507
+ return undefined as WorldStateResponse[T];
508
+ }
509
+ return Buffer.from(resp.value) as WorldStateResponse[T];
510
+ }
511
+
512
+ case WorldStateMessageType.GET_LEAF_PREIMAGE: {
513
+ const b = body as WorldStateRequest[WorldStateMessageType.GET_LEAF_PREIMAGE];
514
+ const resp =
515
+ b.treeId === MerkleTreeId.PUBLIC_DATA_TREE
516
+ ? await this.api.getPublicDataLeafPreimage({
517
+ revision: toWsdbRevision(b.revision),
518
+ leafIndex: Number(b.leafIndex),
519
+ })
520
+ : await this.api.getNullifierLeafPreimage({
521
+ revision: toWsdbRevision(b.revision),
522
+ leafIndex: Number(b.leafIndex),
523
+ });
524
+ if (!resp.preimage) {
525
+ return undefined as WorldStateResponse[T];
526
+ }
527
+ return convertUint8ArraysToBuffers(resp.preimage) as WorldStateResponse[T];
528
+ }
529
+
530
+ case WorldStateMessageType.GET_SIBLING_PATH: {
531
+ const b = body as WorldStateRequest[WorldStateMessageType.GET_SIBLING_PATH];
532
+ const resp = await this.api.getSiblingPath({
533
+ treeId: b.treeId,
534
+ revision: toWsdbRevision(b.revision),
535
+ leafIndex: Number(b.leafIndex),
536
+ });
537
+ return resp.path.map(p => Buffer.from(p)) as WorldStateResponse[T];
538
+ }
539
+
540
+ case WorldStateMessageType.GET_BLOCK_NUMBERS_FOR_LEAF_INDICES: {
541
+ const b = body as WorldStateRequest[WorldStateMessageType.GET_BLOCK_NUMBERS_FOR_LEAF_INDICES];
542
+ const resp = await this.api.getBlockNumbersForLeafIndices({
543
+ treeId: b.treeId,
544
+ revision: toWsdbRevision(b.revision),
545
+ leafIndices: b.leafIndices.map(Number),
546
+ });
547
+ return {
548
+ blockNumbers: resp.blockNumbers.map(n => (n != null ? BigInt(n) : undefined)),
549
+ } as WorldStateResponse[T];
550
+ }
551
+
552
+ // ——— Find operations ———
553
+
554
+ case WorldStateMessageType.FIND_LEAF_INDICES: {
555
+ const b = body as WorldStateRequest[WorldStateMessageType.FIND_LEAF_INDICES];
556
+ const revision = toWsdbRevision(b.revision);
557
+ const startIndex = Number(b.startIndex);
558
+ const resp =
559
+ b.treeId === MerkleTreeId.PUBLIC_DATA_TREE
560
+ ? await this.api.findPublicDataLeafIndices({
561
+ revision,
562
+ leaves: b.leaves.map(toPublicDataLeaf),
563
+ startIndex,
564
+ })
565
+ : b.treeId === MerkleTreeId.NULLIFIER_TREE
566
+ ? await this.api.findNullifierLeafIndices({
567
+ revision,
568
+ leaves: b.leaves.map(toNullifierLeaf),
569
+ startIndex,
570
+ })
571
+ : await this.api.findLeafIndices({
572
+ treeId: b.treeId,
573
+ revision,
574
+ leaves: b.leaves.map(toFrLeaf),
575
+ startIndex,
576
+ });
577
+ return {
578
+ indices: resp.indices.map(n => (n != null ? BigInt(n) : undefined)),
579
+ } as WorldStateResponse[T];
580
+ }
581
+
582
+ case WorldStateMessageType.FIND_LOW_LEAF: {
583
+ const b = body as WorldStateRequest[WorldStateMessageType.FIND_LOW_LEAF];
584
+ const resp = await this.api.findLowLeaf({
585
+ treeId: b.treeId,
586
+ revision: toWsdbRevision(b.revision),
587
+ key: new Uint8Array(b.key.toBuffer()),
588
+ });
589
+ return {
590
+ alreadyPresent: resp.alreadyPresent,
591
+ index: BigInt(resp.index),
592
+ } as WorldStateResponse[T];
593
+ }
594
+
595
+ case WorldStateMessageType.FIND_SIBLING_PATHS: {
596
+ const b = body as WorldStateRequest[WorldStateMessageType.FIND_SIBLING_PATHS];
597
+ const revision = toWsdbRevision(b.revision);
598
+ const resp =
599
+ b.treeId === MerkleTreeId.PUBLIC_DATA_TREE
600
+ ? await this.api.findPublicDataSiblingPaths({
601
+ revision,
602
+ leaves: b.leaves.map(toPublicDataLeaf),
603
+ })
604
+ : b.treeId === MerkleTreeId.NULLIFIER_TREE
605
+ ? await this.api.findNullifierSiblingPaths({
606
+ revision,
607
+ leaves: b.leaves.map(toNullifierLeaf),
608
+ })
609
+ : await this.api.findSiblingPaths({
610
+ treeId: b.treeId,
611
+ revision,
612
+ leaves: b.leaves.map(toFrLeaf),
613
+ });
614
+ return {
615
+ paths: resp.paths.map(convertSiblingPathAndIndex),
616
+ } as WorldStateResponse[T];
617
+ }
618
+
619
+ // ——— Mutations ———
620
+
621
+ case WorldStateMessageType.APPEND_LEAVES: {
622
+ const b = body as WorldStateRequest[WorldStateMessageType.APPEND_LEAVES];
623
+ if (b.treeId === MerkleTreeId.PUBLIC_DATA_TREE) {
624
+ await this.api.appendPublicDataLeaves({ leaves: b.leaves.map(toPublicDataLeaf), forkId: b.forkId });
625
+ } else if (b.treeId === MerkleTreeId.NULLIFIER_TREE) {
626
+ await this.api.appendNullifierLeaves({ leaves: b.leaves.map(toNullifierLeaf), forkId: b.forkId });
627
+ } else {
628
+ await this.api.appendLeaves({ treeId: b.treeId, leaves: b.leaves.map(toFrLeaf), forkId: b.forkId });
629
+ }
630
+ return undefined as WorldStateResponse[T];
631
+ }
632
+
633
+ case WorldStateMessageType.BATCH_INSERT: {
634
+ const b = body as WorldStateRequest[WorldStateMessageType.BATCH_INSERT];
635
+ const resp =
636
+ b.treeId === MerkleTreeId.PUBLIC_DATA_TREE
637
+ ? await this.api.batchInsertPublicData({
638
+ leaves: b.leaves.map(toPublicDataLeaf),
639
+ subtreeDepth: b.subtreeDepth,
640
+ forkId: b.forkId,
641
+ })
642
+ : await this.api.batchInsertNullifier({
643
+ leaves: b.leaves.map(toNullifierLeaf),
644
+ subtreeDepth: b.subtreeDepth,
645
+ forkId: b.forkId,
646
+ });
647
+ return (b.treeId === MerkleTreeId.PUBLIC_DATA_TREE
648
+ ? fromBatchInsertionResult(resp.result as any, fromPublicDataLeaf)
649
+ : fromBatchInsertionResult(resp.result as any, fromNullifierLeaf)) as unknown as WorldStateResponse[T];
650
+ }
651
+
652
+ case WorldStateMessageType.SEQUENTIAL_INSERT: {
653
+ const b = body as WorldStateRequest[WorldStateMessageType.SEQUENTIAL_INSERT];
654
+ const resp =
655
+ b.treeId === MerkleTreeId.PUBLIC_DATA_TREE
656
+ ? await this.api.sequentialInsertPublicData({
657
+ leaves: b.leaves.map(toPublicDataLeaf),
658
+ forkId: b.forkId,
659
+ })
660
+ : await this.api.sequentialInsertNullifier({
661
+ leaves: b.leaves.map(toNullifierLeaf),
662
+ forkId: b.forkId,
663
+ });
664
+ return (b.treeId === MerkleTreeId.PUBLIC_DATA_TREE
665
+ ? fromSequentialInsertionResult(resp.result as any, fromPublicDataLeaf)
666
+ : fromSequentialInsertionResult(resp.result as any, fromNullifierLeaf)) as unknown as WorldStateResponse[T];
667
+ }
668
+
669
+ case WorldStateMessageType.UPDATE_ARCHIVE: {
670
+ const b = body as WorldStateRequest[WorldStateMessageType.UPDATE_ARCHIVE];
671
+ await this.api.updateArchive({
672
+ blockStateRef: blockStateRefToMap(b.blockStateRef as Map<number, readonly [Buffer, number | bigint]>),
673
+ blockHeaderHash: new Uint8Array(b.blockHeaderHash),
674
+ forkId: b.forkId,
675
+ });
676
+ return undefined as WorldStateResponse[T];
677
+ }
678
+
679
+ // ——— Commit / Rollback ———
680
+
681
+ case WorldStateMessageType.COMMIT: {
682
+ await this.api.commit({});
683
+ return undefined as WorldStateResponse[T];
684
+ }
685
+
686
+ case WorldStateMessageType.ROLLBACK: {
687
+ await this.api.rollback({});
688
+ return undefined as WorldStateResponse[T];
689
+ }
690
+
691
+ // ——— Block sync ———
692
+
693
+ case WorldStateMessageType.SYNC_BLOCK: {
694
+ const b = body as WorldStateRequest[WorldStateMessageType.SYNC_BLOCK];
695
+ const resp = await this.api.syncBlock({
696
+ blockNumber: Number(b.blockNumber),
697
+ blockStateRef: blockStateRefToMap(b.blockStateRef as Map<number, readonly [Buffer, number | bigint]>),
698
+ blockHeaderHash: new Uint8Array(b.blockHeaderHash),
699
+ paddedNoteHashes: b.paddedNoteHashes.map(toFrLeaf),
700
+ paddedL1ToL2Messages: b.paddedL1ToL2Messages.map(toFrLeaf),
701
+ paddedNullifiers: b.paddedNullifiers.map(toNullifierLeaf),
702
+ publicDataWrites: b.publicDataWrites.map(toPublicDataLeaf),
703
+ });
704
+ return convertStatusFull(resp.status) as WorldStateResponse[T];
705
+ }
706
+
707
+ // ——— Fork management ———
708
+
709
+ case WorldStateMessageType.CREATE_FORK: {
710
+ const b = body as WorldStateRequest[WorldStateMessageType.CREATE_FORK];
711
+ const resp = await this.api.createFork({
712
+ latest: b.latest,
713
+ blockNumber: Number(b.blockNumber),
714
+ });
715
+ return { forkId: resp.forkId } as WorldStateResponse[T];
716
+ }
717
+
718
+ case WorldStateMessageType.DELETE_FORK: {
719
+ const b = body as WorldStateRequest[WorldStateMessageType.DELETE_FORK];
720
+ await this.api.deleteFork({ forkId: b.forkId });
721
+ return undefined as WorldStateResponse[T];
722
+ }
723
+
724
+ // ——— Block finalization ———
725
+
726
+ case WorldStateMessageType.FINALIZE_BLOCKS: {
727
+ const b = body as WorldStateRequest[WorldStateMessageType.FINALIZE_BLOCKS];
728
+ const resp = await this.api.finalizeBlocks({ toBlockNumber: Number(b.toBlockNumber) });
729
+ return convertStatusSummary(resp.status) as WorldStateResponse[T];
730
+ }
731
+
732
+ case WorldStateMessageType.UNWIND_BLOCKS: {
733
+ const b = body as WorldStateRequest[WorldStateMessageType.UNWIND_BLOCKS];
734
+ const resp = await this.api.unwindBlocks({ toBlockNumber: Number(b.toBlockNumber) });
735
+ return convertStatusFull(resp.status) as WorldStateResponse[T];
736
+ }
737
+
738
+ case WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS: {
739
+ const b = body as WorldStateRequest[WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS];
740
+ const resp = await this.api.removeHistoricalBlocks({ toBlockNumber: Number(b.toBlockNumber) });
741
+ return convertStatusFull(resp.status) as WorldStateResponse[T];
742
+ }
743
+
744
+ // ——— Status ———
745
+
746
+ case WorldStateMessageType.GET_STATUS: {
747
+ const resp = await this.api.getStatus({});
748
+ return convertStatusSummary(resp.status) as WorldStateResponse[T];
749
+ }
750
+
751
+ // ——— Checkpoints ———
752
+
753
+ case WorldStateMessageType.CREATE_CHECKPOINT: {
754
+ const b = body as WorldStateRequest[WorldStateMessageType.CREATE_CHECKPOINT];
755
+ await this.api.createCheckpoint({ forkId: b.forkId });
756
+ const depth = (this.checkpointDepths.get(b.forkId) ?? 0) + 1;
757
+ this.checkpointDepths.set(b.forkId, depth);
758
+ return { depth } as WorldStateResponse[T];
759
+ }
760
+
761
+ case WorldStateMessageType.COMMIT_CHECKPOINT: {
762
+ const b = body as WorldStateRequest[WorldStateMessageType.COMMIT_CHECKPOINT];
763
+ await this.api.commitCheckpoint({ forkId: b.forkId });
764
+ const depth = Math.max(0, (this.checkpointDepths.get(b.forkId) ?? 0) - 1);
765
+ this.checkpointDepths.set(b.forkId, depth);
766
+ return undefined as WorldStateResponse[T];
767
+ }
768
+
769
+ case WorldStateMessageType.REVERT_CHECKPOINT: {
770
+ const b = body as WorldStateRequest[WorldStateMessageType.REVERT_CHECKPOINT];
771
+ await this.api.revertCheckpoint({ forkId: b.forkId });
772
+ const depth = Math.max(0, (this.checkpointDepths.get(b.forkId) ?? 0) - 1);
773
+ this.checkpointDepths.set(b.forkId, depth);
774
+ return undefined as WorldStateResponse[T];
775
+ }
776
+
777
+ case WorldStateMessageType.COMMIT_ALL_CHECKPOINTS: {
778
+ const b = body as WorldStateRequest[WorldStateMessageType.COMMIT_ALL_CHECKPOINTS];
779
+ const targetDepth = b.depth ?? 0;
780
+ const currentDepth = this.checkpointDepths.get(b.forkId) ?? 0;
781
+ if (targetDepth === 0) {
782
+ // Commit everything — use the bulk operation
783
+ await this.api.commitAllCheckpoints({ forkId: b.forkId });
784
+ } else {
785
+ // Commit one level at a time down to target depth
786
+ for (let d = currentDepth; d > targetDepth; d--) {
787
+ await this.api.commitCheckpoint({ forkId: b.forkId });
788
+ }
789
+ }
790
+ this.checkpointDepths.set(b.forkId, targetDepth);
791
+ return undefined as WorldStateResponse[T];
792
+ }
793
+
794
+ case WorldStateMessageType.REVERT_ALL_CHECKPOINTS: {
795
+ const b = body as WorldStateRequest[WorldStateMessageType.REVERT_ALL_CHECKPOINTS];
796
+ const targetDepth = b.depth ?? 0;
797
+ const currentDepth = this.checkpointDepths.get(b.forkId) ?? 0;
798
+ if (targetDepth === 0) {
799
+ // Revert everything — use the bulk operation
800
+ await this.api.revertAllCheckpoints({ forkId: b.forkId });
801
+ } else {
802
+ // Revert one level at a time down to target depth
803
+ for (let d = currentDepth; d > targetDepth; d--) {
804
+ await this.api.revertCheckpoint({ forkId: b.forkId });
805
+ }
806
+ }
807
+ this.checkpointDepths.set(b.forkId, targetDepth);
808
+ return undefined as WorldStateResponse[T];
809
+ }
810
+
811
+ // ——— Misc ———
812
+
813
+ case WorldStateMessageType.COPY_STORES: {
814
+ const b = body as WorldStateRequest[WorldStateMessageType.COPY_STORES];
815
+ await this.api.copyStores({ dstPath: b.dstPath, compact: b.compact });
816
+ return undefined as WorldStateResponse[T];
817
+ }
818
+
819
+ case WorldStateMessageType.CLOSE: {
820
+ return undefined as WorldStateResponse[T];
821
+ }
822
+
823
+ default:
824
+ throw new Error(`Unknown message type: ${messageType}`);
825
+ }
826
+ }
827
+ }
828
+
829
+ /**
830
+ * Helper to create WsdbOptions from standard world state config.
831
+ * Returns the options needed to construct a wsdb service command line.
832
+ */
833
+ export function getWsdbOptions(
834
+ dataDir: string,
835
+ wsTreeMapSizes: WorldStateTreeMapSizes,
836
+ ): {
837
+ treeHeights: Record<number, number>;
838
+ treePrefill: Record<number, number>;
839
+ mapSizes: Record<number, number>;
840
+ initialHeaderGeneratorPoint: number;
841
+ } {
842
+ return {
843
+ treeHeights: {
844
+ [MerkleTreeId.NULLIFIER_TREE]: NULLIFIER_TREE_HEIGHT,
845
+ [MerkleTreeId.NOTE_HASH_TREE]: NOTE_HASH_TREE_HEIGHT,
846
+ [MerkleTreeId.PUBLIC_DATA_TREE]: PUBLIC_DATA_TREE_HEIGHT,
847
+ [MerkleTreeId.L1_TO_L2_MESSAGE_TREE]: L1_TO_L2_MSG_TREE_HEIGHT,
848
+ [MerkleTreeId.ARCHIVE]: ARCHIVE_HEIGHT,
849
+ },
850
+ treePrefill: {
851
+ [MerkleTreeId.NULLIFIER_TREE]: 2 * MAX_NULLIFIERS_PER_TX,
852
+ [MerkleTreeId.PUBLIC_DATA_TREE]: 2 * MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
853
+ },
854
+ mapSizes: {
855
+ [MerkleTreeId.NULLIFIER_TREE]: wsTreeMapSizes.nullifierTreeMapSizeKb,
856
+ [MerkleTreeId.NOTE_HASH_TREE]: wsTreeMapSizes.noteHashTreeMapSizeKb,
857
+ [MerkleTreeId.PUBLIC_DATA_TREE]: wsTreeMapSizes.publicDataTreeMapSizeKb,
858
+ [MerkleTreeId.L1_TO_L2_MESSAGE_TREE]: wsTreeMapSizes.messageTreeMapSizeKb,
859
+ [MerkleTreeId.ARCHIVE]: wsTreeMapSizes.archiveTreeMapSizeKb,
860
+ },
861
+ initialHeaderGeneratorPoint: DomainSeparator.BLOCK_HEADER_HASH,
862
+ };
863
+ }