@aztec/world-state 0.0.1-commit.181e2d196 → 0.0.1-commit.189eedb3

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