@aztec/world-state 0.0.1-commit.d431d1c → 0.0.1-commit.d58ff9d0

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 (76) 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 +13 -11
  7. package/dest/native/fork_checkpoint.d.ts +7 -1
  8. package/dest/native/fork_checkpoint.d.ts.map +1 -1
  9. package/dest/native/fork_checkpoint.js +15 -3
  10. package/dest/native/index.d.ts +2 -1
  11. package/dest/native/index.d.ts.map +1 -1
  12. package/dest/native/index.js +1 -0
  13. package/dest/native/ipc_world_state_instance.d.ts +89 -0
  14. package/dest/native/ipc_world_state_instance.d.ts.map +1 -0
  15. package/dest/native/ipc_world_state_instance.js +661 -0
  16. package/dest/native/merkle_trees_facade.d.ts +12 -8
  17. package/dest/native/merkle_trees_facade.d.ts.map +1 -1
  18. package/dest/native/merkle_trees_facade.js +63 -115
  19. package/dest/native/message.d.ts +14 -221
  20. package/dest/native/message.d.ts.map +1 -1
  21. package/dest/native/message.js +0 -42
  22. package/dest/native/native_world_state.d.ts +22 -10
  23. package/dest/native/native_world_state.d.ts.map +1 -1
  24. package/dest/native/native_world_state.js +122 -59
  25. package/dest/native/native_world_state_instance.d.ts +56 -42
  26. package/dest/native/native_world_state_instance.d.ts.map +1 -1
  27. package/dest/native/native_world_state_instance.js +1 -203
  28. package/dest/native/world_state_operation.d.ts +7 -0
  29. package/dest/native/world_state_operation.d.ts.map +1 -0
  30. package/dest/native/world_state_operation.js +5 -0
  31. package/dest/synchronizer/config.d.ts +3 -5
  32. package/dest/synchronizer/config.d.ts.map +1 -1
  33. package/dest/synchronizer/config.js +15 -18
  34. package/dest/synchronizer/errors.d.ts +8 -1
  35. package/dest/synchronizer/errors.d.ts.map +1 -1
  36. package/dest/synchronizer/errors.js +8 -1
  37. package/dest/synchronizer/factory.d.ts +6 -5
  38. package/dest/synchronizer/factory.d.ts.map +1 -1
  39. package/dest/synchronizer/factory.js +7 -6
  40. package/dest/synchronizer/index.d.ts +2 -1
  41. package/dest/synchronizer/index.d.ts.map +1 -1
  42. package/dest/synchronizer/index.js +1 -0
  43. package/dest/synchronizer/server_world_state_synchronizer.d.ts +13 -8
  44. package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
  45. package/dest/synchronizer/server_world_state_synchronizer.js +134 -49
  46. package/dest/test/utils.d.ts +6 -6
  47. package/dest/test/utils.d.ts.map +1 -1
  48. package/dest/test/utils.js +9 -4
  49. package/dest/testing.d.ts +4 -3
  50. package/dest/testing.d.ts.map +1 -1
  51. package/dest/testing.js +14 -7
  52. package/dest/world-state-db/merkle_tree_db.d.ts +3 -12
  53. package/dest/world-state-db/merkle_tree_db.d.ts.map +1 -1
  54. package/package.json +13 -11
  55. package/src/index.ts +1 -0
  56. package/src/instrumentation/instrumentation.ts +15 -19
  57. package/src/native/fork_checkpoint.ts +19 -3
  58. package/src/native/index.ts +1 -0
  59. package/src/native/ipc_world_state_instance.ts +834 -0
  60. package/src/native/merkle_trees_facade.ts +89 -109
  61. package/src/native/message.ts +13 -286
  62. package/src/native/native_world_state.ts +152 -93
  63. package/src/native/native_world_state_instance.ts +94 -279
  64. package/src/native/world_state_operation.ts +33 -0
  65. package/src/synchronizer/config.ts +16 -23
  66. package/src/synchronizer/errors.ts +8 -0
  67. package/src/synchronizer/factory.ts +18 -11
  68. package/src/synchronizer/index.ts +1 -0
  69. package/src/synchronizer/server_world_state_synchronizer.ts +156 -51
  70. package/src/test/utils.ts +14 -5
  71. package/src/testing.ts +12 -10
  72. package/src/world-state-db/merkle_tree_db.ts +2 -12
  73. package/dest/native/world_state_ops_queue.d.ts +0 -19
  74. package/dest/native/world_state_ops_queue.d.ts.map +0 -1
  75. package/dest/native/world_state_ops_queue.js +0 -146
  76. package/src/native/world_state_ops_queue.ts +0 -190
@@ -1,19 +1,17 @@
1
- import { GENESIS_BLOCK_HEADER_HASH, INITIAL_L2_BLOCK_NUM, INITIAL_L2_CHECKPOINT_NUM } from '@aztec/constants';
2
- import { BlockNumber } from '@aztec/foundation/branded-types';
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';
5
4
  import { promiseWithResolvers } from '@aztec/foundation/promise';
6
5
  import { elapsed } from '@aztec/foundation/timer';
7
6
  import {
8
- GENESIS_CHECKPOINT_HEADER_HASH,
9
- type L2BlockId,
10
- type L2BlockNew,
7
+ type BlockHash,
8
+ EventDrivenL2BlockStream,
9
+ type L2Block,
11
10
  type L2BlockSource,
12
- L2BlockStream,
13
11
  type L2BlockStreamEvent,
14
12
  type L2BlockStreamEventHandler,
15
13
  type L2BlockStreamLocalDataProvider,
16
- type L2Tips,
14
+ type LocalChainTips,
17
15
  } from '@aztec/stdlib/block';
18
16
  import {
19
17
  WorldStateRunningState,
@@ -50,7 +48,7 @@ export class ServerWorldStateSynchronizer
50
48
  private currentState: WorldStateRunningState = WorldStateRunningState.IDLE;
51
49
 
52
50
  private syncPromise = promiseWithResolvers<void>();
53
- protected blockStream: L2BlockStream | undefined;
51
+ protected blockStream: EventDrivenL2BlockStream | undefined;
54
52
 
55
53
  // WorldState doesn't track the proven block number, it only tracks the latest tips of the pending chain and the finalized chain
56
54
  // store the proven block number here, in the synchronizer, so that we don't end up spamming the logs with 'chain-proved' events
@@ -64,7 +62,7 @@ export class ServerWorldStateSynchronizer
64
62
  private readonly log: Logger = createLogger('world_state'),
65
63
  ) {
66
64
  this.merkleTreeCommitted = this.merkleTreeDb.getCommitted();
67
- this.historyToKeep = config.worldStateBlockHistory < 1 ? undefined : config.worldStateBlockHistory;
65
+ this.historyToKeep = config.worldStateCheckpointHistory < 1 ? undefined : config.worldStateCheckpointHistory;
68
66
  this.log.info(
69
67
  `Created world state synchroniser with block history of ${
70
68
  this.historyToKeep === undefined ? 'infinity' : this.historyToKeep
@@ -80,6 +78,46 @@ export class ServerWorldStateSynchronizer
80
78
  return this.merkleTreeDb.getSnapshot(blockNumber);
81
79
  }
82
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
+
83
121
  public fork(blockNumber?: BlockNumber, opts?: { closeDelayMs?: number }): Promise<MerkleTreeWriteOperations> {
84
122
  return this.merkleTreeDb.fork(blockNumber, opts);
85
123
  }
@@ -101,11 +139,7 @@ export class ServerWorldStateSynchronizer
101
139
  }
102
140
 
103
141
  // Get the current latest block number
104
- this.latestBlockNumberAtStart = BlockNumber(
105
- await (this.config.worldStateProvenBlocksOnly
106
- ? this.l2BlockSource.getProvenBlockNumber()
107
- : this.l2BlockSource.getBlockNumber()),
108
- );
142
+ this.latestBlockNumberAtStart = BlockNumber(await this.l2BlockSource.getBlockNumber());
109
143
 
110
144
  const blockToDownloadFrom = (await this.getLatestBlockNumber()) + 1;
111
145
 
@@ -126,10 +160,9 @@ export class ServerWorldStateSynchronizer
126
160
  return this.syncPromise.promise;
127
161
  }
128
162
 
129
- protected createBlockStream(): L2BlockStream {
163
+ protected createBlockStream(): EventDrivenL2BlockStream {
130
164
  const logger = createLogger('world-state:block_stream');
131
- return new L2BlockStream(this.l2BlockSource, this, this, logger, {
132
- proven: this.config.worldStateProvenBlocksOnly,
165
+ return new EventDrivenL2BlockStream(this.l2BlockSource, this, this, logger, {
133
166
  pollIntervalMS: this.config.worldStateBlockCheckIntervalMS,
134
167
  batchSize: this.config.worldStateBlockRequestBatchSize,
135
168
  ignoreCheckpoints: true,
@@ -182,13 +215,10 @@ export class ServerWorldStateSynchronizer
182
215
  /**
183
216
  * Forces an immediate sync.
184
217
  * @param targetBlockNumber - The target block number that we must sync to. Will download unproven blocks if needed to reach it.
185
- * @param skipThrowIfTargetNotReached - Whether to skip throwing if the target block number is not reached.
218
+ * @param blockHash - If provided, verifies the block at targetBlockNumber matches this hash. On mismatch, triggers a resync (reorg detection).
186
219
  * @returns A promise that resolves with the block number the world state was synced to
187
220
  */
188
- public async syncImmediate(
189
- targetBlockNumber?: BlockNumber,
190
- skipThrowIfTargetNotReached?: boolean,
191
- ): Promise<BlockNumber> {
221
+ public async syncImmediate(targetBlockNumber?: BlockNumber, blockHash?: BlockHash): Promise<BlockNumber> {
192
222
  if (this.currentState !== WorldStateRunningState.RUNNING) {
193
223
  throw new Error(`World State is not running. Unable to perform sync.`);
194
224
  }
@@ -200,7 +230,19 @@ export class ServerWorldStateSynchronizer
200
230
  // If we have been given a block number to sync to and we have reached that number then return
201
231
  const currentBlockNumber = await this.getLatestBlockNumber();
202
232
  if (targetBlockNumber !== undefined && targetBlockNumber <= currentBlockNumber) {
203
- return currentBlockNumber;
233
+ if (blockHash === undefined) {
234
+ return currentBlockNumber;
235
+ }
236
+
237
+ // If a block hash was provided, verify we're on the expected fork
238
+ const currentHash = await this.getL2BlockHash(targetBlockNumber);
239
+ if (currentHash === blockHash.toString()) {
240
+ return currentBlockNumber;
241
+ }
242
+ // Hash mismatch: a reorg may have occurred, fall through to trigger sync
243
+ this.log.debug(
244
+ `World state block hash mismatch at ${targetBlockNumber} (expected ${blockHash}, got ${currentHash}). Triggering resync.`,
245
+ );
204
246
  }
205
247
  this.log.debug(`World State at ${currentBlockNumber} told to sync to ${targetBlockNumber ?? 'latest'}`);
206
248
 
@@ -218,7 +260,7 @@ export class ServerWorldStateSynchronizer
218
260
 
219
261
  // If we have been given a block number to sync to and we have not reached that number then fail
220
262
  const updatedBlockNumber = await this.getLatestBlockNumber();
221
- if (!skipThrowIfTargetNotReached && targetBlockNumber !== undefined && targetBlockNumber > updatedBlockNumber) {
263
+ if (targetBlockNumber !== undefined && targetBlockNumber > updatedBlockNumber) {
222
264
  throw new WorldStateSynchronizerError(
223
265
  `Unable to sync to block number ${targetBlockNumber} (last synced is ${updatedBlockNumber})`,
224
266
  {
@@ -232,6 +274,24 @@ export class ServerWorldStateSynchronizer
232
274
  );
233
275
  }
234
276
 
277
+ // If a block hash was provided, verify we're on the expected fork after syncing, throw otherwise
278
+ if (blockHash !== undefined && targetBlockNumber !== undefined) {
279
+ const updatedHash = await this.getL2BlockHash(targetBlockNumber);
280
+ if (updatedHash !== blockHash.toString()) {
281
+ throw new WorldStateSynchronizerError(
282
+ `Block hash mismatch at block ${targetBlockNumber} (expected ${blockHash} but got ${updatedHash})`,
283
+ {
284
+ cause: {
285
+ reason: 'block_hash_mismatch',
286
+ targetBlockNumber,
287
+ expectedHash: blockHash.toString(),
288
+ actualHash: updatedHash,
289
+ },
290
+ },
291
+ );
292
+ }
293
+ }
294
+
235
295
  return updatedBlockNumber;
236
296
  }
237
297
 
@@ -243,8 +303,12 @@ export class ServerWorldStateSynchronizer
243
303
  return this.merkleTreeCommitted.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(number)).then(leaf => leaf?.toString());
244
304
  }
245
305
 
246
- /** Returns the latest L2 block number for each tip of the chain (latest, proven, finalized). */
247
- 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> {
248
312
  const status = await this.merkleTreeDb.getStatusSummary();
249
313
  const unfinalizedBlockHashPromise = this.getL2BlockHash(status.unfinalizedBlockNumber);
250
314
  const finalizedBlockHashPromise = this.getL2BlockHash(status.finalizedBlockNumber);
@@ -258,25 +322,13 @@ export class ServerWorldStateSynchronizer
258
322
  finalizedBlockHashPromise,
259
323
  provenBlockHashPromise,
260
324
  ]);
261
- const latestBlockId: L2BlockId = { number: status.unfinalizedBlockNumber, hash: unfinalizedBlockHash! };
262
-
263
- // World state doesn't track checkpointed blocks or checkpoints themselves.
264
- // but we use a block stream so we need to provide 'local' L2Tips.
265
- // We configure the block stream to ignore checkpoints and set checkpoint values to genesis here.
266
- const genesisCheckpointHeaderHash = GENESIS_CHECKPOINT_HEADER_HASH.toString();
267
325
  return {
268
- proposed: latestBlockId,
269
- checkpointed: {
270
- block: { number: INITIAL_L2_BLOCK_NUM, hash: GENESIS_BLOCK_HEADER_HASH.toString() },
271
- checkpoint: { number: INITIAL_L2_CHECKPOINT_NUM, hash: genesisCheckpointHeaderHash },
272
- },
326
+ proposed: { number: status.unfinalizedBlockNumber, hash: unfinalizedBlockHash },
273
327
  finalized: {
274
- block: { number: status.finalizedBlockNumber, hash: finalizedBlockHash ?? '' },
275
- checkpoint: { number: INITIAL_L2_CHECKPOINT_NUM, hash: genesisCheckpointHeaderHash },
328
+ block: { number: status.finalizedBlockNumber, hash: finalizedBlockHash },
276
329
  },
277
330
  proven: {
278
- block: { number: provenBlockNumber, hash: provenBlockHash ?? '' },
279
- checkpoint: { number: INITIAL_L2_CHECKPOINT_NUM, hash: genesisCheckpointHeaderHash },
331
+ block: { number: provenBlockNumber, hash: provenBlockHash },
280
332
  },
281
333
  };
282
334
  }
@@ -296,6 +348,15 @@ export class ServerWorldStateSynchronizer
296
348
  case 'chain-finalized':
297
349
  await this.handleChainFinalized(event.block.number);
298
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
+ }
299
360
  }
300
361
  }
301
362
 
@@ -304,8 +365,8 @@ export class ServerWorldStateSynchronizer
304
365
  * @param l2Blocks - The L2 blocks to handle.
305
366
  * @returns Whether the block handled was produced by this same node.
306
367
  */
307
- private async handleL2Blocks(l2Blocks: L2BlockNew[]) {
308
- this.log.trace(`Handling L2 blocks ${l2Blocks[0].number} to ${l2Blocks.at(-1)!.number}`);
368
+ private async handleL2Blocks(l2Blocks: L2Block[]) {
369
+ this.log.debug(`Handling L2 blocks ${l2Blocks[0].number} to ${l2Blocks.at(-1)!.number}`);
309
370
 
310
371
  // Fetch the L1->L2 messages for the first block in a checkpoint.
311
372
  const messagesForBlocks = new Map<BlockNumber, Fr[]>();
@@ -345,11 +406,13 @@ export class ServerWorldStateSynchronizer
345
406
  * @param l1ToL2Messages - The L1 to L2 messages for the block.
346
407
  * @returns Whether the block handled was produced by this same node.
347
408
  */
348
- private async handleL2Block(l2Block: L2BlockNew, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull> {
349
- this.log.trace(`Pushing L2 block ${l2Block.number} to merkle tree db `, {
409
+ private async handleL2Block(l2Block: L2Block, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull> {
410
+ this.log.debug(`Pushing L2 block ${l2Block.number} to merkle tree db `, {
350
411
  blockNumber: l2Block.number,
351
412
  blockHash: await l2Block.hash().then(h => h.toString()),
352
413
  l1ToL2Messages: l1ToL2Messages.map(msg => msg.toString()),
414
+ blockHeader: l2Block.header.toInspect(),
415
+ blockStats: l2Block.getStats(),
353
416
  });
354
417
  const result = await this.merkleTreeDb.handleL2BlockAndMessages(l2Block, l1ToL2Messages);
355
418
 
@@ -363,16 +426,56 @@ export class ServerWorldStateSynchronizer
363
426
 
364
427
  private async handleChainFinalized(blockNumber: BlockNumber) {
365
428
  this.log.verbose(`Finalized chain is now at block ${blockNumber}`);
429
+ // If the finalized block number is older than the oldest available block in world state,
430
+ // skip entirely. The finalized block number can jump backwards (e.g. when the finalization
431
+ // heuristic changes) and try to read block data that has already been pruned. When this
432
+ // happens, there is nothing useful to do — the native world state is already finalized
433
+ // past this point and pruning has already happened.
434
+ const currentSummary = await this.merkleTreeDb.getStatusSummary();
435
+ if (blockNumber < currentSummary.oldestHistoricalBlock || blockNumber < 1) {
436
+ this.log.trace(
437
+ `Finalized block ${blockNumber} is older than the oldest available block ${currentSummary.oldestHistoricalBlock}. Skipping.`,
438
+ );
439
+ return;
440
+ }
366
441
  const summary = await this.merkleTreeDb.setFinalized(blockNumber);
367
442
  if (this.historyToKeep === undefined) {
368
443
  return;
369
444
  }
370
- const newHistoricBlock = summary.finalizedBlockNumber - this.historyToKeep + 1;
371
- if (newHistoricBlock <= 1) {
445
+ const finalisedBlockData = await this.l2BlockSource.getBlockData({ number: summary.finalizedBlockNumber });
446
+ if (finalisedBlockData === undefined) {
447
+ this.log.warn(
448
+ `Failed to retrieve checkpointed block for finalized block number: ${summary.finalizedBlockNumber}`,
449
+ );
372
450
  return;
373
451
  }
374
- this.log.verbose(`Pruning historic blocks to ${newHistoricBlock}`);
375
- const status = await this.merkleTreeDb.removeHistoricalBlocks(BlockNumber(newHistoricBlock));
452
+ // Compute the required historic checkpoint number
453
+ const newHistoricCheckpointNumber = finalisedBlockData.checkpointNumber - this.historyToKeep + 1;
454
+ if (newHistoricCheckpointNumber <= 1) {
455
+ return;
456
+ }
457
+ // Retrieve the historic checkpoint
458
+ const historicCheckpoint = await this.l2BlockSource.getCheckpoint({
459
+ number: CheckpointNumber(newHistoricCheckpointNumber),
460
+ });
461
+ if (!historicCheckpoint) {
462
+ this.log.warn(`Failed to retrieve checkpoint number ${newHistoricCheckpointNumber} from Archiver`);
463
+ return;
464
+ }
465
+ if (historicCheckpoint.checkpoint.blocks.length === 0 || historicCheckpoint.checkpoint.blocks[0] === undefined) {
466
+ this.log.warn(`Retrieved checkpoint number ${newHistoricCheckpointNumber} has no blocks!`);
467
+ return;
468
+ }
469
+ // Find the block at the start of the checkpoint and remove blocks up to this one
470
+ const newHistoricBlock = historicCheckpoint.checkpoint.blocks[0];
471
+ if (newHistoricBlock.number <= currentSummary.oldestHistoricalBlock) {
472
+ this.log.debug(
473
+ `Historic block ${newHistoricBlock.number} is not newer than oldest available ${currentSummary.oldestHistoricalBlock}. Skipping prune.`,
474
+ );
475
+ return;
476
+ }
477
+ this.log.verbose(`Pruning historic blocks to ${newHistoricBlock.number}`);
478
+ const status = await this.merkleTreeDb.removeHistoricalBlocks(BlockNumber(newHistoricBlock.number));
376
479
  this.log.debug(`World state summary `, status.summary);
377
480
  }
378
481
 
@@ -383,9 +486,11 @@ export class ServerWorldStateSynchronizer
383
486
  }
384
487
 
385
488
  private async handleChainPruned(blockNumber: BlockNumber) {
386
- this.log.warn(`Chain pruned to block ${blockNumber}`);
489
+ this.log.info(`Chain pruned to block ${blockNumber}`);
387
490
  const status = await this.merkleTreeDb.unwindBlocks(blockNumber);
388
- this.provenBlockNumber = undefined;
491
+ if (this.provenBlockNumber !== undefined && this.provenBlockNumber > blockNumber) {
492
+ this.provenBlockNumber = undefined;
493
+ }
389
494
  this.instrumentation.updateWorldStateMetrics(status);
390
495
  }
391
496
 
package/src/test/utils.ts CHANGED
@@ -8,7 +8,7 @@ import { asyncMap } from '@aztec/foundation/async-map';
8
8
  import { BlockNumber, type CheckpointNumber, IndexWithinCheckpoint } from '@aztec/foundation/branded-types';
9
9
  import { padArrayEnd } from '@aztec/foundation/collection';
10
10
  import { Fr } from '@aztec/foundation/curves/bn254';
11
- import { L2BlockNew } from '@aztec/stdlib/block';
11
+ import { L2Block } from '@aztec/stdlib/block';
12
12
  import type {
13
13
  IndexedTreeId,
14
14
  MerkleTreeReadOperations,
@@ -20,7 +20,7 @@ import { BlockHeader } from '@aztec/stdlib/tx';
20
20
 
21
21
  import type { NativeWorldStateService } from '../native/native_world_state.js';
22
22
 
23
- export async function updateBlockState(block: L2BlockNew, l1ToL2Messages: Fr[], fork: MerkleTreeWriteOperations) {
23
+ export async function updateBlockState(block: L2Block, l1ToL2Messages: Fr[], fork: MerkleTreeWriteOperations) {
24
24
  const insertData = async (
25
25
  treeId: IndexedTreeId,
26
26
  data: Buffer[][],
@@ -60,7 +60,16 @@ export async function updateBlockState(block: L2BlockNew, l1ToL2Messages: Fr[],
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);
@@ -76,7 +85,7 @@ export async function mockBlock(
76
85
  numL1ToL2Messages: number = NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
77
86
  isFirstBlockInCheckpoint: boolean = true,
78
87
  ) {
79
- const block = await L2BlockNew.random(blockNum, {
88
+ const block = await L2Block.random(blockNum, {
80
89
  indexWithinCheckpoint: isFirstBlockInCheckpoint ? IndexWithinCheckpoint(0) : IndexWithinCheckpoint(1),
81
90
  txsPerBlock: size,
82
91
  txOptions: { maxEffects },
@@ -92,7 +101,7 @@ export async function mockBlock(
92
101
  }
93
102
 
94
103
  export async function mockEmptyBlock(blockNum: BlockNumber, fork: MerkleTreeWriteOperations) {
95
- const l2Block = L2BlockNew.empty();
104
+ const l2Block = L2Block.empty();
96
105
  const l1ToL2Messages = Array(16).fill(0).map(Fr.zero);
97
106
 
98
107
  l2Block.header.globalVariables.blockNumber = blockNum;
package/src/testing.ts CHANGED
@@ -3,22 +3,22 @@ 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
+ if (!genesis.prefilledPublicData.length && genesis.genesisTimestamp === 0n) {
11
12
  return {
12
13
  genesisArchiveRoot: new Fr(GENESIS_ARCHIVE_ROOT),
13
14
  };
14
15
  }
15
16
 
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
- );
17
+ // Compute the genesis values on a throwaway world state. The archive root derives only from the
18
+ // prefilled public data and the genesis timestamp, so the fsync-off ephemeral store (no version
19
+ // manager, no crash-recoverability) produces an identical root while skipping the fsync overhead
20
+ // that `tmp` pays. close() removes the tmpdir.
21
+ const ws = await NativeWorldStateService.ephemeral(genesis);
22
22
  const genesisArchiveRoot = new Fr((await ws.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
23
23
  await ws.close();
24
24
 
@@ -33,6 +33,7 @@ export async function getGenesisValues(
33
33
  initialAccounts: AztecAddress[],
34
34
  initialAccountFeeJuice = defaultInitialAccountFeeJuice,
35
35
  genesisPublicData: PublicDataTreeLeaf[] = [],
36
+ genesisTimestamp: bigint = 0n,
36
37
  ) {
37
38
  // Top up the accounts with fee juice.
38
39
  let prefilledPublicData = await Promise.all(
@@ -46,11 +47,12 @@ export async function getGenesisValues(
46
47
 
47
48
  prefilledPublicData.sort((a, b) => (b.slot.lt(a.slot) ? 1 : -1));
48
49
 
49
- const { genesisArchiveRoot } = await generateGenesisValues(prefilledPublicData);
50
+ const genesis: GenesisData = { prefilledPublicData, genesisTimestamp };
51
+ const { genesisArchiveRoot } = await generateGenesisValues(genesis);
50
52
 
51
53
  return {
52
54
  genesisArchiveRoot,
53
- prefilledPublicData,
55
+ genesis,
54
56
  fundingNeeded: BigInt(initialAccounts.length) * initialAccountFeeJuice.toBigInt(),
55
57
  };
56
58
  }
@@ -1,14 +1,12 @@
1
1
  import { MAX_NULLIFIERS_PER_TX, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX } from '@aztec/constants';
2
2
  import type { BlockNumber } from '@aztec/foundation/branded-types';
3
3
  import type { Fr } from '@aztec/foundation/curves/bn254';
4
- import type { IndexedTreeSnapshot, TreeSnapshot } from '@aztec/merkle-tree';
5
- import type { L2BlockNew } from '@aztec/stdlib/block';
4
+ import type { L2Block } from '@aztec/stdlib/block';
6
5
  import type {
7
6
  ForkMerkleTreeOperations,
8
7
  MerkleTreeReadOperations,
9
8
  ReadonlyWorldStateAccess,
10
9
  } from '@aztec/stdlib/interfaces/server';
11
- import type { MerkleTreeId } from '@aztec/stdlib/trees';
12
10
 
13
11
  import type { WorldStateStatusFull, WorldStateStatusSummary } from '../native/message.js';
14
12
 
@@ -31,21 +29,13 @@ export const INITIAL_NULLIFIER_TREE_SIZE = 2 * MAX_NULLIFIERS_PER_TX;
31
29
 
32
30
  export const INITIAL_PUBLIC_DATA_TREE_SIZE = 2 * MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX;
33
31
 
34
- export type TreeSnapshots = {
35
- [MerkleTreeId.NULLIFIER_TREE]: IndexedTreeSnapshot;
36
- [MerkleTreeId.NOTE_HASH_TREE]: TreeSnapshot<Fr>;
37
- [MerkleTreeId.PUBLIC_DATA_TREE]: IndexedTreeSnapshot;
38
- [MerkleTreeId.L1_TO_L2_MESSAGE_TREE]: TreeSnapshot<Fr>;
39
- [MerkleTreeId.ARCHIVE]: TreeSnapshot<Fr>;
40
- };
41
-
42
32
  export interface MerkleTreeAdminDatabase extends ForkMerkleTreeOperations, ReadonlyWorldStateAccess {
43
33
  /**
44
34
  * Handles a single L2 block (i.e. Inserts the new note hashes into the merkle tree).
45
35
  * @param block - The L2 block to handle.
46
36
  * @param l1ToL2Messages - The L1 to L2 messages for the block.
47
37
  */
48
- handleL2BlockAndMessages(block: L2BlockNew, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull>;
38
+ handleL2BlockAndMessages(block: L2Block, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull>;
49
39
 
50
40
  /**
51
41
  * Gets a handle that allows reading the latest committed state
@@ -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
- }