@aztec/world-state 0.0.1-commit.96bb3f7 → 0.0.1-commit.993d240

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 (53) hide show
  1. package/dest/instrumentation/instrumentation.d.ts +1 -1
  2. package/dest/instrumentation/instrumentation.d.ts.map +1 -1
  3. package/dest/instrumentation/instrumentation.js +9 -2
  4. package/dest/native/fork_checkpoint.d.ts +7 -1
  5. package/dest/native/fork_checkpoint.d.ts.map +1 -1
  6. package/dest/native/fork_checkpoint.js +15 -3
  7. package/dest/native/ipc_world_state_instance.d.ts +50 -0
  8. package/dest/native/ipc_world_state_instance.d.ts.map +1 -0
  9. package/dest/native/ipc_world_state_instance.js +628 -0
  10. package/dest/native/merkle_trees_facade.d.ts +9 -6
  11. package/dest/native/merkle_trees_facade.d.ts.map +1 -1
  12. package/dest/native/merkle_trees_facade.js +45 -16
  13. package/dest/native/message.d.ts +13 -5
  14. package/dest/native/message.d.ts.map +1 -1
  15. package/dest/native/native_world_state.d.ts +34 -8
  16. package/dest/native/native_world_state.d.ts.map +1 -1
  17. package/dest/native/native_world_state.js +81 -27
  18. package/dest/native/native_world_state_instance.d.ts +6 -5
  19. package/dest/native/native_world_state_instance.d.ts.map +1 -1
  20. package/dest/native/native_world_state_instance.js +33 -26
  21. package/dest/native/world_state_ops_queue.js +5 -5
  22. package/dest/synchronizer/config.d.ts +3 -5
  23. package/dest/synchronizer/config.d.ts.map +1 -1
  24. package/dest/synchronizer/config.js +15 -18
  25. package/dest/synchronizer/factory.d.ts +6 -5
  26. package/dest/synchronizer/factory.d.ts.map +1 -1
  27. package/dest/synchronizer/factory.js +7 -6
  28. package/dest/synchronizer/server_world_state_synchronizer.d.ts +4 -5
  29. package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
  30. package/dest/synchronizer/server_world_state_synchronizer.js +109 -37
  31. package/dest/test/utils.d.ts +7 -7
  32. package/dest/test/utils.d.ts.map +1 -1
  33. package/dest/test/utils.js +5 -5
  34. package/dest/testing.d.ts +4 -3
  35. package/dest/testing.d.ts.map +1 -1
  36. package/dest/testing.js +10 -6
  37. package/dest/world-state-db/merkle_tree_db.d.ts +3 -12
  38. package/dest/world-state-db/merkle_tree_db.d.ts.map +1 -1
  39. package/package.json +13 -12
  40. package/src/instrumentation/instrumentation.ts +9 -1
  41. package/src/native/fork_checkpoint.ts +19 -3
  42. package/src/native/ipc_world_state_instance.ts +717 -0
  43. package/src/native/merkle_trees_facade.ts +51 -17
  44. package/src/native/message.ts +14 -4
  45. package/src/native/native_world_state.ts +108 -32
  46. package/src/native/native_world_state_instance.ts +44 -32
  47. package/src/native/world_state_ops_queue.ts +5 -5
  48. package/src/synchronizer/config.ts +16 -23
  49. package/src/synchronizer/factory.ts +19 -11
  50. package/src/synchronizer/server_world_state_synchronizer.ts +119 -46
  51. package/src/test/utils.ts +6 -6
  52. package/src/testing.ts +8 -9
  53. package/src/world-state-db/merkle_tree_db.ts +2 -12
@@ -4,6 +4,7 @@ import { createLogger } from '@aztec/foundation/log';
4
4
  import { serializeToBuffer } from '@aztec/foundation/serialize';
5
5
  import { sleep } from '@aztec/foundation/sleep';
6
6
  import { type IndexedTreeLeafPreimage, SiblingPath } from '@aztec/foundation/trees';
7
+ import { BlockHash } from '@aztec/stdlib/block';
7
8
  import type {
8
9
  BatchInsertionResult,
9
10
  IndexedTreeId,
@@ -118,7 +119,7 @@ export class MerkleTreesFacade implements MerkleTreeReadOperations {
118
119
 
119
120
  const leaf = deserializeLeafValue(resp);
120
121
  if (leaf instanceof Fr) {
121
- return leaf as any;
122
+ return treeId === MerkleTreeId.ARCHIVE ? (new BlockHash(leaf) as any) : (leaf as any);
122
123
  } else {
123
124
  return leaf.toBuffer() as any;
124
125
  }
@@ -207,6 +208,7 @@ export class MerkleTreesFacade implements MerkleTreeReadOperations {
207
208
 
208
209
  export class MerkleTreesForkFacade extends MerkleTreesFacade implements MerkleTreeWriteOperations {
209
210
  private log = createLogger('world-state:merkle-trees-fork-facade');
211
+ private closePromise: Promise<void> | undefined;
210
212
 
211
213
  constructor(
212
214
  instance: NativeWorldStateInstance,
@@ -228,7 +230,7 @@ export class MerkleTreesForkFacade extends MerkleTreesFacade implements MerkleTr
228
230
 
229
231
  async appendLeaves<ID extends MerkleTreeId>(treeId: ID, leaves: MerkleTreeLeafType<ID>[]): Promise<void> {
230
232
  await this.instance.call(WorldStateMessageType.APPEND_LEAVES, {
231
- leaves: leaves.map(leaf => leaf as any),
233
+ leaves: leaves.map(leaf => serializeLeaf(hydrateLeaf(treeId, leaf as any))),
232
234
  forkId: this.revision.forkId,
233
235
  treeId,
234
236
  });
@@ -290,19 +292,40 @@ export class MerkleTreesForkFacade extends MerkleTreesFacade implements MerkleTr
290
292
  };
291
293
  }
292
294
 
293
- public async close(): Promise<void> {
295
+ public close(): Promise<void> {
294
296
  assert.notEqual(this.revision.forkId, 0, 'Fork ID must be set');
295
- await this.instance.call(WorldStateMessageType.DELETE_FORK, { forkId: this.revision.forkId });
297
+ // Share the in-flight close promise across duplicate dispose calls so DELETE_FORK is sent at most once.
298
+ if (this.closePromise) {
299
+ return this.closePromise;
300
+ }
301
+ this.closePromise = this.doClose();
302
+ return this.closePromise;
303
+ }
304
+
305
+ private async doClose(): Promise<void> {
306
+ try {
307
+ await this.instance.call(WorldStateMessageType.DELETE_FORK, { forkId: this.revision.forkId });
308
+ } catch (err: any) {
309
+ // Ignore errors due to native instance being closed during shutdown.
310
+ // This can happen when validators are still processing block proposals while the node is stopping.
311
+ if (err?.message === 'Native instance is closed') {
312
+ return;
313
+ }
314
+ // Ignore "Fork not found": the native fork was already destroyed by a pending-chain unwind or a
315
+ // historical prune (both call C++ remove_forks_for_block). Fork IDs are monotonic and never reused,
316
+ // so swallowing this on close cannot mask a deletion of a different fork.
317
+ if (err?.message === 'Fork not found') {
318
+ return;
319
+ }
320
+ throw err;
321
+ }
296
322
  }
297
323
 
298
- async [Symbol.dispose](): Promise<void> {
324
+ async [Symbol.asyncDispose](): Promise<void> {
299
325
  if (this.opts.closeDelayMs) {
300
326
  void sleep(this.opts.closeDelayMs)
301
327
  .then(() => this.close())
302
328
  .catch(err => {
303
- if (err && 'message' in err && err.message === 'Native instance is closed') {
304
- return; // Ignore errors due to native instance being closed
305
- }
306
329
  this.log.warn('Error closing MerkleTreesForkFacade after delay', { err });
307
330
  });
308
331
  } else {
@@ -310,9 +333,10 @@ export class MerkleTreesForkFacade extends MerkleTreesFacade implements MerkleTr
310
333
  }
311
334
  }
312
335
 
313
- public async createCheckpoint(): Promise<void> {
336
+ public async createCheckpoint(): Promise<number> {
314
337
  assert.notEqual(this.revision.forkId, 0, 'Fork ID must be set');
315
- await this.instance.call(WorldStateMessageType.CREATE_CHECKPOINT, { forkId: this.revision.forkId });
338
+ const resp = await this.instance.call(WorldStateMessageType.CREATE_CHECKPOINT, { forkId: this.revision.forkId });
339
+ return resp.depth;
316
340
  }
317
341
 
318
342
  public async commitCheckpoint(): Promise<void> {
@@ -325,20 +349,28 @@ export class MerkleTreesForkFacade extends MerkleTreesFacade implements MerkleTr
325
349
  await this.instance.call(WorldStateMessageType.REVERT_CHECKPOINT, { forkId: this.revision.forkId });
326
350
  }
327
351
 
328
- public async commitAllCheckpoints(): Promise<void> {
352
+ public async commitAllCheckpointsTo(depth: number): Promise<void> {
329
353
  assert.notEqual(this.revision.forkId, 0, 'Fork ID must be set');
330
- await this.instance.call(WorldStateMessageType.COMMIT_ALL_CHECKPOINTS, { forkId: this.revision.forkId });
354
+ await this.instance.call(WorldStateMessageType.COMMIT_ALL_CHECKPOINTS, {
355
+ forkId: this.revision.forkId,
356
+ depth,
357
+ });
331
358
  }
332
359
 
333
- public async revertAllCheckpoints(): Promise<void> {
360
+ public async revertAllCheckpointsTo(depth: number): Promise<void> {
334
361
  assert.notEqual(this.revision.forkId, 0, 'Fork ID must be set');
335
- await this.instance.call(WorldStateMessageType.REVERT_ALL_CHECKPOINTS, { forkId: this.revision.forkId });
362
+ await this.instance.call(WorldStateMessageType.REVERT_ALL_CHECKPOINTS, {
363
+ forkId: this.revision.forkId,
364
+ depth,
365
+ });
336
366
  }
337
367
  }
338
368
 
339
- function hydrateLeaf<ID extends MerkleTreeId>(treeId: ID, leaf: Fr | Buffer) {
369
+ function hydrateLeaf(treeId: MerkleTreeId, leaf: Fr | BlockHash | Buffer) {
340
370
  if (leaf instanceof Fr) {
341
371
  return leaf;
372
+ } else if (leaf instanceof BlockHash) {
373
+ return leaf.toFr();
342
374
  } else if (treeId === MerkleTreeId.NULLIFIER_TREE) {
343
375
  return NullifierLeaf.fromBuffer(leaf);
344
376
  } else if (treeId === MerkleTreeId.PUBLIC_DATA_TREE) {
@@ -348,8 +380,10 @@ function hydrateLeaf<ID extends MerkleTreeId>(treeId: ID, leaf: Fr | Buffer) {
348
380
  }
349
381
  }
350
382
 
351
- export function serializeLeaf(leaf: Fr | NullifierLeaf | PublicDataTreeLeaf): SerializedLeafValue {
352
- if (leaf instanceof Fr) {
383
+ export function serializeLeaf(leaf: Fr | BlockHash | NullifierLeaf | PublicDataTreeLeaf): SerializedLeafValue {
384
+ if (leaf instanceof BlockHash) {
385
+ return leaf.toBuffer();
386
+ } else if (leaf instanceof Fr) {
353
387
  return leaf.toBuffer();
354
388
  } else if (leaf instanceof NullifierLeaf) {
355
389
  return { nullifier: leaf.nullifier.toBuffer() };
@@ -283,6 +283,16 @@ interface WithForkId {
283
283
  forkId: number;
284
284
  }
285
285
 
286
+ interface CreateCheckpointResponse {
287
+ depth: number;
288
+ }
289
+
290
+ /** Request to commit/revert all checkpoints down to a target depth. The resulting depth after the operation equals the given depth. */
291
+ interface CheckpointDepthRequest extends WithForkId {
292
+ /** The target depth after the operation. All checkpoints above this depth are committed/reverted. */
293
+ depth: number;
294
+ }
295
+
286
296
  interface WithWorldStateRevision {
287
297
  revision: WorldStateRevision;
288
298
  }
@@ -412,7 +422,7 @@ interface UpdateArchiveRequest extends WithForkId {
412
422
  interface SyncBlockRequest extends WithCanonicalForkId {
413
423
  blockNumber: BlockNumber;
414
424
  blockStateRef: BlockStateReference;
415
- blockHeaderHash: Fr;
425
+ blockHeaderHash: Buffer;
416
426
  paddedNoteHashes: readonly SerializedLeafValue[];
417
427
  paddedL1ToL2Messages: readonly SerializedLeafValue[];
418
428
  paddedNullifiers: readonly SerializedLeafValue[];
@@ -486,8 +496,8 @@ export type WorldStateRequest = {
486
496
  [WorldStateMessageType.CREATE_CHECKPOINT]: WithForkId;
487
497
  [WorldStateMessageType.COMMIT_CHECKPOINT]: WithForkId;
488
498
  [WorldStateMessageType.REVERT_CHECKPOINT]: WithForkId;
489
- [WorldStateMessageType.COMMIT_ALL_CHECKPOINTS]: WithForkId;
490
- [WorldStateMessageType.REVERT_ALL_CHECKPOINTS]: WithForkId;
499
+ [WorldStateMessageType.COMMIT_ALL_CHECKPOINTS]: CheckpointDepthRequest;
500
+ [WorldStateMessageType.REVERT_ALL_CHECKPOINTS]: CheckpointDepthRequest;
491
501
 
492
502
  [WorldStateMessageType.COPY_STORES]: CopyStoresRequest;
493
503
 
@@ -528,7 +538,7 @@ export type WorldStateResponse = {
528
538
 
529
539
  [WorldStateMessageType.GET_STATUS]: WorldStateStatusSummary;
530
540
 
531
- [WorldStateMessageType.CREATE_CHECKPOINT]: void;
541
+ [WorldStateMessageType.CREATE_CHECKPOINT]: CreateCheckpointResponse;
532
542
  [WorldStateMessageType.COMMIT_CHECKPOINT]: void;
533
543
  [WorldStateMessageType.REVERT_CHECKPOINT]: void;
534
544
  [WorldStateMessageType.COMMIT_ALL_CHECKPOINTS]: void;
@@ -4,9 +4,9 @@ import { fromEntries, padArrayEnd } from '@aztec/foundation/collection';
4
4
  import { Fr } from '@aztec/foundation/curves/bn254';
5
5
  import { EthAddress } from '@aztec/foundation/eth-address';
6
6
  import { tryRmDir } from '@aztec/foundation/fs';
7
- import { type Logger, createLogger } from '@aztec/foundation/log';
8
- import type { L2BlockNew } from '@aztec/stdlib/block';
9
- import { DatabaseVersionManager } from '@aztec/stdlib/database-version';
7
+ import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
8
+ import type { L2Block } from '@aztec/stdlib/block';
9
+ import { DatabaseVersionManager } from '@aztec/stdlib/database-version/manager';
10
10
  import type {
11
11
  IndexedTreeId,
12
12
  MerkleTreeReadOperations,
@@ -14,8 +14,8 @@ import type {
14
14
  } from '@aztec/stdlib/interfaces/server';
15
15
  import type { SnapshotDataKeys } from '@aztec/stdlib/snapshots';
16
16
  import { MerkleTreeId, NullifierLeaf, type NullifierLeafPreimage, PublicDataTreeLeaf } from '@aztec/stdlib/trees';
17
- import { BlockHeader, PartialStateReference, StateReference } from '@aztec/stdlib/tx';
18
- import { WorldStateRevision } from '@aztec/stdlib/world-state';
17
+ import { BlockHeader, GlobalVariables, PartialStateReference, StateReference } from '@aztec/stdlib/tx';
18
+ import { EMPTY_GENESIS_DATA, type GenesisData, WorldStateRevision } from '@aztec/stdlib/world-state';
19
19
  import { getTelemetryClient } from '@aztec/telemetry-client';
20
20
 
21
21
  import assert from 'assert/strict';
@@ -44,6 +44,29 @@ export const WORLD_STATE_DB_VERSION = 2; // The initial version
44
44
 
45
45
  export const WORLD_STATE_DIR = 'world_state';
46
46
 
47
+ const DEFAULT_TMP_TREE_MAP_SIZE_KB = 10 * 1024 * 1024;
48
+
49
+ /**
50
+ * Sets up a fresh `mkdtemp` directory + default `WorldStateTreeMapSizes` shared by both
51
+ * the `.tmp` (fsync-on) and `.ephemeral` (fsync-off) factories. Returns the raw tmpdir,
52
+ * the tree map sizes, and the package logger.
53
+ */
54
+ async function createTmpWorldStateDir(
55
+ bindings?: LoggerBindings,
56
+ ): Promise<{ dataDir: string; wsTreeMapSizes: WorldStateTreeMapSizes; log: Logger }> {
57
+ const log = createLogger('world-state:database', bindings);
58
+ const dataDir = await mkdtemp(join(tmpdir(), 'aztec-world-state-'));
59
+ const wsTreeMapSizes: WorldStateTreeMapSizes = {
60
+ archiveTreeMapSizeKb: DEFAULT_TMP_TREE_MAP_SIZE_KB,
61
+ nullifierTreeMapSizeKb: DEFAULT_TMP_TREE_MAP_SIZE_KB,
62
+ noteHashTreeMapSizeKb: DEFAULT_TMP_TREE_MAP_SIZE_KB,
63
+ messageTreeMapSizeKb: DEFAULT_TMP_TREE_MAP_SIZE_KB,
64
+ publicDataTreeMapSizeKb: DEFAULT_TMP_TREE_MAP_SIZE_KB,
65
+ };
66
+ log.debug(`Created temporary world state database at: ${dataDir} (map size ${DEFAULT_TMP_TREE_MAP_SIZE_KB} KB)`);
67
+ return { dataDir, wsTreeMapSizes, log };
68
+ }
69
+
47
70
  export class NativeWorldStateService implements MerkleTreeDatabase {
48
71
  protected initialHeader: BlockHeader | undefined;
49
72
  // This is read heavily and only changes when data is persisted, so we cache it
@@ -52,32 +75,47 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
52
75
  protected constructor(
53
76
  protected instance: NativeWorldState,
54
77
  protected readonly worldStateInstrumentation: WorldStateInstrumentation,
55
- protected readonly log: Logger = createLogger('world-state:database'),
78
+ protected readonly log: Logger,
79
+ private readonly genesis: GenesisData = EMPTY_GENESIS_DATA,
56
80
  private readonly cleanup = () => Promise.resolve(),
57
81
  ) {}
58
82
 
83
+ /**
84
+ * Opens a persistent world state at `dataDir`. Goes through `DatabaseVersionManager` so the
85
+ * caller's rollup address is bound to the on-disk schema and incompatible versions surface
86
+ * loudly. The LMDB envs commit with full fsync.
87
+ */
59
88
  static async new(
60
89
  rollupAddress: EthAddress,
61
90
  dataDir: string,
62
91
  wsTreeMapSizes: WorldStateTreeMapSizes,
63
- prefilledPublicData: PublicDataTreeLeaf[] = [],
92
+ genesis: GenesisData = EMPTY_GENESIS_DATA,
64
93
  instrumentation = new WorldStateInstrumentation(getTelemetryClient()),
65
- log = createLogger('world-state:database'),
94
+ bindings?: LoggerBindings,
66
95
  cleanup = () => Promise.resolve(),
67
96
  ): Promise<NativeWorldStateService> {
97
+ const log = createLogger('world-state:database', bindings);
68
98
  const worldStateDirectory = join(dataDir, WORLD_STATE_DIR);
69
- // Create a version manager to handle versioning
70
99
  const versionManager = new DatabaseVersionManager({
71
100
  schemaVersion: WORLD_STATE_DB_VERSION,
72
101
  rollupAddress,
73
102
  dataDirectory: worldStateDirectory,
74
- onOpen: (dir: string) => {
75
- return Promise.resolve(new NativeWorldState(dir, wsTreeMapSizes, prefilledPublicData, instrumentation));
76
- },
103
+ onOpen: (dir: string) =>
104
+ Promise.resolve(
105
+ new NativeWorldState(
106
+ dir,
107
+ wsTreeMapSizes,
108
+ genesis,
109
+ instrumentation,
110
+ bindings,
111
+ undefined,
112
+ /*ephemeral=*/ false,
113
+ ),
114
+ ),
77
115
  });
78
116
 
79
117
  const [instance] = await versionManager.open();
80
- const worldState = new this(instance, instrumentation, log, cleanup);
118
+ const worldState = new this(instance, instrumentation, log, genesis, cleanup);
81
119
  try {
82
120
  await worldState.init();
83
121
  } catch (e) {
@@ -88,25 +126,23 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
88
126
  return worldState;
89
127
  }
90
128
 
129
+ /**
130
+ * Opens a world state in a fresh tmpdir with full fsync semantics. Use when you need the
131
+ * on-disk file to remain crash-recoverable (e.g. for snapshot/backup tests) but don't
132
+ * want a persistent dataDir. Pass `cleanupTmpDir=false` to keep the directory after
133
+ * close for inspection.
134
+ *
135
+ * If you don't care about crash-recoverability — i.e. you just want a fast scratch
136
+ * database for tests — use {@link ephemeral} instead.
137
+ */
91
138
  static async tmp(
92
139
  rollupAddress = EthAddress.ZERO,
93
140
  cleanupTmpDir = true,
94
- prefilledPublicData: PublicDataTreeLeaf[] = [],
141
+ genesis: GenesisData = EMPTY_GENESIS_DATA,
95
142
  instrumentation = new WorldStateInstrumentation(getTelemetryClient()),
143
+ bindings?: LoggerBindings,
96
144
  ): Promise<NativeWorldStateService> {
97
- const log = createLogger('world-state:database');
98
- const dataDir = await mkdtemp(join(tmpdir(), 'aztec-world-state-'));
99
- const dbMapSizeKb = 10 * 1024 * 1024;
100
- const worldStateTreeMapSizes: WorldStateTreeMapSizes = {
101
- archiveTreeMapSizeKb: dbMapSizeKb,
102
- nullifierTreeMapSizeKb: dbMapSizeKb,
103
- noteHashTreeMapSizeKb: dbMapSizeKb,
104
- messageTreeMapSizeKb: dbMapSizeKb,
105
- publicDataTreeMapSizeKb: dbMapSizeKb,
106
- };
107
- log.debug(`Created temporary world state database at: ${dataDir} with tree map size: ${dbMapSizeKb}`);
108
-
109
- // pass a cleanup callback because process.on('beforeExit', cleanup) does not work under Jest
145
+ const { dataDir, wsTreeMapSizes, log } = await createTmpWorldStateDir(bindings);
110
146
  const cleanup = async () => {
111
147
  if (cleanupTmpDir) {
112
148
  await rm(dataDir, { recursive: true, force: true, maxRetries: 3 });
@@ -115,8 +151,45 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
115
151
  log.debug(`Leaving temporary world state database: ${dataDir}`);
116
152
  }
117
153
  };
154
+ return this.new(rollupAddress, dataDir, wsTreeMapSizes, genesis, instrumentation, bindings, cleanup);
155
+ }
118
156
 
119
- return this.new(rollupAddress, dataDir, worldStateTreeMapSizes, prefilledPublicData, instrumentation, log, cleanup);
157
+ /**
158
+ * Opens a fully-ephemeral world state. The directory is created in `os.tmpdir()`, the LMDB
159
+ * envs open with `MDB_NOSYNC | MDB_NOMETASYNC` so commits never block on fsync, and the
160
+ * directory is removed on dispose. A crash mid-write leaves the env unrecoverable.
161
+ *
162
+ * For unit tests and other isolated runs. Use {@link tmp} when you need fsync semantics in a
163
+ * tmp dir, and {@link new} for a persistent store. Skips {@link DatabaseVersionManager} —
164
+ * there is no on-disk schema to bind to and no rollup address is taken.
165
+ */
166
+ static async ephemeral(
167
+ genesis: GenesisData = EMPTY_GENESIS_DATA,
168
+ instrumentation = new WorldStateInstrumentation(getTelemetryClient()),
169
+ bindings?: LoggerBindings,
170
+ ): Promise<NativeWorldStateService> {
171
+ const { dataDir, wsTreeMapSizes, log } = await createTmpWorldStateDir(bindings);
172
+ const cleanup = async () => {
173
+ await rm(dataDir, { recursive: true, force: true, maxRetries: 3 });
174
+ log.debug(`Deleted ephemeral world state database: ${dataDir}`);
175
+ };
176
+ const instance = new NativeWorldState(
177
+ join(dataDir, WORLD_STATE_DIR),
178
+ wsTreeMapSizes,
179
+ genesis,
180
+ instrumentation,
181
+ bindings,
182
+ undefined,
183
+ /*ephemeral=*/ true,
184
+ );
185
+ const worldState = new this(instance, instrumentation, log, genesis, cleanup);
186
+ try {
187
+ await worldState.init();
188
+ } catch (e) {
189
+ log.error(`Error initializing ephemeral world state: ${e}`);
190
+ throw e;
191
+ }
192
+ return worldState;
120
193
  }
121
194
 
122
195
  protected async init() {
@@ -173,7 +246,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
173
246
  this.initialHeader!,
174
247
  new WorldStateRevision(
175
248
  /*forkId=*/ resp.forkId,
176
- /* blockNumber=*/ BlockNumber.ZERO,
249
+ /* blockNumber=*/ WorldStateRevision.LATEST,
177
250
  /* includeUncommitted=*/ true,
178
251
  ),
179
252
  opts,
@@ -184,7 +257,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
184
257
  return this.initialHeader!;
185
258
  }
186
259
 
187
- public async handleL2BlockAndMessages(l2Block: L2BlockNew, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull> {
260
+ public async handleL2BlockAndMessages(l2Block: L2Block, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull> {
188
261
  const isFirstBlock = l2Block.indexWithinCheckpoint === 0;
189
262
  if (!isFirstBlock && l1ToL2Messages.length > 0) {
190
263
  throw new Error(
@@ -220,7 +293,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
220
293
  WorldStateMessageType.SYNC_BLOCK,
221
294
  {
222
295
  blockNumber: l2Block.number,
223
- blockHeaderHash: await l2Block.hash(),
296
+ blockHeaderHash: (await l2Block.hash()).toBuffer(),
224
297
  paddedL1ToL2Messages: paddedL1ToL2Messages.map(serializeLeaf),
225
298
  paddedNoteHashes: paddedNoteHashes.map(serializeLeaf),
226
299
  paddedNullifiers: paddedNullifiers.map(serializeLeaf),
@@ -244,7 +317,10 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
244
317
 
245
318
  private async buildInitialHeader(): Promise<BlockHeader> {
246
319
  const state = await this.getInitialStateReference();
247
- return BlockHeader.empty({ state });
320
+ return BlockHeader.empty({
321
+ state,
322
+ globalVariables: GlobalVariables.empty({ timestamp: this.genesis.genesisTimestamp }),
323
+ });
248
324
  }
249
325
 
250
326
  private sanitizeAndCacheSummaryFromFull(response: WorldStateStatusFull) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ARCHIVE_HEIGHT,
3
- GeneratorIndex,
3
+ DomainSeparator,
4
4
  L1_TO_L2_MSG_TREE_HEIGHT,
5
5
  MAX_NULLIFIERS_PER_TX,
6
6
  MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
@@ -8,10 +8,10 @@ import {
8
8
  NULLIFIER_TREE_HEIGHT,
9
9
  PUBLIC_DATA_TREE_HEIGHT,
10
10
  } from '@aztec/constants';
11
- import { type Logger, createLogger } from '@aztec/foundation/log';
11
+ import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
12
12
  import { NativeWorldState as BaseNativeWorldState, MsgpackChannel } from '@aztec/native';
13
13
  import { MerkleTreeId } from '@aztec/stdlib/trees';
14
- import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees';
14
+ import { EMPTY_GENESIS_DATA, type GenesisData } from '@aztec/stdlib/world-state';
15
15
 
16
16
  import assert from 'assert';
17
17
  import { cpus } from 'os';
@@ -55,17 +55,22 @@ export class NativeWorldState implements NativeWorldStateInstance {
55
55
  constructor(
56
56
  private readonly dataDir: string,
57
57
  private readonly wsTreeMapSizes: WorldStateTreeMapSizes,
58
- private readonly prefilledPublicData: PublicDataTreeLeaf[] = [],
58
+ private readonly genesis: GenesisData = EMPTY_GENESIS_DATA,
59
59
  private readonly instrumentation: WorldStateInstrumentation,
60
- private readonly log: Logger = createLogger('world-state:database'),
60
+ bindings?: LoggerBindings,
61
+ private readonly log: Logger = createLogger('world-state:database', bindings),
62
+ private readonly ephemeral: boolean = false,
61
63
  ) {
62
64
  const threads = Math.min(cpus().length, MAX_WORLD_STATE_THREADS);
63
65
  log.info(
64
66
  `Creating world state data store at directory ${dataDir} with map sizes ${JSON.stringify(
65
67
  wsTreeMapSizes,
66
- )} and ${threads} threads.`,
68
+ )} and ${threads} threads (ephemeral=${ephemeral}).`,
67
69
  );
68
- const prefilledPublicDataBufferArray = prefilledPublicData.map(d => [d.slot.toBuffer(), d.value.toBuffer()]);
70
+ const prefilledPublicDataBufferArray = genesis.prefilledPublicData.map(d => [
71
+ d.slot.toBuffer(),
72
+ d.value.toBuffer(),
73
+ ]);
69
74
  const ws = new BaseNativeWorldState(
70
75
  dataDir,
71
76
  {
@@ -80,7 +85,8 @@ export class NativeWorldState implements NativeWorldStateInstance {
80
85
  [MerkleTreeId.PUBLIC_DATA_TREE]: 2 * MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
81
86
  },
82
87
  prefilledPublicDataBufferArray,
83
- GeneratorIndex.BLOCK_HASH,
88
+ DomainSeparator.BLOCK_HEADER_HASH,
89
+ Number(genesis.genesisTimestamp),
84
90
  {
85
91
  [MerkleTreeId.NULLIFIER_TREE]: wsTreeMapSizes.nullifierTreeMapSizeKb,
86
92
  [MerkleTreeId.NOTE_HASH_TREE]: wsTreeMapSizes.noteHashTreeMapSizeKb,
@@ -89,6 +95,7 @@ export class NativeWorldState implements NativeWorldStateInstance {
89
95
  [MerkleTreeId.ARCHIVE]: wsTreeMapSizes.archiveTreeMapSizeKb,
90
96
  },
91
97
  threads,
98
+ ephemeral,
92
99
  );
93
100
  this.instance = new MsgpackChannel(ws);
94
101
  // Manually create the queue for the canonical fork
@@ -103,9 +110,11 @@ export class NativeWorldState implements NativeWorldStateInstance {
103
110
  return new NativeWorldState(
104
111
  this.dataDir,
105
112
  this.wsTreeMapSizes,
106
- this.prefilledPublicData,
113
+ this.genesis,
107
114
  this.instrumentation,
115
+ this.log.getBindings(),
108
116
  this.log,
117
+ this.ephemeral,
109
118
  );
110
119
  }
111
120
 
@@ -178,30 +187,33 @@ export class NativeWorldState implements NativeWorldStateInstance {
178
187
  this.queues.set(forkId, requestQueue);
179
188
  }
180
189
 
181
- // Enqueue the request and wait for the response
182
- const response = await requestQueue.execute(
183
- async () => {
184
- assert.notEqual(messageType, WorldStateMessageType.CLOSE, 'Use close() to close the native instance');
185
- assert.equal(this.open, true, 'Native instance is closed');
186
- let response: WorldStateResponse[T];
187
- try {
188
- response = await this._sendMessage(messageType, body);
189
- } catch (error: any) {
190
- errorHandler(error.message);
191
- throw error;
192
- }
193
- return responseHandler(response);
194
- },
195
- messageType,
196
- committedOnly,
197
- );
198
-
199
- // If the request was to delete the fork then we clean it up here
200
- if (messageType === WorldStateMessageType.DELETE_FORK) {
201
- await requestQueue.stop();
202
- this.queues.delete(forkId);
190
+ // Enqueue the request and wait for the response. The per-fork queue is cleaned up in `finally` even on
191
+ // error, so the JS-side queues map cannot outlive the native fork (e.g. when the native fork was already
192
+ // destroyed by an unwind/historical-prune and DELETE_FORK rejects with "Fork not found").
193
+ try {
194
+ const response = await requestQueue.execute(
195
+ async () => {
196
+ assert.notEqual(messageType, WorldStateMessageType.CLOSE, 'Use close() to close the native instance');
197
+ assert.equal(this.open, true, 'Native instance is closed');
198
+ let response: WorldStateResponse[T];
199
+ try {
200
+ response = await this._sendMessage(messageType, body);
201
+ } catch (error: any) {
202
+ errorHandler(error.message);
203
+ throw error;
204
+ }
205
+ return responseHandler(response);
206
+ },
207
+ messageType,
208
+ committedOnly,
209
+ );
210
+ return response;
211
+ } finally {
212
+ if (messageType === WorldStateMessageType.DELETE_FORK) {
213
+ await requestQueue.stop();
214
+ this.queues.delete(forkId);
215
+ }
203
216
  }
204
- return response;
205
217
  }
206
218
 
207
219
  /**
@@ -96,7 +96,7 @@ export class WorldStateOpsQueue {
96
96
  // then send the request immediately
97
97
  // If a mutating request is in flight then we must wait
98
98
  // If a mutating request is not in flight but something is queued then it must be a mutating request
99
- if (this.inFlightMutatingCount == 0 && this.requests.length == 0) {
99
+ if (this.inFlightMutatingCount === 0 && this.requests.length === 0) {
100
100
  this.sendEnqueuedRequest(op);
101
101
  } else {
102
102
  this.requests.push(op);
@@ -122,7 +122,7 @@ export class WorldStateOpsQueue {
122
122
  --this.inFlightCount;
123
123
 
124
124
  // If there are still requests in flight then do nothing further
125
- if (this.inFlightCount != 0) {
125
+ if (this.inFlightCount !== 0) {
126
126
  return;
127
127
  }
128
128
 
@@ -134,7 +134,7 @@ export class WorldStateOpsQueue {
134
134
  while (this.requests.length > 0) {
135
135
  const next = this.requests[0];
136
136
  if (next.mutating) {
137
- if (this.inFlightCount == 0) {
137
+ if (this.inFlightCount === 0) {
138
138
  // send the mutating request
139
139
  this.requests.shift();
140
140
  this.sendEnqueuedRequest(next);
@@ -149,7 +149,7 @@ export class WorldStateOpsQueue {
149
149
  }
150
150
 
151
151
  // If the queue is empty, there is nothing in flight and we have been told to stop, then resolve the stop promise
152
- if (this.inFlightCount == 0 && this.stopResolve !== undefined) {
152
+ if (this.inFlightCount === 0 && this.stopResolve !== undefined) {
153
153
  this.stopResolve();
154
154
  }
155
155
  }
@@ -182,7 +182,7 @@ export class WorldStateOpsQueue {
182
182
  });
183
183
 
184
184
  // If no outstanding requests then immediately resolve the promise
185
- if (this.requests.length == 0 && this.inFlightCount == 0 && this.stopResolve !== undefined) {
185
+ if (this.requests.length === 0 && this.inFlightCount === 0 && this.stopResolve !== undefined) {
186
186
  this.stopResolve();
187
187
  }
188
188
  return this.stopPromise;