@aztec/world-state 0.0.0-test.1 → 0.0.1-commit.5daedc8

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 (63) hide show
  1. package/dest/index.d.ts +1 -1
  2. package/dest/instrumentation/instrumentation.d.ts +6 -4
  3. package/dest/instrumentation/instrumentation.d.ts.map +1 -1
  4. package/dest/instrumentation/instrumentation.js +16 -8
  5. package/dest/native/bench_metrics.d.ts +23 -0
  6. package/dest/native/bench_metrics.d.ts.map +1 -0
  7. package/dest/native/bench_metrics.js +81 -0
  8. package/dest/native/fork_checkpoint.d.ts +1 -1
  9. package/dest/native/fork_checkpoint.d.ts.map +1 -1
  10. package/dest/native/index.d.ts +1 -1
  11. package/dest/native/merkle_trees_facade.d.ts +10 -4
  12. package/dest/native/merkle_trees_facade.d.ts.map +1 -1
  13. package/dest/native/merkle_trees_facade.js +39 -7
  14. package/dest/native/message.d.ts +65 -45
  15. package/dest/native/message.d.ts.map +1 -1
  16. package/dest/native/message.js +55 -56
  17. package/dest/native/native_world_state.d.ts +20 -15
  18. package/dest/native/native_world_state.d.ts.map +1 -1
  19. package/dest/native/native_world_state.js +90 -33
  20. package/dest/native/native_world_state_instance.d.ts +20 -4
  21. package/dest/native/native_world_state_instance.d.ts.map +1 -1
  22. package/dest/native/native_world_state_instance.js +42 -3
  23. package/dest/native/world_state_ops_queue.d.ts +1 -1
  24. package/dest/native/world_state_ops_queue.d.ts.map +1 -1
  25. package/dest/native/world_state_ops_queue.js +1 -1
  26. package/dest/synchronizer/config.d.ts +12 -2
  27. package/dest/synchronizer/config.d.ts.map +1 -1
  28. package/dest/synchronizer/config.js +26 -1
  29. package/dest/synchronizer/errors.d.ts +4 -0
  30. package/dest/synchronizer/errors.d.ts.map +1 -0
  31. package/dest/synchronizer/errors.js +5 -0
  32. package/dest/synchronizer/factory.d.ts +9 -2
  33. package/dest/synchronizer/factory.d.ts.map +1 -1
  34. package/dest/synchronizer/factory.js +9 -4
  35. package/dest/synchronizer/index.d.ts +1 -1
  36. package/dest/synchronizer/server_world_state_synchronizer.d.ts +14 -18
  37. package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
  38. package/dest/synchronizer/server_world_state_synchronizer.js +78 -38
  39. package/dest/test/index.d.ts +1 -1
  40. package/dest/test/utils.d.ts +2 -2
  41. package/dest/test/utils.d.ts.map +1 -1
  42. package/dest/test/utils.js +21 -18
  43. package/dest/testing.d.ts +2 -2
  44. package/dest/testing.d.ts.map +1 -1
  45. package/dest/testing.js +6 -10
  46. package/dest/world-state-db/index.d.ts +1 -1
  47. package/dest/world-state-db/merkle_tree_db.d.ts +10 -6
  48. package/dest/world-state-db/merkle_tree_db.d.ts.map +1 -1
  49. package/package.json +24 -24
  50. package/src/instrumentation/instrumentation.ts +22 -10
  51. package/src/native/bench_metrics.ts +91 -0
  52. package/src/native/merkle_trees_facade.ts +44 -13
  53. package/src/native/message.ts +81 -63
  54. package/src/native/native_world_state.ts +99 -43
  55. package/src/native/native_world_state_instance.ts +59 -8
  56. package/src/native/world_state_ops_queue.ts +1 -1
  57. package/src/synchronizer/config.ts +47 -2
  58. package/src/synchronizer/errors.ts +5 -0
  59. package/src/synchronizer/factory.ts +31 -8
  60. package/src/synchronizer/server_world_state_synchronizer.ts +93 -40
  61. package/src/test/utils.ts +46 -31
  62. package/src/testing.ts +3 -7
  63. package/src/world-state-db/merkle_tree_db.ts +14 -5
@@ -8,7 +8,7 @@ import {
8
8
  NULLIFIER_TREE_HEIGHT,
9
9
  PUBLIC_DATA_TREE_HEIGHT,
10
10
  } from '@aztec/constants';
11
- import { createLogger } from '@aztec/foundation/log';
11
+ import { type Logger, 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';
@@ -17,6 +17,7 @@ import assert from 'assert';
17
17
  import { cpus } from 'os';
18
18
 
19
19
  import type { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
20
+ import type { WorldStateTreeMapSizes } from '../synchronizer/factory.js';
20
21
  import {
21
22
  WorldStateMessageType,
22
23
  type WorldStateRequest,
@@ -35,6 +36,8 @@ export interface NativeWorldStateInstance {
35
36
  messageType: T,
36
37
  body: WorldStateRequest[T] & WorldStateRequestCategories,
37
38
  ): Promise<WorldStateResponse[T]>;
39
+ // TODO(dbanks12): this returns any type, but we should strongly type it
40
+ getHandle(): any;
38
41
  }
39
42
 
40
43
  /**
@@ -50,15 +53,17 @@ export class NativeWorldState implements NativeWorldStateInstance {
50
53
 
51
54
  /** Creates a new native WorldState instance */
52
55
  constructor(
53
- dataDir: string,
54
- dbMapSizeKb: number,
55
- prefilledPublicData: PublicDataTreeLeaf[] = [],
56
- private instrumentation: WorldStateInstrumentation,
57
- private log = createLogger('world-state:database'),
56
+ private readonly dataDir: string,
57
+ private readonly wsTreeMapSizes: WorldStateTreeMapSizes,
58
+ private readonly prefilledPublicData: PublicDataTreeLeaf[] = [],
59
+ private readonly instrumentation: WorldStateInstrumentation,
60
+ private readonly log: Logger = createLogger('world-state:database'),
58
61
  ) {
59
62
  const threads = Math.min(cpus().length, MAX_WORLD_STATE_THREADS);
60
63
  log.info(
61
- `Creating world state data store at directory ${dataDir} with map size ${dbMapSizeKb} KB and ${threads} threads.`,
64
+ `Creating world state data store at directory ${dataDir} with map sizes ${JSON.stringify(
65
+ wsTreeMapSizes,
66
+ )} and ${threads} threads.`,
62
67
  );
63
68
  const prefilledPublicDataBufferArray = prefilledPublicData.map(d => [d.slot.toBuffer(), d.value.toBuffer()]);
64
69
  const ws = new BaseNativeWorldState(
@@ -76,7 +81,13 @@ export class NativeWorldState implements NativeWorldStateInstance {
76
81
  },
77
82
  prefilledPublicDataBufferArray,
78
83
  GeneratorIndex.BLOCK_HASH,
79
- dbMapSizeKb,
84
+ {
85
+ [MerkleTreeId.NULLIFIER_TREE]: wsTreeMapSizes.nullifierTreeMapSizeKb,
86
+ [MerkleTreeId.NOTE_HASH_TREE]: wsTreeMapSizes.noteHashTreeMapSizeKb,
87
+ [MerkleTreeId.PUBLIC_DATA_TREE]: wsTreeMapSizes.publicDataTreeMapSizeKb,
88
+ [MerkleTreeId.L1_TO_L2_MESSAGE_TREE]: wsTreeMapSizes.messageTreeMapSizeKb,
89
+ [MerkleTreeId.ARCHIVE]: wsTreeMapSizes.archiveTreeMapSizeKb,
90
+ },
80
91
  threads,
81
92
  );
82
93
  this.instance = new MsgpackChannel(ws);
@@ -84,6 +95,46 @@ export class NativeWorldState implements NativeWorldStateInstance {
84
95
  this.queues.set(0, new WorldStateOpsQueue());
85
96
  }
86
97
 
98
+ public getDataDir() {
99
+ return this.dataDir;
100
+ }
101
+
102
+ public clone() {
103
+ return new NativeWorldState(
104
+ this.dataDir,
105
+ this.wsTreeMapSizes,
106
+ this.prefilledPublicData,
107
+ this.instrumentation,
108
+ this.log,
109
+ );
110
+ }
111
+
112
+ /**
113
+ * Gets the native WorldState handle from the underlying native instance.
114
+ * We call the getHandle() method on the native WorldState to get a NAPI External
115
+ * that wraps the underlying C++ WorldState pointer.
116
+ * @returns The NAPI External handle to the native WorldState instance,since
117
+ * the NAPI external type is opaque, we return any (we could also use an opaque symbol type)
118
+ */
119
+ public getHandle(): any {
120
+ const worldStateWrapper = (this.instance as any).dest;
121
+
122
+ if (!worldStateWrapper) {
123
+ throw new Error('No WorldStateWrapper found');
124
+ }
125
+
126
+ if (typeof worldStateWrapper.getHandle !== 'function') {
127
+ throw new Error('WorldStateWrapper does not have getHandle method');
128
+ }
129
+
130
+ // Call getHandle() to get the NAPI External
131
+ try {
132
+ return worldStateWrapper.getHandle();
133
+ } catch (error) {
134
+ this.log.error('Failed to get native WorldState handle', error);
135
+ }
136
+ }
137
+
87
138
  /**
88
139
  * Sends a message to the native instance and returns the response.
89
140
  * @param messageType - The type of message to send
@@ -35,7 +35,7 @@ export const MUTATING_MSG_TYPES = new Set([
35
35
  WorldStateMessageType.SYNC_BLOCK,
36
36
  WorldStateMessageType.CREATE_FORK,
37
37
  WorldStateMessageType.DELETE_FORK,
38
- WorldStateMessageType.FINALISE_BLOCKS,
38
+ WorldStateMessageType.FINALIZE_BLOCKS,
39
39
  WorldStateMessageType.UNWIND_BLOCKS,
40
40
  WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS,
41
41
  WorldStateMessageType.CREATE_CHECKPOINT,
@@ -16,9 +16,24 @@ export interface WorldStateConfig {
16
16
  /** Size of the batch for each get-blocks request from the synchronizer to the archiver. */
17
17
  worldStateBlockRequestBatchSize?: number;
18
18
 
19
- /** The map size to be provided to LMDB for each world state tree DB, optional, will inherit from the general dataStoreMapSizeKB if not specified*/
19
+ /** The map size to be provided to LMDB for each world state tree DB, optional, will inherit from the general dataStoreMapSizeKb if not specified*/
20
20
  worldStateDbMapSizeKb?: number;
21
21
 
22
+ /** The map size to be provided to LMDB for each world state archive tree, optional, will inherit from the general worldStateDbMapSizeKb if not specified*/
23
+ archiveTreeMapSizeKb?: number;
24
+
25
+ /** The map size to be provided to LMDB for each world state nullifier tree, optional, will inherit from the general worldStateDbMapSizeKb if not specified*/
26
+ nullifierTreeMapSizeKb?: number;
27
+
28
+ /** The map size to be provided to LMDB for each world state note hash tree, optional, will inherit from the general worldStateDbMapSizeKb if not specified*/
29
+ noteHashTreeMapSizeKb?: number;
30
+
31
+ /** The map size to be provided to LMDB for each world state message tree, optional, will inherit from the general worldStateDbMapSizeKb if not specified*/
32
+ messageTreeMapSizeKb?: number;
33
+
34
+ /** The map size to be provided to LMDB for each world state public data tree, optional, will inherit from the general worldStateDbMapSizeKb if not specified*/
35
+ publicDataTreeMapSizeKb?: number;
36
+
22
37
  /** Optional directory for the world state DB, if unspecified will default to the general data directory */
23
38
  worldStateDataDirectory?: string;
24
39
 
@@ -46,7 +61,37 @@ export const worldStateConfigMappings: ConfigMappingsType<WorldStateConfig> = {
46
61
  worldStateDbMapSizeKb: {
47
62
  env: 'WS_DB_MAP_SIZE_KB',
48
63
  parseEnv: (val: string | undefined) => (val ? +val : undefined),
49
- description: 'The maximum possible size of the world state DB',
64
+ description: 'The maximum possible size of the world state DB in KB. Overwrites the general dataStoreMapSizeKb.',
65
+ },
66
+ archiveTreeMapSizeKb: {
67
+ env: 'ARCHIVE_TREE_MAP_SIZE_KB',
68
+ parseEnv: (val: string | undefined) => (val ? +val : undefined),
69
+ description:
70
+ 'The maximum possible size of the world state archive tree in KB. Overwrites the general worldStateDbMapSizeKb.',
71
+ },
72
+ nullifierTreeMapSizeKb: {
73
+ env: 'NULLIFIER_TREE_MAP_SIZE_KB',
74
+ parseEnv: (val: string | undefined) => (val ? +val : undefined),
75
+ description:
76
+ 'The maximum possible size of the world state nullifier tree in KB. Overwrites the general worldStateDbMapSizeKb.',
77
+ },
78
+ noteHashTreeMapSizeKb: {
79
+ env: 'NOTE_HASH_TREE_MAP_SIZE_KB',
80
+ parseEnv: (val: string | undefined) => (val ? +val : undefined),
81
+ description:
82
+ 'The maximum possible size of the world state note hash tree in KB. Overwrites the general worldStateDbMapSizeKb.',
83
+ },
84
+ messageTreeMapSizeKb: {
85
+ env: 'MESSAGE_TREE_MAP_SIZE_KB',
86
+ parseEnv: (val: string | undefined) => (val ? +val : undefined),
87
+ description:
88
+ 'The maximum possible size of the world state message tree in KB. Overwrites the general worldStateDbMapSizeKb.',
89
+ },
90
+ publicDataTreeMapSizeKb: {
91
+ env: 'PUBLIC_DATA_TREE_MAP_SIZE_KB',
92
+ parseEnv: (val: string | undefined) => (val ? +val : undefined),
93
+ description:
94
+ 'The maximum possible size of the world state public data tree in KB. Overwrites the general worldStateDbMapSizeKb.',
50
95
  },
51
96
  worldStateDataDirectory: {
52
97
  env: 'WS_DATA_DIRECTORY',
@@ -0,0 +1,5 @@
1
+ export class WorldStateSynchronizerError extends Error {
2
+ constructor(message: string, options?: ErrorOptions) {
3
+ super(message, options);
4
+ }
5
+ }
@@ -9,6 +9,14 @@ import { NativeWorldStateService } from '../native/native_world_state.js';
9
9
  import type { WorldStateConfig } from './config.js';
10
10
  import { ServerWorldStateSynchronizer } from './server_world_state_synchronizer.js';
11
11
 
12
+ export interface WorldStateTreeMapSizes {
13
+ archiveTreeMapSizeKb: number;
14
+ nullifierTreeMapSizeKb: number;
15
+ noteHashTreeMapSizeKb: number;
16
+ messageTreeMapSizeKb: number;
17
+ publicDataTreeMapSizeKb: number;
18
+ }
19
+
12
20
  export async function createWorldStateSynchronizer(
13
21
  config: WorldStateConfig & DataStoreConfig,
14
22
  l2BlockSource: L2BlockSource & L1ToL2MessageSource,
@@ -21,25 +29,40 @@ export async function createWorldStateSynchronizer(
21
29
  }
22
30
 
23
31
  export async function createWorldState(
24
- config: WorldStateConfig & DataStoreConfig,
32
+ config: Pick<
33
+ WorldStateConfig,
34
+ | 'worldStateDataDirectory'
35
+ | 'worldStateDbMapSizeKb'
36
+ | 'archiveTreeMapSizeKb'
37
+ | 'nullifierTreeMapSizeKb'
38
+ | 'noteHashTreeMapSizeKb'
39
+ | 'messageTreeMapSizeKb'
40
+ | 'publicDataTreeMapSizeKb'
41
+ > &
42
+ Pick<DataStoreConfig, 'dataDirectory' | 'dataStoreMapSizeKb' | 'l1Contracts'>,
25
43
  prefilledPublicData: PublicDataTreeLeaf[] = [],
26
44
  instrumentation: WorldStateInstrumentation = new WorldStateInstrumentation(getTelemetryClient()),
27
45
  ) {
28
- const newConfig = {
29
- dataDirectory: config.worldStateDataDirectory ?? config.dataDirectory,
30
- dataStoreMapSizeKB: config.worldStateDbMapSizeKb ?? config.dataStoreMapSizeKB,
31
- } as DataStoreConfig;
46
+ const dataDirectory = config.worldStateDataDirectory ?? config.dataDirectory;
47
+ const dataStoreMapSizeKb = config.worldStateDbMapSizeKb ?? config.dataStoreMapSizeKb;
48
+ const wsTreeMapSizes: WorldStateTreeMapSizes = {
49
+ archiveTreeMapSizeKb: config.archiveTreeMapSizeKb ?? dataStoreMapSizeKb,
50
+ nullifierTreeMapSizeKb: config.nullifierTreeMapSizeKb ?? dataStoreMapSizeKb,
51
+ noteHashTreeMapSizeKb: config.noteHashTreeMapSizeKb ?? dataStoreMapSizeKb,
52
+ messageTreeMapSizeKb: config.messageTreeMapSizeKb ?? dataStoreMapSizeKb,
53
+ publicDataTreeMapSizeKb: config.publicDataTreeMapSizeKb ?? dataStoreMapSizeKb,
54
+ };
32
55
 
33
56
  if (!config.l1Contracts?.rollupAddress) {
34
57
  throw new Error('Rollup address is required to create a world state synchronizer.');
35
58
  }
36
59
 
37
60
  // If a data directory is provided in config, then create a persistent store.
38
- const merkleTrees = newConfig.dataDirectory
61
+ const merkleTrees = dataDirectory
39
62
  ? await NativeWorldStateService.new(
40
63
  config.l1Contracts.rollupAddress,
41
- newConfig.dataDirectory,
42
- newConfig.dataStoreMapSizeKB,
64
+ dataDirectory,
65
+ wsTreeMapSizes,
43
66
  prefilledPublicData,
44
67
  instrumentation,
45
68
  )
@@ -1,10 +1,10 @@
1
1
  import { L1_TO_L2_MSG_SUBTREE_HEIGHT } from '@aztec/constants';
2
+ import { SHA256Trunc } from '@aztec/foundation/crypto';
2
3
  import type { Fr } from '@aztec/foundation/fields';
3
- import { createLogger } from '@aztec/foundation/log';
4
+ import { type Logger, createLogger } from '@aztec/foundation/log';
4
5
  import { promiseWithResolvers } from '@aztec/foundation/promise';
5
6
  import { elapsed } from '@aztec/foundation/timer';
6
7
  import { MerkleTreeCalculator } from '@aztec/foundation/trees';
7
- import { SHA256Trunc } from '@aztec/merkle-tree';
8
8
  import type {
9
9
  L2Block,
10
10
  L2BlockId,
@@ -22,6 +22,7 @@ import {
22
22
  type WorldStateSynchronizerStatus,
23
23
  } from '@aztec/stdlib/interfaces/server';
24
24
  import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
25
+ import type { SnapshotDataKeys } from '@aztec/stdlib/snapshots';
25
26
  import type { L2BlockHandledStats } from '@aztec/stdlib/stats';
26
27
  import { MerkleTreeId, type MerkleTreeReadOperations, type MerkleTreeWriteOperations } from '@aztec/stdlib/trees';
27
28
  import { TraceableL2BlockStream, getTelemetryClient } from '@aztec/telemetry-client';
@@ -30,6 +31,9 @@ import { WorldStateInstrumentation } from '../instrumentation/instrumentation.js
30
31
  import type { WorldStateStatusFull } from '../native/message.js';
31
32
  import type { MerkleTreeAdminDatabase } from '../world-state-db/merkle_tree_db.js';
32
33
  import type { WorldStateConfig } from './config.js';
34
+ import { WorldStateSynchronizerError } from './errors.js';
35
+
36
+ export type { SnapshotDataKeys };
33
37
 
34
38
  /**
35
39
  * Synchronizes the world state with the L2 blocks from a L2BlockSource via a block stream.
@@ -49,12 +53,16 @@ export class ServerWorldStateSynchronizer
49
53
  private syncPromise = promiseWithResolvers<void>();
50
54
  protected blockStream: L2BlockStream | undefined;
51
55
 
56
+ // WorldState doesn't track the proven block number, it only tracks the latest tips of the pending chain and the finalized chain
57
+ // store the proven block number here, in the synchronizer, so that we don't end up spamming the logs with 'chain-proved' events
58
+ private provenBlockNumber: bigint | undefined;
59
+
52
60
  constructor(
53
61
  private readonly merkleTreeDb: MerkleTreeAdminDatabase,
54
62
  private readonly l2BlockSource: L2BlockSource & L1ToL2MessageSource,
55
63
  private readonly config: WorldStateConfig,
56
64
  private instrumentation = new WorldStateInstrumentation(getTelemetryClient()),
57
- private readonly log = createLogger('world_state'),
65
+ private readonly log: Logger = createLogger('world_state'),
58
66
  ) {
59
67
  this.merkleTreeCommitted = this.merkleTreeDb.getCommitted();
60
68
  this.historyToKeep = config.worldStateBlockHistory < 1 ? undefined : config.worldStateBlockHistory;
@@ -77,6 +85,14 @@ export class ServerWorldStateSynchronizer
77
85
  return this.merkleTreeDb.fork(blockNumber);
78
86
  }
79
87
 
88
+ public backupTo(dstPath: string, compact?: boolean): Promise<Record<Exclude<SnapshotDataKeys, 'archiver'>, string>> {
89
+ return this.merkleTreeDb.backupTo(dstPath, compact);
90
+ }
91
+
92
+ public clear(): Promise<void> {
93
+ return this.merkleTreeDb.clear();
94
+ }
95
+
80
96
  public async start() {
81
97
  if (this.currentState === WorldStateRunningState.STOPPED) {
82
98
  throw new Error('Synchronizer already stopped');
@@ -131,9 +147,9 @@ export class ServerWorldStateSynchronizer
131
147
  public async status(): Promise<WorldStateSynchronizerStatus> {
132
148
  const summary = await this.merkleTreeDb.getStatusSummary();
133
149
  const status: WorldStateSyncStatus = {
134
- latestBlockNumber: Number(summary.unfinalisedBlockNumber),
135
- latestBlockHash: (await this.getL2BlockHash(Number(summary.unfinalisedBlockNumber))) ?? '',
136
- finalisedBlockNumber: Number(summary.finalisedBlockNumber),
150
+ latestBlockNumber: Number(summary.unfinalizedBlockNumber),
151
+ latestBlockHash: (await this.getL2BlockHash(Number(summary.unfinalizedBlockNumber))) ?? '',
152
+ finalizedBlockNumber: Number(summary.finalizedBlockNumber),
137
153
  oldestHistoricBlockNumber: Number(summary.oldestHistoricalBlock),
138
154
  treesAreSynched: summary.treesAreSynched,
139
155
  };
@@ -147,16 +163,36 @@ export class ServerWorldStateSynchronizer
147
163
  return (await this.getL2Tips()).latest.number;
148
164
  }
149
165
 
166
+ public async stopSync() {
167
+ this.log.debug('Stopping sync...');
168
+ await this.blockStream?.stop();
169
+ this.log.info('Stopped sync');
170
+ }
171
+
172
+ public resumeSync() {
173
+ if (!this.blockStream) {
174
+ throw new Error('Cannot resume sync as block stream is not initialized');
175
+ }
176
+ this.log.debug('Resuming sync...');
177
+ this.blockStream.start();
178
+ this.log.info('Resumed sync');
179
+ }
180
+
150
181
  /**
151
182
  * Forces an immediate sync.
152
- * @param targetBlockNumber - The target block number that we must sync to. Will download unproven blocks if needed to reach it. Throws if cannot be reached.
183
+ * @param targetBlockNumber - The target block number that we must sync to. Will download unproven blocks if needed to reach it.
184
+ * @param skipThrowIfTargetNotReached - Whether to skip throwing if the target block number is not reached.
153
185
  * @returns A promise that resolves with the block number the world state was synced to
154
186
  */
155
- public async syncImmediate(targetBlockNumber?: number): Promise<number> {
156
- if (this.currentState !== WorldStateRunningState.RUNNING || this.blockStream === undefined) {
187
+ public async syncImmediate(targetBlockNumber?: number, skipThrowIfTargetNotReached?: boolean): Promise<number> {
188
+ if (this.currentState !== WorldStateRunningState.RUNNING) {
157
189
  throw new Error(`World State is not running. Unable to perform sync.`);
158
190
  }
159
191
 
192
+ if (this.blockStream === undefined) {
193
+ throw new Error('Block stream is not initialized. Unable to perform sync.');
194
+ }
195
+
160
196
  // If we have been given a block number to sync to and we have reached that number then return
161
197
  const currentBlockNumber = await this.getLatestBlockNumber();
162
198
  if (targetBlockNumber !== undefined && targetBlockNumber <= currentBlockNumber) {
@@ -164,13 +200,32 @@ export class ServerWorldStateSynchronizer
164
200
  }
165
201
  this.log.debug(`World State at ${currentBlockNumber} told to sync to ${targetBlockNumber ?? 'latest'}`);
166
202
 
203
+ // If the archiver is behind the target block, force an archiver sync
204
+ if (targetBlockNumber) {
205
+ const archiverLatestBlock = await this.l2BlockSource.getBlockNumber();
206
+ if (archiverLatestBlock < targetBlockNumber) {
207
+ this.log.debug(`Archiver is at ${archiverLatestBlock} behind target block ${targetBlockNumber}.`);
208
+ await this.l2BlockSource.syncImmediate();
209
+ }
210
+ }
211
+
167
212
  // Force the block stream to sync against the archiver now
168
213
  await this.blockStream.sync();
169
214
 
170
215
  // If we have been given a block number to sync to and we have not reached that number then fail
171
216
  const updatedBlockNumber = await this.getLatestBlockNumber();
172
- if (targetBlockNumber !== undefined && targetBlockNumber > updatedBlockNumber) {
173
- throw new Error(`Unable to sync to block number ${targetBlockNumber} (last synced is ${updatedBlockNumber})`);
217
+ if (!skipThrowIfTargetNotReached && targetBlockNumber !== undefined && targetBlockNumber > updatedBlockNumber) {
218
+ throw new WorldStateSynchronizerError(
219
+ `Unable to sync to block number ${targetBlockNumber} (last synced is ${updatedBlockNumber})`,
220
+ {
221
+ cause: {
222
+ reason: 'block_not_available',
223
+ previousBlockNumber: currentBlockNumber,
224
+ updatedBlockNumber,
225
+ targetBlockNumber,
226
+ },
227
+ },
228
+ );
174
229
  }
175
230
 
176
231
  return updatedBlockNumber;
@@ -195,35 +250,31 @@ export class ServerWorldStateSynchronizer
195
250
  /** Returns the latest L2 block number for each tip of the chain (latest, proven, finalized). */
196
251
  public async getL2Tips(): Promise<L2Tips> {
197
252
  const status = await this.merkleTreeDb.getStatusSummary();
198
- const unfinalisedBlockHash = await this.getL2BlockHash(Number(status.unfinalisedBlockNumber));
199
- const latestBlockId: L2BlockId = { number: Number(status.unfinalisedBlockNumber), hash: unfinalisedBlockHash! };
253
+ const unfinalizedBlockHash = await this.getL2BlockHash(Number(status.unfinalizedBlockNumber));
254
+ const latestBlockId: L2BlockId = { number: Number(status.unfinalizedBlockNumber), hash: unfinalizedBlockHash! };
200
255
 
201
256
  return {
202
257
  latest: latestBlockId,
203
- finalized: { number: Number(status.finalisedBlockNumber), hash: '' },
204
- proven: { number: Number(status.finalisedBlockNumber), hash: '' }, // TODO(palla/reorg): Using finalised as proven for now
258
+ finalized: { number: Number(status.finalizedBlockNumber), hash: '' },
259
+ proven: { number: Number(this.provenBlockNumber ?? status.finalizedBlockNumber), hash: '' }, // TODO(palla/reorg): Using finalized as proven for now
205
260
  };
206
261
  }
207
262
 
208
263
  /** Handles an event emitted by the block stream. */
209
264
  public async handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void> {
210
- try {
211
- switch (event.type) {
212
- case 'blocks-added':
213
- await this.handleL2Blocks(event.blocks);
214
- break;
215
- case 'chain-pruned':
216
- await this.handleChainPruned(event.blockNumber);
217
- break;
218
- case 'chain-proven':
219
- await this.handleChainProven(event.blockNumber);
220
- break;
221
- case 'chain-finalized':
222
- await this.handleChainFinalized(event.blockNumber);
223
- break;
224
- }
225
- } catch (err) {
226
- this.log.error('Error processing block stream', err);
265
+ switch (event.type) {
266
+ case 'blocks-added':
267
+ await this.handleL2Blocks(event.blocks.map(b => b.block));
268
+ break;
269
+ case 'chain-pruned':
270
+ await this.handleChainPruned(event.block.number);
271
+ break;
272
+ case 'chain-proven':
273
+ await this.handleChainProven(event.block.number);
274
+ break;
275
+ case 'chain-finalized':
276
+ await this.handleChainFinalized(event.block.number);
277
+ break;
227
278
  }
228
279
  }
229
280
 
@@ -235,17 +286,17 @@ export class ServerWorldStateSynchronizer
235
286
  private async handleL2Blocks(l2Blocks: L2Block[]) {
236
287
  this.log.trace(`Handling L2 blocks ${l2Blocks[0].number} to ${l2Blocks.at(-1)!.number}`);
237
288
 
238
- const messagePromises = l2Blocks.map(block => this.l2BlockSource.getL1ToL2Messages(BigInt(block.number)));
289
+ const messagePromises = l2Blocks.map(block => this.l2BlockSource.getL1ToL2Messages(block.number));
239
290
  const l1ToL2Messages: Fr[][] = await Promise.all(messagePromises);
240
291
  let updateStatus: WorldStateStatusFull | undefined = undefined;
241
292
 
242
293
  for (let i = 0; i < l2Blocks.length; i++) {
243
294
  const [duration, result] = await elapsed(() => this.handleL2Block(l2Blocks[i], l1ToL2Messages[i]));
244
- this.log.verbose(`World state updated with L2 block ${l2Blocks[i].number}`, {
295
+ this.log.info(`World state updated with L2 block ${l2Blocks[i].number}`, {
245
296
  eventName: 'l2-block-handled',
246
297
  duration,
247
- unfinalisedBlockNumber: result.summary.unfinalisedBlockNumber,
248
- finalisedBlockNumber: result.summary.finalisedBlockNumber,
298
+ unfinalizedBlockNumber: result.summary.unfinalizedBlockNumber,
299
+ finalizedBlockNumber: result.summary.finalizedBlockNumber,
249
300
  oldestHistoricBlock: result.summary.oldestHistoricalBlock,
250
301
  ...l2Blocks[i].getStats(),
251
302
  } satisfies L2BlockHandledStats);
@@ -288,11 +339,11 @@ export class ServerWorldStateSynchronizer
288
339
 
289
340
  private async handleChainFinalized(blockNumber: number) {
290
341
  this.log.verbose(`Finalized chain is now at block ${blockNumber}`);
291
- const summary = await this.merkleTreeDb.setFinalised(BigInt(blockNumber));
342
+ const summary = await this.merkleTreeDb.setFinalized(BigInt(blockNumber));
292
343
  if (this.historyToKeep === undefined) {
293
344
  return;
294
345
  }
295
- const newHistoricBlock = summary.finalisedBlockNumber - BigInt(this.historyToKeep) + 1n;
346
+ const newHistoricBlock = summary.finalizedBlockNumber - BigInt(this.historyToKeep) + 1n;
296
347
  if (newHistoricBlock <= 1) {
297
348
  return;
298
349
  }
@@ -302,6 +353,7 @@ export class ServerWorldStateSynchronizer
302
353
  }
303
354
 
304
355
  private handleChainProven(blockNumber: number) {
356
+ this.provenBlockNumber = BigInt(blockNumber);
305
357
  this.log.debug(`Proven chain is now at block ${blockNumber}`);
306
358
  return Promise.resolve();
307
359
  }
@@ -310,6 +362,7 @@ export class ServerWorldStateSynchronizer
310
362
  this.log.warn(`Chain pruned to block ${blockNumber}`);
311
363
  const status = await this.merkleTreeDb.unwindBlocks(BigInt(blockNumber));
312
364
  this.latestBlockHashQuery = undefined;
365
+ this.provenBlockNumber = undefined;
313
366
  this.instrumentation.updateWorldStateMetrics(status);
314
367
  }
315
368
 
@@ -328,7 +381,7 @@ export class ServerWorldStateSynchronizer
328
381
  * @param inHash - The inHash of the block.
329
382
  * @throws If the L1 to L2 messages do not hash to the block inHash.
330
383
  */
331
- protected async verifyMessagesHashToInHash(l1ToL2Messages: Fr[], inHash: Buffer) {
384
+ protected async verifyMessagesHashToInHash(l1ToL2Messages: Fr[], inHash: Fr) {
332
385
  const treeCalculator = await MerkleTreeCalculator.create(
333
386
  L1_TO_L2_MSG_SUBTREE_HEIGHT,
334
387
  Buffer.alloc(32),
@@ -337,7 +390,7 @@ export class ServerWorldStateSynchronizer
337
390
 
338
391
  const root = await treeCalculator.computeTreeRoot(l1ToL2Messages.map(msg => msg.toBuffer()));
339
392
 
340
- if (!root.equals(inHash)) {
393
+ if (!root.equals(inHash.toBuffer())) {
341
394
  throw new Error('Obtained L1 to L2 messages failed to be hashed to the block inHash');
342
395
  }
343
396
  }
package/src/test/utils.ts CHANGED
@@ -7,49 +7,64 @@ import {
7
7
  import { padArrayEnd } from '@aztec/foundation/collection';
8
8
  import { Fr } from '@aztec/foundation/fields';
9
9
  import { L2Block } from '@aztec/stdlib/block';
10
- import type { MerkleTreeReadOperations, MerkleTreeWriteOperations } from '@aztec/stdlib/interfaces/server';
10
+ import type {
11
+ IndexedTreeId,
12
+ MerkleTreeReadOperations,
13
+ MerkleTreeWriteOperations,
14
+ } from '@aztec/stdlib/interfaces/server';
11
15
  import { AppendOnlyTreeSnapshot, MerkleTreeId } from '@aztec/stdlib/trees';
12
16
 
13
17
  import type { NativeWorldStateService } from '../native/native_world_state.js';
14
18
 
15
- export async function mockBlock(blockNum: number, size: number, fork: MerkleTreeWriteOperations) {
16
- const l2Block = await L2Block.random(blockNum, size);
17
- const l1ToL2Messages = Array(16).fill(0).map(Fr.random);
19
+ export async function mockBlock(
20
+ blockNum: number,
21
+ size: number,
22
+ fork: MerkleTreeWriteOperations,
23
+ maxEffects: number | undefined = 1000, // Defaults to the maximum tx effects.
24
+ ) {
25
+ const l2Block = await L2Block.random(blockNum, size, undefined, undefined, undefined, undefined, maxEffects);
26
+ const l1ToL2Messages = Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(0).map(Fr.random);
18
27
 
19
- // Sync the append only trees
20
28
  {
29
+ const insertData = async (
30
+ treeId: IndexedTreeId,
31
+ data: Buffer[][],
32
+ subTreeHeight: number,
33
+ fork: MerkleTreeWriteOperations,
34
+ ) => {
35
+ for (const dataBatch of data) {
36
+ await fork.batchInsert(treeId, dataBatch, subTreeHeight);
37
+ }
38
+ };
39
+
40
+ const publicDataInsert = insertData(
41
+ MerkleTreeId.PUBLIC_DATA_TREE,
42
+ l2Block.body.txEffects.map(txEffect => txEffect.publicDataWrites.map(write => write.toBuffer())),
43
+ 0,
44
+ fork,
45
+ );
46
+ const nullifierInsert = insertData(
47
+ MerkleTreeId.NULLIFIER_TREE,
48
+ l2Block.body.txEffects.map(txEffect =>
49
+ padArrayEnd(txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX).map(nullifier => nullifier.toBuffer()),
50
+ ),
51
+ NULLIFIER_SUBTREE_HEIGHT,
52
+ fork,
53
+ );
21
54
  const noteHashesPadded = l2Block.body.txEffects.flatMap(txEffect =>
22
55
  padArrayEnd(txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX),
23
56
  );
24
- await fork.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, noteHashesPadded);
25
-
26
- const l1ToL2MessagesPadded = padArrayEnd(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
27
- await fork.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2MessagesPadded);
28
- }
29
-
30
- // Sync the indexed trees
31
- {
32
- // We insert the public data tree leaves with one batch per tx to avoid updating the same key twice
33
- for (const txEffect of l2Block.body.txEffects) {
34
- await fork.batchInsert(
35
- MerkleTreeId.PUBLIC_DATA_TREE,
36
- txEffect.publicDataWrites.map(write => write.toBuffer()),
37
- 0,
38
- );
39
57
 
40
- const nullifiersPadded = padArrayEnd(txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX);
58
+ const l1ToL2MessagesPadded = padArrayEnd<Fr, number>(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
41
59
 
42
- await fork.batchInsert(
43
- MerkleTreeId.NULLIFIER_TREE,
44
- nullifiersPadded.map(nullifier => nullifier.toBuffer()),
45
- NULLIFIER_SUBTREE_HEIGHT,
46
- );
47
- }
60
+ const noteHashInsert = fork.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, noteHashesPadded);
61
+ const messageInsert = fork.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2MessagesPadded);
62
+ await Promise.all([publicDataInsert, nullifierInsert, noteHashInsert, messageInsert]);
48
63
  }
49
64
 
50
65
  const state = await fork.getStateReference();
51
66
  l2Block.header.state = state;
52
- await fork.updateArchive(l2Block.header);
67
+ await fork.updateArchive(l2Block.getBlockHeader());
53
68
 
54
69
  const archiveState = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
55
70
 
@@ -65,7 +80,7 @@ export async function mockEmptyBlock(blockNum: number, fork: MerkleTreeWriteOper
65
80
  const l2Block = L2Block.empty();
66
81
  const l1ToL2Messages = Array(16).fill(0).map(Fr.zero);
67
82
 
68
- l2Block.header.globalVariables.blockNumber = new Fr(blockNum);
83
+ l2Block.header.globalVariables.blockNumber = blockNum;
69
84
 
70
85
  // Sync the append only trees
71
86
  {
@@ -74,7 +89,7 @@ export async function mockEmptyBlock(blockNum: number, fork: MerkleTreeWriteOper
74
89
  );
75
90
  await fork.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, noteHashesPadded);
76
91
 
77
- const l1ToL2MessagesPadded = padArrayEnd(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
92
+ const l1ToL2MessagesPadded = padArrayEnd<Fr, number>(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
78
93
  await fork.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2MessagesPadded);
79
94
  }
80
95
 
@@ -100,7 +115,7 @@ export async function mockEmptyBlock(blockNum: number, fork: MerkleTreeWriteOper
100
115
 
101
116
  const state = await fork.getStateReference();
102
117
  l2Block.header.state = state;
103
- await fork.updateArchive(l2Block.header);
118
+ await fork.updateArchive(l2Block.getBlockHeader());
104
119
 
105
120
  const archiveState = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
106
121