@aztec/world-state 0.0.1-commit.c2eed6949 → 0.0.1-commit.c52d6e7

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 (69) 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 +89 -0
  11. package/dest/native/ipc_world_state_instance.d.ts.map +1 -0
  12. package/dest/native/ipc_world_state_instance.js +670 -0
  13. package/dest/native/merkle_trees_facade.d.ts +8 -4
  14. package/dest/native/merkle_trees_facade.d.ts.map +1 -1
  15. package/dest/native/merkle_trees_facade.js +60 -115
  16. package/dest/native/message.d.ts +14 -230
  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 +19 -7
  20. package/dest/native/native_world_state.d.ts.map +1 -1
  21. package/dest/native/native_world_state.js +120 -58
  22. package/dest/native/native_world_state_instance.d.ts +56 -42
  23. package/dest/native/native_world_state_instance.d.ts.map +1 -1
  24. package/dest/native/native_world_state_instance.js +1 -203
  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/config.d.ts +1 -1
  29. package/dest/synchronizer/config.d.ts.map +1 -1
  30. package/dest/synchronizer/config.js +9 -10
  31. package/dest/synchronizer/errors.d.ts +8 -1
  32. package/dest/synchronizer/errors.d.ts.map +1 -1
  33. package/dest/synchronizer/errors.js +8 -1
  34. package/dest/synchronizer/factory.d.ts +4 -4
  35. package/dest/synchronizer/factory.d.ts.map +1 -1
  36. package/dest/synchronizer/factory.js +7 -6
  37. package/dest/synchronizer/index.d.ts +2 -1
  38. package/dest/synchronizer/index.d.ts.map +1 -1
  39. package/dest/synchronizer/index.js +1 -0
  40. package/dest/synchronizer/server_world_state_synchronizer.d.ts +11 -6
  41. package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
  42. package/dest/synchronizer/server_world_state_synchronizer.js +67 -40
  43. package/dest/test/utils.d.ts +1 -1
  44. package/dest/test/utils.d.ts.map +1 -1
  45. package/dest/test/utils.js +6 -1
  46. package/dest/testing.d.ts +4 -3
  47. package/dest/testing.d.ts.map +1 -1
  48. package/dest/testing.js +22 -7
  49. package/package.json +13 -10
  50. package/src/index.ts +1 -0
  51. package/src/instrumentation/instrumentation.ts +6 -18
  52. package/src/native/index.ts +1 -0
  53. package/src/native/ipc_world_state_instance.ts +847 -0
  54. package/src/native/merkle_trees_facade.ts +85 -112
  55. package/src/native/message.ts +13 -297
  56. package/src/native/native_world_state.ts +144 -97
  57. package/src/native/native_world_state_instance.ts +94 -281
  58. package/src/native/world_state_operation.ts +33 -0
  59. package/src/synchronizer/config.ts +14 -10
  60. package/src/synchronizer/errors.ts +8 -0
  61. package/src/synchronizer/factory.ts +11 -10
  62. package/src/synchronizer/index.ts +1 -0
  63. package/src/synchronizer/server_world_state_synchronizer.ts +73 -36
  64. package/src/test/utils.ts +10 -1
  65. package/src/testing.ts +19 -10
  66. package/dest/native/world_state_ops_queue.d.ts +0 -19
  67. package/dest/native/world_state_ops_queue.d.ts.map +0 -1
  68. package/dest/native/world_state_ops_queue.js +0 -146
  69. package/src/native/world_state_ops_queue.ts +0 -190
@@ -1,4 +1,3 @@
1
- import { GENESIS_BLOCK_HEADER_HASH, INITIAL_CHECKPOINT_NUMBER, INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
2
1
  import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
3
2
  import type { Fr } from '@aztec/foundation/curves/bn254';
4
3
  import { type Logger, createLogger } from '@aztec/foundation/log';
@@ -6,15 +5,13 @@ import { promiseWithResolvers } from '@aztec/foundation/promise';
6
5
  import { elapsed } from '@aztec/foundation/timer';
7
6
  import {
8
7
  type BlockHash,
9
- GENESIS_CHECKPOINT_HEADER_HASH,
8
+ EventDrivenL2BlockStream,
10
9
  type L2Block,
11
- type L2BlockId,
12
10
  type L2BlockSource,
13
- L2BlockStream,
14
11
  type L2BlockStreamEvent,
15
12
  type L2BlockStreamEventHandler,
16
13
  type L2BlockStreamLocalDataProvider,
17
- type L2Tips,
14
+ type LocalChainTips,
18
15
  } from '@aztec/stdlib/block';
19
16
  import {
20
17
  WorldStateRunningState,
@@ -51,7 +48,7 @@ export class ServerWorldStateSynchronizer
51
48
  private currentState: WorldStateRunningState = WorldStateRunningState.IDLE;
52
49
 
53
50
  private syncPromise = promiseWithResolvers<void>();
54
- protected blockStream: L2BlockStream | undefined;
51
+ protected blockStream: EventDrivenL2BlockStream | undefined;
55
52
 
56
53
  // WorldState doesn't track the proven block number, it only tracks the latest tips of the pending chain and the finalized chain
57
54
  // store the proven block number here, in the synchronizer, so that we don't end up spamming the logs with 'chain-proved' events
@@ -81,6 +78,46 @@ export class ServerWorldStateSynchronizer
81
78
  return this.merkleTreeDb.getSnapshot(blockNumber);
82
79
  }
83
80
 
81
+ public async getVerifiedSnapshot(blockNumber: BlockNumber, blockHash: BlockHash): Promise<MerkleTreeReadOperations> {
82
+ const snapshot = this.merkleTreeDb.getSnapshot(blockNumber);
83
+ // Block 0's snapshot is the pre-genesis archive view (size 0), so archive leaf 0 is not visible from it;
84
+ // verify against the initial header hash instead. For later blocks, read archive leaf `blockNumber` from the
85
+ // snapshot's own view so the exact handle we return is validated against the requested fork.
86
+ const actualHash =
87
+ blockNumber === BlockNumber.ZERO
88
+ ? (await this.merkleTreeCommitted.getInitialHeader().hash()).toString()
89
+ : (await snapshot.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber)))?.toString();
90
+
91
+ if (actualHash === undefined) {
92
+ // A missing archive leaf means either the block's history has been pruned away (permanent: the block predates
93
+ // the oldest historical block kept by world state) or a reorg flipped the fork between the sync and this read
94
+ // (transient). Only the latter is worth retrying, so it alone surfaces as WorldStateSynchronizerError.
95
+ const { oldestHistoricalBlock } = await this.merkleTreeDb.getStatusSummary();
96
+ if (blockNumber < oldestHistoricalBlock) {
97
+ throw new Error(
98
+ `Unable to find leaf for block ${blockNumber} in the archive tree: world state history has been pruned to block ${oldestHistoricalBlock}`,
99
+ );
100
+ }
101
+ throw new WorldStateSynchronizerError(`Unable to read block hash at block ${blockNumber} to verify snapshot`, {
102
+ cause: { reason: 'block_not_available', targetBlockNumber: blockNumber },
103
+ });
104
+ }
105
+ if (actualHash !== blockHash.toString()) {
106
+ throw new WorldStateSynchronizerError(
107
+ `Block hash mismatch at block ${blockNumber} (expected ${blockHash} but got ${actualHash})`,
108
+ {
109
+ cause: {
110
+ reason: 'block_hash_mismatch',
111
+ targetBlockNumber: blockNumber,
112
+ expectedHash: blockHash.toString(),
113
+ actualHash,
114
+ },
115
+ },
116
+ );
117
+ }
118
+ return snapshot;
119
+ }
120
+
84
121
  public fork(blockNumber?: BlockNumber, opts?: { closeDelayMs?: number }): Promise<MerkleTreeWriteOperations> {
85
122
  return this.merkleTreeDb.fork(blockNumber, opts);
86
123
  }
@@ -123,9 +160,9 @@ export class ServerWorldStateSynchronizer
123
160
  return this.syncPromise.promise;
124
161
  }
125
162
 
126
- protected createBlockStream(): L2BlockStream {
163
+ protected createBlockStream(): EventDrivenL2BlockStream {
127
164
  const logger = createLogger('world-state:block_stream');
128
- return new L2BlockStream(this.l2BlockSource, this, this, logger, {
165
+ return new EventDrivenL2BlockStream(this.l2BlockSource, this, this, logger, {
129
166
  pollIntervalMS: this.config.worldStateBlockCheckIntervalMS,
130
167
  batchSize: this.config.worldStateBlockRequestBatchSize,
131
168
  ignoreCheckpoints: true,
@@ -266,8 +303,12 @@ export class ServerWorldStateSynchronizer
266
303
  return this.merkleTreeCommitted.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(number)).then(leaf => leaf?.toString());
267
304
  }
268
305
 
269
- /** Returns the latest L2 block number for each tip of the chain (latest, proven, finalized). */
270
- public async getL2Tips(): Promise<L2Tips> {
306
+ /**
307
+ * Returns the proposed, proven, and finalized block tips of the chain. World state drives its block stream with
308
+ * `ignoreCheckpoints`, so it does not track checkpointed blocks or checkpoints and omits `checkpointed` from the tips
309
+ * it reports.
310
+ */
311
+ public async getL2Tips(): Promise<LocalChainTips> {
271
312
  const status = await this.merkleTreeDb.getStatusSummary();
272
313
  const unfinalizedBlockHashPromise = this.getL2BlockHash(status.unfinalizedBlockNumber);
273
314
  const finalizedBlockHashPromise = this.getL2BlockHash(status.finalizedBlockNumber);
@@ -281,25 +322,13 @@ export class ServerWorldStateSynchronizer
281
322
  finalizedBlockHashPromise,
282
323
  provenBlockHashPromise,
283
324
  ]);
284
- const latestBlockId: L2BlockId = { number: status.unfinalizedBlockNumber, hash: unfinalizedBlockHash! };
285
-
286
- // World state doesn't track checkpointed blocks or checkpoints themselves.
287
- // but we use a block stream so we need to provide 'local' L2Tips.
288
- // We configure the block stream to ignore checkpoints and set checkpoint values to genesis here.
289
- const genesisCheckpointHeaderHash = GENESIS_CHECKPOINT_HEADER_HASH.toString();
290
325
  return {
291
- proposed: latestBlockId,
292
- checkpointed: {
293
- block: { number: INITIAL_L2_BLOCK_NUM, hash: GENESIS_BLOCK_HEADER_HASH.toString() },
294
- checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
295
- },
326
+ proposed: { number: status.unfinalizedBlockNumber, hash: unfinalizedBlockHash },
296
327
  finalized: {
297
- block: { number: status.finalizedBlockNumber, hash: finalizedBlockHash ?? '' },
298
- checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
328
+ block: { number: status.finalizedBlockNumber, hash: finalizedBlockHash },
299
329
  },
300
330
  proven: {
301
- block: { number: provenBlockNumber, hash: provenBlockHash ?? '' },
302
- checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
331
+ block: { number: provenBlockNumber, hash: provenBlockHash },
303
332
  },
304
333
  };
305
334
  }
@@ -319,6 +348,15 @@ export class ServerWorldStateSynchronizer
319
348
  case 'chain-finalized':
320
349
  await this.handleChainFinalized(event.block.number);
321
350
  break;
351
+ // World state runs in block mode with ignoreCheckpoints: it tracks tips via blocks-added/pruned/proven/finalized
352
+ // and ignores the thin tip events (it never anchors on them).
353
+ case 'chain-proposed':
354
+ case 'chain-checkpointed':
355
+ break;
356
+ default: {
357
+ const _: never = event;
358
+ break;
359
+ }
322
360
  }
323
361
  }
324
362
 
@@ -404,29 +442,26 @@ export class ServerWorldStateSynchronizer
404
442
  if (this.historyToKeep === undefined) {
405
443
  return;
406
444
  }
407
- // Get the checkpointed block for the finalized block number
408
- const finalisedCheckpoint = await this.l2BlockSource.getCheckpointedBlock(summary.finalizedBlockNumber);
409
- if (finalisedCheckpoint === undefined) {
445
+ const finalisedBlockData = await this.l2BlockSource.getBlockData({ number: summary.finalizedBlockNumber });
446
+ if (finalisedBlockData === undefined) {
410
447
  this.log.warn(
411
448
  `Failed to retrieve checkpointed block for finalized block number: ${summary.finalizedBlockNumber}`,
412
449
  );
413
450
  return;
414
451
  }
415
452
  // Compute the required historic checkpoint number
416
- const newHistoricCheckpointNumber = finalisedCheckpoint.checkpointNumber - this.historyToKeep + 1;
453
+ const newHistoricCheckpointNumber = finalisedBlockData.checkpointNumber - this.historyToKeep + 1;
417
454
  if (newHistoricCheckpointNumber <= 1) {
418
455
  return;
419
456
  }
420
457
  // Retrieve the historic checkpoint
421
- const historicCheckpoints = await this.l2BlockSource.getCheckpoints(
422
- CheckpointNumber(newHistoricCheckpointNumber),
423
- 1,
424
- );
425
- if (historicCheckpoints.length === 0 || historicCheckpoints[0] === undefined) {
458
+ const historicCheckpoint = await this.l2BlockSource.getCheckpoint({
459
+ number: CheckpointNumber(newHistoricCheckpointNumber),
460
+ });
461
+ if (!historicCheckpoint) {
426
462
  this.log.warn(`Failed to retrieve checkpoint number ${newHistoricCheckpointNumber} from Archiver`);
427
463
  return;
428
464
  }
429
- const historicCheckpoint = historicCheckpoints[0];
430
465
  if (historicCheckpoint.checkpoint.blocks.length === 0 || historicCheckpoint.checkpoint.blocks[0] === undefined) {
431
466
  this.log.warn(`Retrieved checkpoint number ${newHistoricCheckpointNumber} has no blocks!`);
432
467
  return;
@@ -453,7 +488,9 @@ export class ServerWorldStateSynchronizer
453
488
  private async handleChainPruned(blockNumber: BlockNumber) {
454
489
  this.log.info(`Chain pruned to block ${blockNumber}`);
455
490
  const status = await this.merkleTreeDb.unwindBlocks(blockNumber);
456
- this.provenBlockNumber = undefined;
491
+ if (this.provenBlockNumber !== undefined && this.provenBlockNumber > blockNumber) {
492
+ this.provenBlockNumber = undefined;
493
+ }
457
494
  this.instrumentation.updateWorldStateMetrics(status);
458
495
  }
459
496
 
package/src/test/utils.ts CHANGED
@@ -60,7 +60,16 @@ export async function updateBlockState(block: L2Block, l1ToL2Messages: Fr[], for
60
60
  await Promise.all([publicDataInsert, nullifierInsert, noteHashInsert, messageInsert]);
61
61
 
62
62
  const state = await fork.getStateReference();
63
- block.header = BlockHeader.from({ ...block.header, state });
63
+
64
+ // Capture the archive root *before* appending this block so the header's lastArchive chains off the previous block.
65
+ // sync_block now verifies lastArchive against the committed archive root, so a block carrying an unchained value
66
+ // (e.g. the random lastArchive from L2Block.random) would be rejected as a divergence from the canonical chain.
67
+ const previousArchive = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
68
+ block.header = BlockHeader.from({
69
+ ...block.header,
70
+ state,
71
+ lastArchive: new AppendOnlyTreeSnapshot(Fr.fromBuffer(previousArchive.root), Number(previousArchive.size)),
72
+ });
64
73
  await fork.updateArchive(block.header);
65
74
 
66
75
  const archiveState = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
package/src/testing.ts CHANGED
@@ -3,22 +3,24 @@ import { Fr } from '@aztec/foundation/curves/bn254';
3
3
  import { computeFeePayerBalanceLeafSlot } from '@aztec/protocol-contracts/fee-juice';
4
4
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
5
5
  import { MerkleTreeId, PublicDataTreeLeaf } from '@aztec/stdlib/trees';
6
+ import type { GenesisData } from '@aztec/stdlib/world-state';
6
7
 
7
8
  import { NativeWorldStateService } from './native/index.js';
8
9
 
9
- async function generateGenesisValues(prefilledPublicData: PublicDataTreeLeaf[]) {
10
- if (!prefilledPublicData.length) {
10
+ async function generateGenesisValues(genesis: GenesisData) {
11
+ // The GENESIS_ARCHIVE_ROOT constant reflects the canonical empty genesis (no public data, no prefilled nullifiers,
12
+ // timestamp 0), so we can only short-circuit when this genesis adds none of those on top.
13
+ if (!genesis.prefilledPublicData.length && genesis.genesisTimestamp === 0n && !genesis.prefilledNullifiers?.length) {
11
14
  return {
12
15
  genesisArchiveRoot: new Fr(GENESIS_ARCHIVE_ROOT),
13
16
  };
14
17
  }
15
18
 
16
- // Create a temporary world state to compute the genesis values.
17
- const ws = await NativeWorldStateService.tmp(
18
- undefined /* rollupAddress */,
19
- true /* cleanupTmpDir */,
20
- prefilledPublicData,
21
- );
19
+ // Compute the genesis values on a throwaway world state. The archive root derives deterministically from the
20
+ // prefilled public data, the prefilled nullifiers, and the genesis timestamp, so the fsync-off ephemeral store (no
21
+ // version manager, no crash-recoverability) produces an identical root while skipping the fsync overhead that `tmp`
22
+ // pays. close() removes the tmpdir.
23
+ const ws = await NativeWorldStateService.ephemeral(genesis);
22
24
  const genesisArchiveRoot = new Fr((await ws.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
23
25
  await ws.close();
24
26
 
@@ -33,6 +35,8 @@ export async function getGenesisValues(
33
35
  initialAccounts: AztecAddress[],
34
36
  initialAccountFeeJuice = defaultInitialAccountFeeJuice,
35
37
  genesisPublicData: PublicDataTreeLeaf[] = [],
38
+ genesisTimestamp: bigint = 0n,
39
+ prefilledNullifiers: Fr[] = [],
36
40
  ) {
37
41
  // Top up the accounts with fee juice.
38
42
  let prefilledPublicData = await Promise.all(
@@ -46,11 +50,16 @@ export async function getGenesisValues(
46
50
 
47
51
  prefilledPublicData.sort((a, b) => (b.slot.lt(a.slot) ? 1 : -1));
48
52
 
49
- const { genesisArchiveRoot } = await generateGenesisValues(prefilledPublicData);
53
+ // The indexed nullifier tree requires its prefilled leaves to be unique and strictly increasing, so sort ascending
54
+ // here (a copy, to avoid mutating the caller's array) rather than relying on the caller's ordering.
55
+ const sortedNullifiers = [...prefilledNullifiers].sort((a, b) => (a.toBigInt() < b.toBigInt() ? -1 : 1));
56
+
57
+ const genesis: GenesisData = { prefilledPublicData, prefilledNullifiers: sortedNullifiers, genesisTimestamp };
58
+ const { genesisArchiveRoot } = await generateGenesisValues(genesis);
50
59
 
51
60
  return {
52
61
  genesisArchiveRoot,
53
- prefilledPublicData,
62
+ genesis,
54
63
  fundingNeeded: BigInt(initialAccounts.length) * initialAccountFeeJuice.toBigInt(),
55
64
  };
56
65
  }
@@ -1,19 +0,0 @@
1
- import { WorldStateMessageType } from './message.js';
2
- export declare const MUTATING_MSG_TYPES: Set<WorldStateMessageType>;
3
- export declare class WorldStateOpsQueue {
4
- private requests;
5
- private inFlightMutatingCount;
6
- private inFlightCount;
7
- private stopPromise?;
8
- private stopResolve?;
9
- private requestId;
10
- private ops;
11
- execute(request: () => Promise<any>, messageType: WorldStateMessageType, committedOnly: boolean): Promise<any>;
12
- private executeMutating;
13
- private executeNonMutatingUncommitted;
14
- private executeNonMutatingCommitted;
15
- private checkAndEnqueue;
16
- private sendEnqueuedRequest;
17
- stop(): Promise<void>;
18
- }
19
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid29ybGRfc3RhdGVfb3BzX3F1ZXVlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbmF0aXZlL3dvcmxkX3N0YXRlX29wc19xdWV1ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQSxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxjQUFjLENBQUM7QUF5QnJELGVBQU8sTUFBTSxrQkFBa0IsNEJBZ0I3QixDQUFDO0FBR0gscUJBQWEsa0JBQWtCO0lBQzdCLE9BQU8sQ0FBQyxRQUFRLENBQXNCO0lBQ3RDLE9BQU8sQ0FBQyxxQkFBcUIsQ0FBSztJQUNsQyxPQUFPLENBQUMsYUFBYSxDQUFLO0lBQzFCLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBZ0I7SUFDcEMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxDQUFhO0lBQ2pDLE9BQU8sQ0FBQyxTQUFTLENBQUs7SUFDdEIsT0FBTyxDQUFDLEdBQUcsQ0FBd0M7SUFJNUMsT0FBTyxDQUFDLE9BQU8sRUFBRSxNQUFNLE9BQU8sQ0FBQyxHQUFHLENBQUMsRUFBRSxXQUFXLEVBQUUscUJBQXFCLEVBQUUsYUFBYSxFQUFFLE9BQU8sZ0JBc0JyRztJQUdELE9BQU8sQ0FBQyxlQUFlO0lBV3ZCLE9BQU8sQ0FBQyw2QkFBNkI7SUFZckMsT0FBTyxDQUFDLDJCQUEyQjtJQVVuQyxPQUFPLENBQUMsZUFBZTtJQXlDdkIsT0FBTyxDQUFDLG1CQUFtQjtJQWdCcEIsSUFBSSxrQkFnQlY7Q0FDRiJ9
@@ -1 +0,0 @@
1
- {"version":3,"file":"world_state_ops_queue.d.ts","sourceRoot":"","sources":["../../src/native/world_state_ops_queue.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAyBrD,eAAO,MAAM,kBAAkB,4BAgB7B,CAAC;AAGH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,qBAAqB,CAAK;IAClC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,WAAW,CAAC,CAAgB;IACpC,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,GAAG,CAAwC;IAI5C,OAAO,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE,WAAW,EAAE,qBAAqB,EAAE,aAAa,EAAE,OAAO,gBAsBrG;IAGD,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,6BAA6B;IAYrC,OAAO,CAAC,2BAA2B;IAUnC,OAAO,CAAC,eAAe;IAyCvB,OAAO,CAAC,mBAAmB;IAgBpB,IAAI,kBAgBV;CACF"}
@@ -1,146 +0,0 @@
1
- import { promiseWithResolvers } from '@aztec/foundation/promise';
2
- import { WorldStateMessageType } from './message.js';
3
- // These are the set of message types that implement mutating operations
4
- // Messages of these types require exclusive access to their given forks
5
- export const MUTATING_MSG_TYPES = new Set([
6
- WorldStateMessageType.APPEND_LEAVES,
7
- WorldStateMessageType.BATCH_INSERT,
8
- WorldStateMessageType.SEQUENTIAL_INSERT,
9
- WorldStateMessageType.UPDATE_ARCHIVE,
10
- WorldStateMessageType.COMMIT,
11
- WorldStateMessageType.ROLLBACK,
12
- WorldStateMessageType.SYNC_BLOCK,
13
- WorldStateMessageType.CREATE_FORK,
14
- WorldStateMessageType.DELETE_FORK,
15
- WorldStateMessageType.FINALIZE_BLOCKS,
16
- WorldStateMessageType.UNWIND_BLOCKS,
17
- WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS,
18
- WorldStateMessageType.CREATE_CHECKPOINT,
19
- WorldStateMessageType.COMMIT_CHECKPOINT,
20
- WorldStateMessageType.REVERT_CHECKPOINT
21
- ]);
22
- // This class implements the per-fork operation queue
23
- export class WorldStateOpsQueue {
24
- requests = [];
25
- inFlightMutatingCount = 0;
26
- inFlightCount = 0;
27
- stopPromise;
28
- stopResolve;
29
- requestId = 0;
30
- ops = new Map();
31
- // The primary public api, this is where an operation is queued
32
- // We return a promise that will ultimately be resolved/rejected with the response/error generated by the 'request' argument
33
- execute(request, messageType, committedOnly) {
34
- if (this.stopResolve !== undefined) {
35
- throw new Error('Unable to send request to world state, queue already stopped');
36
- }
37
- const op = {
38
- requestId: this.requestId++,
39
- mutating: MUTATING_MSG_TYPES.has(messageType),
40
- request,
41
- promise: promiseWithResolvers()
42
- };
43
- this.ops.set(op.requestId, op);
44
- // Perform the appropriate action based upon the queueing rules
45
- if (op.mutating) {
46
- this.executeMutating(op);
47
- } else if (committedOnly === false) {
48
- this.executeNonMutatingUncommitted(op);
49
- } else {
50
- this.executeNonMutatingCommitted(op);
51
- }
52
- return op.promise.promise;
53
- }
54
- // Mutating requests need exclusive access
55
- executeMutating(op) {
56
- // If nothing is in flight then we send the request immediately
57
- // Otherwise add to the queue
58
- if (this.inFlightCount === 0) {
59
- this.sendEnqueuedRequest(op);
60
- } else {
61
- this.requests.push(op);
62
- }
63
- }
64
- // Non mutating requests including uncommitted state
65
- executeNonMutatingUncommitted(op) {
66
- // If there are no mutating requests in flight and there is nothing queued
67
- // then send the request immediately
68
- // If a mutating request is in flight then we must wait
69
- // If a mutating request is not in flight but something is queued then it must be a mutating request
70
- if (this.inFlightMutatingCount == 0 && this.requests.length == 0) {
71
- this.sendEnqueuedRequest(op);
72
- } else {
73
- this.requests.push(op);
74
- }
75
- }
76
- executeNonMutatingCommitted(op) {
77
- // This is a non-mutating request for committed data
78
- // It can always be sent
79
- op.request().then(op.promise.resolve, op.promise.reject).finally(()=>{
80
- this.ops.delete(op.requestId);
81
- });
82
- }
83
- checkAndEnqueue(completedOp) {
84
- // As request has completed
85
- // First we decrements the relevant in flight counters
86
- if (completedOp.mutating) {
87
- --this.inFlightMutatingCount;
88
- }
89
- --this.inFlightCount;
90
- // If there are still requests in flight then do nothing further
91
- if (this.inFlightCount != 0) {
92
- return;
93
- }
94
- // No requests in flight, send next queued requests
95
- // We loop and send:
96
- // 1 mutating request if it is next in the queue
97
- // As many non-mutating requests as we encounter until
98
- // we exhaust the queue or we reach a mutating request
99
- while(this.requests.length > 0){
100
- const next = this.requests[0];
101
- if (next.mutating) {
102
- if (this.inFlightCount == 0) {
103
- // send the mutating request
104
- this.requests.shift();
105
- this.sendEnqueuedRequest(next);
106
- }
107
- break;
108
- } else {
109
- // not mutating, send and go round again
110
- this.requests.shift();
111
- this.sendEnqueuedRequest(next);
112
- }
113
- }
114
- // If the queue is empty, there is nothing in flight and we have been told to stop, then resolve the stop promise
115
- if (this.inFlightCount == 0 && this.stopResolve !== undefined) {
116
- this.stopResolve();
117
- }
118
- }
119
- sendEnqueuedRequest(op) {
120
- // Here we increment the in flight counts before sending
121
- ++this.inFlightCount;
122
- if (op.mutating) {
123
- ++this.inFlightMutatingCount;
124
- }
125
- // Make the request and pass the response/error through to the stored promise
126
- op.request().then(op.promise.resolve, op.promise.reject).finally(()=>{
127
- this.checkAndEnqueue(op);
128
- this.ops.delete(op.requestId);
129
- });
130
- }
131
- stop() {
132
- // If there is already a stop promise then return it
133
- if (this.stopPromise) {
134
- return this.stopPromise;
135
- }
136
- // Otherwise create a new one and capture the resolve method
137
- this.stopPromise = new Promise((resolve)=>{
138
- this.stopResolve = resolve;
139
- });
140
- // If no outstanding requests then immediately resolve the promise
141
- if (this.requests.length == 0 && this.inFlightCount == 0 && this.stopResolve !== undefined) {
142
- this.stopResolve();
143
- }
144
- return this.stopPromise;
145
- }
146
- }
@@ -1,190 +0,0 @@
1
- import { promiseWithResolvers } from '@aztec/foundation/promise';
2
-
3
- import { WorldStateMessageType } from './message.js';
4
-
5
- /**
6
- * This is the implementation for queueing requests to the world state.
7
- * Requests need to be queued for the world state to ensure that writes are correctly ordered
8
- * and reads return the correct data.
9
- * Due to the nature of the NAPI we can't really do this there.
10
- *
11
- * The rules for queueing are as follows:
12
- *
13
- * 1. Reads of committed state never need to be queued. LMDB uses MVCC to ensure readers see a consistent view of the DB.
14
- * 2. Reads of uncommitted state can happen concurrently with other reads of uncommitted state on the same fork (or reads of committed state)
15
- * 3. All writes require exclusive access to their respective fork
16
- *
17
- */
18
-
19
- type WorldStateOp = {
20
- requestId: number;
21
- mutating: boolean;
22
- request: () => Promise<any>;
23
- promise: PromiseWithResolvers<any>;
24
- };
25
-
26
- // These are the set of message types that implement mutating operations
27
- // Messages of these types require exclusive access to their given forks
28
- export const MUTATING_MSG_TYPES = new Set([
29
- WorldStateMessageType.APPEND_LEAVES,
30
- WorldStateMessageType.BATCH_INSERT,
31
- WorldStateMessageType.SEQUENTIAL_INSERT,
32
- WorldStateMessageType.UPDATE_ARCHIVE,
33
- WorldStateMessageType.COMMIT,
34
- WorldStateMessageType.ROLLBACK,
35
- WorldStateMessageType.SYNC_BLOCK,
36
- WorldStateMessageType.CREATE_FORK,
37
- WorldStateMessageType.DELETE_FORK,
38
- WorldStateMessageType.FINALIZE_BLOCKS,
39
- WorldStateMessageType.UNWIND_BLOCKS,
40
- WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS,
41
- WorldStateMessageType.CREATE_CHECKPOINT,
42
- WorldStateMessageType.COMMIT_CHECKPOINT,
43
- WorldStateMessageType.REVERT_CHECKPOINT,
44
- ]);
45
-
46
- // This class implements the per-fork operation queue
47
- export class WorldStateOpsQueue {
48
- private requests: WorldStateOp[] = [];
49
- private inFlightMutatingCount = 0;
50
- private inFlightCount = 0;
51
- private stopPromise?: Promise<void>;
52
- private stopResolve?: () => void;
53
- private requestId = 0;
54
- private ops: Map<number, WorldStateOp> = new Map();
55
-
56
- // The primary public api, this is where an operation is queued
57
- // We return a promise that will ultimately be resolved/rejected with the response/error generated by the 'request' argument
58
- public execute(request: () => Promise<any>, messageType: WorldStateMessageType, committedOnly: boolean) {
59
- if (this.stopResolve !== undefined) {
60
- throw new Error('Unable to send request to world state, queue already stopped');
61
- }
62
-
63
- const op: WorldStateOp = {
64
- requestId: this.requestId++,
65
- mutating: MUTATING_MSG_TYPES.has(messageType),
66
- request,
67
- promise: promiseWithResolvers(),
68
- };
69
- this.ops.set(op.requestId, op);
70
-
71
- // Perform the appropriate action based upon the queueing rules
72
- if (op.mutating) {
73
- this.executeMutating(op);
74
- } else if (committedOnly === false) {
75
- this.executeNonMutatingUncommitted(op);
76
- } else {
77
- this.executeNonMutatingCommitted(op);
78
- }
79
- return op.promise.promise;
80
- }
81
-
82
- // Mutating requests need exclusive access
83
- private executeMutating(op: WorldStateOp) {
84
- // If nothing is in flight then we send the request immediately
85
- // Otherwise add to the queue
86
- if (this.inFlightCount === 0) {
87
- this.sendEnqueuedRequest(op);
88
- } else {
89
- this.requests.push(op);
90
- }
91
- }
92
-
93
- // Non mutating requests including uncommitted state
94
- private executeNonMutatingUncommitted(op: WorldStateOp) {
95
- // If there are no mutating requests in flight and there is nothing queued
96
- // then send the request immediately
97
- // If a mutating request is in flight then we must wait
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) {
100
- this.sendEnqueuedRequest(op);
101
- } else {
102
- this.requests.push(op);
103
- }
104
- }
105
-
106
- private executeNonMutatingCommitted(op: WorldStateOp) {
107
- // This is a non-mutating request for committed data
108
- // It can always be sent
109
- op.request()
110
- .then(op.promise.resolve, op.promise.reject)
111
- .finally(() => {
112
- this.ops.delete(op.requestId);
113
- });
114
- }
115
-
116
- private checkAndEnqueue(completedOp: WorldStateOp) {
117
- // As request has completed
118
- // First we decrements the relevant in flight counters
119
- if (completedOp.mutating) {
120
- --this.inFlightMutatingCount;
121
- }
122
- --this.inFlightCount;
123
-
124
- // If there are still requests in flight then do nothing further
125
- if (this.inFlightCount != 0) {
126
- return;
127
- }
128
-
129
- // No requests in flight, send next queued requests
130
- // We loop and send:
131
- // 1 mutating request if it is next in the queue
132
- // As many non-mutating requests as we encounter until
133
- // we exhaust the queue or we reach a mutating request
134
- while (this.requests.length > 0) {
135
- const next = this.requests[0];
136
- if (next.mutating) {
137
- if (this.inFlightCount == 0) {
138
- // send the mutating request
139
- this.requests.shift();
140
- this.sendEnqueuedRequest(next);
141
- }
142
- // this request is mutating, we need to stop here
143
- break;
144
- } else {
145
- // not mutating, send and go round again
146
- this.requests.shift();
147
- this.sendEnqueuedRequest(next);
148
- }
149
- }
150
-
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) {
153
- this.stopResolve();
154
- }
155
- }
156
-
157
- private sendEnqueuedRequest(op: WorldStateOp) {
158
- // Here we increment the in flight counts before sending
159
- ++this.inFlightCount;
160
- if (op.mutating) {
161
- ++this.inFlightMutatingCount;
162
- }
163
-
164
- // Make the request and pass the response/error through to the stored promise
165
- op.request()
166
- .then(op.promise.resolve, op.promise.reject)
167
- .finally(() => {
168
- this.checkAndEnqueue(op);
169
- this.ops.delete(op.requestId);
170
- });
171
- }
172
-
173
- public stop() {
174
- // If there is already a stop promise then return it
175
- if (this.stopPromise) {
176
- return this.stopPromise;
177
- }
178
-
179
- // Otherwise create a new one and capture the resolve method
180
- this.stopPromise = new Promise(resolve => {
181
- this.stopResolve = resolve;
182
- });
183
-
184
- // If no outstanding requests then immediately resolve the promise
185
- if (this.requests.length == 0 && this.inFlightCount == 0 && this.stopResolve !== undefined) {
186
- this.stopResolve();
187
- }
188
- return this.stopPromise;
189
- }
190
- }