@aztec/world-state 0.67.1 → 0.68.0

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 (39) hide show
  1. package/dest/{synchronizer → instrumentation}/instrumentation.d.ts +4 -1
  2. package/dest/instrumentation/instrumentation.d.ts.map +1 -0
  3. package/dest/instrumentation/instrumentation.js +89 -0
  4. package/dest/native/merkle_trees_facade.d.ts +2 -2
  5. package/dest/native/merkle_trees_facade.d.ts.map +1 -1
  6. package/dest/native/merkle_trees_facade.js +14 -12
  7. package/dest/native/message.d.ts +9 -7
  8. package/dest/native/message.d.ts.map +1 -1
  9. package/dest/native/message.js +2 -2
  10. package/dest/native/native_world_state.d.ts +5 -3
  11. package/dest/native/native_world_state.d.ts.map +1 -1
  12. package/dest/native/native_world_state.js +12 -8
  13. package/dest/native/native_world_state_instance.d.ts +3 -1
  14. package/dest/native/native_world_state_instance.d.ts.map +1 -1
  15. package/dest/native/native_world_state_instance.js +17 -23
  16. package/dest/synchronizer/factory.d.ts +2 -2
  17. package/dest/synchronizer/factory.d.ts.map +1 -1
  18. package/dest/synchronizer/factory.js +9 -13
  19. package/dest/synchronizer/server_world_state_synchronizer.d.ts +4 -4
  20. package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
  21. package/dest/synchronizer/server_world_state_synchronizer.js +10 -6
  22. package/dest/world-state-db/merkle_tree_operations_facade.d.ts +2 -2
  23. package/dest/world-state-db/merkle_tree_operations_facade.d.ts.map +1 -1
  24. package/dest/world-state-db/merkle_tree_operations_facade.js +5 -5
  25. package/dest/world-state-db/merkle_tree_snapshot_operations_facade.d.ts +2 -2
  26. package/dest/world-state-db/merkle_tree_snapshot_operations_facade.d.ts.map +1 -1
  27. package/dest/world-state-db/merkle_tree_snapshot_operations_facade.js +5 -5
  28. package/package.json +14 -13
  29. package/src/{synchronizer → instrumentation}/instrumentation.ts +37 -11
  30. package/src/native/merkle_trees_facade.ts +14 -12
  31. package/src/native/message.ts +9 -7
  32. package/src/native/native_world_state.ts +14 -5
  33. package/src/native/native_world_state_instance.ts +25 -21
  34. package/src/synchronizer/factory.ts +7 -10
  35. package/src/synchronizer/server_world_state_synchronizer.ts +9 -8
  36. package/src/world-state-db/merkle_tree_operations_facade.ts +11 -6
  37. package/src/world-state-db/merkle_tree_snapshot_operations_facade.ts +9 -6
  38. package/dest/synchronizer/instrumentation.d.ts.map +0 -1
  39. package/dest/synchronizer/instrumentation.js +0 -77
@@ -1,8 +1,21 @@
1
1
  import { MerkleTreeId } from '@aztec/circuit-types';
2
2
  import { createLogger } from '@aztec/foundation/log';
3
- import { Attributes, type Gauge, type TelemetryClient, ValueType } from '@aztec/telemetry-client';
4
-
5
- import { type DBStats, type TreeDBStats, type TreeMeta, type WorldStateStatusFull } from '../native/message.js';
3
+ import {
4
+ Attributes,
5
+ type Gauge,
6
+ type Histogram,
7
+ Metrics,
8
+ type TelemetryClient,
9
+ ValueType,
10
+ } from '@aztec/telemetry-client';
11
+
12
+ import {
13
+ type DBStats,
14
+ type TreeDBStats,
15
+ type TreeMeta,
16
+ WorldStateMessageType,
17
+ type WorldStateStatusFull,
18
+ } from '../native/message.js';
6
19
 
7
20
  type DBTypeString = 'leaf_preimage' | 'leaf_indices' | 'nodes' | 'blocks' | 'block_indices';
8
21
 
@@ -14,43 +27,50 @@ export class WorldStateInstrumentation {
14
27
  private oldestBlock: Gauge;
15
28
  private dbNumItems: Gauge;
16
29
  private dbUsedSize: Gauge;
30
+ private requestHistogram: Histogram;
17
31
 
18
- constructor(telemetry: TelemetryClient, private log = createLogger('world-state:instrumentation')) {
32
+ constructor(public readonly telemetry: TelemetryClient, private log = createLogger('world-state:instrumentation')) {
19
33
  const meter = telemetry.getMeter('World State');
20
- this.dbMapSize = meter.createGauge(`aztec.world_state.db_map_size`, {
34
+ this.dbMapSize = meter.createGauge(Metrics.WORLD_STATE_DB_MAP_SIZE, {
21
35
  description: `The current configured map size for each merkle tree`,
22
36
  valueType: ValueType.INT,
23
37
  });
24
38
 
25
- this.treeSize = meter.createGauge(`aztec.world_state.tree_size`, {
39
+ this.treeSize = meter.createGauge(Metrics.WORLD_STATE_TREE_SIZE, {
26
40
  description: `The current number of leaves in each merkle tree`,
27
41
  valueType: ValueType.INT,
28
42
  });
29
43
 
30
- this.unfinalisedHeight = meter.createGauge(`aztec.world_state.unfinalised_height`, {
44
+ this.unfinalisedHeight = meter.createGauge(Metrics.WORLD_STATE_UNFINALISED_HEIGHT, {
31
45
  description: `The unfinalised block height of each merkle tree`,
32
46
  valueType: ValueType.INT,
33
47
  });
34
48
 
35
- this.finalisedHeight = meter.createGauge(`aztec.world_state.finalised_height`, {
49
+ this.finalisedHeight = meter.createGauge(Metrics.WORLD_STATE_FINALISED_HEIGHT, {
36
50
  description: `The finalised block height of each merkle tree`,
37
51
  valueType: ValueType.INT,
38
52
  });
39
53
 
40
- this.oldestBlock = meter.createGauge(`aztec.world_state.oldest_block`, {
54
+ this.oldestBlock = meter.createGauge(Metrics.WORLD_STATE_OLDEST_BLOCK, {
41
55
  description: `The oldest historical block of each merkle tree`,
42
56
  valueType: ValueType.INT,
43
57
  });
44
58
 
45
- this.dbUsedSize = meter.createGauge(`aztec.world_state.db_used_size`, {
59
+ this.dbUsedSize = meter.createGauge(Metrics.WORLD_STATE_DB_USED_SIZE, {
46
60
  description: `The current used database size for each db of each merkle tree`,
47
61
  valueType: ValueType.INT,
48
62
  });
49
63
 
50
- this.dbNumItems = meter.createGauge(`aztec.world_state.db_num_items`, {
64
+ this.dbNumItems = meter.createGauge(Metrics.WORLD_STATE_DB_NUM_ITEMS, {
51
65
  description: `The current number of items in each database of each merkle tree`,
52
66
  valueType: ValueType.INT,
53
67
  });
68
+
69
+ this.requestHistogram = meter.createHistogram(Metrics.WORLD_STATE_REQUEST_TIME, {
70
+ description: 'The round trip time of world state requests',
71
+ unit: 'us',
72
+ valueType: ValueType.INT,
73
+ });
54
74
  }
55
75
 
56
76
  private updateTreeStats(treeDbStats: TreeDBStats, treeMeta: TreeMeta, tree: MerkleTreeId) {
@@ -119,4 +139,10 @@ export class WorldStateInstrumentation {
119
139
  MerkleTreeId.PUBLIC_DATA_TREE,
120
140
  );
121
141
  }
142
+
143
+ public recordRoundTrip(timeUs: number, request: WorldStateMessageType) {
144
+ this.requestHistogram.record(Math.ceil(timeUs), {
145
+ [Attributes.WORLD_STATE_REQUEST_TYPE]: WorldStateMessageType[request],
146
+ });
147
+ }
122
148
  }
@@ -45,27 +45,29 @@ export class MerkleTreesFacade implements MerkleTreeReadOperations {
45
45
  return this.initialHeader;
46
46
  }
47
47
 
48
- findLeafIndex(treeId: MerkleTreeId, value: MerkleTreeLeafType<MerkleTreeId>): Promise<bigint | undefined> {
49
- return this.findLeafIndexAfter(treeId, value, 0n);
48
+ findLeafIndices(treeId: MerkleTreeId, values: MerkleTreeLeafType<MerkleTreeId>[]): Promise<(bigint | undefined)[]> {
49
+ return this.findLeafIndicesAfter(treeId, values, 0n);
50
50
  }
51
51
 
52
- async findLeafIndexAfter(
52
+ async findLeafIndicesAfter(
53
53
  treeId: MerkleTreeId,
54
- leaf: MerkleTreeLeafType<MerkleTreeId>,
54
+ leaves: MerkleTreeLeafType<MerkleTreeId>[],
55
55
  startIndex: bigint,
56
- ): Promise<bigint | undefined> {
57
- const index = await this.instance.call(WorldStateMessageType.FIND_LEAF_INDEX, {
58
- leaf: serializeLeaf(hydrateLeaf(treeId, leaf)),
56
+ ): Promise<(bigint | undefined)[]> {
57
+ const response = await this.instance.call(WorldStateMessageType.FIND_LEAF_INDICES, {
58
+ leaves: leaves.map(leaf => serializeLeaf(hydrateLeaf(treeId, leaf))),
59
59
  revision: this.revision,
60
60
  treeId,
61
61
  startIndex,
62
62
  });
63
63
 
64
- if (typeof index === 'number' || typeof index === 'bigint') {
65
- return BigInt(index);
66
- } else {
67
- return undefined;
68
- }
64
+ return response.indices.map(index => {
65
+ if (typeof index === 'number' || typeof index === 'bigint') {
66
+ return BigInt(index);
67
+ } else {
68
+ return undefined;
69
+ }
70
+ });
69
71
  }
70
72
 
71
73
  async getLeafPreimage(treeId: IndexedTreeId, leafIndex: bigint): Promise<IndexedTreeLeafPreimage | undefined> {
@@ -56,7 +56,7 @@ export enum WorldStateMessageType {
56
56
  GET_SIBLING_PATH,
57
57
  GET_BLOCK_NUMBERS_FOR_LEAF_INDICES,
58
58
 
59
- FIND_LEAF_INDEX,
59
+ FIND_LEAF_INDICES,
60
60
  FIND_LOW_LEAF,
61
61
 
62
62
  APPEND_LEAVES,
@@ -329,8 +329,8 @@ export type SerializedIndexedLeaf = {
329
329
  nextValue: Buffer; // Fr
330
330
  };
331
331
 
332
- interface WithLeafValue {
333
- leaf: SerializedLeafValue;
332
+ interface WithLeafValues {
333
+ leaves: SerializedLeafValue[];
334
334
  }
335
335
 
336
336
  interface BlockShiftRequest {
@@ -371,10 +371,12 @@ type GetLeafResponse = SerializedLeafValue | undefined;
371
371
  interface GetLeafPreImageRequest extends WithTreeId, WithLeafIndex, WithWorldStateRevision {}
372
372
  type GetLeafPreImageResponse = SerializedIndexedLeaf | undefined;
373
373
 
374
- interface FindLeafIndexRequest extends WithTreeId, WithLeafValue, WithWorldStateRevision {
374
+ interface FindLeafIndicesRequest extends WithTreeId, WithLeafValues, WithWorldStateRevision {
375
375
  startIndex: bigint;
376
376
  }
377
- type FindLeafIndexResponse = bigint | null;
377
+ interface FindLeafIndicesResponse {
378
+ indices: bigint[];
379
+ }
378
380
 
379
381
  interface FindLowLeafRequest extends WithTreeId, WithWorldStateRevision {
380
382
  key: Fr;
@@ -461,7 +463,7 @@ export type WorldStateRequest = {
461
463
  [WorldStateMessageType.GET_SIBLING_PATH]: GetSiblingPathRequest;
462
464
  [WorldStateMessageType.GET_BLOCK_NUMBERS_FOR_LEAF_INDICES]: GetBlockNumbersForLeafIndicesRequest;
463
465
 
464
- [WorldStateMessageType.FIND_LEAF_INDEX]: FindLeafIndexRequest;
466
+ [WorldStateMessageType.FIND_LEAF_INDICES]: FindLeafIndicesRequest;
465
467
  [WorldStateMessageType.FIND_LOW_LEAF]: FindLowLeafRequest;
466
468
 
467
469
  [WorldStateMessageType.APPEND_LEAVES]: AppendLeavesRequest;
@@ -497,7 +499,7 @@ export type WorldStateResponse = {
497
499
  [WorldStateMessageType.GET_SIBLING_PATH]: GetSiblingPathResponse;
498
500
  [WorldStateMessageType.GET_BLOCK_NUMBERS_FOR_LEAF_INDICES]: GetBlockNumbersForLeafIndicesResponse;
499
501
 
500
- [WorldStateMessageType.FIND_LEAF_INDEX]: FindLeafIndexResponse;
502
+ [WorldStateMessageType.FIND_LEAF_INDICES]: FindLeafIndicesResponse;
501
503
  [WorldStateMessageType.FIND_LOW_LEAF]: FindLowLeafResponse;
502
504
 
503
505
  [WorldStateMessageType.APPEND_LEAVES]: void;
@@ -21,12 +21,14 @@ import {
21
21
  } from '@aztec/circuits.js';
22
22
  import { padArrayEnd } from '@aztec/foundation/collection';
23
23
  import { createLogger } from '@aztec/foundation/log';
24
+ import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
24
25
 
25
26
  import assert from 'assert/strict';
26
27
  import { mkdir, mkdtemp, rm } from 'fs/promises';
27
28
  import { tmpdir } from 'os';
28
29
  import { join } from 'path';
29
30
 
31
+ import { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
30
32
  import { type MerkleTreeAdminDatabase as MerkleTreeDatabase } from '../world-state-db/merkle_tree_db.js';
31
33
  import { MerkleTreesFacade, MerkleTreesForkFacade, serializeLeaf } from './merkle_trees_facade.js';
32
34
  import {
@@ -58,6 +60,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
58
60
 
59
61
  protected constructor(
60
62
  protected readonly instance: NativeWorldState,
63
+ protected readonly worldStateInstrumentation: WorldStateInstrumentation,
61
64
  protected readonly log = createLogger('world-state:database'),
62
65
  private readonly cleanup = () => Promise.resolve(),
63
66
  ) {}
@@ -66,6 +69,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
66
69
  rollupAddress: EthAddress,
67
70
  dataDir: string,
68
71
  dbMapSizeKb: number,
72
+ instrumentation = new WorldStateInstrumentation(new NoopTelemetryClient()),
69
73
  log = createLogger('world-state:database'),
70
74
  cleanup = () => Promise.resolve(),
71
75
  ): Promise<NativeWorldStateService> {
@@ -89,8 +93,8 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
89
93
  await mkdir(worldStateDirectory, { recursive: true });
90
94
  await newWorldStateVersion.writeVersionFile(versionFile);
91
95
 
92
- const instance = new NativeWorldState(worldStateDirectory, dbMapSizeKb);
93
- const worldState = new this(instance, log, cleanup);
96
+ const instance = new NativeWorldState(worldStateDirectory, dbMapSizeKb, instrumentation);
97
+ const worldState = new this(instance, instrumentation, log, cleanup);
94
98
  try {
95
99
  await worldState.init();
96
100
  } catch (e) {
@@ -101,7 +105,11 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
101
105
  return worldState;
102
106
  }
103
107
 
104
- static async tmp(rollupAddress = EthAddress.ZERO, cleanupTmpDir = true): Promise<NativeWorldStateService> {
108
+ static async tmp(
109
+ rollupAddress = EthAddress.ZERO,
110
+ cleanupTmpDir = true,
111
+ instrumentation = new WorldStateInstrumentation(new NoopTelemetryClient()),
112
+ ): Promise<NativeWorldStateService> {
105
113
  const log = createLogger('world-state:database');
106
114
  const dataDir = await mkdtemp(join(tmpdir(), 'aztec-world-state-'));
107
115
  const dbMapSizeKb = 10 * 1024 * 1024;
@@ -117,7 +125,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
117
125
  }
118
126
  };
119
127
 
120
- return this.new(rollupAddress, dataDir, dbMapSizeKb, log, cleanup);
128
+ return this.new(rollupAddress, dataDir, dbMapSizeKb, instrumentation, log, cleanup);
121
129
  }
122
130
 
123
131
  protected async init() {
@@ -136,7 +144,8 @@ export class NativeWorldStateService implements MerkleTreeDatabase {
136
144
 
137
145
  // the initial header _must_ be the first element in the archive tree
138
146
  // if this assertion fails, check that the hashing done in Header in yarn-project matches the initial header hash done in world_state.cpp
139
- const initialHeaderIndex = await committed.findLeafIndex(MerkleTreeId.ARCHIVE, this.initialHeader.hash());
147
+ const indices = await committed.findLeafIndices(MerkleTreeId.ARCHIVE, [this.initialHeader.hash()]);
148
+ const initialHeaderIndex = indices[0];
140
149
  assert.strictEqual(initialHeaderIndex, 0n, 'Invalid initial archive state');
141
150
  }
142
151
 
@@ -12,7 +12,6 @@ import {
12
12
  } from '@aztec/circuits.js';
13
13
  import { createLogger } from '@aztec/foundation/log';
14
14
  import { SerialQueue } from '@aztec/foundation/queue';
15
- import { Timer } from '@aztec/foundation/timer';
16
15
 
17
16
  import assert from 'assert';
18
17
  import bindings from 'bindings';
@@ -20,6 +19,7 @@ import { Decoder, Encoder, addExtension } from 'msgpackr';
20
19
  import { cpus } from 'os';
21
20
  import { isAnyArrayBuffer } from 'util/types';
22
21
 
22
+ import { type WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
23
23
  import {
24
24
  MessageHeader,
25
25
  TypedMessage,
@@ -46,7 +46,7 @@ const NATIVE_LIBRARY_NAME = 'world_state_napi';
46
46
  const NATIVE_CLASS_NAME = 'WorldState';
47
47
 
48
48
  const NATIVE_MODULE = bindings(NATIVE_LIBRARY_NAME);
49
- const MAX_WORLD_STATE_THREADS = 16;
49
+ const MAX_WORLD_STATE_THREADS = +(process.env.HARDWARE_CONCURRENCY || '16');
50
50
 
51
51
  export interface NativeWorldStateInstance {
52
52
  call<T extends WorldStateMessageType>(messageType: T, body: WorldStateRequest[T]): Promise<WorldStateResponse[T]>;
@@ -82,8 +82,16 @@ export class NativeWorldState implements NativeWorldStateInstance {
82
82
  private queue = new SerialQueue();
83
83
 
84
84
  /** Creates a new native WorldState instance */
85
- constructor(dataDir: string, dbMapSizeKb: number, private log = createLogger('world-state:database')) {
86
- log.info(`Creating world state data store at directory ${dataDir} with map size ${dbMapSizeKb} KB`);
85
+ constructor(
86
+ dataDir: string,
87
+ dbMapSizeKb: number,
88
+ private instrumentation: WorldStateInstrumentation,
89
+ private log = createLogger('world-state:database'),
90
+ ) {
91
+ const threads = Math.min(cpus().length, MAX_WORLD_STATE_THREADS);
92
+ log.info(
93
+ `Creating world state data store at directory ${dataDir} with map size ${dbMapSizeKb} KB and ${threads} threads.`,
94
+ );
87
95
  this.instance = new NATIVE_MODULE[NATIVE_CLASS_NAME](
88
96
  dataDir,
89
97
  {
@@ -99,7 +107,7 @@ export class NativeWorldState implements NativeWorldStateInstance {
99
107
  },
100
108
  GeneratorIndex.BLOCK_HASH,
101
109
  dbMapSizeKb,
102
- Math.min(cpus().length, MAX_WORLD_STATE_THREADS),
110
+ threads,
103
111
  );
104
112
  this.queue.start();
105
113
  }
@@ -180,17 +188,6 @@ export class NativeWorldState implements NativeWorldStateInstance {
180
188
  data['blockHeaderHash'] = '0x' + body.blockHeaderHash.toString('hex');
181
189
  }
182
190
 
183
- if ('leaf' in body) {
184
- if (Buffer.isBuffer(body.leaf)) {
185
- data['leaf'] = '0x' + body.leaf.toString('hex');
186
- } else if ('slot' in body.leaf) {
187
- data['slot'] = '0x' + body.leaf.slot.toString('hex');
188
- data['value'] = '0x' + body.leaf.value.toString('hex');
189
- } else {
190
- data['nullifier'] = '0x' + body.leaf.value.toString('hex');
191
- }
192
- }
193
-
194
191
  if ('leaves' in body) {
195
192
  data['leavesCount'] = body.leaves.length;
196
193
  }
@@ -208,11 +205,12 @@ export class NativeWorldState implements NativeWorldStateInstance {
208
205
  this.log.trace(`Calling messageId=${messageId} ${WorldStateMessageType[messageType]}`);
209
206
  }
210
207
 
211
- const timer = new Timer();
208
+ const start = process.hrtime.bigint();
212
209
 
213
210
  const request = new TypedMessage(messageType, new MessageHeader({ messageId }), body);
214
211
  const encodedRequest = this.encoder.encode(request);
215
- const encodingDuration = timer.ms();
212
+ const encodingEnd = process.hrtime.bigint();
213
+ const encodingDuration = Number(encodingEnd - start) / 1_000_000;
216
214
 
217
215
  let encodedResponse: any;
218
216
  try {
@@ -222,7 +220,9 @@ export class NativeWorldState implements NativeWorldStateInstance {
222
220
  throw error;
223
221
  }
224
222
 
225
- const callDuration = timer.ms() - encodingDuration;
223
+ const callEnd = process.hrtime.bigint();
224
+
225
+ const callDuration = Number(callEnd - encodingEnd) / 1_000_000;
226
226
 
227
227
  const buf = Buffer.isBuffer(encodedResponse)
228
228
  ? encodedResponse
@@ -246,8 +246,9 @@ export class NativeWorldState implements NativeWorldStateInstance {
246
246
  }
247
247
 
248
248
  const response = TypedMessage.fromMessagePack<T, WorldStateResponse[T]>(decodedResponse);
249
- const decodingDuration = timer.ms() - callDuration;
250
- const totalDuration = timer.ms();
249
+ const decodingEnd = process.hrtime.bigint();
250
+ const decodingDuration = Number(decodingEnd - callEnd) / 1_000_000;
251
+ const totalDuration = Number(decodingEnd - start) / 1_000_000;
251
252
  this.log.trace(`Call messageId=${messageId} ${WorldStateMessageType[messageType]} took (ms)`, {
252
253
  totalDuration,
253
254
  encodingDuration,
@@ -265,6 +266,9 @@ export class NativeWorldState implements NativeWorldStateInstance {
265
266
  throw new Error('Invalid response message type: ' + response.msgType + ' != ' + messageType);
266
267
  }
267
268
 
269
+ const callDurationUs = Number(callEnd - encodingEnd) / 1000;
270
+ this.instrumentation.recordRoundTrip(callDurationUs, messageType);
271
+
268
272
  return response.value;
269
273
  }
270
274
  }
@@ -1,12 +1,9 @@
1
1
  import { type L1ToL2MessageSource, type L2BlockSource } from '@aztec/circuit-types';
2
- import { createLogger } from '@aztec/foundation/log';
3
2
  import { type DataStoreConfig } from '@aztec/kv-store/config';
4
- import { createStore } from '@aztec/kv-store/lmdb';
5
3
  import { type TelemetryClient } from '@aztec/telemetry-client';
6
- import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
7
4
 
5
+ import { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
8
6
  import { NativeWorldStateService } from '../native/native_world_state.js';
9
- import { MerkleTrees } from '../world-state-db/merkle_trees.js';
10
7
  import { type WorldStateConfig } from './config.js';
11
8
  import { ServerWorldStateSynchronizer } from './server_world_state_synchronizer.js';
12
9
 
@@ -15,13 +12,14 @@ export async function createWorldStateSynchronizer(
15
12
  l2BlockSource: L2BlockSource & L1ToL2MessageSource,
16
13
  client: TelemetryClient,
17
14
  ) {
18
- const merkleTrees = await createWorldState(config, client);
19
- return new ServerWorldStateSynchronizer(merkleTrees, l2BlockSource, config, client);
15
+ const instrumentation = new WorldStateInstrumentation(client);
16
+ const merkleTrees = await createWorldState(config, instrumentation);
17
+ return new ServerWorldStateSynchronizer(merkleTrees, l2BlockSource, config, instrumentation);
20
18
  }
21
19
 
22
20
  export async function createWorldState(
23
21
  config: WorldStateConfig & DataStoreConfig,
24
- client: TelemetryClient = new NoopTelemetryClient(),
22
+ instrumentation: WorldStateInstrumentation,
25
23
  ) {
26
24
  const newConfig = {
27
25
  dataDirectory: config.worldStateDataDirectory ?? config.dataDirectory,
@@ -33,13 +31,12 @@ export async function createWorldState(
33
31
  }
34
32
 
35
33
  // If a data directory is provided in config, then create a persistent store.
36
- const merkleTrees = ['true', '1'].includes(process.env.USE_LEGACY_WORLD_STATE ?? '')
37
- ? await MerkleTrees.new(await createStore('world-state', newConfig, createLogger('world-state:lmdb')), client)
38
- : newConfig.dataDirectory
34
+ const merkleTrees = newConfig.dataDirectory
39
35
  ? await NativeWorldStateService.new(
40
36
  config.l1Contracts.rollupAddress,
41
37
  newConfig.dataDirectory,
42
38
  newConfig.dataStoreMapSizeKB,
39
+ instrumentation,
43
40
  )
44
41
  : await NativeWorldStateService.tmp(
45
42
  config.l1Contracts.rollupAddress,
@@ -3,7 +3,7 @@ import {
3
3
  type L2Block,
4
4
  type L2BlockId,
5
5
  type L2BlockSource,
6
- L2BlockStream,
6
+ type L2BlockStream,
7
7
  type L2BlockStreamEvent,
8
8
  type L2BlockStreamEventHandler,
9
9
  type L2BlockStreamLocalDataProvider,
@@ -23,12 +23,13 @@ import { createLogger } from '@aztec/foundation/log';
23
23
  import { promiseWithResolvers } from '@aztec/foundation/promise';
24
24
  import { elapsed } from '@aztec/foundation/timer';
25
25
  import { SHA256Trunc } from '@aztec/merkle-tree';
26
- import { type TelemetryClient } from '@aztec/telemetry-client';
26
+ import { TraceableL2BlockStream } from '@aztec/telemetry-client';
27
+ import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
27
28
 
29
+ import { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
28
30
  import { type WorldStateStatusFull } from '../native/message.js';
29
31
  import { type MerkleTreeAdminDatabase } from '../world-state-db/merkle_tree_db.js';
30
32
  import { type WorldStateConfig } from './config.js';
31
- import { WorldStateInstrumentation } from './instrumentation.js';
32
33
 
33
34
  /**
34
35
  * Synchronizes the world state with the L2 blocks from a L2BlockSource via a block stream.
@@ -47,16 +48,14 @@ export class ServerWorldStateSynchronizer
47
48
 
48
49
  private syncPromise = promiseWithResolvers<void>();
49
50
  protected blockStream: L2BlockStream | undefined;
50
- private instrumentation: WorldStateInstrumentation;
51
51
 
52
52
  constructor(
53
53
  private readonly merkleTreeDb: MerkleTreeAdminDatabase,
54
54
  private readonly l2BlockSource: L2BlockSource & L1ToL2MessageSource,
55
55
  private readonly config: WorldStateConfig,
56
- telemetry: TelemetryClient,
56
+ private instrumentation = new WorldStateInstrumentation(new NoopTelemetryClient()),
57
57
  private readonly log = createLogger('world_state'),
58
58
  ) {
59
- this.instrumentation = new WorldStateInstrumentation(telemetry);
60
59
  this.merkleTreeCommitted = this.merkleTreeDb.getCommitted();
61
60
  this.historyToKeep = config.worldStateBlockHistory < 1 ? undefined : config.worldStateBlockHistory;
62
61
  this.log.info(
@@ -110,8 +109,10 @@ export class ServerWorldStateSynchronizer
110
109
  return this.syncPromise.promise;
111
110
  }
112
111
 
113
- protected createBlockStream() {
114
- return new L2BlockStream(this.l2BlockSource, this, this, createLogger('world_state:block_stream'), {
112
+ protected createBlockStream(): L2BlockStream {
113
+ const tracer = this.instrumentation.telemetry.getTracer('WorldStateL2BlockStream');
114
+ const logger = createLogger('world-state:block_stream');
115
+ return new TraceableL2BlockStream(this.l2BlockSource, this, this, tracer, 'WorldStateL2BlockStream', logger, {
115
116
  proven: this.config.worldStateProvenBlocksOnly,
116
117
  pollIntervalMS: this.config.worldStateBlockCheckIntervalMS,
117
118
  batchSize: this.config.worldStateBlockRequestBatchSize,
@@ -110,8 +110,11 @@ export class MerkleTreeReadOperationsFacade implements MerkleTreeWriteOperations
110
110
  * @param value - The leaf value to look for.
111
111
  * @returns The index of the first leaf found with a given value (undefined if not found).
112
112
  */
113
- findLeafIndex<ID extends MerkleTreeId>(treeId: ID, value: MerkleTreeLeafType<ID>): Promise<bigint | undefined> {
114
- return this.trees.findLeafIndex(treeId, value, this.includeUncommitted);
113
+ findLeafIndices<ID extends MerkleTreeId>(
114
+ treeId: ID,
115
+ values: MerkleTreeLeafType<ID>[],
116
+ ): Promise<(bigint | undefined)[]> {
117
+ return Promise.all(values.map(leaf => this.trees.findLeafIndex(treeId, leaf, this.includeUncommitted)));
115
118
  }
116
119
 
117
120
  /**
@@ -120,12 +123,14 @@ export class MerkleTreeReadOperationsFacade implements MerkleTreeWriteOperations
120
123
  * @param value - The value to search for in the tree.
121
124
  * @param startIndex - The index to start searching from (used when skipping nullified messages)
122
125
  */
123
- findLeafIndexAfter<ID extends MerkleTreeId>(
126
+ findLeafIndicesAfter<ID extends MerkleTreeId>(
124
127
  treeId: ID,
125
- value: MerkleTreeLeafType<ID>,
128
+ values: MerkleTreeLeafType<ID>[],
126
129
  startIndex: bigint,
127
- ): Promise<bigint | undefined> {
128
- return this.trees.findLeafIndexAfter(treeId, value, startIndex, this.includeUncommitted);
130
+ ): Promise<(bigint | undefined)[]> {
131
+ return Promise.all(
132
+ values.map(leaf => this.trees.findLeafIndexAfter(treeId, leaf, startIndex, this.includeUncommitted)),
133
+ );
129
134
  }
130
135
 
131
136
  /**
@@ -40,20 +40,23 @@ export class MerkleTreeSnapshotOperationsFacade implements MerkleTreeReadOperati
40
40
  return this.#treeSnapshots[treeId]!;
41
41
  }
42
42
 
43
- async findLeafIndex<ID extends MerkleTreeId>(treeId: ID, value: MerkleTreeLeafType<ID>): Promise<bigint | undefined> {
43
+ async findLeafIndices<ID extends MerkleTreeId>(
44
+ treeId: ID,
45
+ values: MerkleTreeLeafType<ID>[],
46
+ ): Promise<(bigint | undefined)[]> {
44
47
  const tree = await this.#getTreeSnapshot(treeId);
45
48
  // TODO #5448 fix "as any"
46
- return tree.findLeafIndex(value as any);
49
+ return values.map(leaf => tree.findLeafIndex(leaf as any));
47
50
  }
48
51
 
49
- async findLeafIndexAfter<ID extends MerkleTreeId>(
52
+ async findLeafIndicesAfter<ID extends MerkleTreeId>(
50
53
  treeId: MerkleTreeId,
51
- value: MerkleTreeLeafType<ID>,
54
+ values: MerkleTreeLeafType<ID>[],
52
55
  startIndex: bigint,
53
- ): Promise<bigint | undefined> {
56
+ ): Promise<(bigint | undefined)[]> {
54
57
  const tree = await this.#getTreeSnapshot(treeId);
55
58
  // TODO #5448 fix "as any"
56
- return tree.findLeafIndexAfter(value as any, startIndex);
59
+ return values.map(leaf => tree.findLeafIndexAfter(leaf as any, startIndex));
57
60
  }
58
61
 
59
62
  async getLeafPreimage<ID extends IndexedTreeId>(
@@ -1 +0,0 @@
1
- {"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/synchronizer/instrumentation.ts"],"names":[],"mappings":"AAEA,OAAO,EAA0B,KAAK,eAAe,EAAa,MAAM,yBAAyB,CAAC;AAElG,OAAO,EAAiD,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAIhH,qBAAa,yBAAyB;IASI,OAAO,CAAC,GAAG;IARnD,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,iBAAiB,CAAQ;IACjC,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,UAAU,CAAQ;gBAEd,SAAS,EAAE,eAAe,EAAU,GAAG,yCAA8C;IAsCjG,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,iBAAiB;IAWlB,uBAAuB,CAAC,gBAAgB,EAAE,oBAAoB;CA+BtE"}
@@ -1,77 +0,0 @@
1
- import { MerkleTreeId } from '@aztec/circuit-types';
2
- import { createLogger } from '@aztec/foundation/log';
3
- import { Attributes, ValueType } from '@aztec/telemetry-client';
4
- export class WorldStateInstrumentation {
5
- constructor(telemetry, log = createLogger('world-state:instrumentation')) {
6
- this.log = log;
7
- const meter = telemetry.getMeter('World State');
8
- this.dbMapSize = meter.createGauge(`aztec.world_state.db_map_size`, {
9
- description: `The current configured map size for each merkle tree`,
10
- valueType: ValueType.INT,
11
- });
12
- this.treeSize = meter.createGauge(`aztec.world_state.tree_size`, {
13
- description: `The current number of leaves in each merkle tree`,
14
- valueType: ValueType.INT,
15
- });
16
- this.unfinalisedHeight = meter.createGauge(`aztec.world_state.unfinalised_height`, {
17
- description: `The unfinalised block height of each merkle tree`,
18
- valueType: ValueType.INT,
19
- });
20
- this.finalisedHeight = meter.createGauge(`aztec.world_state.finalised_height`, {
21
- description: `The finalised block height of each merkle tree`,
22
- valueType: ValueType.INT,
23
- });
24
- this.oldestBlock = meter.createGauge(`aztec.world_state.oldest_block`, {
25
- description: `The oldest historical block of each merkle tree`,
26
- valueType: ValueType.INT,
27
- });
28
- this.dbUsedSize = meter.createGauge(`aztec.world_state.db_used_size`, {
29
- description: `The current used database size for each db of each merkle tree`,
30
- valueType: ValueType.INT,
31
- });
32
- this.dbNumItems = meter.createGauge(`aztec.world_state.db_num_items`, {
33
- description: `The current number of items in each database of each merkle tree`,
34
- valueType: ValueType.INT,
35
- });
36
- }
37
- updateTreeStats(treeDbStats, treeMeta, tree) {
38
- this.dbMapSize.record(Number(treeDbStats.mapSize), {
39
- [Attributes.MERKLE_TREE_NAME]: MerkleTreeId[tree],
40
- });
41
- this.treeSize.record(Number(treeMeta.size), {
42
- [Attributes.MERKLE_TREE_NAME]: MerkleTreeId[tree],
43
- });
44
- this.unfinalisedHeight.record(Number(treeMeta.unfinalisedBlockHeight), {
45
- [Attributes.MERKLE_TREE_NAME]: MerkleTreeId[tree],
46
- });
47
- this.finalisedHeight.record(Number(treeMeta.finalisedBlockHeight), {
48
- [Attributes.MERKLE_TREE_NAME]: MerkleTreeId[tree],
49
- });
50
- this.oldestBlock.record(Number(treeMeta.oldestHistoricBlock), {
51
- [Attributes.MERKLE_TREE_NAME]: MerkleTreeId[tree],
52
- });
53
- this.updateTreeDBStats(treeDbStats.blockIndicesDBStats, 'block_indices', tree);
54
- this.updateTreeDBStats(treeDbStats.blocksDBStats, 'blocks', tree);
55
- this.updateTreeDBStats(treeDbStats.leafIndicesDBStats, 'leaf_indices', tree);
56
- this.updateTreeDBStats(treeDbStats.leafPreimagesDBStats, 'leaf_preimage', tree);
57
- this.updateTreeDBStats(treeDbStats.nodesDBStats, 'nodes', tree);
58
- }
59
- updateTreeDBStats(dbStats, dbType, tree) {
60
- this.dbNumItems.record(Number(dbStats.numDataItems), {
61
- [Attributes.WS_DB_DATA_TYPE]: dbType,
62
- [Attributes.MERKLE_TREE_NAME]: MerkleTreeId[tree],
63
- });
64
- this.dbUsedSize.record(Number(dbStats.totalUsedSize), {
65
- [Attributes.WS_DB_DATA_TYPE]: dbType,
66
- [Attributes.MERKLE_TREE_NAME]: MerkleTreeId[tree],
67
- });
68
- }
69
- updateWorldStateMetrics(worldStateStatus) {
70
- this.updateTreeStats(worldStateStatus.dbStats.archiveTreeStats, worldStateStatus.meta.archiveTreeMeta, MerkleTreeId.ARCHIVE);
71
- this.updateTreeStats(worldStateStatus.dbStats.messageTreeStats, worldStateStatus.meta.messageTreeMeta, MerkleTreeId.L1_TO_L2_MESSAGE_TREE);
72
- this.updateTreeStats(worldStateStatus.dbStats.noteHashTreeStats, worldStateStatus.meta.noteHashTreeMeta, MerkleTreeId.NOTE_HASH_TREE);
73
- this.updateTreeStats(worldStateStatus.dbStats.nullifierTreeStats, worldStateStatus.meta.nullifierTreeMeta, MerkleTreeId.NULLIFIER_TREE);
74
- this.updateTreeStats(worldStateStatus.dbStats.publicDataTreeStats, worldStateStatus.meta.publicDataTreeMeta, MerkleTreeId.PUBLIC_DATA_TREE);
75
- }
76
- }
77
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5zdHJ1bWVudGF0aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3N5bmNocm9uaXplci9pbnN0cnVtZW50YXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ3BELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUNyRCxPQUFPLEVBQUUsVUFBVSxFQUFvQyxTQUFTLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQU1sRyxNQUFNLE9BQU8seUJBQXlCO0lBU3BDLFlBQVksU0FBMEIsRUFBVSxNQUFNLFlBQVksQ0FBQyw2QkFBNkIsQ0FBQztRQUFqRCxRQUFHLEdBQUgsR0FBRyxDQUE4QztRQUMvRixNQUFNLEtBQUssR0FBRyxTQUFTLENBQUMsUUFBUSxDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBQ2hELElBQUksQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDLFdBQVcsQ0FBQywrQkFBK0IsRUFBRTtZQUNsRSxXQUFXLEVBQUUsc0RBQXNEO1lBQ25FLFNBQVMsRUFBRSxTQUFTLENBQUMsR0FBRztTQUN6QixDQUFDLENBQUM7UUFFSCxJQUFJLENBQUMsUUFBUSxHQUFHLEtBQUssQ0FBQyxXQUFXLENBQUMsNkJBQTZCLEVBQUU7WUFDL0QsV0FBVyxFQUFFLGtEQUFrRDtZQUMvRCxTQUFTLEVBQUUsU0FBUyxDQUFDLEdBQUc7U0FDekIsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLGlCQUFpQixHQUFHLEtBQUssQ0FBQyxXQUFXLENBQUMsc0NBQXNDLEVBQUU7WUFDakYsV0FBVyxFQUFFLGtEQUFrRDtZQUMvRCxTQUFTLEVBQUUsU0FBUyxDQUFDLEdBQUc7U0FDekIsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLGVBQWUsR0FBRyxLQUFLLENBQUMsV0FBVyxDQUFDLG9DQUFvQyxFQUFFO1lBQzdFLFdBQVcsRUFBRSxnREFBZ0Q7WUFDN0Qs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