@aztec/world-state 0.0.1-commit.2f68f620 → 0.0.1-commit.3100065

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dest/instrumentation/instrumentation.d.ts +3 -4
  2. package/dest/instrumentation/instrumentation.d.ts.map +1 -1
  3. package/dest/instrumentation/instrumentation.js +9 -4
  4. package/dest/native/ipc_world_state_instance.d.ts +22 -59
  5. package/dest/native/ipc_world_state_instance.d.ts.map +1 -1
  6. package/dest/native/ipc_world_state_instance.js +495 -520
  7. package/dest/native/merkle_trees_facade.d.ts +3 -4
  8. package/dest/native/merkle_trees_facade.d.ts.map +1 -1
  9. package/dest/native/merkle_trees_facade.js +109 -37
  10. package/dest/native/message.d.ts +233 -14
  11. package/dest/native/message.d.ts.map +1 -1
  12. package/dest/native/message.js +42 -0
  13. package/dest/native/native_world_state.d.ts +28 -14
  14. package/dest/native/native_world_state.d.ts.map +1 -1
  15. package/dest/native/native_world_state.js +102 -101
  16. package/dest/native/native_world_state_instance.d.ts +42 -58
  17. package/dest/native/native_world_state_instance.d.ts.map +1 -1
  18. package/dest/native/native_world_state_instance.js +222 -5
  19. package/dest/native/world_state_ops_queue.d.ts +19 -0
  20. package/dest/native/world_state_ops_queue.d.ts.map +1 -0
  21. package/dest/native/world_state_ops_queue.js +146 -0
  22. package/dest/synchronizer/errors.d.ts +8 -1
  23. package/dest/synchronizer/errors.d.ts.map +1 -1
  24. package/dest/synchronizer/errors.js +8 -1
  25. package/dest/synchronizer/factory.d.ts +1 -1
  26. package/dest/synchronizer/factory.d.ts.map +1 -1
  27. package/dest/synchronizer/factory.js +1 -1
  28. package/dest/synchronizer/index.d.ts +2 -1
  29. package/dest/synchronizer/index.d.ts.map +1 -1
  30. package/dest/synchronizer/index.js +1 -0
  31. package/dest/synchronizer/server_world_state_synchronizer.d.ts +11 -6
  32. package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
  33. package/dest/synchronizer/server_world_state_synchronizer.js +55 -43
  34. package/dest/test/utils.d.ts +1 -1
  35. package/dest/test/utils.d.ts.map +1 -1
  36. package/dest/test/utils.js +6 -1
  37. package/dest/testing.d.ts +2 -2
  38. package/dest/testing.d.ts.map +1 -1
  39. package/dest/testing.js +15 -4
  40. package/package.json +10 -12
  41. package/src/instrumentation/instrumentation.ts +18 -6
  42. package/src/native/ipc_world_state_instance.ts +503 -608
  43. package/src/native/merkle_trees_facade.ts +104 -59
  44. package/src/native/message.ts +300 -13
  45. package/src/native/native_world_state.ts +156 -121
  46. package/src/native/native_world_state_instance.ts +307 -96
  47. package/src/native/world_state_ops_queue.ts +190 -0
  48. package/src/synchronizer/errors.ts +8 -0
  49. package/src/synchronizer/factory.ts +1 -0
  50. package/src/synchronizer/index.ts +1 -0
  51. package/src/synchronizer/server_world_state_synchronizer.ts +63 -30
  52. package/src/test/utils.ts +10 -1
  53. package/src/testing.ts +14 -4
  54. package/dest/native/world_state_operation.d.ts +0 -7
  55. package/dest/native/world_state_operation.d.ts.map +0 -1
  56. package/dest/native/world_state_operation.js +0 -5
  57. package/src/native/world_state_operation.ts +0 -33
@@ -1,139 +1,38 @@
1
+ import { AsyncApi } from '@aztec/bb.js/aztec-wsdb';
1
2
  import { ARCHIVE_HEIGHT, DomainSeparator, L1_TO_L2_MSG_TREE_HEIGHT, MAX_NULLIFIERS_PER_TX, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NOTE_HASH_TREE_HEIGHT, NULLIFIER_TREE_HEIGHT, PUBLIC_DATA_TREE_HEIGHT } from '@aztec/constants';
2
3
  import { createLogger } from '@aztec/foundation/log';
3
4
  import { MerkleTreeId } from '@aztec/stdlib/trees';
4
- import { WsdbService } from '@aztec/wsdb';
5
- import { cpus } from 'node:os';
5
+ import assert from 'assert';
6
+ import { Decoder, Encoder } from 'msgpackr';
7
+ import { WorldStateMessageType, isWithCanonical, isWithForkId, isWithRevision } from './message.js';
8
+ import { WorldStateOpsQueue } from './world_state_ops_queue.js';
9
+ // ————— Msgpack helpers —————
10
+ const msgpackEncoder = new Encoder({
11
+ useRecords: false
12
+ });
13
+ const msgpackDecoder = new Decoder({
14
+ useRecords: false
15
+ });
16
+ /** Msgpack-encode a SerializedLeafValue into bytes for IPC transport. */ function serializeLeafToBytes(leaf) {
17
+ return Buffer.from(msgpackEncoder.pack(leaf));
18
+ }
6
19
  // ————— Request conversion helpers —————
7
20
  function toWsdbRevision(rev) {
8
21
  return {
9
- forkId: rev.forkId,
10
- blockNumber: Number(rev.blockNumber),
11
- includeUncommitted: rev.includeUncommitted
22
+ forkid: rev.forkId,
23
+ blocknumber: Number(rev.blockNumber),
24
+ includeuncommitted: rev.includeUncommitted
12
25
  };
13
26
  }
14
27
  function blockStateRefToMap(ref) {
15
- return [
16
- ...ref.entries()
17
- ].map(([treeId, [root, size]])=>({
18
- treeId,
19
- root: new Uint8Array(root),
20
- size: Number(size)
21
- }));
22
- }
23
- function toPublicDataLeaf(leaf) {
24
- if (leaf instanceof Buffer || !('slot' in leaf)) {
25
- throw new Error('Expected public data leaf');
26
- }
27
- return {
28
- slot: new Uint8Array(leaf.slot),
29
- value: new Uint8Array(leaf.value)
30
- };
31
- }
32
- function toNullifierLeaf(leaf) {
33
- if (leaf instanceof Buffer || !('nullifier' in leaf)) {
34
- throw new Error('Expected nullifier leaf');
35
- }
36
- return {
37
- nullifier: new Uint8Array(leaf.nullifier)
38
- };
39
- }
40
- function fromPublicDataLeaf(leaf) {
41
- return {
42
- slot: Buffer.from(leaf.slot),
43
- value: Buffer.from(leaf.value)
44
- };
45
- }
46
- function fromNullifierLeaf(leaf) {
47
- return {
48
- nullifier: Buffer.from(leaf.nullifier)
49
- };
50
- }
51
- function fromIndexedLeaf(leaf, convertLeaf) {
52
- return {
53
- leaf: convertLeaf(leaf.leaf),
54
- nextIndex: leaf.nextIndex,
55
- nextKey: Buffer.from(leaf.nextKey)
56
- };
57
- }
58
- function fromLeafUpdateWitnessData(data, convertLeaf) {
59
- return {
60
- leaf: fromIndexedLeaf(data.leaf, convertLeaf),
61
- index: data.index,
62
- path: data.path.map((p)=>Buffer.from(p))
63
- };
64
- }
65
- function fromBatchInsertionResult(result, convertLeaf) {
66
- return {
67
- lowLeafWitnessData: result.lowLeafWitnessData.map((data)=>fromLeafUpdateWitnessData(data, convertLeaf)),
68
- sortedLeaves: result.sortedLeaves.map(({ leaf, index })=>[
69
- convertLeaf(leaf),
70
- index
71
- ]),
72
- subtreePath: result.subtreePath.map((p)=>Buffer.from(p))
73
- };
74
- }
75
- function fromSequentialInsertionResult(result, convertLeaf) {
76
- return {
77
- lowLeafWitnessData: result.lowLeafWitnessData.map((data)=>fromLeafUpdateWitnessData(data, convertLeaf)),
78
- insertionWitnessData: result.insertionWitnessData.map((data)=>fromLeafUpdateWitnessData(data, convertLeaf))
79
- };
80
- }
81
- function toFrLeaf(leaf) {
82
- if (!(leaf instanceof Buffer)) {
83
- throw new Error('Expected field leaf');
28
+ const result = new Map();
29
+ for (const [treeId, [root, size]] of ref.entries()){
30
+ result.set(treeId, [
31
+ new Uint8Array(root),
32
+ Number(size)
33
+ ]);
84
34
  }
85
- return new Uint8Array(leaf);
86
- }
87
- function formatMap(map) {
88
- if (!map || Object.keys(map).length === 0) {
89
- return undefined;
90
- }
91
- return `{${Object.entries(map).map(([key, value])=>`${key}:${value}`).join(',')}}`;
92
- }
93
- /** SHM request/response ring size for the spawned aztec-wsdb process (32 MiB). */ const WSDB_SHM_RING_SIZE = 32 * 1024 * 1024;
94
- function getWsdbThreadCount() {
95
- return Math.min(16, cpus().length);
96
- }
97
- function getWsdbExtraArgs(dataDir, wsTreeMapSizes, genesis, threads) {
98
- const options = getWsdbOptions(dataDir, wsTreeMapSizes);
99
- const args = [
100
- '--data-dir',
101
- dataDir,
102
- '--threads',
103
- threads.toString()
104
- ];
105
- // Size the SHM rings generously. Responses such as batch-insertion witnesses
106
- // run to a few MB, and an SHM frame is capped at half the ring (the wrap
107
- // handling needs that headroom), so 32 MiB rings give ~16 MiB per message.
108
- // Ignored by the UDS transport. Pages are demand-faulted, so unused capacity
109
- // is cheap.
110
- args.push('--request-ring-size', WSDB_SHM_RING_SIZE.toString());
111
- args.push('--response-ring-size', WSDB_SHM_RING_SIZE.toString());
112
- const treeHeights = formatMap(options.treeHeights);
113
- if (treeHeights) {
114
- args.push('--tree-heights', treeHeights);
115
- }
116
- const treePrefill = formatMap(options.treePrefill);
117
- if (treePrefill) {
118
- args.push('--tree-prefill', treePrefill);
119
- }
120
- const mapSizes = formatMap(options.mapSizes);
121
- if (mapSizes) {
122
- args.push('--map-sizes', mapSizes);
123
- }
124
- args.push('--initial-header-generator-point', options.initialHeaderGeneratorPoint.toString());
125
- if (genesis.prefilledPublicData.length > 0) {
126
- const pairs = genesis.prefilledPublicData.map((data)=>[
127
- data.slot.toBuffer().toString('hex'),
128
- data.value.toBuffer().toString('hex')
129
- ]);
130
- args.push('--prefilled-public-data', JSON.stringify(pairs));
131
- }
132
- const genesisTimestamp = Number(genesis.genesisTimestamp);
133
- if (genesisTimestamp !== 0) {
134
- args.push('--genesis-timestamp', genesisTimestamp.toString());
135
- }
136
- return args;
35
+ return result;
137
36
  }
138
37
  // ————— Response conversion helpers —————
139
38
  /** Convert Uint8Array fields to Buffer recursively (for opaque blob responses). */ function convertUint8ArraysToBuffers(obj) {
@@ -152,49 +51,57 @@ function getWsdbExtraArgs(dataDir, wsTreeMapSizes, genesis, threads) {
152
51
  }
153
52
  return obj;
154
53
  }
54
+ /** Decode a msgpack-encoded leaf value blob and convert Uint8Arrays to Buffers. */ function decodeLeafValue(encoded) {
55
+ const decoded = msgpackDecoder.unpack(Buffer.from(encoded));
56
+ return convertUint8ArraysToBuffers(decoded);
57
+ }
58
+ /** Decode a msgpack-encoded indexed leaf preimage blob. */ function decodeLeafPreimage(encoded) {
59
+ const decoded = msgpackDecoder.unpack(Buffer.from(encoded));
60
+ return convertUint8ArraysToBuffers(decoded);
61
+ }
155
62
  /** Convert Wsdb state reference (Record<number, [Uint8Array, number]>) to NAPI format. */ function convertStateRef(state) {
156
63
  const result = {};
157
- for (const { treeId, root, size } of state){
158
- result[treeId] = [
64
+ for (const [key, [root, size]] of Object.entries(state)){
65
+ result[Number(key)] = [
159
66
  Buffer.from(root),
160
67
  BigInt(size)
161
68
  ];
162
69
  }
163
70
  return result;
164
71
  }
165
- /** Convert Wsdb WorldStateStatusSummary to the native world-state format. */ function convertStatusSummary(s) {
72
+ /** Convert Wsdb WorldStateStatusSummary (lowercase) to NAPI format (camelCase). */ function convertStatusSummary(s) {
166
73
  return {
167
- unfinalizedBlockNumber: s.unfinalizedBlockNumber,
168
- finalizedBlockNumber: s.finalizedBlockNumber,
169
- oldestHistoricalBlock: s.oldestHistoricalBlock,
170
- treesAreSynched: s.treesAreSynched
74
+ unfinalizedBlockNumber: s.unfinalizedblocknumber,
75
+ finalizedBlockNumber: s.finalizedblocknumber,
76
+ oldestHistoricalBlock: s.oldesthistoricalblock,
77
+ treesAreSynched: s.treesaresynched
171
78
  };
172
79
  }
173
80
  function convertDBStats(s) {
174
81
  return {
175
82
  name: s.name,
176
- numDataItems: s.numDataItems,
177
- totalUsedSize: s.totalUsedSize
83
+ numDataItems: s.numdataitems,
84
+ totalUsedSize: s.totalusedsize
178
85
  };
179
86
  }
180
87
  function convertTreeDBStats(s) {
181
88
  return {
182
- mapSize: s.mapSize,
183
- physicalFileSize: s.physicalFileSize,
184
- blocksDBStats: convertDBStats(s.blocksDBStats),
185
- nodesDBStats: convertDBStats(s.nodesDBStats),
186
- leafPreimagesDBStats: convertDBStats(s.leafPreimagesDBStats),
187
- leafIndicesDBStats: convertDBStats(s.leafIndicesDBStats),
188
- blockIndicesDBStats: convertDBStats(s.blockIndicesDBStats)
89
+ mapSize: s.mapsize,
90
+ physicalFileSize: s.physicalfilesize,
91
+ blocksDBStats: convertDBStats(s.blocksdbstats),
92
+ nodesDBStats: convertDBStats(s.nodesdbstats),
93
+ leafPreimagesDBStats: convertDBStats(s.leafpreimagesdbstats),
94
+ leafIndicesDBStats: convertDBStats(s.leafindicesdbstats),
95
+ blockIndicesDBStats: convertDBStats(s.blockindicesdbstats)
189
96
  };
190
97
  }
191
98
  function convertWorldStateDBStats(s) {
192
99
  return {
193
- noteHashTreeStats: convertTreeDBStats(s.noteHashTreeStats),
194
- messageTreeStats: convertTreeDBStats(s.messageTreeStats),
195
- archiveTreeStats: convertTreeDBStats(s.archiveTreeStats),
196
- publicDataTreeStats: convertTreeDBStats(s.publicDataTreeStats),
197
- nullifierTreeStats: convertTreeDBStats(s.nullifierTreeStats)
100
+ noteHashTreeStats: convertTreeDBStats(s.notehashtreestats),
101
+ messageTreeStats: convertTreeDBStats(s.messagetreestats),
102
+ archiveTreeStats: convertTreeDBStats(s.archivetreestats),
103
+ publicDataTreeStats: convertTreeDBStats(s.publicdatatreestats),
104
+ nullifierTreeStats: convertTreeDBStats(s.nullifiertreestats)
198
105
  };
199
106
  }
200
107
  function convertTreeMeta(m) {
@@ -202,28 +109,28 @@ function convertTreeMeta(m) {
202
109
  name: m.name,
203
110
  depth: m.depth,
204
111
  size: m.size,
205
- committedSize: m.committedSize,
112
+ committedSize: m.committedsize,
206
113
  root: m.root,
207
- initialSize: m.initialSize,
208
- initialRoot: m.initialRoot,
209
- oldestHistoricBlock: m.oldestHistoricBlock,
210
- unfinalizedBlockHeight: m.unfinalizedBlockHeight,
211
- finalizedBlockHeight: m.finalizedBlockHeight
114
+ initialSize: m.initialsize,
115
+ initialRoot: m.initialroot,
116
+ oldestHistoricBlock: m.oldesthistoricblock,
117
+ unfinalizedBlockHeight: m.unfinalizedblockheight,
118
+ finalizedBlockHeight: m.finalizedblockheight
212
119
  };
213
120
  }
214
121
  function convertWorldStateMeta(m) {
215
122
  return {
216
- noteHashTreeMeta: convertTreeMeta(m.noteHashTreeMeta),
217
- messageTreeMeta: convertTreeMeta(m.messageTreeMeta),
218
- archiveTreeMeta: convertTreeMeta(m.archiveTreeMeta),
219
- publicDataTreeMeta: convertTreeMeta(m.publicDataTreeMeta),
220
- nullifierTreeMeta: convertTreeMeta(m.nullifierTreeMeta)
123
+ noteHashTreeMeta: convertTreeMeta(m.notehashtreemeta),
124
+ messageTreeMeta: convertTreeMeta(m.messagetreemeta),
125
+ archiveTreeMeta: convertTreeMeta(m.archivetreemeta),
126
+ publicDataTreeMeta: convertTreeMeta(m.publicdatatreemeta),
127
+ nullifierTreeMeta: convertTreeMeta(m.nullifiertreemeta)
221
128
  };
222
129
  }
223
130
  function convertStatusFull(s) {
224
131
  return {
225
132
  summary: convertStatusSummary(s.summary),
226
- dbStats: convertWorldStateDBStats(s.dbStats),
133
+ dbStats: convertWorldStateDBStats(s.dbstats),
227
134
  meta: convertWorldStateMeta(s.meta)
228
135
  };
229
136
  }
@@ -236,405 +143,473 @@ function convertStatusFull(s) {
236
143
  path: s.path.map((p)=>Buffer.from(p))
237
144
  };
238
145
  }
239
- // ————— Public API —————
240
146
  /**
241
147
  * IPC-backed world state instance.
242
- * Uses WsdbService (spawns aztec-wsdb binary) and the generated AsyncApi
148
+ * Uses WsdbBackend (spawns aztec-wsdb binary) and the generated AsyncApi
243
149
  * to communicate via the NamedUnion IPC protocol.
244
150
  */ export class IpcWorldState {
245
- wsdb;
151
+ wsdbBackend;
246
152
  instrumentation;
247
153
  log;
248
154
  open;
155
+ queues;
249
156
  api;
250
157
  /** Tracks checkpoint depth per fork (WSDB IPC doesn't return depth in response). */ checkpointDepths;
251
- constructor(wsdb, instrumentation, bindings, log = createLogger('world-state:ipc-database', bindings)){
252
- this.wsdb = wsdb;
158
+ constructor(wsdbBackend, instrumentation, bindings, log = createLogger('world-state:ipc-database', bindings)){
159
+ this.wsdbBackend = wsdbBackend;
253
160
  this.instrumentation = instrumentation;
254
161
  this.log = log;
255
162
  this.open = true;
163
+ this.queues = new Map();
256
164
  this.checkpointDepths = new Map();
257
- this.api = wsdb;
165
+ this.api = new AsyncApi(wsdbBackend);
166
+ this.queues.set(0, new WorldStateOpsQueue());
258
167
  this.log.info('Created IPC-backed world state instance');
259
168
  }
169
+ /** Returns the socket path of the underlying wsdb server. */ getSocketPath() {
170
+ return this.wsdbBackend.getSocketPath();
171
+ }
260
172
  /**
261
- * Spawn an `aztec-wsdb` subprocess and return an IPC-backed world state wrapping it.
262
- * Encapsulates wsdb binary discovery, service construction, and readiness wait.
263
- */ static async spawn(dataDir, wsTreeMapSizes, genesis, instrumentation, bindings) {
264
- const threads = getWsdbThreadCount();
265
- const transport = process.env.WSDB_TRANSPORT === 'shm' ? 'shm' : 'uds';
266
- const wsdb = await WsdbService.spawn({
267
- transport,
268
- extraArgs: getWsdbExtraArgs(dataDir, wsTreeMapSizes, genesis, threads),
269
- env: {
270
- HARDWARE_CONCURRENCY: threads.toString()
173
+ * Required by `NativeWorldStateInstance` for compatibility with the in-process
174
+ * NAPI path. The IPC backend does not expose an in-process pointer; callers that
175
+ * need to reach the WSDB process must use {@link getSocketPath} instead.
176
+ */ getHandle() {
177
+ throw new Error('IpcWorldState has no in-process handle; use getSocketPath() instead');
178
+ }
179
+ async call(messageType, body, responseHandler = (response)=>response, errorHandler = (_)=>{}) {
180
+ let forkId = -1;
181
+ let committedOnly = false;
182
+ if (isWithCanonical(body)) {
183
+ forkId = 0;
184
+ } else if (isWithForkId(body)) {
185
+ forkId = body.forkId;
186
+ } else if (isWithRevision(body)) {
187
+ forkId = body.revision.forkId;
188
+ committedOnly = body.revision.includeUncommitted === false;
189
+ } else {
190
+ const _ = body;
191
+ throw new Error(`Unable to determine forkId for message=${WorldStateMessageType[messageType]}`);
192
+ }
193
+ let requestQueue = this.queues.get(forkId);
194
+ if (requestQueue === undefined) {
195
+ requestQueue = new WorldStateOpsQueue();
196
+ this.queues.set(forkId, requestQueue);
197
+ }
198
+ // The per-fork queue is cleaned up in `finally` even on error, so the JS-side queues map cannot outlive
199
+ // the native fork (e.g. when the native fork was already destroyed by an unwind/historical-prune and
200
+ // DELETE_FORK rejects with "Fork not found").
201
+ let shouldDeleteForkQueue = false;
202
+ try {
203
+ const response = await requestQueue.execute(async ()=>{
204
+ assert.notEqual(messageType, WorldStateMessageType.CLOSE, 'Use close() to close the IPC instance');
205
+ assert.equal(this.open, true, 'IPC instance is closed');
206
+ let response;
207
+ try {
208
+ response = await this._sendMessage(messageType, body);
209
+ } catch (error) {
210
+ errorHandler(error.message);
211
+ throw error;
212
+ }
213
+ return responseHandler(response);
214
+ }, messageType, committedOnly);
215
+ return response;
216
+ } catch (err) {
217
+ shouldDeleteForkQueue = forkId !== 0 && err?.message === 'Fork not found';
218
+ throw err;
219
+ } finally{
220
+ if (messageType === WorldStateMessageType.DELETE_FORK || shouldDeleteForkQueue) {
221
+ await requestQueue.stop();
222
+ this.queues.delete(forkId);
271
223
  }
272
- });
273
- return new IpcWorldState(wsdb, instrumentation, bindings);
274
- }
275
- getIpcPath() {
276
- return this.wsdb.getIpcPath();
224
+ }
277
225
  }
278
226
  async close() {
279
227
  if (!this.open) {
280
228
  return;
281
229
  }
282
230
  this.open = false;
283
- await this.wsdb.destroy();
284
- }
285
- getTreeInfo(treeId, revision) {
286
- return this.execute('getTreeInfo', revision.forkId, revision.includeUncommitted === false, async ()=>{
287
- const resp = await this.api.getTreeInfo({
288
- treeId,
289
- revision: toWsdbRevision(revision)
290
- });
291
- return {
292
- treeId: resp.treeId,
293
- root: Buffer.from(resp.root),
294
- size: BigInt(resp.size),
295
- depth: resp.depth
296
- };
297
- });
298
- }
299
- getStateReference(revision) {
300
- return this.execute('getStateReference', revision.forkId, revision.includeUncommitted === false, async ()=>{
301
- const resp = await this.api.getStateReference({
302
- revision: toWsdbRevision(revision)
303
- });
304
- return convertStateRef(resp.state);
305
- });
306
- }
307
- getInitialStateReference() {
308
- return this.execute('getInitialStateReference', 0, false, async ()=>{
309
- const resp = await this.api.getInitialStateReference({});
310
- return convertStateRef(resp.state);
311
- });
312
- }
313
- getLeafValue(treeId, revision, leafIndex) {
314
- return this.execute('getLeafValue', revision.forkId, revision.includeUncommitted === false, async ()=>{
315
- const wsdbRevision = toWsdbRevision(revision);
316
- const wsdbLeafIndex = Number(leafIndex);
317
- if (treeId === MerkleTreeId.PUBLIC_DATA_TREE) {
318
- const resp = await this.api.getPublicDataLeafValue({
319
- revision: wsdbRevision,
320
- leafIndex: wsdbLeafIndex
321
- });
322
- return resp.value ? fromPublicDataLeaf(resp.value) : undefined;
323
- }
324
- if (treeId === MerkleTreeId.NULLIFIER_TREE) {
325
- const resp = await this.api.getNullifierLeafValue({
326
- revision: wsdbRevision,
327
- leafIndex: wsdbLeafIndex
328
- });
329
- return resp.value ? fromNullifierLeaf(resp.value) : undefined;
330
- }
331
- const resp = await this.api.getLeafValue({
332
- treeId,
333
- revision: wsdbRevision,
334
- leafIndex: wsdbLeafIndex
335
- });
336
- return resp.value ? Buffer.from(resp.value) : undefined;
337
- });
338
- }
339
- getLeafPreimage(treeId, revision, leafIndex) {
340
- return this.execute('getLeafPreimage', revision.forkId, revision.includeUncommitted === false, async ()=>{
341
- const resp = treeId === MerkleTreeId.PUBLIC_DATA_TREE ? await this.api.getPublicDataLeafPreimage({
342
- revision: toWsdbRevision(revision),
343
- leafIndex: Number(leafIndex)
344
- }) : await this.api.getNullifierLeafPreimage({
345
- revision: toWsdbRevision(revision),
346
- leafIndex: Number(leafIndex)
347
- });
348
- return resp.preimage ? convertUint8ArraysToBuffers(resp.preimage) : undefined;
349
- });
350
- }
351
- getSiblingPath(treeId, revision, leafIndex) {
352
- return this.execute('getSiblingPath', revision.forkId, revision.includeUncommitted === false, async ()=>{
353
- const resp = await this.api.getSiblingPath({
354
- treeId,
355
- revision: toWsdbRevision(revision),
356
- leafIndex: Number(leafIndex)
357
- });
358
- return resp.path.map((p)=>Buffer.from(p));
359
- });
360
- }
361
- getBlockNumbersForLeafIndices(treeId, revision, leafIndices) {
362
- return this.execute('getBlockNumbersForLeafIndices', revision.forkId, revision.includeUncommitted === false, async ()=>{
363
- const resp = await this.api.getBlockNumbersForLeafIndices({
364
- treeId,
365
- revision: toWsdbRevision(revision),
366
- leafIndices: leafIndices.map(Number)
367
- });
368
- return resp.blockNumbers.map((n)=>n != null ? BigInt(n) : undefined);
369
- });
370
- }
371
- findLeafIndices(treeId, revision, leaves, startIndex) {
372
- return this.execute('findLeafIndices', revision.forkId, revision.includeUncommitted === false, async ()=>{
373
- const wsdbRevision = toWsdbRevision(revision);
374
- const wsdbStartIndex = Number(startIndex);
375
- const resp = treeId === MerkleTreeId.PUBLIC_DATA_TREE ? await this.api.findPublicDataLeafIndices({
376
- revision: wsdbRevision,
377
- leaves: leaves.map(toPublicDataLeaf),
378
- startIndex: wsdbStartIndex
379
- }) : treeId === MerkleTreeId.NULLIFIER_TREE ? await this.api.findNullifierLeafIndices({
380
- revision: wsdbRevision,
381
- leaves: leaves.map(toNullifierLeaf),
382
- startIndex: wsdbStartIndex
383
- }) : await this.api.findLeafIndices({
384
- treeId,
385
- revision: wsdbRevision,
386
- leaves: leaves.map(toFrLeaf),
387
- startIndex: wsdbStartIndex
388
- });
389
- return resp.indices.map((n)=>n != null ? BigInt(n) : undefined);
390
- });
391
- }
392
- findLowLeaf(treeId, revision, key) {
393
- return this.execute('findLowLeaf', revision.forkId, revision.includeUncommitted === false, async ()=>{
394
- const resp = await this.api.findLowLeaf({
395
- treeId,
396
- revision: toWsdbRevision(revision),
397
- key: new Uint8Array(key.toBuffer())
398
- });
399
- return {
400
- alreadyPresent: resp.alreadyPresent,
401
- index: BigInt(resp.index)
402
- };
403
- });
404
- }
405
- findSiblingPaths(treeId, revision, leaves) {
406
- return this.execute('findSiblingPaths', revision.forkId, revision.includeUncommitted === false, async ()=>{
407
- const wsdbRevision = toWsdbRevision(revision);
408
- const resp = treeId === MerkleTreeId.PUBLIC_DATA_TREE ? await this.api.findPublicDataSiblingPaths({
409
- revision: wsdbRevision,
410
- leaves: leaves.map(toPublicDataLeaf)
411
- }) : treeId === MerkleTreeId.NULLIFIER_TREE ? await this.api.findNullifierSiblingPaths({
412
- revision: wsdbRevision,
413
- leaves: leaves.map(toNullifierLeaf)
414
- }) : await this.api.findSiblingPaths({
415
- treeId,
416
- revision: wsdbRevision,
417
- leaves: leaves.map(toFrLeaf)
418
- });
419
- return resp.paths.map(convertSiblingPathAndIndex);
420
- });
421
- }
422
- updateArchive(forkId, blockStateRef, blockHeaderHash) {
423
- return this.execute('updateArchive', forkId, false, async ()=>{
424
- await this.api.updateArchive({
425
- blockStateRef: blockStateRefToMap(blockStateRef),
426
- blockHeaderHash: new Uint8Array(blockHeaderHash),
427
- forkId
428
- });
429
- });
430
- }
431
- appendLeaves(treeId, forkId, leaves) {
432
- return this.execute('appendLeaves', forkId, false, async ()=>{
433
- if (treeId === MerkleTreeId.PUBLIC_DATA_TREE) {
434
- await this.api.appendPublicDataLeaves({
435
- leaves: leaves.map(toPublicDataLeaf),
436
- forkId
437
- });
438
- } else if (treeId === MerkleTreeId.NULLIFIER_TREE) {
439
- await this.api.appendNullifierLeaves({
440
- leaves: leaves.map(toNullifierLeaf),
441
- forkId
442
- });
443
- } else {
444
- await this.api.appendLeaves({
445
- treeId,
446
- leaves: leaves.map(toFrLeaf),
447
- forkId
448
- });
449
- }
450
- });
451
- }
452
- batchInsert(treeId, forkId, leaves, subtreeDepth) {
453
- return this.execute('batchInsert', forkId, false, async ()=>{
454
- const resp = treeId === MerkleTreeId.PUBLIC_DATA_TREE ? await this.api.batchInsertPublicData({
455
- leaves: leaves.map(toPublicDataLeaf),
456
- subtreeDepth,
457
- forkId
458
- }) : await this.api.batchInsertNullifier({
459
- leaves: leaves.map(toNullifierLeaf),
460
- subtreeDepth,
461
- forkId
462
- });
463
- return treeId === MerkleTreeId.PUBLIC_DATA_TREE ? fromBatchInsertionResult(resp.result, fromPublicDataLeaf) : fromBatchInsertionResult(resp.result, fromNullifierLeaf);
464
- });
465
- }
466
- sequentialInsert(treeId, forkId, leaves) {
467
- return this.execute('sequentialInsert', forkId, false, async ()=>{
468
- const resp = treeId === MerkleTreeId.PUBLIC_DATA_TREE ? await this.api.sequentialInsertPublicData({
469
- leaves: leaves.map(toPublicDataLeaf),
470
- forkId
471
- }) : await this.api.sequentialInsertNullifier({
472
- leaves: leaves.map(toNullifierLeaf),
473
- forkId
474
- });
475
- return treeId === MerkleTreeId.PUBLIC_DATA_TREE ? fromSequentialInsertionResult(resp.result, fromPublicDataLeaf) : fromSequentialInsertionResult(resp.result, fromNullifierLeaf);
476
- });
477
- }
478
- syncBlock(input) {
479
- return this.execute('syncBlock', 0, false, async ()=>{
480
- const resp = await this.api.syncBlock({
481
- blockNumber: Number(input.blockNumber),
482
- blockStateRef: blockStateRefToMap(input.blockStateRef),
483
- blockHeaderHash: new Uint8Array(input.blockHeaderHash),
484
- paddedNoteHashes: input.paddedNoteHashes.map(toFrLeaf),
485
- paddedL1ToL2Messages: input.paddedL1ToL2Messages.map(toFrLeaf),
486
- paddedNullifiers: input.paddedNullifiers.map(toNullifierLeaf),
487
- publicDataWrites: input.publicDataWrites.map(toPublicDataLeaf)
488
- });
489
- return convertStatusFull(resp.status);
490
- });
491
- }
492
- createFork(input) {
493
- return this.execute('createFork', 0, false, async ()=>{
494
- const resp = await this.api.createFork({
495
- latest: input.latest,
496
- blockNumber: Number(input.blockNumber)
497
- });
498
- return resp.forkId;
499
- });
500
- }
501
- deleteFork(forkId) {
502
- return this.execute('deleteFork', forkId, false, async ()=>{
503
- await this.api.deleteFork({
504
- forkId
505
- });
506
- });
507
- }
508
- finalizeBlocks(toBlockNumber) {
509
- return this.execute('finalizeBlocks', 0, false, async ()=>{
510
- const resp = await this.api.finalizeBlocks({
511
- toBlockNumber: Number(toBlockNumber)
512
- });
513
- return convertStatusSummary(resp.status);
514
- });
515
- }
516
- unwindBlocks(toBlockNumber) {
517
- return this.execute('unwindBlocks', 0, false, async ()=>{
518
- const resp = await this.api.unwindBlocks({
519
- toBlockNumber: Number(toBlockNumber)
520
- });
521
- return convertStatusFull(resp.status);
522
- });
523
- }
524
- removeHistoricalBlocks(toBlockNumber) {
525
- return this.execute('removeHistoricalBlocks', 0, false, async ()=>{
526
- const resp = await this.api.removeHistoricalBlocks({
527
- toBlockNumber: Number(toBlockNumber)
528
- });
529
- return convertStatusFull(resp.status);
530
- });
531
- }
532
- getStatus() {
533
- return this.execute('getStatus', 0, false, async ()=>{
534
- const resp = await this.api.getStatus({});
535
- return convertStatusSummary(resp.status);
536
- });
537
- }
538
- createCheckpoint(forkId) {
539
- return this.execute('createCheckpoint', forkId, false, async ()=>{
540
- await this.api.createCheckpoint({
541
- forkId
542
- });
543
- const depth = (this.checkpointDepths.get(forkId) ?? 0) + 1;
544
- this.checkpointDepths.set(forkId, depth);
545
- return depth;
546
- });
547
- }
548
- commitCheckpoint(forkId) {
549
- return this.execute('commitCheckpoint', forkId, false, async ()=>{
550
- await this.api.commitCheckpoint({
551
- forkId
552
- });
553
- const depth = Math.max(0, (this.checkpointDepths.get(forkId) ?? 0) - 1);
554
- this.checkpointDepths.set(forkId, depth);
555
- });
556
- }
557
- revertCheckpoint(forkId) {
558
- return this.execute('revertCheckpoint', forkId, false, async ()=>{
559
- await this.api.revertCheckpoint({
560
- forkId
561
- });
562
- const depth = Math.max(0, (this.checkpointDepths.get(forkId) ?? 0) - 1);
563
- this.checkpointDepths.set(forkId, depth);
564
- });
565
- }
566
- commitAllCheckpoints(forkId, depth) {
567
- return this.execute('commitAllCheckpoints', forkId, false, async ()=>{
568
- const currentDepth = this.checkpointDepths.get(forkId) ?? 0;
569
- if (depth === 0) {
570
- await this.api.commitAllCheckpoints({
571
- forkId
572
- });
573
- } else {
574
- for(let d = currentDepth; d > depth; d--){
575
- await this.api.commitCheckpoint({
576
- forkId
577
- });
578
- }
579
- }
580
- this.checkpointDepths.set(forkId, depth);
581
- });
582
- }
583
- revertAllCheckpoints(forkId, depth) {
584
- return this.execute('revertAllCheckpoints', forkId, false, async ()=>{
585
- const currentDepth = this.checkpointDepths.get(forkId) ?? 0;
586
- if (depth === 0) {
587
- await this.api.revertAllCheckpoints({
588
- forkId
589
- });
590
- } else {
591
- for(let d = currentDepth; d > depth; d--){
592
- await this.api.revertCheckpoint({
593
- forkId
594
- });
595
- }
596
- }
597
- this.checkpointDepths.set(forkId, depth);
598
- });
599
- }
600
- copyStores(dstPath, compact) {
601
- return this.execute('copyStores', 0, false, async ()=>{
602
- await this.api.copyStores({
603
- dstPath,
604
- compact
605
- });
606
- });
607
- }
608
- // Read/write ordering is enforced server-side by the wsdb per-fork scheduler,
609
- // so the client no longer queues: every op is sent immediately and the server
610
- // serializes writes per fork while running reads concurrently. `_forkId` and
611
- // `_committedOnly` are retained in the signature (they describe the op) but are
612
- // no longer needed for client-side ordering.
613
- async execute(operation, _forkId, _committedOnly, request) {
614
- if (!this.open) {
615
- throw new Error('IPC instance is closed');
231
+ const queue = this.queues.get(0);
232
+ // Send shutdown command. Under normal operation, the WSDB process sends its
233
+ // response before exiting (via ShutdownRequested in ipc_server.hpp). The
234
+ // try/catch is defensive: if the process is killed externally (SIGKILL, OOM)
235
+ // before responding, the pending IPC callback would be rejected by the socket
236
+ // close handler. We proceed to destroy the backend regardless.
237
+ try {
238
+ await queue.execute(async ()=>{
239
+ await this.api.wsdbShutdown({});
240
+ }, WorldStateMessageType.CLOSE, false);
241
+ } catch (err) {
242
+ this.log.debug(`wsdbShutdown completed with error: ${err.message}`);
243
+ }
244
+ await queue.stop();
245
+ if (this.wsdbBackend.destroy) {
246
+ await this.wsdbBackend.destroy();
616
247
  }
617
- return await this.send(operation, request);
618
248
  }
619
- async send(operation, request) {
249
+ async _sendMessage(messageType, body) {
620
250
  const start = performance.now();
621
251
  try {
622
- const response = await request();
252
+ const response = await this.dispatch(messageType, body);
623
253
  const durationMs = performance.now() - start;
624
- this.log.trace(`Call ${operation} took (ms)`, {
254
+ this.log.trace(`Call ${WorldStateMessageType[messageType]} took (ms)`, {
625
255
  duration: durationMs
626
256
  });
627
- this.instrumentation.recordRoundTrip(durationMs * 1000, operation);
257
+ this.instrumentation.recordRoundTrip(durationMs * 1000, messageType);
628
258
  return response;
629
259
  } catch (error) {
630
- this.log.error(`Call ${operation} failed: ${error}`, error);
260
+ this.log.error(`Call ${WorldStateMessageType[messageType]} failed: ${error}`, error);
631
261
  throw error;
632
262
  }
633
263
  }
264
+ async dispatch(messageType, body) {
265
+ switch(messageType){
266
+ // ——— Tree info & state reference ———
267
+ case WorldStateMessageType.GET_TREE_INFO:
268
+ {
269
+ const b = body;
270
+ const resp = await this.api.wsdbGetTreeInfo({
271
+ treeid: b.treeId,
272
+ revision: toWsdbRevision(b.revision)
273
+ });
274
+ return {
275
+ treeId: resp.treeid,
276
+ root: Buffer.from(resp.root),
277
+ size: resp.size,
278
+ depth: resp.depth
279
+ };
280
+ }
281
+ case WorldStateMessageType.GET_STATE_REFERENCE:
282
+ {
283
+ const b = body;
284
+ const resp = await this.api.wsdbGetStateReference({
285
+ revision: toWsdbRevision(b.revision)
286
+ });
287
+ return {
288
+ state: convertStateRef(resp.state)
289
+ };
290
+ }
291
+ case WorldStateMessageType.GET_INITIAL_STATE_REFERENCE:
292
+ {
293
+ const resp = await this.api.wsdbGetInitialStateReference({});
294
+ return {
295
+ state: convertStateRef(resp.state)
296
+ };
297
+ }
298
+ // ——— Leaf queries ———
299
+ case WorldStateMessageType.GET_LEAF_VALUE:
300
+ {
301
+ const b = body;
302
+ const resp = await this.api.wsdbGetLeafValue({
303
+ treeid: b.treeId,
304
+ revision: toWsdbRevision(b.revision),
305
+ leafindex: Number(b.leafIndex)
306
+ });
307
+ if (!resp.value) {
308
+ return undefined;
309
+ }
310
+ return decodeLeafValue(resp.value);
311
+ }
312
+ case WorldStateMessageType.GET_LEAF_PREIMAGE:
313
+ {
314
+ const b = body;
315
+ const resp = await this.api.wsdbGetLeafPreimage({
316
+ treeid: b.treeId,
317
+ revision: toWsdbRevision(b.revision),
318
+ leafindex: Number(b.leafIndex)
319
+ });
320
+ if (!resp.preimage) {
321
+ return undefined;
322
+ }
323
+ return decodeLeafPreimage(resp.preimage);
324
+ }
325
+ case WorldStateMessageType.GET_SIBLING_PATH:
326
+ {
327
+ const b = body;
328
+ const resp = await this.api.wsdbGetSiblingPath({
329
+ treeid: b.treeId,
330
+ revision: toWsdbRevision(b.revision),
331
+ leafindex: Number(b.leafIndex)
332
+ });
333
+ return resp.path.map((p)=>Buffer.from(p));
334
+ }
335
+ case WorldStateMessageType.GET_BLOCK_NUMBERS_FOR_LEAF_INDICES:
336
+ {
337
+ const b = body;
338
+ const resp = await this.api.wsdbGetBlockNumbersForLeafIndices({
339
+ treeid: b.treeId,
340
+ revision: toWsdbRevision(b.revision),
341
+ leafindices: b.leafIndices.map(Number)
342
+ });
343
+ return {
344
+ blockNumbers: resp.blocknumbers.map((n)=>n != null ? BigInt(n) : undefined)
345
+ };
346
+ }
347
+ // ——— Find operations ———
348
+ case WorldStateMessageType.FIND_LEAF_INDICES:
349
+ {
350
+ const b = body;
351
+ const resp = await this.api.wsdbFindLeafIndices({
352
+ treeid: b.treeId,
353
+ revision: toWsdbRevision(b.revision),
354
+ leaves: b.leaves.map(serializeLeafToBytes),
355
+ startindex: Number(b.startIndex)
356
+ });
357
+ return {
358
+ indices: resp.indices.map((n)=>n != null ? BigInt(n) : undefined)
359
+ };
360
+ }
361
+ case WorldStateMessageType.FIND_LOW_LEAF:
362
+ {
363
+ const b = body;
364
+ const resp = await this.api.wsdbFindLowLeaf({
365
+ treeid: b.treeId,
366
+ revision: toWsdbRevision(b.revision),
367
+ key: new Uint8Array(b.key.toBuffer())
368
+ });
369
+ return {
370
+ alreadyPresent: resp.alreadypresent,
371
+ index: BigInt(resp.index)
372
+ };
373
+ }
374
+ case WorldStateMessageType.FIND_SIBLING_PATHS:
375
+ {
376
+ const b = body;
377
+ const resp = await this.api.wsdbFindSiblingPaths({
378
+ treeid: b.treeId,
379
+ revision: toWsdbRevision(b.revision),
380
+ leaves: b.leaves.map(serializeLeafToBytes)
381
+ });
382
+ return {
383
+ paths: resp.paths.map(convertSiblingPathAndIndex)
384
+ };
385
+ }
386
+ // ——— Mutations ———
387
+ case WorldStateMessageType.APPEND_LEAVES:
388
+ {
389
+ const b = body;
390
+ await this.api.wsdbAppendLeaves({
391
+ treeid: b.treeId,
392
+ leaves: b.leaves.map(serializeLeafToBytes),
393
+ forkid: b.forkId
394
+ });
395
+ return undefined;
396
+ }
397
+ case WorldStateMessageType.BATCH_INSERT:
398
+ {
399
+ const b = body;
400
+ const resp = await this.api.wsdbBatchInsert({
401
+ treeid: b.treeId,
402
+ leaves: b.leaves.map(serializeLeafToBytes),
403
+ subtreedepth: b.subtreeDepth,
404
+ forkid: b.forkId
405
+ });
406
+ const decoded = msgpackDecoder.unpack(Buffer.from(resp.result));
407
+ return convertUint8ArraysToBuffers(decoded);
408
+ }
409
+ case WorldStateMessageType.SEQUENTIAL_INSERT:
410
+ {
411
+ const b = body;
412
+ const resp = await this.api.wsdbSequentialInsert({
413
+ treeid: b.treeId,
414
+ leaves: b.leaves.map(serializeLeafToBytes),
415
+ forkid: b.forkId
416
+ });
417
+ const decoded = msgpackDecoder.unpack(Buffer.from(resp.result));
418
+ return convertUint8ArraysToBuffers(decoded);
419
+ }
420
+ case WorldStateMessageType.UPDATE_ARCHIVE:
421
+ {
422
+ const b = body;
423
+ await this.api.wsdbUpdateArchive({
424
+ blockstateref: blockStateRefToMap(b.blockStateRef),
425
+ blockheaderhash: new Uint8Array(b.blockHeaderHash),
426
+ forkid: b.forkId
427
+ });
428
+ return undefined;
429
+ }
430
+ // ——— Commit / Rollback ———
431
+ case WorldStateMessageType.COMMIT:
432
+ {
433
+ await this.api.wsdbCommit({});
434
+ return undefined;
435
+ }
436
+ case WorldStateMessageType.ROLLBACK:
437
+ {
438
+ await this.api.wsdbRollback({});
439
+ return undefined;
440
+ }
441
+ // ——— Block sync ———
442
+ case WorldStateMessageType.SYNC_BLOCK:
443
+ {
444
+ const b = body;
445
+ const resp = await this.api.wsdbSyncBlock({
446
+ blocknumber: Number(b.blockNumber),
447
+ blockstateref: blockStateRefToMap(b.blockStateRef),
448
+ blockheaderhash: new Uint8Array(b.blockHeaderHash),
449
+ // Forwarded so the wsdb (IPC) sync path enforces the same archive-root divergence check as the napi path.
450
+ expectedarchiveroot: new Uint8Array(b.expectedArchiveRoot),
451
+ expectedpreviousarchiveroot: new Uint8Array(b.expectedPreviousArchiveRoot),
452
+ paddednotehashes: b.paddedNoteHashes.map((l)=>new Uint8Array(l)),
453
+ paddedl1tol2messages: b.paddedL1ToL2Messages.map((l)=>new Uint8Array(l)),
454
+ paddednullifiers: b.paddedNullifiers.map((l)=>({
455
+ nullifier: new Uint8Array(l.nullifier)
456
+ })),
457
+ publicdatawrites: b.publicDataWrites.map((l)=>({
458
+ slot: new Uint8Array(l.slot),
459
+ value: new Uint8Array(l.value)
460
+ }))
461
+ });
462
+ return convertStatusFull(resp.status);
463
+ }
464
+ // ——— Fork management ———
465
+ case WorldStateMessageType.CREATE_FORK:
466
+ {
467
+ const b = body;
468
+ const resp = await this.api.wsdbCreateFork({
469
+ latest: b.latest,
470
+ blocknumber: Number(b.blockNumber)
471
+ });
472
+ return {
473
+ forkId: resp.forkid
474
+ };
475
+ }
476
+ case WorldStateMessageType.DELETE_FORK:
477
+ {
478
+ const b = body;
479
+ await this.api.wsdbDeleteFork({
480
+ forkid: b.forkId
481
+ });
482
+ return undefined;
483
+ }
484
+ // ——— Block finalization ———
485
+ case WorldStateMessageType.FINALIZE_BLOCKS:
486
+ {
487
+ const b = body;
488
+ const resp = await this.api.wsdbFinalizeBlocks({
489
+ toblocknumber: Number(b.toBlockNumber)
490
+ });
491
+ return convertStatusSummary(resp.status);
492
+ }
493
+ case WorldStateMessageType.UNWIND_BLOCKS:
494
+ {
495
+ const b = body;
496
+ const resp = await this.api.wsdbUnwindBlocks({
497
+ toblocknumber: Number(b.toBlockNumber)
498
+ });
499
+ return convertStatusFull(resp.status);
500
+ }
501
+ case WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS:
502
+ {
503
+ const b = body;
504
+ const resp = await this.api.wsdbRemoveHistoricalBlocks({
505
+ toblocknumber: Number(b.toBlockNumber)
506
+ });
507
+ return convertStatusFull(resp.status);
508
+ }
509
+ // ——— Status ———
510
+ case WorldStateMessageType.GET_STATUS:
511
+ {
512
+ const resp = await this.api.wsdbGetStatus({});
513
+ return convertStatusSummary(resp.status);
514
+ }
515
+ // ——— Checkpoints ———
516
+ case WorldStateMessageType.CREATE_CHECKPOINT:
517
+ {
518
+ const b = body;
519
+ await this.api.wsdbCreateCheckpoint({
520
+ forkid: b.forkId
521
+ });
522
+ const depth = (this.checkpointDepths.get(b.forkId) ?? 0) + 1;
523
+ this.checkpointDepths.set(b.forkId, depth);
524
+ return {
525
+ depth
526
+ };
527
+ }
528
+ case WorldStateMessageType.COMMIT_CHECKPOINT:
529
+ {
530
+ const b = body;
531
+ await this.api.wsdbCommitCheckpoint({
532
+ forkid: b.forkId
533
+ });
534
+ const depth = Math.max(0, (this.checkpointDepths.get(b.forkId) ?? 0) - 1);
535
+ this.checkpointDepths.set(b.forkId, depth);
536
+ return undefined;
537
+ }
538
+ case WorldStateMessageType.REVERT_CHECKPOINT:
539
+ {
540
+ const b = body;
541
+ await this.api.wsdbRevertCheckpoint({
542
+ forkid: b.forkId
543
+ });
544
+ const depth = Math.max(0, (this.checkpointDepths.get(b.forkId) ?? 0) - 1);
545
+ this.checkpointDepths.set(b.forkId, depth);
546
+ return undefined;
547
+ }
548
+ case WorldStateMessageType.COMMIT_ALL_CHECKPOINTS:
549
+ {
550
+ const b = body;
551
+ const targetDepth = b.depth ?? 0;
552
+ const currentDepth = this.checkpointDepths.get(b.forkId) ?? 0;
553
+ if (targetDepth === 0) {
554
+ // Commit everything — use the bulk operation
555
+ await this.api.wsdbCommitAllCheckpoints({
556
+ forkid: b.forkId
557
+ });
558
+ } else {
559
+ // Commit one level at a time down to target depth
560
+ for(let d = currentDepth; d > targetDepth; d--){
561
+ await this.api.wsdbCommitCheckpoint({
562
+ forkid: b.forkId
563
+ });
564
+ }
565
+ }
566
+ this.checkpointDepths.set(b.forkId, targetDepth);
567
+ return undefined;
568
+ }
569
+ case WorldStateMessageType.REVERT_ALL_CHECKPOINTS:
570
+ {
571
+ const b = body;
572
+ const targetDepth = b.depth ?? 0;
573
+ const currentDepth = this.checkpointDepths.get(b.forkId) ?? 0;
574
+ if (targetDepth === 0) {
575
+ // Revert everything — use the bulk operation
576
+ await this.api.wsdbRevertAllCheckpoints({
577
+ forkid: b.forkId
578
+ });
579
+ } else {
580
+ // Revert one level at a time down to target depth
581
+ for(let d = currentDepth; d > targetDepth; d--){
582
+ await this.api.wsdbRevertCheckpoint({
583
+ forkid: b.forkId
584
+ });
585
+ }
586
+ }
587
+ this.checkpointDepths.set(b.forkId, targetDepth);
588
+ return undefined;
589
+ }
590
+ // ——— Misc ———
591
+ case WorldStateMessageType.COPY_STORES:
592
+ {
593
+ const b = body;
594
+ await this.api.wsdbCopyStores({
595
+ dstpath: b.dstPath,
596
+ compact: b.compact
597
+ });
598
+ return undefined;
599
+ }
600
+ case WorldStateMessageType.CLOSE:
601
+ {
602
+ await this.api.wsdbShutdown({});
603
+ return undefined;
604
+ }
605
+ default:
606
+ throw new Error(`Unknown message type: ${messageType}`);
607
+ }
608
+ }
634
609
  }
635
610
  /**
636
611
  * Helper to create WsdbOptions from standard world state config.
637
- * Returns the options needed to construct a wsdb service command line.
612
+ * Returns the options needed to construct a WsdbBackend.
638
613
  */ export function getWsdbOptions(dataDir, wsTreeMapSizes) {
639
614
  return {
640
615
  treeHeights: {