@aztec/world-state 0.0.1-commit.3100065 → 0.0.1-commit.321f6a9

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 (45) hide show
  1. package/dest/index.d.ts +2 -1
  2. package/dest/index.d.ts.map +1 -1
  3. package/dest/index.js +1 -0
  4. package/dest/instrumentation/instrumentation.d.ts +4 -3
  5. package/dest/instrumentation/instrumentation.d.ts.map +1 -1
  6. package/dest/instrumentation/instrumentation.js +4 -9
  7. package/dest/native/index.d.ts +2 -1
  8. package/dest/native/index.d.ts.map +1 -1
  9. package/dest/native/index.js +1 -0
  10. package/dest/native/ipc_world_state_instance.d.ts +61 -22
  11. package/dest/native/ipc_world_state_instance.d.ts.map +1 -1
  12. package/dest/native/ipc_world_state_instance.js +530 -495
  13. package/dest/native/merkle_trees_facade.d.ts +4 -3
  14. package/dest/native/merkle_trees_facade.d.ts.map +1 -1
  15. package/dest/native/merkle_trees_facade.js +37 -109
  16. package/dest/native/message.d.ts +14 -233
  17. package/dest/native/message.d.ts.map +1 -1
  18. package/dest/native/message.js +0 -42
  19. package/dest/native/native_world_state.d.ts +14 -28
  20. package/dest/native/native_world_state.d.ts.map +1 -1
  21. package/dest/native/native_world_state.js +105 -99
  22. package/dest/native/native_world_state_instance.d.ts +56 -43
  23. package/dest/native/native_world_state_instance.d.ts.map +1 -1
  24. package/dest/native/native_world_state_instance.js +1 -223
  25. package/dest/native/world_state_operation.d.ts +7 -0
  26. package/dest/native/world_state_operation.d.ts.map +1 -0
  27. package/dest/native/world_state_operation.js +5 -0
  28. package/dest/synchronizer/factory.d.ts +1 -1
  29. package/dest/synchronizer/factory.d.ts.map +1 -1
  30. package/dest/synchronizer/factory.js +1 -1
  31. package/package.json +11 -10
  32. package/src/index.ts +1 -0
  33. package/src/instrumentation/instrumentation.ts +6 -18
  34. package/src/native/index.ts +1 -0
  35. package/src/native/ipc_world_state_instance.ts +625 -503
  36. package/src/native/merkle_trees_facade.ts +59 -104
  37. package/src/native/message.ts +13 -300
  38. package/src/native/native_world_state.ts +136 -156
  39. package/src/native/native_world_state_instance.ts +94 -308
  40. package/src/native/world_state_operation.ts +33 -0
  41. package/src/synchronizer/factory.ts +0 -1
  42. package/dest/native/world_state_ops_queue.d.ts +0 -19
  43. package/dest/native/world_state_ops_queue.d.ts.map +0 -1
  44. package/dest/native/world_state_ops_queue.js +0 -146
  45. package/src/native/world_state_ops_queue.ts +0 -190
@@ -1,14 +1,3 @@
1
- import { AsyncApi } from '@aztec/bb.js/aztec-wsdb';
2
- import type {
3
- WorldStateDBStats as WsdbDBStats,
4
- DBStats as WsdbDBStatsInner,
5
- WorldStateMeta as WsdbMeta,
6
- SiblingPathAndIndex as WsdbSiblingPathAndIndex,
7
- WorldStateStatusFull as WsdbStatusFull,
8
- WorldStateStatusSummary as WsdbStatusSummary,
9
- TreeDBStats as WsdbTreeDBStats,
10
- TreeMeta as WsdbTreeMeta,
11
- } from '@aztec/bb.js/aztec-wsdb';
12
1
  import {
13
2
  ARCHIVE_HEIGHT,
14
3
  DomainSeparator,
@@ -19,62 +8,224 @@ import {
19
8
  NULLIFIER_TREE_HEIGHT,
20
9
  PUBLIC_DATA_TREE_HEIGHT,
21
10
  } from '@aztec/constants';
11
+ import type { BlockNumber } from '@aztec/foundation/branded-types';
12
+ import type { Fr } from '@aztec/foundation/curves/bn254';
22
13
  import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
14
+ import type { IndexedTreeId, TreeInfo } from '@aztec/stdlib/interfaces/server';
23
15
  import { MerkleTreeId } from '@aztec/stdlib/trees';
24
- import type { WorldStateRevision } from '@aztec/stdlib/world-state';
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';
25
28
 
26
- import assert from 'assert';
27
- import { Decoder, Encoder } from 'msgpackr';
29
+ import { cpus } from 'node:os';
28
30
 
29
31
  import type { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
30
32
  import type { WorldStateTreeMapSizes } from '../synchronizer/factory.js';
31
- import {
32
- type DBStats,
33
- type SerializedIndexedLeaf,
34
- type SerializedLeafValue,
35
- type TreeDBStats,
36
- type TreeMeta,
37
- type WorldStateDBStats,
38
- WorldStateMessageType,
39
- type WorldStateMeta,
40
- type WorldStateRequest,
41
- type WorldStateRequestCategories,
42
- type WorldStateResponse,
43
- type WorldStateStatusFull,
44
- type WorldStateStatusSummary,
45
- isWithCanonical,
46
- isWithForkId,
47
- isWithRevision,
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
48
  } from './message.js';
49
49
  import type { NativeWorldStateInstance } from './native_world_state_instance.js';
50
- import { WorldStateOpsQueue } from './world_state_ops_queue.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
+ }
51
76
 
52
- // ————— Msgpack helpers —————
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
+ }
53
83
 
54
- const msgpackEncoder = new Encoder({ useRecords: false });
55
- const msgpackDecoder = new Decoder({ useRecords: false });
84
+ function fromPublicDataLeaf(leaf: { slot: Uint8Array; value: Uint8Array }): Exclude<SerializedLeafValue, Buffer> {
85
+ return { slot: Buffer.from(leaf.slot), value: Buffer.from(leaf.value) };
86
+ }
56
87
 
57
- /** Msgpack-encode a SerializedLeafValue into bytes for IPC transport. */
58
- function serializeLeafToBytes(leaf: SerializedLeafValue): Uint8Array {
59
- return Buffer.from(msgpackEncoder.pack(leaf));
88
+ function fromNullifierLeaf(leaf: { nullifier: Uint8Array }): Exclude<SerializedLeafValue, Buffer> {
89
+ return { nullifier: Buffer.from(leaf.nullifier) };
60
90
  }
61
91
 
62
- // ————— Request conversion helpers —————
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
+ }
63
131
 
64
- function toWsdbRevision(rev: WorldStateRevision): { forkid: number; blocknumber: number; includeuncommitted: boolean } {
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>) {
65
139
  return {
66
- forkid: rev.forkId,
67
- blocknumber: Number(rev.blockNumber),
68
- includeuncommitted: rev.includeUncommitted,
140
+ lowLeafWitnessData: result.lowLeafWitnessData.map(data => fromLeafUpdateWitnessData(data, convertLeaf)),
141
+ insertionWitnessData: result.insertionWitnessData.map(data => fromLeafUpdateWitnessData(data, convertLeaf)),
69
142
  };
70
143
  }
71
144
 
72
- function blockStateRefToMap(ref: Map<number, readonly [Buffer, number | bigint]>): Map<number, [Uint8Array, number]> {
73
- const result = new Map<number, [Uint8Array, number]>();
74
- for (const [treeId, [root, size]] of ref.entries()) {
75
- result.set(treeId, [new Uint8Array(root), Number(size)]);
145
+ function toFrLeaf(leaf: SerializedLeafValue): Uint8Array {
146
+ if (!(leaf instanceof Buffer)) {
147
+ throw new Error('Expected field leaf');
76
148
  }
77
- return result;
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 prefilledNullifiers = genesis.prefilledNullifiers ?? [];
211
+ for (let i = 1; i < prefilledNullifiers.length; i++) {
212
+ if (prefilledNullifiers[i].toBigInt() <= prefilledNullifiers[i - 1].toBigInt()) {
213
+ throw new Error('Prefilled genesis nullifiers must be unique and strictly increasing');
214
+ }
215
+ }
216
+ if (prefilledNullifiers.length > 0) {
217
+ args.push(
218
+ '--prefilled-nullifiers',
219
+ JSON.stringify(prefilledNullifiers.map(nullifier => nullifier.toBuffer().toString('hex'))),
220
+ );
221
+ }
222
+
223
+ const genesisTimestamp = Number(genesis.genesisTimestamp);
224
+ if (genesisTimestamp !== 0) {
225
+ args.push('--genesis-timestamp', genesisTimestamp.toString());
226
+ }
227
+
228
+ return args;
78
229
  }
79
230
 
80
231
  // ————— Response conversion helpers —————
@@ -97,66 +248,54 @@ function convertUint8ArraysToBuffers(obj: unknown): unknown {
97
248
  return obj;
98
249
  }
99
250
 
100
- /** Decode a msgpack-encoded leaf value blob and convert Uint8Arrays to Buffers. */
101
- function decodeLeafValue(encoded: Uint8Array): SerializedLeafValue {
102
- const decoded = msgpackDecoder.unpack(Buffer.from(encoded));
103
- return convertUint8ArraysToBuffers(decoded) as SerializedLeafValue;
104
- }
105
-
106
- /** Decode a msgpack-encoded indexed leaf preimage blob. */
107
- function decodeLeafPreimage(encoded: Uint8Array): SerializedIndexedLeaf {
108
- const decoded = msgpackDecoder.unpack(Buffer.from(encoded));
109
- return convertUint8ArraysToBuffers(decoded) as SerializedIndexedLeaf;
110
- }
111
-
112
251
  /** Convert Wsdb state reference (Record<number, [Uint8Array, number]>) to NAPI format. */
113
252
  function convertStateRef(
114
- state: Record<number, [Uint8Array, number]>,
253
+ state: Array<{ treeId: number; root: Uint8Array; size: number }>,
115
254
  ): Record<number, readonly [Buffer, number | bigint]> {
116
255
  const result: Record<number, readonly [Buffer, number | bigint]> = {};
117
- for (const [key, [root, size]] of Object.entries(state)) {
118
- result[Number(key)] = [Buffer.from(root), BigInt(size)] as const;
256
+ for (const { treeId, root, size } of state) {
257
+ result[treeId] = [Buffer.from(root), BigInt(size)] as const;
119
258
  }
120
259
  return result;
121
260
  }
122
261
 
123
- /** Convert Wsdb WorldStateStatusSummary (lowercase) to NAPI format (camelCase). */
262
+ /** Convert Wsdb WorldStateStatusSummary to the native world-state format. */
124
263
  function convertStatusSummary(s: WsdbStatusSummary): WorldStateStatusSummary {
125
264
  return {
126
- unfinalizedBlockNumber: s.unfinalizedblocknumber,
127
- finalizedBlockNumber: s.finalizedblocknumber,
128
- oldestHistoricalBlock: s.oldesthistoricalblock,
129
- treesAreSynched: s.treesaresynched,
265
+ unfinalizedBlockNumber: s.unfinalizedBlockNumber,
266
+ finalizedBlockNumber: s.finalizedBlockNumber,
267
+ oldestHistoricalBlock: s.oldestHistoricalBlock,
268
+ treesAreSynched: s.treesAreSynched,
130
269
  } as unknown as WorldStateStatusSummary;
131
270
  }
132
271
 
133
272
  function convertDBStats(s: WsdbDBStatsInner): DBStats {
134
273
  return {
135
274
  name: s.name,
136
- numDataItems: s.numdataitems,
137
- totalUsedSize: s.totalusedsize,
275
+ numDataItems: s.numDataItems,
276
+ totalUsedSize: s.totalUsedSize,
138
277
  } as unknown as DBStats;
139
278
  }
140
279
 
141
280
  function convertTreeDBStats(s: WsdbTreeDBStats): TreeDBStats {
142
281
  return {
143
- mapSize: s.mapsize,
144
- physicalFileSize: s.physicalfilesize,
145
- blocksDBStats: convertDBStats(s.blocksdbstats),
146
- nodesDBStats: convertDBStats(s.nodesdbstats),
147
- leafPreimagesDBStats: convertDBStats(s.leafpreimagesdbstats),
148
- leafIndicesDBStats: convertDBStats(s.leafindicesdbstats),
149
- blockIndicesDBStats: convertDBStats(s.blockindicesdbstats),
282
+ mapSize: s.mapSize,
283
+ physicalFileSize: s.physicalFileSize,
284
+ blocksDBStats: convertDBStats(s.blocksDBStats),
285
+ nodesDBStats: convertDBStats(s.nodesDBStats),
286
+ leafPreimagesDBStats: convertDBStats(s.leafPreimagesDBStats),
287
+ leafIndicesDBStats: convertDBStats(s.leafIndicesDBStats),
288
+ blockIndicesDBStats: convertDBStats(s.blockIndicesDBStats),
150
289
  } as unknown as TreeDBStats;
151
290
  }
152
291
 
153
292
  function convertWorldStateDBStats(s: WsdbDBStats): WorldStateDBStats {
154
293
  return {
155
- noteHashTreeStats: convertTreeDBStats(s.notehashtreestats),
156
- messageTreeStats: convertTreeDBStats(s.messagetreestats),
157
- archiveTreeStats: convertTreeDBStats(s.archivetreestats),
158
- publicDataTreeStats: convertTreeDBStats(s.publicdatatreestats),
159
- nullifierTreeStats: convertTreeDBStats(s.nullifiertreestats),
294
+ noteHashTreeStats: convertTreeDBStats(s.noteHashTreeStats),
295
+ messageTreeStats: convertTreeDBStats(s.messageTreeStats),
296
+ archiveTreeStats: convertTreeDBStats(s.archiveTreeStats),
297
+ publicDataTreeStats: convertTreeDBStats(s.publicDataTreeStats),
298
+ nullifierTreeStats: convertTreeDBStats(s.nullifierTreeStats),
160
299
  } as unknown as WorldStateDBStats;
161
300
  }
162
301
 
@@ -165,37 +304,37 @@ function convertTreeMeta(m: WsdbTreeMeta): TreeMeta {
165
304
  name: m.name,
166
305
  depth: m.depth,
167
306
  size: m.size,
168
- committedSize: m.committedsize,
307
+ committedSize: m.committedSize,
169
308
  root: m.root,
170
- initialSize: m.initialsize,
171
- initialRoot: m.initialroot,
172
- oldestHistoricBlock: m.oldesthistoricblock,
173
- unfinalizedBlockHeight: m.unfinalizedblockheight,
174
- finalizedBlockHeight: m.finalizedblockheight,
309
+ initialSize: m.initialSize,
310
+ initialRoot: m.initialRoot,
311
+ oldestHistoricBlock: m.oldestHistoricBlock,
312
+ unfinalizedBlockHeight: m.unfinalizedBlockHeight,
313
+ finalizedBlockHeight: m.finalizedBlockHeight,
175
314
  } as unknown as TreeMeta;
176
315
  }
177
316
 
178
317
  function convertWorldStateMeta(m: WsdbMeta): WorldStateMeta {
179
318
  return {
180
- noteHashTreeMeta: convertTreeMeta(m.notehashtreemeta),
181
- messageTreeMeta: convertTreeMeta(m.messagetreemeta),
182
- archiveTreeMeta: convertTreeMeta(m.archivetreemeta),
183
- publicDataTreeMeta: convertTreeMeta(m.publicdatatreemeta),
184
- nullifierTreeMeta: convertTreeMeta(m.nullifiertreemeta),
319
+ noteHashTreeMeta: convertTreeMeta(m.noteHashTreeMeta),
320
+ messageTreeMeta: convertTreeMeta(m.messageTreeMeta),
321
+ archiveTreeMeta: convertTreeMeta(m.archiveTreeMeta),
322
+ publicDataTreeMeta: convertTreeMeta(m.publicDataTreeMeta),
323
+ nullifierTreeMeta: convertTreeMeta(m.nullifierTreeMeta),
185
324
  } as unknown as WorldStateMeta;
186
325
  }
187
326
 
188
327
  function convertStatusFull(s: WsdbStatusFull): WorldStateStatusFull {
189
328
  return {
190
329
  summary: convertStatusSummary(s.summary),
191
- dbStats: convertWorldStateDBStats(s.dbstats),
330
+ dbStats: convertWorldStateDBStats(s.dbStats),
192
331
  meta: convertWorldStateMeta(s.meta),
193
332
  } as unknown as WorldStateStatusFull;
194
333
  }
195
334
 
196
335
  /** Convert Wsdb SiblingPathAndIndex to NAPI format. */
197
336
  function convertSiblingPathAndIndex(
198
- s: WsdbSiblingPathAndIndex | undefined,
337
+ s: WsdbSiblingPathAndIndex | null | undefined,
199
338
  ): { index: bigint; path: Buffer[] } | undefined {
200
339
  if (!s) {
201
340
  return undefined;
@@ -208,108 +347,50 @@ function convertSiblingPathAndIndex(
208
347
 
209
348
  // ————— Public API —————
210
349
 
211
- /** Backend interface matching WsdbBackend from bb.js. */
212
- export interface WsdbIpcBackend {
213
- call(inputBuffer: Uint8Array): Promise<Uint8Array>;
214
- getSocketPath(): string;
215
- destroy?(): Promise<void>;
216
- }
217
-
218
350
  /**
219
351
  * IPC-backed world state instance.
220
- * Uses WsdbBackend (spawns aztec-wsdb binary) and the generated AsyncApi
352
+ * Uses WsdbService (spawns aztec-wsdb binary) and the generated AsyncApi
221
353
  * to communicate via the NamedUnion IPC protocol.
222
354
  */
223
355
  export class IpcWorldState implements NativeWorldStateInstance {
224
356
  private open = true;
225
- private queues = new Map<number, WorldStateOpsQueue>();
226
357
  private api: AsyncApi;
227
358
  /** Tracks checkpoint depth per fork (WSDB IPC doesn't return depth in response). */
228
359
  private checkpointDepths = new Map<number, number>();
229
360
 
230
361
  constructor(
231
- private readonly wsdbBackend: WsdbIpcBackend,
362
+ private readonly wsdb: WsdbService,
232
363
  private readonly instrumentation: WorldStateInstrumentation,
233
364
  bindings?: LoggerBindings,
234
365
  private readonly log: Logger = createLogger('world-state:ipc-database', bindings),
235
366
  ) {
236
- this.api = new AsyncApi(wsdbBackend as any);
237
- this.queues.set(0, new WorldStateOpsQueue());
367
+ this.api = wsdb;
238
368
  this.log.info('Created IPC-backed world state instance');
239
369
  }
240
370
 
241
- /** Returns the socket path of the underlying wsdb server. */
242
- getSocketPath(): string {
243
- return this.wsdbBackend.getSocketPath();
244
- }
245
-
246
371
  /**
247
- * Required by `NativeWorldStateInstance` for compatibility with the in-process
248
- * NAPI path. The IPC backend does not expose an in-process pointer; callers that
249
- * need to reach the WSDB process must use {@link getSocketPath} instead.
372
+ * Spawn an `aztec-wsdb` subprocess and return an IPC-backed world state wrapping it.
373
+ * Encapsulates wsdb binary discovery, service construction, and readiness wait.
250
374
  */
251
- getHandle(): any {
252
- throw new Error('IpcWorldState has no in-process handle; use getSocketPath() instead');
253
- }
254
-
255
- async call<T extends WorldStateMessageType>(
256
- messageType: T,
257
- body: WorldStateRequest[T] & WorldStateRequestCategories,
258
- responseHandler = (response: WorldStateResponse[T]): WorldStateResponse[T] => response,
259
- errorHandler = (_: string) => {},
260
- ): Promise<WorldStateResponse[T]> {
261
- let forkId = -1;
262
- let committedOnly = false;
263
-
264
- if (isWithCanonical(body)) {
265
- forkId = 0;
266
- } else if (isWithForkId(body)) {
267
- forkId = body.forkId;
268
- } else if (isWithRevision(body)) {
269
- forkId = body.revision.forkId;
270
- committedOnly = body.revision.includeUncommitted === false;
271
- } else {
272
- const _: never = body;
273
- throw new Error(`Unable to determine forkId for message=${WorldStateMessageType[messageType]}`);
274
- }
275
-
276
- let requestQueue = this.queues.get(forkId);
277
- if (requestQueue === undefined) {
278
- requestQueue = new WorldStateOpsQueue();
279
- this.queues.set(forkId, requestQueue);
280
- }
375
+ static async spawn(
376
+ dataDir: string,
377
+ wsTreeMapSizes: WorldStateTreeMapSizes,
378
+ genesis: GenesisData,
379
+ instrumentation: WorldStateInstrumentation,
380
+ bindings?: LoggerBindings,
381
+ threads: number = getWsdbThreadCount(),
382
+ ): Promise<IpcWorldState> {
383
+ const transport = process.env.WSDB_TRANSPORT === 'shm' ? 'shm' : 'uds';
384
+ const wsdb = await WsdbService.spawn({
385
+ transport,
386
+ extraArgs: getWsdbExtraArgs(dataDir, wsTreeMapSizes, genesis, threads),
387
+ env: { HARDWARE_CONCURRENCY: threads.toString() },
388
+ });
389
+ return new IpcWorldState(wsdb, instrumentation, bindings);
390
+ }
281
391
 
282
- // The per-fork queue is cleaned up in `finally` even on error, so the JS-side queues map cannot outlive
283
- // the native fork (e.g. when the native fork was already destroyed by an unwind/historical-prune and
284
- // DELETE_FORK rejects with "Fork not found").
285
- let shouldDeleteForkQueue = false;
286
- try {
287
- const response = await requestQueue.execute(
288
- async () => {
289
- assert.notEqual(messageType, WorldStateMessageType.CLOSE, 'Use close() to close the IPC instance');
290
- assert.equal(this.open, true, 'IPC instance is closed');
291
- let response: WorldStateResponse[T];
292
- try {
293
- response = await this._sendMessage(messageType, body);
294
- } catch (error: any) {
295
- errorHandler(error.message);
296
- throw error;
297
- }
298
- return responseHandler(response);
299
- },
300
- messageType,
301
- committedOnly,
302
- );
303
- return response;
304
- } catch (err: any) {
305
- shouldDeleteForkQueue = forkId !== 0 && err?.message === 'Fork not found';
306
- throw err;
307
- } finally {
308
- if (messageType === WorldStateMessageType.DELETE_FORK || shouldDeleteForkQueue) {
309
- await requestQueue.stop();
310
- this.queues.delete(forkId);
311
- }
312
- }
392
+ getIpcPath(): string {
393
+ return this.wsdb.getIpcPath();
313
394
  }
314
395
 
315
396
  async close(): Promise<void> {
@@ -317,380 +398,421 @@ export class IpcWorldState implements NativeWorldStateInstance {
317
398
  return;
318
399
  }
319
400
  this.open = false;
320
- const queue = this.queues.get(0)!;
321
-
322
- // Send shutdown command. Under normal operation, the WSDB process sends its
323
- // response before exiting (via ShutdownRequested in ipc_server.hpp). The
324
- // try/catch is defensive: if the process is killed externally (SIGKILL, OOM)
325
- // before responding, the pending IPC callback would be rejected by the socket
326
- // close handler. We proceed to destroy the backend regardless.
327
- try {
328
- await queue.execute(
329
- async () => {
330
- await this.api.wsdbShutdown({});
331
- },
332
- WorldStateMessageType.CLOSE,
333
- false,
334
- );
335
- } catch (err: any) {
336
- this.log.debug(`wsdbShutdown completed with error: ${err.message}`);
337
- }
338
- await queue.stop();
339
-
340
- if (this.wsdbBackend.destroy) {
341
- await this.wsdbBackend.destroy();
342
- }
401
+ await this.wsdb.destroy();
343
402
  }
344
403
 
345
- private async _sendMessage<T extends WorldStateMessageType>(
346
- messageType: T,
347
- body: WorldStateRequest[T] & WorldStateRequestCategories,
348
- ): Promise<WorldStateResponse[T]> {
349
- const start = performance.now();
350
- try {
351
- const response = await this.dispatch(messageType, body);
352
- const durationMs = performance.now() - start;
353
- this.log.trace(`Call ${WorldStateMessageType[messageType]} took (ms)`, { duration: durationMs });
354
- this.instrumentation.recordRoundTrip(durationMs * 1000, messageType);
355
- return response;
356
- } catch (error) {
357
- this.log.error(`Call ${WorldStateMessageType[messageType]} failed: ${error}`, error);
358
- throw error;
359
- }
404
+ getTreeInfo(treeId: MerkleTreeId, revision: WorldStateRevision): Promise<TreeInfo> {
405
+ return this.execute('getTreeInfo', revision.forkId, revision.includeUncommitted === false, async () => {
406
+ const resp = await this.api.getTreeInfo({ treeId, revision: toWsdbRevision(revision) });
407
+ return {
408
+ treeId: resp.treeId,
409
+ root: Buffer.from(resp.root),
410
+ size: BigInt(resp.size),
411
+ depth: resp.depth,
412
+ };
413
+ });
360
414
  }
361
415
 
362
- private async dispatch<T extends WorldStateMessageType>(
363
- messageType: T,
364
- body: WorldStateRequest[T] & WorldStateRequestCategories,
365
- ): Promise<WorldStateResponse[T]> {
366
- switch (messageType) {
367
- // ——— Tree info & state reference ———
368
-
369
- case WorldStateMessageType.GET_TREE_INFO: {
370
- const b = body as WorldStateRequest[WorldStateMessageType.GET_TREE_INFO];
371
- const resp = await this.api.wsdbGetTreeInfo({
372
- treeid: b.treeId,
373
- revision: toWsdbRevision(b.revision),
374
- });
375
- return {
376
- treeId: resp.treeid,
377
- root: Buffer.from(resp.root),
378
- size: resp.size,
379
- depth: resp.depth,
380
- } as WorldStateResponse[T];
381
- }
382
-
383
- case WorldStateMessageType.GET_STATE_REFERENCE: {
384
- const b = body as WorldStateRequest[WorldStateMessageType.GET_STATE_REFERENCE];
385
- const resp = await this.api.wsdbGetStateReference({
386
- revision: toWsdbRevision(b.revision),
387
- });
388
- return { state: convertStateRef(resp.state) } as WorldStateResponse[T];
389
- }
390
-
391
- case WorldStateMessageType.GET_INITIAL_STATE_REFERENCE: {
392
- const resp = await this.api.wsdbGetInitialStateReference({});
393
- return { state: convertStateRef(resp.state) } as WorldStateResponse[T];
394
- }
395
-
396
- // ——— Leaf queries ———
397
-
398
- case WorldStateMessageType.GET_LEAF_VALUE: {
399
- const b = body as WorldStateRequest[WorldStateMessageType.GET_LEAF_VALUE];
400
- const resp = await this.api.wsdbGetLeafValue({
401
- treeid: b.treeId,
402
- revision: toWsdbRevision(b.revision),
403
- leafindex: Number(b.leafIndex),
404
- });
405
- if (!resp.value) {
406
- return undefined as WorldStateResponse[T];
407
- }
408
- return decodeLeafValue(resp.value) as WorldStateResponse[T];
409
- }
410
-
411
- case WorldStateMessageType.GET_LEAF_PREIMAGE: {
412
- const b = body as WorldStateRequest[WorldStateMessageType.GET_LEAF_PREIMAGE];
413
- const resp = await this.api.wsdbGetLeafPreimage({
414
- treeid: b.treeId,
415
- revision: toWsdbRevision(b.revision),
416
- leafindex: Number(b.leafIndex),
417
- });
418
- if (!resp.preimage) {
419
- return undefined as WorldStateResponse[T];
420
- }
421
- return decodeLeafPreimage(resp.preimage) as WorldStateResponse[T];
422
- }
423
-
424
- case WorldStateMessageType.GET_SIBLING_PATH: {
425
- const b = body as WorldStateRequest[WorldStateMessageType.GET_SIBLING_PATH];
426
- const resp = await this.api.wsdbGetSiblingPath({
427
- treeid: b.treeId,
428
- revision: toWsdbRevision(b.revision),
429
- leafindex: Number(b.leafIndex),
430
- });
431
- return resp.path.map(p => Buffer.from(p)) as WorldStateResponse[T];
432
- }
433
-
434
- case WorldStateMessageType.GET_BLOCK_NUMBERS_FOR_LEAF_INDICES: {
435
- const b = body as WorldStateRequest[WorldStateMessageType.GET_BLOCK_NUMBERS_FOR_LEAF_INDICES];
436
- const resp = await this.api.wsdbGetBlockNumbersForLeafIndices({
437
- treeid: b.treeId,
438
- revision: toWsdbRevision(b.revision),
439
- leafindices: b.leafIndices.map(Number),
440
- });
441
- return {
442
- blockNumbers: resp.blocknumbers.map(n => (n != null ? BigInt(n) : undefined)),
443
- } as WorldStateResponse[T];
444
- }
445
-
446
- // ——— Find operations ———
416
+ getStateReference(revision: WorldStateRevision): Promise<Record<number, TreeStateReference>> {
417
+ return this.execute('getStateReference', revision.forkId, revision.includeUncommitted === false, async () => {
418
+ const resp = await this.api.getStateReference({ revision: toWsdbRevision(revision) });
419
+ return convertStateRef(resp.state);
420
+ });
421
+ }
447
422
 
448
- case WorldStateMessageType.FIND_LEAF_INDICES: {
449
- const b = body as WorldStateRequest[WorldStateMessageType.FIND_LEAF_INDICES];
450
- const resp = await this.api.wsdbFindLeafIndices({
451
- treeid: b.treeId,
452
- revision: toWsdbRevision(b.revision),
453
- leaves: b.leaves.map(serializeLeafToBytes),
454
- startindex: Number(b.startIndex),
455
- });
456
- return {
457
- indices: resp.indices.map(n => (n != null ? BigInt(n) : undefined)),
458
- } as WorldStateResponse[T];
459
- }
423
+ getInitialStateReference(): Promise<Record<number, TreeStateReference>> {
424
+ return this.execute('getInitialStateReference', 0, false, async () => {
425
+ const resp = await this.api.getInitialStateReference({});
426
+ return convertStateRef(resp.state);
427
+ });
428
+ }
460
429
 
461
- case WorldStateMessageType.FIND_LOW_LEAF: {
462
- const b = body as WorldStateRequest[WorldStateMessageType.FIND_LOW_LEAF];
463
- const resp = await this.api.wsdbFindLowLeaf({
464
- treeid: b.treeId,
465
- revision: toWsdbRevision(b.revision),
466
- key: new Uint8Array(b.key.toBuffer()),
467
- });
468
- return {
469
- alreadyPresent: resp.alreadypresent,
470
- index: BigInt(resp.index),
471
- } as WorldStateResponse[T];
430
+ getLeafValue(
431
+ treeId: MerkleTreeId,
432
+ revision: WorldStateRevision,
433
+ leafIndex: bigint,
434
+ ): Promise<SerializedLeafValue | undefined> {
435
+ return this.execute('getLeafValue', revision.forkId, revision.includeUncommitted === false, async () => {
436
+ const wsdbRevision = toWsdbRevision(revision);
437
+ const wsdbLeafIndex = Number(leafIndex);
438
+
439
+ if (treeId === MerkleTreeId.PUBLIC_DATA_TREE) {
440
+ const resp = await this.api.getPublicDataLeafValue({ revision: wsdbRevision, leafIndex: wsdbLeafIndex });
441
+ return resp.value ? fromPublicDataLeaf(resp.value) : undefined;
472
442
  }
473
443
 
474
- case WorldStateMessageType.FIND_SIBLING_PATHS: {
475
- const b = body as WorldStateRequest[WorldStateMessageType.FIND_SIBLING_PATHS];
476
- const resp = await this.api.wsdbFindSiblingPaths({
477
- treeid: b.treeId,
478
- revision: toWsdbRevision(b.revision),
479
- leaves: b.leaves.map(serializeLeafToBytes),
480
- });
481
- return {
482
- paths: resp.paths.map(convertSiblingPathAndIndex),
483
- } as WorldStateResponse[T];
444
+ if (treeId === MerkleTreeId.NULLIFIER_TREE) {
445
+ const resp = await this.api.getNullifierLeafValue({ revision: wsdbRevision, leafIndex: wsdbLeafIndex });
446
+ return resp.value ? fromNullifierLeaf(resp.value) : undefined;
484
447
  }
485
448
 
486
- // ——— Mutations ———
487
-
488
- case WorldStateMessageType.APPEND_LEAVES: {
489
- const b = body as WorldStateRequest[WorldStateMessageType.APPEND_LEAVES];
490
- await this.api.wsdbAppendLeaves({
491
- treeid: b.treeId,
492
- leaves: b.leaves.map(serializeLeafToBytes),
493
- forkid: b.forkId,
494
- });
495
- return undefined as WorldStateResponse[T];
496
- }
449
+ const resp = await this.api.getLeafValue({ treeId, revision: wsdbRevision, leafIndex: wsdbLeafIndex });
450
+ return resp.value ? Buffer.from(resp.value) : undefined;
451
+ });
452
+ }
497
453
 
498
- case WorldStateMessageType.BATCH_INSERT: {
499
- const b = body as WorldStateRequest[WorldStateMessageType.BATCH_INSERT];
500
- const resp = await this.api.wsdbBatchInsert({
501
- treeid: b.treeId,
502
- leaves: b.leaves.map(serializeLeafToBytes),
503
- subtreedepth: b.subtreeDepth,
504
- forkid: b.forkId,
505
- });
506
- const decoded = msgpackDecoder.unpack(Buffer.from(resp.result));
507
- return convertUint8ArraysToBuffers(decoded) as WorldStateResponse[T];
508
- }
454
+ getLeafPreimage(
455
+ treeId: IndexedTreeId,
456
+ revision: WorldStateRevision,
457
+ leafIndex: bigint,
458
+ ): Promise<SerializedIndexedLeaf | undefined> {
459
+ return this.execute('getLeafPreimage', revision.forkId, revision.includeUncommitted === false, async () => {
460
+ const resp =
461
+ treeId === MerkleTreeId.PUBLIC_DATA_TREE
462
+ ? await this.api.getPublicDataLeafPreimage({
463
+ revision: toWsdbRevision(revision),
464
+ leafIndex: Number(leafIndex),
465
+ })
466
+ : await this.api.getNullifierLeafPreimage({
467
+ revision: toWsdbRevision(revision),
468
+ leafIndex: Number(leafIndex),
469
+ });
470
+ return resp.preimage ? (convertUint8ArraysToBuffers(resp.preimage) as SerializedIndexedLeaf) : undefined;
471
+ });
472
+ }
509
473
 
510
- case WorldStateMessageType.SEQUENTIAL_INSERT: {
511
- const b = body as WorldStateRequest[WorldStateMessageType.SEQUENTIAL_INSERT];
512
- const resp = await this.api.wsdbSequentialInsert({
513
- treeid: b.treeId,
514
- leaves: b.leaves.map(serializeLeafToBytes),
515
- forkid: b.forkId,
516
- });
517
- const decoded = msgpackDecoder.unpack(Buffer.from(resp.result));
518
- return convertUint8ArraysToBuffers(decoded) as WorldStateResponse[T];
519
- }
474
+ getSiblingPath(treeId: MerkleTreeId, revision: WorldStateRevision, leafIndex: bigint): Promise<Buffer[]> {
475
+ return this.execute('getSiblingPath', revision.forkId, revision.includeUncommitted === false, async () => {
476
+ const resp = await this.api.getSiblingPath({
477
+ treeId,
478
+ revision: toWsdbRevision(revision),
479
+ leafIndex: Number(leafIndex),
480
+ });
481
+ return resp.path.map(p => Buffer.from(p));
482
+ });
483
+ }
520
484
 
521
- case WorldStateMessageType.UPDATE_ARCHIVE: {
522
- const b = body as WorldStateRequest[WorldStateMessageType.UPDATE_ARCHIVE];
523
- await this.api.wsdbUpdateArchive({
524
- blockstateref: blockStateRefToMap(b.blockStateRef as Map<number, readonly [Buffer, number | bigint]>) as any,
525
- blockheaderhash: new Uint8Array(b.blockHeaderHash),
526
- forkid: b.forkId,
485
+ getBlockNumbersForLeafIndices(
486
+ treeId: MerkleTreeId,
487
+ revision: WorldStateRevision,
488
+ leafIndices: bigint[],
489
+ ): Promise<(bigint | undefined)[]> {
490
+ return this.execute(
491
+ 'getBlockNumbersForLeafIndices',
492
+ revision.forkId,
493
+ revision.includeUncommitted === false,
494
+ async () => {
495
+ const resp = await this.api.getBlockNumbersForLeafIndices({
496
+ treeId,
497
+ revision: toWsdbRevision(revision),
498
+ leafIndices: leafIndices.map(Number),
527
499
  });
528
- return undefined as WorldStateResponse[T];
529
- }
500
+ return resp.blockNumbers.map(n => (n != null ? BigInt(n) : undefined));
501
+ },
502
+ );
503
+ }
530
504
 
531
- // ——— Commit / Rollback ———
505
+ findLeafIndices(
506
+ treeId: MerkleTreeId,
507
+ revision: WorldStateRevision,
508
+ leaves: SerializedLeafValue[],
509
+ startIndex: bigint,
510
+ ): Promise<(bigint | undefined)[]> {
511
+ return this.execute('findLeafIndices', revision.forkId, revision.includeUncommitted === false, async () => {
512
+ const wsdbRevision = toWsdbRevision(revision);
513
+ const wsdbStartIndex = Number(startIndex);
514
+ const resp =
515
+ treeId === MerkleTreeId.PUBLIC_DATA_TREE
516
+ ? await this.api.findPublicDataLeafIndices({
517
+ revision: wsdbRevision,
518
+ leaves: leaves.map(toPublicDataLeaf),
519
+ startIndex: wsdbStartIndex,
520
+ })
521
+ : treeId === MerkleTreeId.NULLIFIER_TREE
522
+ ? await this.api.findNullifierLeafIndices({
523
+ revision: wsdbRevision,
524
+ leaves: leaves.map(toNullifierLeaf),
525
+ startIndex: wsdbStartIndex,
526
+ })
527
+ : await this.api.findLeafIndices({
528
+ treeId,
529
+ revision: wsdbRevision,
530
+ leaves: leaves.map(toFrLeaf),
531
+ startIndex: wsdbStartIndex,
532
+ });
533
+ return resp.indices.map(n => (n != null ? BigInt(n) : undefined));
534
+ });
535
+ }
532
536
 
533
- case WorldStateMessageType.COMMIT: {
534
- await this.api.wsdbCommit({});
535
- return undefined as WorldStateResponse[T];
536
- }
537
+ findLowLeaf(
538
+ treeId: IndexedTreeId,
539
+ revision: WorldStateRevision,
540
+ key: Fr,
541
+ ): Promise<{ index: bigint; alreadyPresent: boolean }> {
542
+ return this.execute('findLowLeaf', revision.forkId, revision.includeUncommitted === false, async () => {
543
+ const resp = await this.api.findLowLeaf({
544
+ treeId,
545
+ revision: toWsdbRevision(revision),
546
+ key: new Uint8Array(key.toBuffer()),
547
+ });
548
+ return {
549
+ alreadyPresent: resp.alreadyPresent,
550
+ index: BigInt(resp.index),
551
+ };
552
+ });
553
+ }
537
554
 
538
- case WorldStateMessageType.ROLLBACK: {
539
- await this.api.wsdbRollback({});
540
- return undefined as WorldStateResponse[T];
541
- }
555
+ findSiblingPaths(
556
+ treeId: MerkleTreeId,
557
+ revision: WorldStateRevision,
558
+ leaves: SerializedLeafValue[],
559
+ ): Promise<(SerializedSiblingPathAndIndex | undefined)[]> {
560
+ return this.execute('findSiblingPaths', revision.forkId, revision.includeUncommitted === false, async () => {
561
+ const wsdbRevision = toWsdbRevision(revision);
562
+ const resp =
563
+ treeId === MerkleTreeId.PUBLIC_DATA_TREE
564
+ ? await this.api.findPublicDataSiblingPaths({
565
+ revision: wsdbRevision,
566
+ leaves: leaves.map(toPublicDataLeaf),
567
+ })
568
+ : treeId === MerkleTreeId.NULLIFIER_TREE
569
+ ? await this.api.findNullifierSiblingPaths({
570
+ revision: wsdbRevision,
571
+ leaves: leaves.map(toNullifierLeaf),
572
+ })
573
+ : await this.api.findSiblingPaths({
574
+ treeId,
575
+ revision: wsdbRevision,
576
+ leaves: leaves.map(toFrLeaf),
577
+ });
578
+ return resp.paths.map(convertSiblingPathAndIndex);
579
+ });
580
+ }
542
581
 
543
- // ——— Block sync ———
544
-
545
- case WorldStateMessageType.SYNC_BLOCK: {
546
- const b = body as WorldStateRequest[WorldStateMessageType.SYNC_BLOCK];
547
- const resp = await this.api.wsdbSyncBlock({
548
- blocknumber: Number(b.blockNumber),
549
- blockstateref: blockStateRefToMap(b.blockStateRef as Map<number, readonly [Buffer, number | bigint]>) as any,
550
- blockheaderhash: new Uint8Array(b.blockHeaderHash),
551
- // Forwarded so the wsdb (IPC) sync path enforces the same archive-root divergence check as the napi path.
552
- expectedarchiveroot: new Uint8Array(b.expectedArchiveRoot),
553
- expectedpreviousarchiveroot: new Uint8Array(b.expectedPreviousArchiveRoot),
554
- paddednotehashes: b.paddedNoteHashes.map(l => new Uint8Array(l as Buffer)),
555
- paddedl1tol2messages: b.paddedL1ToL2Messages.map(l => new Uint8Array(l as Buffer)),
556
- paddednullifiers: b.paddedNullifiers.map(l => ({
557
- nullifier: new Uint8Array((l as { nullifier: Buffer }).nullifier),
558
- })),
559
- publicdatawrites: b.publicDataWrites.map(l => ({
560
- slot: new Uint8Array((l as { slot: Buffer; value: Buffer }).slot),
561
- value: new Uint8Array((l as { slot: Buffer; value: Buffer }).value),
562
- })),
563
- });
582
+ updateArchive(forkId: number, blockStateRef: BlockStateReference, blockHeaderHash: Buffer): Promise<void> {
583
+ return this.execute('updateArchive', forkId, false, async () => {
584
+ await this.api.updateArchive({
585
+ blockStateRef: blockStateRefToMap(blockStateRef),
586
+ blockHeaderHash: new Uint8Array(blockHeaderHash),
587
+ forkId,
588
+ });
589
+ });
590
+ }
564
591
 
565
- return convertStatusFull(resp.status) as WorldStateResponse[T];
592
+ appendLeaves(treeId: MerkleTreeId, forkId: number, leaves: SerializedLeafValue[]): Promise<void> {
593
+ return this.execute('appendLeaves', forkId, false, async () => {
594
+ if (treeId === MerkleTreeId.PUBLIC_DATA_TREE) {
595
+ await this.api.appendPublicDataLeaves({ leaves: leaves.map(toPublicDataLeaf), forkId });
596
+ } else if (treeId === MerkleTreeId.NULLIFIER_TREE) {
597
+ await this.api.appendNullifierLeaves({ leaves: leaves.map(toNullifierLeaf), forkId });
598
+ } else {
599
+ await this.api.appendLeaves({ treeId, leaves: leaves.map(toFrLeaf), forkId });
566
600
  }
601
+ });
602
+ }
567
603
 
568
- // ——— Fork management ———
569
-
570
- case WorldStateMessageType.CREATE_FORK: {
571
- const b = body as WorldStateRequest[WorldStateMessageType.CREATE_FORK];
572
- const resp = await this.api.wsdbCreateFork({
573
- latest: b.latest,
574
- blocknumber: Number(b.blockNumber),
575
- });
576
- return { forkId: resp.forkid } as WorldStateResponse[T];
577
- }
604
+ batchInsert(
605
+ treeId: IndexedTreeId,
606
+ forkId: number,
607
+ leaves: SerializedLeafValue[],
608
+ subtreeDepth: number,
609
+ ): Promise<SerializedBatchInsertionResult> {
610
+ return this.execute('batchInsert', forkId, false, async () => {
611
+ const resp =
612
+ treeId === MerkleTreeId.PUBLIC_DATA_TREE
613
+ ? await this.api.batchInsertPublicData({
614
+ leaves: leaves.map(toPublicDataLeaf),
615
+ subtreeDepth,
616
+ forkId,
617
+ })
618
+ : await this.api.batchInsertNullifier({
619
+ leaves: leaves.map(toNullifierLeaf),
620
+ subtreeDepth,
621
+ forkId,
622
+ });
623
+ return treeId === MerkleTreeId.PUBLIC_DATA_TREE
624
+ ? fromBatchInsertionResult(resp.result as any, fromPublicDataLeaf)
625
+ : fromBatchInsertionResult(resp.result as any, fromNullifierLeaf);
626
+ });
627
+ }
578
628
 
579
- case WorldStateMessageType.DELETE_FORK: {
580
- const b = body as WorldStateRequest[WorldStateMessageType.DELETE_FORK];
581
- await this.api.wsdbDeleteFork({ forkid: b.forkId });
582
- return undefined as WorldStateResponse[T];
583
- }
629
+ sequentialInsert(
630
+ treeId: IndexedTreeId,
631
+ forkId: number,
632
+ leaves: SerializedLeafValue[],
633
+ ): Promise<SerializedSequentialInsertionResult> {
634
+ return this.execute('sequentialInsert', forkId, false, async () => {
635
+ const resp =
636
+ treeId === MerkleTreeId.PUBLIC_DATA_TREE
637
+ ? await this.api.sequentialInsertPublicData({
638
+ leaves: leaves.map(toPublicDataLeaf),
639
+ forkId,
640
+ })
641
+ : await this.api.sequentialInsertNullifier({
642
+ leaves: leaves.map(toNullifierLeaf),
643
+ forkId,
644
+ });
645
+ return treeId === MerkleTreeId.PUBLIC_DATA_TREE
646
+ ? fromSequentialInsertionResult(resp.result as any, fromPublicDataLeaf)
647
+ : fromSequentialInsertionResult(resp.result as any, fromNullifierLeaf);
648
+ });
649
+ }
584
650
 
585
- // ——— Block finalization ———
651
+ syncBlock(input: {
652
+ blockNumber: BlockNumber;
653
+ blockStateRef: BlockStateReference;
654
+ blockHeaderHash: Buffer;
655
+ expectedArchiveRoot: Buffer;
656
+ expectedPreviousArchiveRoot: Buffer;
657
+ paddedNoteHashes: readonly SerializedLeafValue[];
658
+ paddedL1ToL2Messages: readonly SerializedLeafValue[];
659
+ paddedNullifiers: readonly SerializedLeafValue[];
660
+ publicDataWrites: readonly SerializedLeafValue[];
661
+ }): Promise<WorldStateStatusFull> {
662
+ return this.execute('syncBlock', 0, false, async () => {
663
+ const resp = await this.api.syncBlock({
664
+ blockNumber: Number(input.blockNumber),
665
+ blockStateRef: blockStateRefToMap(input.blockStateRef),
666
+ blockHeaderHash: new Uint8Array(input.blockHeaderHash),
667
+ expectedArchiveRoot: new Uint8Array(input.expectedArchiveRoot),
668
+ expectedPreviousArchiveRoot: new Uint8Array(input.expectedPreviousArchiveRoot),
669
+ paddedNoteHashes: input.paddedNoteHashes.map(toFrLeaf),
670
+ paddedL1ToL2Messages: input.paddedL1ToL2Messages.map(toFrLeaf),
671
+ paddedNullifiers: input.paddedNullifiers.map(toNullifierLeaf),
672
+ publicDataWrites: input.publicDataWrites.map(toPublicDataLeaf),
673
+ });
674
+ return convertStatusFull(resp.status);
675
+ });
676
+ }
586
677
 
587
- case WorldStateMessageType.FINALIZE_BLOCKS: {
588
- const b = body as WorldStateRequest[WorldStateMessageType.FINALIZE_BLOCKS];
589
- const resp = await this.api.wsdbFinalizeBlocks({ toblocknumber: Number(b.toBlockNumber) });
590
- return convertStatusSummary(resp.status) as WorldStateResponse[T];
591
- }
678
+ createFork(input: { latest: boolean; blockNumber: BlockNumber }): Promise<number> {
679
+ return this.execute('createFork', 0, false, async () => {
680
+ const resp = await this.api.createFork({
681
+ latest: input.latest,
682
+ blockNumber: Number(input.blockNumber),
683
+ });
684
+ return resp.forkId;
685
+ });
686
+ }
592
687
 
593
- case WorldStateMessageType.UNWIND_BLOCKS: {
594
- const b = body as WorldStateRequest[WorldStateMessageType.UNWIND_BLOCKS];
595
- const resp = await this.api.wsdbUnwindBlocks({ toblocknumber: Number(b.toBlockNumber) });
596
- return convertStatusFull(resp.status) as WorldStateResponse[T];
597
- }
688
+ deleteFork(forkId: number): Promise<void> {
689
+ return this.execute('deleteFork', forkId, false, async () => {
690
+ await this.api.deleteFork({ forkId });
691
+ });
692
+ }
598
693
 
599
- case WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS: {
600
- const b = body as WorldStateRequest[WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS];
601
- const resp = await this.api.wsdbRemoveHistoricalBlocks({ toblocknumber: Number(b.toBlockNumber) });
602
- return convertStatusFull(resp.status) as WorldStateResponse[T];
603
- }
694
+ finalizeBlocks(toBlockNumber: BlockNumber): Promise<WorldStateStatusSummary> {
695
+ return this.execute('finalizeBlocks', 0, false, async () => {
696
+ const resp = await this.api.finalizeBlocks({ toBlockNumber: Number(toBlockNumber) });
697
+ return convertStatusSummary(resp.status);
698
+ });
699
+ }
604
700
 
605
- // ——— Status ———
701
+ unwindBlocks(toBlockNumber: BlockNumber): Promise<WorldStateStatusFull> {
702
+ return this.execute('unwindBlocks', 0, false, async () => {
703
+ const resp = await this.api.unwindBlocks({ toBlockNumber: Number(toBlockNumber) });
704
+ return convertStatusFull(resp.status);
705
+ });
706
+ }
606
707
 
607
- case WorldStateMessageType.GET_STATUS: {
608
- const resp = await this.api.wsdbGetStatus({});
609
- return convertStatusSummary(resp.status) as WorldStateResponse[T];
610
- }
708
+ removeHistoricalBlocks(toBlockNumber: BlockNumber): Promise<WorldStateStatusFull> {
709
+ return this.execute('removeHistoricalBlocks', 0, false, async () => {
710
+ const resp = await this.api.removeHistoricalBlocks({ toBlockNumber: Number(toBlockNumber) });
711
+ return convertStatusFull(resp.status);
712
+ });
713
+ }
611
714
 
612
- // ——— Checkpoints ———
715
+ getStatus(): Promise<WorldStateStatusSummary> {
716
+ return this.execute('getStatus', 0, false, async () => {
717
+ const resp = await this.api.getStatus({});
718
+ return convertStatusSummary(resp.status);
719
+ });
720
+ }
613
721
 
614
- case WorldStateMessageType.CREATE_CHECKPOINT: {
615
- const b = body as WorldStateRequest[WorldStateMessageType.CREATE_CHECKPOINT];
616
- await this.api.wsdbCreateCheckpoint({ forkid: b.forkId });
617
- const depth = (this.checkpointDepths.get(b.forkId) ?? 0) + 1;
618
- this.checkpointDepths.set(b.forkId, depth);
619
- return { depth } as WorldStateResponse[T];
620
- }
722
+ createCheckpoint(forkId: number): Promise<number> {
723
+ return this.execute('createCheckpoint', forkId, false, async () => {
724
+ await this.api.createCheckpoint({ forkId });
725
+ const depth = (this.checkpointDepths.get(forkId) ?? 0) + 1;
726
+ this.checkpointDepths.set(forkId, depth);
727
+ return depth;
728
+ });
729
+ }
621
730
 
622
- case WorldStateMessageType.COMMIT_CHECKPOINT: {
623
- const b = body as WorldStateRequest[WorldStateMessageType.COMMIT_CHECKPOINT];
624
- await this.api.wsdbCommitCheckpoint({ forkid: b.forkId });
625
- const depth = Math.max(0, (this.checkpointDepths.get(b.forkId) ?? 0) - 1);
626
- this.checkpointDepths.set(b.forkId, depth);
627
- return undefined as WorldStateResponse[T];
628
- }
731
+ commitCheckpoint(forkId: number): Promise<void> {
732
+ return this.execute('commitCheckpoint', forkId, false, async () => {
733
+ await this.api.commitCheckpoint({ forkId });
734
+ const depth = Math.max(0, (this.checkpointDepths.get(forkId) ?? 0) - 1);
735
+ this.checkpointDepths.set(forkId, depth);
736
+ });
737
+ }
629
738
 
630
- case WorldStateMessageType.REVERT_CHECKPOINT: {
631
- const b = body as WorldStateRequest[WorldStateMessageType.REVERT_CHECKPOINT];
632
- await this.api.wsdbRevertCheckpoint({ forkid: b.forkId });
633
- const depth = Math.max(0, (this.checkpointDepths.get(b.forkId) ?? 0) - 1);
634
- this.checkpointDepths.set(b.forkId, depth);
635
- return undefined as WorldStateResponse[T];
636
- }
739
+ revertCheckpoint(forkId: number): Promise<void> {
740
+ return this.execute('revertCheckpoint', forkId, false, async () => {
741
+ await this.api.revertCheckpoint({ forkId });
742
+ const depth = Math.max(0, (this.checkpointDepths.get(forkId) ?? 0) - 1);
743
+ this.checkpointDepths.set(forkId, depth);
744
+ });
745
+ }
637
746
 
638
- case WorldStateMessageType.COMMIT_ALL_CHECKPOINTS: {
639
- const b = body as WorldStateRequest[WorldStateMessageType.COMMIT_ALL_CHECKPOINTS];
640
- const targetDepth = b.depth ?? 0;
641
- const currentDepth = this.checkpointDepths.get(b.forkId) ?? 0;
642
- if (targetDepth === 0) {
643
- // Commit everything — use the bulk operation
644
- await this.api.wsdbCommitAllCheckpoints({ forkid: b.forkId });
645
- } else {
646
- // Commit one level at a time down to target depth
647
- for (let d = currentDepth; d > targetDepth; d--) {
648
- await this.api.wsdbCommitCheckpoint({ forkid: b.forkId });
649
- }
747
+ commitAllCheckpoints(forkId: number, depth: number): Promise<void> {
748
+ return this.execute('commitAllCheckpoints', forkId, false, async () => {
749
+ const currentDepth = this.checkpointDepths.get(forkId) ?? 0;
750
+ if (depth === 0) {
751
+ await this.api.commitAllCheckpoints({ forkId });
752
+ } else {
753
+ for (let d = currentDepth; d > depth; d--) {
754
+ await this.api.commitCheckpoint({ forkId });
650
755
  }
651
- this.checkpointDepths.set(b.forkId, targetDepth);
652
- return undefined as WorldStateResponse[T];
653
756
  }
757
+ this.checkpointDepths.set(forkId, depth);
758
+ });
759
+ }
654
760
 
655
- case WorldStateMessageType.REVERT_ALL_CHECKPOINTS: {
656
- const b = body as WorldStateRequest[WorldStateMessageType.REVERT_ALL_CHECKPOINTS];
657
- const targetDepth = b.depth ?? 0;
658
- const currentDepth = this.checkpointDepths.get(b.forkId) ?? 0;
659
- if (targetDepth === 0) {
660
- // Revert everything — use the bulk operation
661
- await this.api.wsdbRevertAllCheckpoints({ forkid: b.forkId });
662
- } else {
663
- // Revert one level at a time down to target depth
664
- for (let d = currentDepth; d > targetDepth; d--) {
665
- await this.api.wsdbRevertCheckpoint({ forkid: b.forkId });
666
- }
761
+ revertAllCheckpoints(forkId: number, depth: number): Promise<void> {
762
+ return this.execute('revertAllCheckpoints', forkId, false, async () => {
763
+ const currentDepth = this.checkpointDepths.get(forkId) ?? 0;
764
+ if (depth === 0) {
765
+ await this.api.revertAllCheckpoints({ forkId });
766
+ } else {
767
+ for (let d = currentDepth; d > depth; d--) {
768
+ await this.api.revertCheckpoint({ forkId });
667
769
  }
668
- this.checkpointDepths.set(b.forkId, targetDepth);
669
- return undefined as WorldStateResponse[T];
670
770
  }
771
+ this.checkpointDepths.set(forkId, depth);
772
+ });
773
+ }
671
774
 
672
- // ——— Misc ———
673
-
674
- case WorldStateMessageType.COPY_STORES: {
675
- const b = body as WorldStateRequest[WorldStateMessageType.COPY_STORES];
676
- await this.api.wsdbCopyStores({ dstpath: b.dstPath, compact: b.compact });
677
- return undefined as WorldStateResponse[T];
678
- }
775
+ copyStores(dstPath: string, compact: boolean): Promise<void> {
776
+ return this.execute('copyStores', 0, false, async () => {
777
+ await this.api.copyStores({ dstPath, compact });
778
+ });
779
+ }
679
780
 
680
- case WorldStateMessageType.CLOSE: {
681
- await this.api.wsdbShutdown({});
682
- return undefined as WorldStateResponse[T];
683
- }
781
+ // Read/write ordering is enforced server-side by the wsdb per-fork scheduler,
782
+ // so the client no longer queues: every op is sent immediately and the server
783
+ // serializes writes per fork while running reads concurrently. `_forkId` and
784
+ // `_committedOnly` are retained in the signature (they describe the op) but are
785
+ // no longer needed for client-side ordering.
786
+ private async execute<T>(
787
+ operation: WorldStateOperationName,
788
+ _forkId: number,
789
+ _committedOnly: boolean,
790
+ request: () => Promise<T>,
791
+ ): Promise<T> {
792
+ if (!this.open) {
793
+ throw new Error('IPC instance is closed');
794
+ }
795
+ return await this.send(operation, request);
796
+ }
684
797
 
685
- default:
686
- throw new Error(`Unknown message type: ${messageType}`);
798
+ private async send<T>(operation: WorldStateOperationName, request: () => Promise<T>): Promise<T> {
799
+ const start = performance.now();
800
+ try {
801
+ const response = await request();
802
+ const durationMs = performance.now() - start;
803
+ this.log.trace(`Call ${operation} took (ms)`, { duration: durationMs });
804
+ this.instrumentation.recordRoundTrip(durationMs * 1000, operation);
805
+ return response;
806
+ } catch (error) {
807
+ this.log.error(`Call ${operation} failed: ${error}`, error);
808
+ throw error;
687
809
  }
688
810
  }
689
811
  }
690
812
 
691
813
  /**
692
814
  * Helper to create WsdbOptions from standard world state config.
693
- * Returns the options needed to construct a WsdbBackend.
815
+ * Returns the options needed to construct a wsdb service command line.
694
816
  */
695
817
  export function getWsdbOptions(
696
818
  dataDir: string,