@aztec/world-state 0.0.1-commit.3469e52 → 0.0.1-commit.35158ae7e

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 (43) 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 +5 -5
  8. package/dest/native/merkle_trees_facade.d.ts.map +1 -1
  9. package/dest/native/merkle_trees_facade.js +9 -6
  10. package/dest/native/message.d.ts +14 -5
  11. package/dest/native/message.d.ts.map +1 -1
  12. package/dest/native/native_world_state.d.ts +7 -7
  13. package/dest/native/native_world_state.d.ts.map +1 -1
  14. package/dest/native/native_world_state.js +9 -8
  15. package/dest/native/native_world_state_instance.d.ts +3 -3
  16. package/dest/native/native_world_state_instance.d.ts.map +1 -1
  17. package/dest/native/native_world_state_instance.js +4 -4
  18. package/dest/synchronizer/config.d.ts +3 -5
  19. package/dest/synchronizer/config.d.ts.map +1 -1
  20. package/dest/synchronizer/config.js +7 -9
  21. package/dest/synchronizer/factory.d.ts +5 -4
  22. package/dest/synchronizer/factory.d.ts.map +1 -1
  23. package/dest/synchronizer/factory.js +5 -5
  24. package/dest/synchronizer/server_world_state_synchronizer.d.ts +4 -4
  25. package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
  26. package/dest/synchronizer/server_world_state_synchronizer.js +78 -20
  27. package/dest/test/utils.d.ts +6 -6
  28. package/dest/test/utils.d.ts.map +1 -1
  29. package/dest/test/utils.js +3 -3
  30. package/dest/world-state-db/merkle_tree_db.d.ts +3 -12
  31. package/dest/world-state-db/merkle_tree_db.d.ts.map +1 -1
  32. package/package.json +9 -10
  33. package/src/instrumentation/instrumentation.ts +9 -1
  34. package/src/native/fork_checkpoint.ts +19 -3
  35. package/src/native/merkle_trees_facade.ts +14 -7
  36. package/src/native/message.ts +15 -4
  37. package/src/native/native_world_state.ts +22 -10
  38. package/src/native/native_world_state_instance.ts +6 -4
  39. package/src/synchronizer/config.ts +8 -19
  40. package/src/synchronizer/factory.ts +8 -2
  41. package/src/synchronizer/server_world_state_synchronizer.ts +97 -29
  42. package/src/test/utils.ts +4 -4
  43. package/src/world-state-db/merkle_tree_db.ts +2 -12
@@ -3,11 +3,14 @@ import type { MerkleTreeCheckpointOperations } from '@aztec/stdlib/interfaces/se
3
3
  export class ForkCheckpoint {
4
4
  private completed = false;
5
5
 
6
- private constructor(private readonly fork: MerkleTreeCheckpointOperations) {}
6
+ private constructor(
7
+ private readonly fork: MerkleTreeCheckpointOperations,
8
+ public readonly depth: number,
9
+ ) {}
7
10
 
8
11
  static async new(fork: MerkleTreeCheckpointOperations): Promise<ForkCheckpoint> {
9
- await fork.createCheckpoint();
10
- return new ForkCheckpoint(fork);
12
+ const depth = await fork.createCheckpoint();
13
+ return new ForkCheckpoint(fork, depth);
11
14
  }
12
15
 
13
16
  async commit(): Promise<void> {
@@ -27,4 +30,17 @@ export class ForkCheckpoint {
27
30
  await this.fork.revertCheckpoint();
28
31
  this.completed = true;
29
32
  }
33
+
34
+ /**
35
+ * Reverts this checkpoint and any nested checkpoints created on top of it,
36
+ * leaving the checkpoint depth at the level it was before this checkpoint was created.
37
+ */
38
+ async revertToCheckpoint(): Promise<void> {
39
+ if (this.completed) {
40
+ return;
41
+ }
42
+
43
+ await this.fork.revertAllCheckpointsTo(this.depth - 1);
44
+ this.completed = true;
45
+ }
30
46
  }
@@ -304,7 +304,7 @@ export class MerkleTreesForkFacade extends MerkleTreesFacade implements MerkleTr
304
304
  }
305
305
  }
306
306
 
307
- async [Symbol.dispose](): Promise<void> {
307
+ async [Symbol.asyncDispose](): Promise<void> {
308
308
  if (this.opts.closeDelayMs) {
309
309
  void sleep(this.opts.closeDelayMs)
310
310
  .then(() => this.close())
@@ -319,9 +319,10 @@ export class MerkleTreesForkFacade extends MerkleTreesFacade implements MerkleTr
319
319
  }
320
320
  }
321
321
 
322
- public async createCheckpoint(): Promise<void> {
322
+ public async createCheckpoint(): Promise<number> {
323
323
  assert.notEqual(this.revision.forkId, 0, 'Fork ID must be set');
324
- await this.instance.call(WorldStateMessageType.CREATE_CHECKPOINT, { forkId: this.revision.forkId });
324
+ const resp = await this.instance.call(WorldStateMessageType.CREATE_CHECKPOINT, { forkId: this.revision.forkId });
325
+ return resp.depth;
325
326
  }
326
327
 
327
328
  public async commitCheckpoint(): Promise<void> {
@@ -334,14 +335,20 @@ export class MerkleTreesForkFacade extends MerkleTreesFacade implements MerkleTr
334
335
  await this.instance.call(WorldStateMessageType.REVERT_CHECKPOINT, { forkId: this.revision.forkId });
335
336
  }
336
337
 
337
- public async commitAllCheckpoints(): Promise<void> {
338
+ public async commitAllCheckpointsTo(depth: number): Promise<void> {
338
339
  assert.notEqual(this.revision.forkId, 0, 'Fork ID must be set');
339
- await this.instance.call(WorldStateMessageType.COMMIT_ALL_CHECKPOINTS, { forkId: this.revision.forkId });
340
+ await this.instance.call(WorldStateMessageType.COMMIT_ALL_CHECKPOINTS, {
341
+ forkId: this.revision.forkId,
342
+ depth,
343
+ });
340
344
  }
341
345
 
342
- public async revertAllCheckpoints(): Promise<void> {
346
+ public async revertAllCheckpointsTo(depth: number): Promise<void> {
343
347
  assert.notEqual(this.revision.forkId, 0, 'Fork ID must be set');
344
- await this.instance.call(WorldStateMessageType.REVERT_ALL_CHECKPOINTS, { forkId: this.revision.forkId });
348
+ await this.instance.call(WorldStateMessageType.REVERT_ALL_CHECKPOINTS, {
349
+ forkId: this.revision.forkId,
350
+ depth,
351
+ });
345
352
  }
346
353
  }
347
354
 
@@ -1,6 +1,7 @@
1
1
  import { BlockNumber } from '@aztec/foundation/branded-types';
2
2
  import { Fr } from '@aztec/foundation/curves/bn254';
3
3
  import type { Tuple } from '@aztec/foundation/serialize';
4
+ import type { BlockHash } from '@aztec/stdlib/block';
4
5
  import { AppendOnlyTreeSnapshot, MerkleTreeId } from '@aztec/stdlib/trees';
5
6
  import type { StateReference } from '@aztec/stdlib/tx';
6
7
  import type { UInt32 } from '@aztec/stdlib/types';
@@ -283,6 +284,16 @@ interface WithForkId {
283
284
  forkId: number;
284
285
  }
285
286
 
287
+ interface CreateCheckpointResponse {
288
+ depth: number;
289
+ }
290
+
291
+ /** Request to commit/revert all checkpoints down to a target depth. The resulting depth after the operation equals the given depth. */
292
+ interface CheckpointDepthRequest extends WithForkId {
293
+ /** The target depth after the operation. All checkpoints above this depth are committed/reverted. */
294
+ depth: number;
295
+ }
296
+
286
297
  interface WithWorldStateRevision {
287
298
  revision: WorldStateRevision;
288
299
  }
@@ -412,7 +423,7 @@ interface UpdateArchiveRequest extends WithForkId {
412
423
  interface SyncBlockRequest extends WithCanonicalForkId {
413
424
  blockNumber: BlockNumber;
414
425
  blockStateRef: BlockStateReference;
415
- blockHeaderHash: Fr;
426
+ blockHeaderHash: BlockHash;
416
427
  paddedNoteHashes: readonly SerializedLeafValue[];
417
428
  paddedL1ToL2Messages: readonly SerializedLeafValue[];
418
429
  paddedNullifiers: readonly SerializedLeafValue[];
@@ -486,8 +497,8 @@ export type WorldStateRequest = {
486
497
  [WorldStateMessageType.CREATE_CHECKPOINT]: WithForkId;
487
498
  [WorldStateMessageType.COMMIT_CHECKPOINT]: WithForkId;
488
499
  [WorldStateMessageType.REVERT_CHECKPOINT]: WithForkId;
489
- [WorldStateMessageType.COMMIT_ALL_CHECKPOINTS]: WithForkId;
490
- [WorldStateMessageType.REVERT_ALL_CHECKPOINTS]: WithForkId;
500
+ [WorldStateMessageType.COMMIT_ALL_CHECKPOINTS]: CheckpointDepthRequest;
501
+ [WorldStateMessageType.REVERT_ALL_CHECKPOINTS]: CheckpointDepthRequest;
491
502
 
492
503
  [WorldStateMessageType.COPY_STORES]: CopyStoresRequest;
493
504
 
@@ -528,7 +539,7 @@ export type WorldStateResponse = {
528
539
 
529
540
  [WorldStateMessageType.GET_STATUS]: WorldStateStatusSummary;
530
541
 
531
- [WorldStateMessageType.CREATE_CHECKPOINT]: void;
542
+ [WorldStateMessageType.CREATE_CHECKPOINT]: CreateCheckpointResponse;
532
543
  [WorldStateMessageType.COMMIT_CHECKPOINT]: void;
533
544
  [WorldStateMessageType.REVERT_CHECKPOINT]: void;
534
545
  [WorldStateMessageType.COMMIT_ALL_CHECKPOINTS]: void;
@@ -4,9 +4,9 @@ import { fromEntries, padArrayEnd } from '@aztec/foundation/collection';
4
4
  import { Fr } from '@aztec/foundation/curves/bn254';
5
5
  import { EthAddress } from '@aztec/foundation/eth-address';
6
6
  import { tryRmDir } from '@aztec/foundation/fs';
7
- import { type Logger, createLogger } from '@aztec/foundation/log';
8
- import type { L2BlockNew } from '@aztec/stdlib/block';
9
- import { DatabaseVersionManager } from '@aztec/stdlib/database-version';
7
+ import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
8
+ import type { L2Block } from '@aztec/stdlib/block';
9
+ import { DatabaseVersionManager } from '@aztec/stdlib/database-version/manager';
10
10
  import type {
11
11
  IndexedTreeId,
12
12
  MerkleTreeReadOperations,
@@ -52,7 +52,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
52
52
  protected constructor(
53
53
  protected instance: NativeWorldState,
54
54
  protected readonly worldStateInstrumentation: WorldStateInstrumentation,
55
- protected readonly log: Logger = createLogger('world-state:database'),
55
+ protected readonly log: Logger,
56
56
  private readonly cleanup = () => Promise.resolve(),
57
57
  ) {}
58
58
 
@@ -62,9 +62,10 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
62
62
  wsTreeMapSizes: WorldStateTreeMapSizes,
63
63
  prefilledPublicData: PublicDataTreeLeaf[] = [],
64
64
  instrumentation = new WorldStateInstrumentation(getTelemetryClient()),
65
- log = createLogger('world-state:database'),
65
+ bindings?: LoggerBindings,
66
66
  cleanup = () => Promise.resolve(),
67
67
  ): Promise<NativeWorldStateService> {
68
+ const log = createLogger('world-state:database', bindings);
68
69
  const worldStateDirectory = join(dataDir, WORLD_STATE_DIR);
69
70
  // Create a version manager to handle versioning
70
71
  const versionManager = new DatabaseVersionManager({
@@ -72,7 +73,9 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
72
73
  rollupAddress,
73
74
  dataDirectory: worldStateDirectory,
74
75
  onOpen: (dir: string) => {
75
- return Promise.resolve(new NativeWorldState(dir, wsTreeMapSizes, prefilledPublicData, instrumentation));
76
+ return Promise.resolve(
77
+ new NativeWorldState(dir, wsTreeMapSizes, prefilledPublicData, instrumentation, bindings),
78
+ );
76
79
  },
77
80
  });
78
81
 
@@ -93,8 +96,9 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
93
96
  cleanupTmpDir = true,
94
97
  prefilledPublicData: PublicDataTreeLeaf[] = [],
95
98
  instrumentation = new WorldStateInstrumentation(getTelemetryClient()),
99
+ bindings?: LoggerBindings,
96
100
  ): Promise<NativeWorldStateService> {
97
- const log = createLogger('world-state:database');
101
+ const log = createLogger('world-state:database', bindings);
98
102
  const dataDir = await mkdtemp(join(tmpdir(), 'aztec-world-state-'));
99
103
  const dbMapSizeKb = 10 * 1024 * 1024;
100
104
  const worldStateTreeMapSizes: WorldStateTreeMapSizes = {
@@ -116,7 +120,15 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
116
120
  }
117
121
  };
118
122
 
119
- return this.new(rollupAddress, dataDir, worldStateTreeMapSizes, prefilledPublicData, instrumentation, log, cleanup);
123
+ return this.new(
124
+ rollupAddress,
125
+ dataDir,
126
+ worldStateTreeMapSizes,
127
+ prefilledPublicData,
128
+ instrumentation,
129
+ bindings,
130
+ cleanup,
131
+ );
120
132
  }
121
133
 
122
134
  protected async init() {
@@ -135,7 +147,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
135
147
 
136
148
  // the initial header _must_ be the first element in the archive tree
137
149
  // if this assertion fails, check that the hashing done in Header in yarn-project matches the initial header hash done in world_state.cpp
138
- const indices = await committed.findLeafIndices(MerkleTreeId.ARCHIVE, [await this.initialHeader.hash()]);
150
+ const indices = await committed.findLeafIndices(MerkleTreeId.ARCHIVE, [(await this.initialHeader.hash()).toFr()]);
139
151
  const initialHeaderIndex = indices[0];
140
152
  assert.strictEqual(initialHeaderIndex, 0n, 'Invalid initial archive state');
141
153
  }
@@ -184,7 +196,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
184
196
  return this.initialHeader!;
185
197
  }
186
198
 
187
- public async handleL2BlockAndMessages(l2Block: L2BlockNew, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull> {
199
+ public async handleL2BlockAndMessages(l2Block: L2Block, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull> {
188
200
  const isFirstBlock = l2Block.indexWithinCheckpoint === 0;
189
201
  if (!isFirstBlock && l1ToL2Messages.length > 0) {
190
202
  throw new Error(
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ARCHIVE_HEIGHT,
3
- GeneratorIndex,
3
+ DomainSeparator,
4
4
  L1_TO_L2_MSG_TREE_HEIGHT,
5
5
  MAX_NULLIFIERS_PER_TX,
6
6
  MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
@@ -8,7 +8,7 @@ import {
8
8
  NULLIFIER_TREE_HEIGHT,
9
9
  PUBLIC_DATA_TREE_HEIGHT,
10
10
  } from '@aztec/constants';
11
- import { type Logger, createLogger } from '@aztec/foundation/log';
11
+ import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
12
12
  import { NativeWorldState as BaseNativeWorldState, MsgpackChannel } from '@aztec/native';
13
13
  import { MerkleTreeId } from '@aztec/stdlib/trees';
14
14
  import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees';
@@ -57,7 +57,8 @@ export class NativeWorldState implements NativeWorldStateInstance {
57
57
  private readonly wsTreeMapSizes: WorldStateTreeMapSizes,
58
58
  private readonly prefilledPublicData: PublicDataTreeLeaf[] = [],
59
59
  private readonly instrumentation: WorldStateInstrumentation,
60
- private readonly log: Logger = createLogger('world-state:database'),
60
+ bindings?: LoggerBindings,
61
+ private readonly log: Logger = createLogger('world-state:database', bindings),
61
62
  ) {
62
63
  const threads = Math.min(cpus().length, MAX_WORLD_STATE_THREADS);
63
64
  log.info(
@@ -80,7 +81,7 @@ export class NativeWorldState implements NativeWorldStateInstance {
80
81
  [MerkleTreeId.PUBLIC_DATA_TREE]: 2 * MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
81
82
  },
82
83
  prefilledPublicDataBufferArray,
83
- GeneratorIndex.BLOCK_HASH,
84
+ DomainSeparator.BLOCK_HEADER_HASH,
84
85
  {
85
86
  [MerkleTreeId.NULLIFIER_TREE]: wsTreeMapSizes.nullifierTreeMapSizeKb,
86
87
  [MerkleTreeId.NOTE_HASH_TREE]: wsTreeMapSizes.noteHashTreeMapSizeKb,
@@ -105,6 +106,7 @@ export class NativeWorldState implements NativeWorldStateInstance {
105
106
  this.wsTreeMapSizes,
106
107
  this.prefilledPublicData,
107
108
  this.instrumentation,
109
+ this.log.getBindings(),
108
110
  this.log,
109
111
  );
110
112
  }
@@ -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,11 +40,6 @@ 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
45
  parseEnv: (val: string | undefined) => (val ? +val : undefined),
@@ -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,5 +1,6 @@
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
5
  import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees';
5
6
  import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
@@ -22,9 +23,10 @@ export async function createWorldStateSynchronizer(
22
23
  l2BlockSource: L2BlockSource & L1ToL2MessageSource,
23
24
  prefilledPublicData: PublicDataTreeLeaf[] = [],
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, prefilledPublicData, instrumentation, bindings);
28
30
  return new ServerWorldStateSynchronizer(merkleTrees, l2BlockSource, config, instrumentation);
29
31
  }
30
32
 
@@ -42,6 +44,7 @@ export async function createWorldState(
42
44
  Pick<DataStoreConfig, 'dataDirectory' | 'dataStoreMapSizeKb' | 'l1Contracts'>,
43
45
  prefilledPublicData: PublicDataTreeLeaf[] = [],
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;
@@ -65,11 +68,14 @@ export async function createWorldState(
65
68
  wsTreeMapSizes,
66
69
  prefilledPublicData,
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
76
  prefilledPublicData,
77
+ instrumentation,
78
+ bindings,
73
79
  );
74
80
 
75
81
  return merkleTrees;
@@ -1,13 +1,14 @@
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 { GENESIS_BLOCK_HEADER_HASH, 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,
8
9
  GENESIS_CHECKPOINT_HEADER_HASH,
10
+ type L2Block,
9
11
  type L2BlockId,
10
- type L2BlockNew,
11
12
  type L2BlockSource,
12
13
  L2BlockStream,
13
14
  type L2BlockStreamEvent,
@@ -64,7 +65,7 @@ export class ServerWorldStateSynchronizer
64
65
  private readonly log: Logger = createLogger('world_state'),
65
66
  ) {
66
67
  this.merkleTreeCommitted = this.merkleTreeDb.getCommitted();
67
- this.historyToKeep = config.worldStateBlockHistory < 1 ? undefined : config.worldStateBlockHistory;
68
+ this.historyToKeep = config.worldStateCheckpointHistory < 1 ? undefined : config.worldStateCheckpointHistory;
68
69
  this.log.info(
69
70
  `Created world state synchroniser with block history of ${
70
71
  this.historyToKeep === undefined ? 'infinity' : this.historyToKeep
@@ -101,11 +102,7 @@ export class ServerWorldStateSynchronizer
101
102
  }
102
103
 
103
104
  // Get the current latest block number
104
- this.latestBlockNumberAtStart = BlockNumber(
105
- await (this.config.worldStateProvenBlocksOnly
106
- ? this.l2BlockSource.getProvenBlockNumber()
107
- : this.l2BlockSource.getBlockNumber()),
108
- );
105
+ this.latestBlockNumberAtStart = BlockNumber(await this.l2BlockSource.getBlockNumber());
109
106
 
110
107
  const blockToDownloadFrom = (await this.getLatestBlockNumber()) + 1;
111
108
 
@@ -129,7 +126,6 @@ export class ServerWorldStateSynchronizer
129
126
  protected createBlockStream(): L2BlockStream {
130
127
  const logger = createLogger('world-state:block_stream');
131
128
  return new L2BlockStream(this.l2BlockSource, this, this, logger, {
132
- proven: this.config.worldStateProvenBlocksOnly,
133
129
  pollIntervalMS: this.config.worldStateBlockCheckIntervalMS,
134
130
  batchSize: this.config.worldStateBlockRequestBatchSize,
135
131
  ignoreCheckpoints: true,
@@ -182,13 +178,10 @@ export class ServerWorldStateSynchronizer
182
178
  /**
183
179
  * Forces an immediate sync.
184
180
  * @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.
181
+ * @param blockHash - If provided, verifies the block at targetBlockNumber matches this hash. On mismatch, triggers a resync (reorg detection).
186
182
  * @returns A promise that resolves with the block number the world state was synced to
187
183
  */
188
- public async syncImmediate(
189
- targetBlockNumber?: BlockNumber,
190
- skipThrowIfTargetNotReached?: boolean,
191
- ): Promise<BlockNumber> {
184
+ public async syncImmediate(targetBlockNumber?: BlockNumber, blockHash?: BlockHash): Promise<BlockNumber> {
192
185
  if (this.currentState !== WorldStateRunningState.RUNNING) {
193
186
  throw new Error(`World State is not running. Unable to perform sync.`);
194
187
  }
@@ -200,7 +193,19 @@ export class ServerWorldStateSynchronizer
200
193
  // If we have been given a block number to sync to and we have reached that number then return
201
194
  const currentBlockNumber = await this.getLatestBlockNumber();
202
195
  if (targetBlockNumber !== undefined && targetBlockNumber <= currentBlockNumber) {
203
- return currentBlockNumber;
196
+ if (blockHash === undefined) {
197
+ return currentBlockNumber;
198
+ }
199
+
200
+ // If a block hash was provided, verify we're on the expected fork
201
+ const currentHash = await this.getL2BlockHash(targetBlockNumber);
202
+ if (currentHash === blockHash.toString()) {
203
+ return currentBlockNumber;
204
+ }
205
+ // Hash mismatch: a reorg may have occurred, fall through to trigger sync
206
+ this.log.debug(
207
+ `World state block hash mismatch at ${targetBlockNumber} (expected ${blockHash}, got ${currentHash}). Triggering resync.`,
208
+ );
204
209
  }
205
210
  this.log.debug(`World State at ${currentBlockNumber} told to sync to ${targetBlockNumber ?? 'latest'}`);
206
211
 
@@ -218,7 +223,7 @@ export class ServerWorldStateSynchronizer
218
223
 
219
224
  // If we have been given a block number to sync to and we have not reached that number then fail
220
225
  const updatedBlockNumber = await this.getLatestBlockNumber();
221
- if (!skipThrowIfTargetNotReached && targetBlockNumber !== undefined && targetBlockNumber > updatedBlockNumber) {
226
+ if (targetBlockNumber !== undefined && targetBlockNumber > updatedBlockNumber) {
222
227
  throw new WorldStateSynchronizerError(
223
228
  `Unable to sync to block number ${targetBlockNumber} (last synced is ${updatedBlockNumber})`,
224
229
  {
@@ -232,6 +237,24 @@ export class ServerWorldStateSynchronizer
232
237
  );
233
238
  }
234
239
 
240
+ // If a block hash was provided, verify we're on the expected fork after syncing, throw otherwise
241
+ if (blockHash !== undefined && targetBlockNumber !== undefined) {
242
+ const updatedHash = await this.getL2BlockHash(targetBlockNumber);
243
+ if (updatedHash !== blockHash.toString()) {
244
+ throw new WorldStateSynchronizerError(
245
+ `Block hash mismatch at block ${targetBlockNumber} (expected ${blockHash} but got ${updatedHash})`,
246
+ {
247
+ cause: {
248
+ reason: 'block_hash_mismatch',
249
+ targetBlockNumber,
250
+ expectedHash: blockHash.toString(),
251
+ actualHash: updatedHash,
252
+ },
253
+ },
254
+ );
255
+ }
256
+ }
257
+
235
258
  return updatedBlockNumber;
236
259
  }
237
260
 
@@ -268,15 +291,15 @@ export class ServerWorldStateSynchronizer
268
291
  proposed: latestBlockId,
269
292
  checkpointed: {
270
293
  block: { number: INITIAL_L2_BLOCK_NUM, hash: GENESIS_BLOCK_HEADER_HASH.toString() },
271
- checkpoint: { number: INITIAL_L2_CHECKPOINT_NUM, hash: genesisCheckpointHeaderHash },
294
+ checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
272
295
  },
273
296
  finalized: {
274
297
  block: { number: status.finalizedBlockNumber, hash: finalizedBlockHash ?? '' },
275
- checkpoint: { number: INITIAL_L2_CHECKPOINT_NUM, hash: genesisCheckpointHeaderHash },
298
+ checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
276
299
  },
277
300
  proven: {
278
301
  block: { number: provenBlockNumber, hash: provenBlockHash ?? '' },
279
- checkpoint: { number: INITIAL_L2_CHECKPOINT_NUM, hash: genesisCheckpointHeaderHash },
302
+ checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
280
303
  },
281
304
  };
282
305
  }
@@ -304,8 +327,8 @@ export class ServerWorldStateSynchronizer
304
327
  * @param l2Blocks - The L2 blocks to handle.
305
328
  * @returns Whether the block handled was produced by this same node.
306
329
  */
307
- private async handleL2Blocks(l2Blocks: L2BlockNew[]) {
308
- this.log.trace(`Handling L2 blocks ${l2Blocks[0].number} to ${l2Blocks.at(-1)!.number}`);
330
+ private async handleL2Blocks(l2Blocks: L2Block[]) {
331
+ this.log.debug(`Handling L2 blocks ${l2Blocks[0].number} to ${l2Blocks.at(-1)!.number}`);
309
332
 
310
333
  // Fetch the L1->L2 messages for the first block in a checkpoint.
311
334
  const messagesForBlocks = new Map<BlockNumber, Fr[]>();
@@ -345,11 +368,13 @@ export class ServerWorldStateSynchronizer
345
368
  * @param l1ToL2Messages - The L1 to L2 messages for the block.
346
369
  * @returns Whether the block handled was produced by this same node.
347
370
  */
348
- private async handleL2Block(l2Block: L2BlockNew, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull> {
349
- this.log.trace(`Pushing L2 block ${l2Block.number} to merkle tree db `, {
371
+ private async handleL2Block(l2Block: L2Block, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull> {
372
+ this.log.debug(`Pushing L2 block ${l2Block.number} to merkle tree db `, {
350
373
  blockNumber: l2Block.number,
351
374
  blockHash: await l2Block.hash().then(h => h.toString()),
352
375
  l1ToL2Messages: l1ToL2Messages.map(msg => msg.toString()),
376
+ blockHeader: l2Block.header.toInspect(),
377
+ blockStats: l2Block.getStats(),
353
378
  });
354
379
  const result = await this.merkleTreeDb.handleL2BlockAndMessages(l2Block, l1ToL2Messages);
355
380
 
@@ -363,16 +388,59 @@ export class ServerWorldStateSynchronizer
363
388
 
364
389
  private async handleChainFinalized(blockNumber: BlockNumber) {
365
390
  this.log.verbose(`Finalized chain is now at block ${blockNumber}`);
391
+ // If the finalized block number is older than the oldest available block in world state,
392
+ // skip entirely. The finalized block number can jump backwards (e.g. when the finalization
393
+ // heuristic changes) and try to read block data that has already been pruned. When this
394
+ // happens, there is nothing useful to do — the native world state is already finalized
395
+ // past this point and pruning has already happened.
396
+ const currentSummary = await this.merkleTreeDb.getStatusSummary();
397
+ if (blockNumber < currentSummary.oldestHistoricalBlock || blockNumber < 1) {
398
+ this.log.trace(
399
+ `Finalized block ${blockNumber} is older than the oldest available block ${currentSummary.oldestHistoricalBlock}. Skipping.`,
400
+ );
401
+ return;
402
+ }
366
403
  const summary = await this.merkleTreeDb.setFinalized(blockNumber);
367
404
  if (this.historyToKeep === undefined) {
368
405
  return;
369
406
  }
370
- const newHistoricBlock = summary.finalizedBlockNumber - this.historyToKeep + 1;
371
- if (newHistoricBlock <= 1) {
407
+ // Get the checkpointed block for the finalized block number
408
+ const finalisedCheckpoint = await this.l2BlockSource.getCheckpointedBlock(summary.finalizedBlockNumber);
409
+ if (finalisedCheckpoint === undefined) {
410
+ this.log.warn(
411
+ `Failed to retrieve checkpointed block for finalized block number: ${summary.finalizedBlockNumber}`,
412
+ );
413
+ return;
414
+ }
415
+ // Compute the required historic checkpoint number
416
+ const newHistoricCheckpointNumber = finalisedCheckpoint.checkpointNumber - this.historyToKeep + 1;
417
+ if (newHistoricCheckpointNumber <= 1) {
418
+ return;
419
+ }
420
+ // 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) {
426
+ this.log.warn(`Failed to retrieve checkpoint number ${newHistoricCheckpointNumber} from Archiver`);
427
+ return;
428
+ }
429
+ const historicCheckpoint = historicCheckpoints[0];
430
+ if (historicCheckpoint.checkpoint.blocks.length === 0 || historicCheckpoint.checkpoint.blocks[0] === undefined) {
431
+ this.log.warn(`Retrieved checkpoint number ${newHistoricCheckpointNumber} has no blocks!`);
432
+ return;
433
+ }
434
+ // Find the block at the start of the checkpoint and remove blocks up to this one
435
+ const newHistoricBlock = historicCheckpoint.checkpoint.blocks[0];
436
+ if (newHistoricBlock.number <= currentSummary.oldestHistoricalBlock) {
437
+ this.log.debug(
438
+ `Historic block ${newHistoricBlock.number} is not newer than oldest available ${currentSummary.oldestHistoricalBlock}. Skipping prune.`,
439
+ );
372
440
  return;
373
441
  }
374
- this.log.verbose(`Pruning historic blocks to ${newHistoricBlock}`);
375
- const status = await this.merkleTreeDb.removeHistoricalBlocks(BlockNumber(newHistoricBlock));
442
+ this.log.verbose(`Pruning historic blocks to ${newHistoricBlock.number}`);
443
+ const status = await this.merkleTreeDb.removeHistoricalBlocks(BlockNumber(newHistoricBlock.number));
376
444
  this.log.debug(`World state summary `, status.summary);
377
445
  }
378
446
 
@@ -383,7 +451,7 @@ export class ServerWorldStateSynchronizer
383
451
  }
384
452
 
385
453
  private async handleChainPruned(blockNumber: BlockNumber) {
386
- this.log.warn(`Chain pruned to block ${blockNumber}`);
454
+ this.log.info(`Chain pruned to block ${blockNumber}`);
387
455
  const status = await this.merkleTreeDb.unwindBlocks(blockNumber);
388
456
  this.provenBlockNumber = undefined;
389
457
  this.instrumentation.updateWorldStateMetrics(status);
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;