@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.
- package/dest/{synchronizer → instrumentation}/instrumentation.d.ts +4 -1
- package/dest/instrumentation/instrumentation.d.ts.map +1 -0
- package/dest/instrumentation/instrumentation.js +89 -0
- package/dest/native/merkle_trees_facade.d.ts +2 -2
- package/dest/native/merkle_trees_facade.d.ts.map +1 -1
- package/dest/native/merkle_trees_facade.js +14 -12
- package/dest/native/message.d.ts +9 -7
- package/dest/native/message.d.ts.map +1 -1
- package/dest/native/message.js +2 -2
- package/dest/native/native_world_state.d.ts +5 -3
- package/dest/native/native_world_state.d.ts.map +1 -1
- package/dest/native/native_world_state.js +12 -8
- package/dest/native/native_world_state_instance.d.ts +3 -1
- package/dest/native/native_world_state_instance.d.ts.map +1 -1
- package/dest/native/native_world_state_instance.js +17 -23
- package/dest/synchronizer/factory.d.ts +2 -2
- package/dest/synchronizer/factory.d.ts.map +1 -1
- package/dest/synchronizer/factory.js +9 -13
- package/dest/synchronizer/server_world_state_synchronizer.d.ts +4 -4
- package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
- package/dest/synchronizer/server_world_state_synchronizer.js +10 -6
- package/dest/world-state-db/merkle_tree_operations_facade.d.ts +2 -2
- package/dest/world-state-db/merkle_tree_operations_facade.d.ts.map +1 -1
- package/dest/world-state-db/merkle_tree_operations_facade.js +5 -5
- package/dest/world-state-db/merkle_tree_snapshot_operations_facade.d.ts +2 -2
- package/dest/world-state-db/merkle_tree_snapshot_operations_facade.d.ts.map +1 -1
- package/dest/world-state-db/merkle_tree_snapshot_operations_facade.js +5 -5
- package/package.json +14 -13
- package/src/{synchronizer → instrumentation}/instrumentation.ts +37 -11
- package/src/native/merkle_trees_facade.ts +14 -12
- package/src/native/message.ts +9 -7
- package/src/native/native_world_state.ts +14 -5
- package/src/native/native_world_state_instance.ts +25 -21
- package/src/synchronizer/factory.ts +7 -10
- package/src/synchronizer/server_world_state_synchronizer.ts +9 -8
- package/src/world-state-db/merkle_tree_operations_facade.ts +11 -6
- package/src/world-state-db/merkle_tree_snapshot_operations_facade.ts +9 -6
- package/dest/synchronizer/instrumentation.d.ts.map +0 -1
- 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 {
|
|
4
|
-
|
|
5
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
49
|
-
return this.
|
|
48
|
+
findLeafIndices(treeId: MerkleTreeId, values: MerkleTreeLeafType<MerkleTreeId>[]): Promise<(bigint | undefined)[]> {
|
|
49
|
+
return this.findLeafIndicesAfter(treeId, values, 0n);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
async
|
|
52
|
+
async findLeafIndicesAfter(
|
|
53
53
|
treeId: MerkleTreeId,
|
|
54
|
-
|
|
54
|
+
leaves: MerkleTreeLeafType<MerkleTreeId>[],
|
|
55
55
|
startIndex: bigint,
|
|
56
|
-
): Promise<bigint | undefined> {
|
|
57
|
-
const
|
|
58
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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> {
|
package/src/native/message.ts
CHANGED
|
@@ -56,7 +56,7 @@ export enum WorldStateMessageType {
|
|
|
56
56
|
GET_SIBLING_PATH,
|
|
57
57
|
GET_BLOCK_NUMBERS_FOR_LEAF_INDICES,
|
|
58
58
|
|
|
59
|
-
|
|
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
|
|
333
|
-
|
|
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
|
|
374
|
+
interface FindLeafIndicesRequest extends WithTreeId, WithLeafValues, WithWorldStateRevision {
|
|
375
375
|
startIndex: bigint;
|
|
376
376
|
}
|
|
377
|
-
|
|
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.
|
|
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.
|
|
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(
|
|
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
|
|
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(
|
|
86
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
250
|
-
const
|
|
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
|
|
19
|
-
|
|
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
|
-
|
|
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 =
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
114
|
-
|
|
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
|
-
|
|
126
|
+
findLeafIndicesAfter<ID extends MerkleTreeId>(
|
|
124
127
|
treeId: ID,
|
|
125
|
-
|
|
128
|
+
values: MerkleTreeLeafType<ID>[],
|
|
126
129
|
startIndex: bigint,
|
|
127
|
-
): Promise<bigint | undefined> {
|
|
128
|
-
return
|
|
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
|
|
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(
|
|
49
|
+
return values.map(leaf => tree.findLeafIndex(leaf as any));
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
async
|
|
52
|
+
async findLeafIndicesAfter<ID extends MerkleTreeId>(
|
|
50
53
|
treeId: MerkleTreeId,
|
|
51
|
-
|
|
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(
|
|
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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
|