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

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 (49) 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/merkle_trees_facade.d.ts +7 -6
  8. package/dest/native/merkle_trees_facade.d.ts.map +1 -1
  9. package/dest/native/merkle_trees_facade.js +17 -9
  10. package/dest/native/message.d.ts +13 -5
  11. package/dest/native/message.d.ts.map +1 -1
  12. package/dest/native/native_world_state.d.ts +10 -8
  13. package/dest/native/native_world_state.d.ts.map +1 -1
  14. package/dest/native/native_world_state.js +18 -12
  15. package/dest/native/native_world_state_instance.d.ts +5 -5
  16. package/dest/native/native_world_state_instance.d.ts.map +1 -1
  17. package/dest/native/native_world_state_instance.js +8 -7
  18. package/dest/native/world_state_ops_queue.js +5 -5
  19. package/dest/synchronizer/config.d.ts +3 -5
  20. package/dest/synchronizer/config.d.ts.map +1 -1
  21. package/dest/synchronizer/config.js +14 -16
  22. package/dest/synchronizer/factory.d.ts +6 -5
  23. package/dest/synchronizer/factory.d.ts.map +1 -1
  24. package/dest/synchronizer/factory.js +6 -5
  25. package/dest/synchronizer/server_world_state_synchronizer.d.ts +4 -4
  26. package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
  27. package/dest/synchronizer/server_world_state_synchronizer.js +92 -22
  28. package/dest/test/utils.d.ts +6 -6
  29. package/dest/test/utils.d.ts.map +1 -1
  30. package/dest/test/utils.js +3 -3
  31. package/dest/testing.d.ts +4 -3
  32. package/dest/testing.d.ts.map +1 -1
  33. package/dest/testing.js +10 -6
  34. package/dest/world-state-db/merkle_tree_db.d.ts +3 -12
  35. package/dest/world-state-db/merkle_tree_db.d.ts.map +1 -1
  36. package/package.json +9 -10
  37. package/src/instrumentation/instrumentation.ts +9 -1
  38. package/src/native/fork_checkpoint.ts +19 -3
  39. package/src/native/merkle_trees_facade.ts +24 -12
  40. package/src/native/message.ts +14 -4
  41. package/src/native/native_world_state.ts +22 -16
  42. package/src/native/native_world_state_instance.ts +14 -8
  43. package/src/native/world_state_ops_queue.ts +5 -5
  44. package/src/synchronizer/config.ts +15 -26
  45. package/src/synchronizer/factory.ts +13 -7
  46. package/src/synchronizer/server_world_state_synchronizer.ts +105 -30
  47. package/src/test/utils.ts +4 -4
  48. package/src/testing.ts +8 -9
  49. package/src/world-state-db/merkle_tree_db.ts +2 -12
@@ -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;
@@ -1,18 +1,10 @@
1
- import {
2
- type ConfigMappingsType,
3
- booleanConfigHelper,
4
- getConfigFromMappings,
5
- numberConfigHelper,
6
- } from '@aztec/foundation/config';
1
+ import { type ConfigMappingsType, getConfigFromMappings, numberConfigHelper } from '@aztec/foundation/config';
7
2
 
8
3
  /** World State synchronizer configuration values. */
9
4
  export interface WorldStateConfig {
10
5
  /** The frequency in which to check. */
11
6
  worldStateBlockCheckIntervalMS: number;
12
7
 
13
- /** Whether to follow only the proven chain. */
14
- worldStateProvenBlocksOnly: boolean;
15
-
16
8
  /** Size of the batch for each get-blocks request from the synchronizer to the archiver. */
17
9
  worldStateBlockRequestBatchSize?: number;
18
10
 
@@ -37,8 +29,8 @@ export interface WorldStateConfig {
37
29
  /** Optional directory for the world state DB, if unspecified will default to the general data directory */
38
30
  worldStateDataDirectory?: string;
39
31
 
40
- /** The number of historic blocks to maintain */
41
- worldStateBlockHistory: number;
32
+ /** The number of historic checkpoints worth of blocks to maintain */
33
+ worldStateCheckpointHistory: number;
42
34
  }
43
35
 
44
36
  export const worldStateConfigMappings: ConfigMappingsType<WorldStateConfig> = {
@@ -48,48 +40,43 @@ export const worldStateConfigMappings: ConfigMappingsType<WorldStateConfig> = {
48
40
  defaultValue: 100,
49
41
  description: 'The frequency in which to check.',
50
42
  },
51
- worldStateProvenBlocksOnly: {
52
- env: 'WS_PROVEN_BLOCKS_ONLY',
53
- description: 'Whether to follow only the proven chain.',
54
- ...booleanConfigHelper(),
55
- },
56
43
  worldStateBlockRequestBatchSize: {
57
44
  env: 'WS_BLOCK_REQUEST_BATCH_SIZE',
58
- parseEnv: (val: string | undefined) => (val ? +val : undefined),
45
+ parseEnv: (val: string) => +val,
59
46
  description: 'Size of the batch for each get-blocks request from the synchronizer to the archiver.',
60
47
  },
61
48
  worldStateDbMapSizeKb: {
62
49
  env: 'WS_DB_MAP_SIZE_KB',
63
- parseEnv: (val: string | undefined) => (val ? +val : undefined),
50
+ parseEnv: (val: string) => +val,
64
51
  description: 'The maximum possible size of the world state DB in KB. Overwrites the general dataStoreMapSizeKb.',
65
52
  },
66
53
  archiveTreeMapSizeKb: {
67
54
  env: 'ARCHIVE_TREE_MAP_SIZE_KB',
68
- parseEnv: (val: string | undefined) => (val ? +val : undefined),
55
+ parseEnv: (val: string) => +val,
69
56
  description:
70
57
  'The maximum possible size of the world state archive tree in KB. Overwrites the general worldStateDbMapSizeKb.',
71
58
  },
72
59
  nullifierTreeMapSizeKb: {
73
60
  env: 'NULLIFIER_TREE_MAP_SIZE_KB',
74
- parseEnv: (val: string | undefined) => (val ? +val : undefined),
61
+ parseEnv: (val: string) => +val,
75
62
  description:
76
63
  'The maximum possible size of the world state nullifier tree in KB. Overwrites the general worldStateDbMapSizeKb.',
77
64
  },
78
65
  noteHashTreeMapSizeKb: {
79
66
  env: 'NOTE_HASH_TREE_MAP_SIZE_KB',
80
- parseEnv: (val: string | undefined) => (val ? +val : undefined),
67
+ parseEnv: (val: string) => +val,
81
68
  description:
82
69
  'The maximum possible size of the world state note hash tree in KB. Overwrites the general worldStateDbMapSizeKb.',
83
70
  },
84
71
  messageTreeMapSizeKb: {
85
72
  env: 'MESSAGE_TREE_MAP_SIZE_KB',
86
- parseEnv: (val: string | undefined) => (val ? +val : undefined),
73
+ parseEnv: (val: string) => +val,
87
74
  description:
88
75
  'The maximum possible size of the world state message tree in KB. Overwrites the general worldStateDbMapSizeKb.',
89
76
  },
90
77
  publicDataTreeMapSizeKb: {
91
78
  env: 'PUBLIC_DATA_TREE_MAP_SIZE_KB',
92
- parseEnv: (val: string | undefined) => (val ? +val : undefined),
79
+ parseEnv: (val: string) => +val,
93
80
  description:
94
81
  'The maximum possible size of the world state public data tree in KB. Overwrites the general worldStateDbMapSizeKb.',
95
82
  },
@@ -97,9 +84,11 @@ export const worldStateConfigMappings: ConfigMappingsType<WorldStateConfig> = {
97
84
  env: 'WS_DATA_DIRECTORY',
98
85
  description: 'Optional directory for the world state database',
99
86
  },
100
- worldStateBlockHistory: {
101
- env: 'WS_NUM_HISTORIC_BLOCKS',
102
- description: 'The number of historic blocks to maintain. Values less than 1 mean all history is maintained',
87
+ worldStateCheckpointHistory: {
88
+ env: 'WS_NUM_HISTORIC_CHECKPOINTS',
89
+ description:
90
+ 'The number of historic checkpoints worth of blocks to maintain. Values less than 1 mean all history is maintained',
91
+ fallback: ['WS_NUM_HISTORIC_BLOCKS'],
103
92
  ...numberConfigHelper(64),
104
93
  },
105
94
  };
@@ -1,7 +1,8 @@
1
- import type { DataStoreConfig } from '@aztec/kv-store/config';
1
+ import type { LoggerBindings } from '@aztec/foundation/log';
2
2
  import type { L2BlockSource } from '@aztec/stdlib/block';
3
+ import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
3
4
  import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
4
- import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees';
5
+ import { EMPTY_GENESIS_DATA, type GenesisData } from '@aztec/stdlib/world-state';
5
6
  import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
6
7
 
7
8
  import { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
@@ -20,11 +21,12 @@ export interface WorldStateTreeMapSizes {
20
21
  export async function createWorldStateSynchronizer(
21
22
  config: WorldStateConfig & DataStoreConfig,
22
23
  l2BlockSource: L2BlockSource & L1ToL2MessageSource,
23
- prefilledPublicData: PublicDataTreeLeaf[] = [],
24
+ genesis: GenesisData = EMPTY_GENESIS_DATA,
24
25
  client: TelemetryClient = getTelemetryClient(),
26
+ bindings?: LoggerBindings,
25
27
  ) {
26
28
  const instrumentation = new WorldStateInstrumentation(client);
27
- const merkleTrees = await createWorldState(config, prefilledPublicData, instrumentation);
29
+ const merkleTrees = await createWorldState(config, genesis, instrumentation, bindings);
28
30
  return new ServerWorldStateSynchronizer(merkleTrees, l2BlockSource, config, instrumentation);
29
31
  }
30
32
 
@@ -40,8 +42,9 @@ export async function createWorldState(
40
42
  | 'publicDataTreeMapSizeKb'
41
43
  > &
42
44
  Pick<DataStoreConfig, 'dataDirectory' | 'dataStoreMapSizeKb' | 'l1Contracts'>,
43
- prefilledPublicData: PublicDataTreeLeaf[] = [],
45
+ genesis: GenesisData = EMPTY_GENESIS_DATA,
44
46
  instrumentation: WorldStateInstrumentation = new WorldStateInstrumentation(getTelemetryClient()),
47
+ bindings?: LoggerBindings,
45
48
  ) {
46
49
  const dataDirectory = config.worldStateDataDirectory ?? config.dataDirectory;
47
50
  const dataStoreMapSizeKb = config.worldStateDbMapSizeKb ?? config.dataStoreMapSizeKb;
@@ -63,13 +66,16 @@ export async function createWorldState(
63
66
  config.l1Contracts.rollupAddress,
64
67
  dataDirectory,
65
68
  wsTreeMapSizes,
66
- prefilledPublicData,
69
+ genesis,
67
70
  instrumentation,
71
+ bindings,
68
72
  )
69
73
  : await NativeWorldStateService.tmp(
70
74
  config.l1Contracts.rollupAddress,
71
75
  !['true', '1'].includes(process.env.DEBUG_WORLD_STATE!),
72
- prefilledPublicData,
76
+ genesis,
77
+ instrumentation,
78
+ bindings,
73
79
  );
74
80
 
75
81
  return merkleTrees;
@@ -1,13 +1,15 @@
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 { INITIAL_CHECKPOINT_NUMBER, INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
2
+ import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
3
3
  import type { Fr } from '@aztec/foundation/curves/bn254';
4
4
  import { type Logger, createLogger } from '@aztec/foundation/log';
5
5
  import { promiseWithResolvers } from '@aztec/foundation/promise';
6
6
  import { elapsed } from '@aztec/foundation/timer';
7
7
  import {
8
+ type BlockHash,
9
+ GENESIS_BLOCK_HEADER_HASH,
8
10
  GENESIS_CHECKPOINT_HEADER_HASH,
11
+ type L2Block,
9
12
  type L2BlockId,
10
- type L2BlockNew,
11
13
  type L2BlockSource,
12
14
  L2BlockStream,
13
15
  type L2BlockStreamEvent,
@@ -64,7 +66,7 @@ export class ServerWorldStateSynchronizer
64
66
  private readonly log: Logger = createLogger('world_state'),
65
67
  ) {
66
68
  this.merkleTreeCommitted = this.merkleTreeDb.getCommitted();
67
- this.historyToKeep = config.worldStateBlockHistory < 1 ? undefined : config.worldStateBlockHistory;
69
+ this.historyToKeep = config.worldStateCheckpointHistory < 1 ? undefined : config.worldStateCheckpointHistory;
68
70
  this.log.info(
69
71
  `Created world state synchroniser with block history of ${
70
72
  this.historyToKeep === undefined ? 'infinity' : this.historyToKeep
@@ -101,11 +103,7 @@ export class ServerWorldStateSynchronizer
101
103
  }
102
104
 
103
105
  // Get the current latest block number
104
- this.latestBlockNumberAtStart = BlockNumber(
105
- await (this.config.worldStateProvenBlocksOnly
106
- ? this.l2BlockSource.getProvenBlockNumber()
107
- : this.l2BlockSource.getBlockNumber()),
108
- );
106
+ this.latestBlockNumberAtStart = BlockNumber(await this.l2BlockSource.getBlockNumber());
109
107
 
110
108
  const blockToDownloadFrom = (await this.getLatestBlockNumber()) + 1;
111
109
 
@@ -129,7 +127,6 @@ export class ServerWorldStateSynchronizer
129
127
  protected createBlockStream(): L2BlockStream {
130
128
  const logger = createLogger('world-state:block_stream');
131
129
  return new L2BlockStream(this.l2BlockSource, this, this, logger, {
132
- proven: this.config.worldStateProvenBlocksOnly,
133
130
  pollIntervalMS: this.config.worldStateBlockCheckIntervalMS,
134
131
  batchSize: this.config.worldStateBlockRequestBatchSize,
135
132
  ignoreCheckpoints: true,
@@ -182,13 +179,10 @@ export class ServerWorldStateSynchronizer
182
179
  /**
183
180
  * Forces an immediate sync.
184
181
  * @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.
182
+ * @param blockHash - If provided, verifies the block at targetBlockNumber matches this hash. On mismatch, triggers a resync (reorg detection).
186
183
  * @returns A promise that resolves with the block number the world state was synced to
187
184
  */
188
- public async syncImmediate(
189
- targetBlockNumber?: BlockNumber,
190
- skipThrowIfTargetNotReached?: boolean,
191
- ): Promise<BlockNumber> {
185
+ public async syncImmediate(targetBlockNumber?: BlockNumber, blockHash?: BlockHash): Promise<BlockNumber> {
192
186
  if (this.currentState !== WorldStateRunningState.RUNNING) {
193
187
  throw new Error(`World State is not running. Unable to perform sync.`);
194
188
  }
@@ -200,7 +194,19 @@ export class ServerWorldStateSynchronizer
200
194
  // If we have been given a block number to sync to and we have reached that number then return
201
195
  const currentBlockNumber = await this.getLatestBlockNumber();
202
196
  if (targetBlockNumber !== undefined && targetBlockNumber <= currentBlockNumber) {
203
- return currentBlockNumber;
197
+ if (blockHash === undefined) {
198
+ return currentBlockNumber;
199
+ }
200
+
201
+ // If a block hash was provided, verify we're on the expected fork
202
+ const currentHash = await this.getL2BlockHash(targetBlockNumber);
203
+ if (currentHash === blockHash.toString()) {
204
+ return currentBlockNumber;
205
+ }
206
+ // Hash mismatch: a reorg may have occurred, fall through to trigger sync
207
+ this.log.debug(
208
+ `World state block hash mismatch at ${targetBlockNumber} (expected ${blockHash}, got ${currentHash}). Triggering resync.`,
209
+ );
204
210
  }
205
211
  this.log.debug(`World State at ${currentBlockNumber} told to sync to ${targetBlockNumber ?? 'latest'}`);
206
212
 
@@ -218,7 +224,7 @@ export class ServerWorldStateSynchronizer
218
224
 
219
225
  // If we have been given a block number to sync to and we have not reached that number then fail
220
226
  const updatedBlockNumber = await this.getLatestBlockNumber();
221
- if (!skipThrowIfTargetNotReached && targetBlockNumber !== undefined && targetBlockNumber > updatedBlockNumber) {
227
+ if (targetBlockNumber !== undefined && targetBlockNumber > updatedBlockNumber) {
222
228
  throw new WorldStateSynchronizerError(
223
229
  `Unable to sync to block number ${targetBlockNumber} (last synced is ${updatedBlockNumber})`,
224
230
  {
@@ -232,6 +238,24 @@ export class ServerWorldStateSynchronizer
232
238
  );
233
239
  }
234
240
 
241
+ // If a block hash was provided, verify we're on the expected fork after syncing, throw otherwise
242
+ if (blockHash !== undefined && targetBlockNumber !== undefined) {
243
+ const updatedHash = await this.getL2BlockHash(targetBlockNumber);
244
+ if (updatedHash !== blockHash.toString()) {
245
+ throw new WorldStateSynchronizerError(
246
+ `Block hash mismatch at block ${targetBlockNumber} (expected ${blockHash} but got ${updatedHash})`,
247
+ {
248
+ cause: {
249
+ reason: 'block_hash_mismatch',
250
+ targetBlockNumber,
251
+ expectedHash: blockHash.toString(),
252
+ actualHash: updatedHash,
253
+ },
254
+ },
255
+ );
256
+ }
257
+ }
258
+
235
259
  return updatedBlockNumber;
236
260
  }
237
261
 
@@ -268,15 +292,19 @@ export class ServerWorldStateSynchronizer
268
292
  proposed: latestBlockId,
269
293
  checkpointed: {
270
294
  block: { number: INITIAL_L2_BLOCK_NUM, hash: GENESIS_BLOCK_HEADER_HASH.toString() },
271
- checkpoint: { number: INITIAL_L2_CHECKPOINT_NUM, hash: genesisCheckpointHeaderHash },
295
+ checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
296
+ },
297
+ proposedCheckpoint: {
298
+ block: { number: INITIAL_L2_BLOCK_NUM, hash: GENESIS_BLOCK_HEADER_HASH.toString() },
299
+ checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
272
300
  },
273
301
  finalized: {
274
302
  block: { number: status.finalizedBlockNumber, hash: finalizedBlockHash ?? '' },
275
- checkpoint: { number: INITIAL_L2_CHECKPOINT_NUM, hash: genesisCheckpointHeaderHash },
303
+ checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
276
304
  },
277
305
  proven: {
278
306
  block: { number: provenBlockNumber, hash: provenBlockHash ?? '' },
279
- checkpoint: { number: INITIAL_L2_CHECKPOINT_NUM, hash: genesisCheckpointHeaderHash },
307
+ checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
280
308
  },
281
309
  };
282
310
  }
@@ -304,8 +332,8 @@ export class ServerWorldStateSynchronizer
304
332
  * @param l2Blocks - The L2 blocks to handle.
305
333
  * @returns Whether the block handled was produced by this same node.
306
334
  */
307
- private async handleL2Blocks(l2Blocks: L2BlockNew[]) {
308
- this.log.trace(`Handling L2 blocks ${l2Blocks[0].number} to ${l2Blocks.at(-1)!.number}`);
335
+ private async handleL2Blocks(l2Blocks: L2Block[]) {
336
+ this.log.debug(`Handling L2 blocks ${l2Blocks[0].number} to ${l2Blocks.at(-1)!.number}`);
309
337
 
310
338
  // Fetch the L1->L2 messages for the first block in a checkpoint.
311
339
  const messagesForBlocks = new Map<BlockNumber, Fr[]>();
@@ -345,11 +373,13 @@ export class ServerWorldStateSynchronizer
345
373
  * @param l1ToL2Messages - The L1 to L2 messages for the block.
346
374
  * @returns Whether the block handled was produced by this same node.
347
375
  */
348
- private async handleL2Block(l2Block: L2BlockNew, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull> {
349
- this.log.trace(`Pushing L2 block ${l2Block.number} to merkle tree db `, {
376
+ private async handleL2Block(l2Block: L2Block, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull> {
377
+ this.log.debug(`Pushing L2 block ${l2Block.number} to merkle tree db `, {
350
378
  blockNumber: l2Block.number,
351
379
  blockHash: await l2Block.hash().then(h => h.toString()),
352
380
  l1ToL2Messages: l1ToL2Messages.map(msg => msg.toString()),
381
+ blockHeader: l2Block.header.toInspect(),
382
+ blockStats: l2Block.getStats(),
353
383
  });
354
384
  const result = await this.merkleTreeDb.handleL2BlockAndMessages(l2Block, l1ToL2Messages);
355
385
 
@@ -363,16 +393,59 @@ export class ServerWorldStateSynchronizer
363
393
 
364
394
  private async handleChainFinalized(blockNumber: BlockNumber) {
365
395
  this.log.verbose(`Finalized chain is now at block ${blockNumber}`);
396
+ // If the finalized block number is older than the oldest available block in world state,
397
+ // skip entirely. The finalized block number can jump backwards (e.g. when the finalization
398
+ // heuristic changes) and try to read block data that has already been pruned. When this
399
+ // happens, there is nothing useful to do — the native world state is already finalized
400
+ // past this point and pruning has already happened.
401
+ const currentSummary = await this.merkleTreeDb.getStatusSummary();
402
+ if (blockNumber < currentSummary.oldestHistoricalBlock || blockNumber < 1) {
403
+ this.log.trace(
404
+ `Finalized block ${blockNumber} is older than the oldest available block ${currentSummary.oldestHistoricalBlock}. Skipping.`,
405
+ );
406
+ return;
407
+ }
366
408
  const summary = await this.merkleTreeDb.setFinalized(blockNumber);
367
409
  if (this.historyToKeep === undefined) {
368
410
  return;
369
411
  }
370
- const newHistoricBlock = summary.finalizedBlockNumber - this.historyToKeep + 1;
371
- if (newHistoricBlock <= 1) {
412
+ // Get the checkpointed block for the finalized block number
413
+ const finalisedCheckpoint = await this.l2BlockSource.getCheckpointedBlock(summary.finalizedBlockNumber);
414
+ if (finalisedCheckpoint === undefined) {
415
+ this.log.warn(
416
+ `Failed to retrieve checkpointed block for finalized block number: ${summary.finalizedBlockNumber}`,
417
+ );
418
+ return;
419
+ }
420
+ // Compute the required historic checkpoint number
421
+ const newHistoricCheckpointNumber = finalisedCheckpoint.checkpointNumber - this.historyToKeep + 1;
422
+ if (newHistoricCheckpointNumber <= 1) {
423
+ return;
424
+ }
425
+ // Retrieve the historic checkpoint
426
+ const historicCheckpoints = await this.l2BlockSource.getCheckpoints(
427
+ CheckpointNumber(newHistoricCheckpointNumber),
428
+ 1,
429
+ );
430
+ if (historicCheckpoints.length === 0 || historicCheckpoints[0] === undefined) {
431
+ this.log.warn(`Failed to retrieve checkpoint number ${newHistoricCheckpointNumber} from Archiver`);
372
432
  return;
373
433
  }
374
- this.log.verbose(`Pruning historic blocks to ${newHistoricBlock}`);
375
- const status = await this.merkleTreeDb.removeHistoricalBlocks(BlockNumber(newHistoricBlock));
434
+ const historicCheckpoint = historicCheckpoints[0];
435
+ if (historicCheckpoint.checkpoint.blocks.length === 0 || historicCheckpoint.checkpoint.blocks[0] === undefined) {
436
+ this.log.warn(`Retrieved checkpoint number ${newHistoricCheckpointNumber} has no blocks!`);
437
+ return;
438
+ }
439
+ // Find the block at the start of the checkpoint and remove blocks up to this one
440
+ const newHistoricBlock = historicCheckpoint.checkpoint.blocks[0];
441
+ if (newHistoricBlock.number <= currentSummary.oldestHistoricalBlock) {
442
+ this.log.debug(
443
+ `Historic block ${newHistoricBlock.number} is not newer than oldest available ${currentSummary.oldestHistoricalBlock}. Skipping prune.`,
444
+ );
445
+ return;
446
+ }
447
+ this.log.verbose(`Pruning historic blocks to ${newHistoricBlock.number}`);
448
+ const status = await this.merkleTreeDb.removeHistoricalBlocks(BlockNumber(newHistoricBlock.number));
376
449
  this.log.debug(`World state summary `, status.summary);
377
450
  }
378
451
 
@@ -383,9 +456,11 @@ export class ServerWorldStateSynchronizer
383
456
  }
384
457
 
385
458
  private async handleChainPruned(blockNumber: BlockNumber) {
386
- this.log.warn(`Chain pruned to block ${blockNumber}`);
459
+ this.log.info(`Chain pruned to block ${blockNumber}`);
387
460
  const status = await this.merkleTreeDb.unwindBlocks(blockNumber);
388
- this.provenBlockNumber = undefined;
461
+ if (this.provenBlockNumber !== undefined && this.provenBlockNumber > blockNumber) {
462
+ this.provenBlockNumber = undefined;
463
+ }
389
464
  this.instrumentation.updateWorldStateMetrics(status);
390
465
  }
391
466
 
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[][],
@@ -76,7 +76,7 @@ export async function mockBlock(
76
76
  numL1ToL2Messages: number = NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
77
77
  isFirstBlockInCheckpoint: boolean = true,
78
78
  ) {
79
- const block = await L2BlockNew.random(blockNum, {
79
+ const block = await L2Block.random(blockNum, {
80
80
  indexWithinCheckpoint: isFirstBlockInCheckpoint ? IndexWithinCheckpoint(0) : IndexWithinCheckpoint(1),
81
81
  txsPerBlock: size,
82
82
  txOptions: { maxEffects },
@@ -92,7 +92,7 @@ export async function mockBlock(
92
92
  }
93
93
 
94
94
  export async function mockEmptyBlock(blockNum: BlockNumber, fork: MerkleTreeWriteOperations) {
95
- const l2Block = L2BlockNew.empty();
95
+ const l2Block = L2Block.empty();
96
96
  const l1ToL2Messages = Array(16).fill(0).map(Fr.zero);
97
97
 
98
98
  l2Block.header.globalVariables.blockNumber = blockNum;
package/src/testing.ts CHANGED
@@ -3,22 +3,19 @@ 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
17
  // 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
- );
18
+ const ws = await NativeWorldStateService.tmp(undefined /* rollupAddress */, true /* cleanupTmpDir */, genesis);
22
19
  const genesisArchiveRoot = new Fr((await ws.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
23
20
  await ws.close();
24
21
 
@@ -33,6 +30,7 @@ export async function getGenesisValues(
33
30
  initialAccounts: AztecAddress[],
34
31
  initialAccountFeeJuice = defaultInitialAccountFeeJuice,
35
32
  genesisPublicData: PublicDataTreeLeaf[] = [],
33
+ genesisTimestamp: bigint = 0n,
36
34
  ) {
37
35
  // Top up the accounts with fee juice.
38
36
  let prefilledPublicData = await Promise.all(
@@ -46,11 +44,12 @@ export async function getGenesisValues(
46
44
 
47
45
  prefilledPublicData.sort((a, b) => (b.slot.lt(a.slot) ? 1 : -1));
48
46
 
49
- const { genesisArchiveRoot } = await generateGenesisValues(prefilledPublicData);
47
+ const genesis: GenesisData = { prefilledPublicData, genesisTimestamp };
48
+ const { genesisArchiveRoot } = await generateGenesisValues(genesis);
50
49
 
51
50
  return {
52
51
  genesisArchiveRoot,
53
- prefilledPublicData,
52
+ genesis,
54
53
  fundingNeeded: BigInt(initialAccounts.length) * initialAccountFeeJuice.toBigInt(),
55
54
  };
56
55
  }
@@ -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