@aztec/world-state 0.0.0-test.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/README.md +40 -0
- package/dest/index.d.ts +5 -0
- package/dest/index.d.ts.map +1 -0
- package/dest/index.js +4 -0
- package/dest/instrumentation/instrumentation.d.ts +22 -0
- package/dest/instrumentation/instrumentation.d.ts.map +1 -0
- package/dest/instrumentation/instrumentation.js +117 -0
- package/dest/native/fork_checkpoint.d.ts +10 -0
- package/dest/native/fork_checkpoint.d.ts.map +1 -0
- package/dest/native/fork_checkpoint.js +26 -0
- package/dest/native/index.d.ts +3 -0
- package/dest/native/index.d.ts.map +1 -0
- package/dest/native/index.js +2 -0
- package/dest/native/merkle_trees_facade.d.ts +42 -0
- package/dest/native/merkle_trees_facade.d.ts.map +1 -0
- package/dest/native/merkle_trees_facade.js +240 -0
- package/dest/native/message.d.ts +331 -0
- package/dest/native/message.d.ts.map +1 -0
- package/dest/native/message.js +192 -0
- package/dest/native/native_world_state.d.ts +57 -0
- package/dest/native/native_world_state.d.ts.map +1 -0
- package/dest/native/native_world_state.js +229 -0
- package/dest/native/native_world_state_instance.d.ts +33 -0
- package/dest/native/native_world_state_instance.d.ts.map +1 -0
- package/dest/native/native_world_state_instance.js +164 -0
- package/dest/native/world_state_ops_queue.d.ts +19 -0
- package/dest/native/world_state_ops_queue.d.ts.map +1 -0
- package/dest/native/world_state_ops_queue.js +146 -0
- package/dest/synchronizer/config.d.ts +23 -0
- package/dest/synchronizer/config.d.ts.map +1 -0
- package/dest/synchronizer/config.js +39 -0
- package/dest/synchronizer/factory.d.ts +12 -0
- package/dest/synchronizer/factory.d.ts.map +1 -0
- package/dest/synchronizer/factory.js +24 -0
- package/dest/synchronizer/index.d.ts +3 -0
- package/dest/synchronizer/index.d.ts.map +1 -0
- package/dest/synchronizer/index.js +2 -0
- package/dest/synchronizer/server_world_state_synchronizer.d.ts +79 -0
- package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -0
- package/dest/synchronizer/server_world_state_synchronizer.js +277 -0
- package/dest/test/index.d.ts +2 -0
- package/dest/test/index.d.ts.map +1 -0
- package/dest/test/index.js +1 -0
- package/dest/test/utils.d.ts +19 -0
- package/dest/test/utils.d.ts.map +1 -0
- package/dest/test/utils.js +99 -0
- package/dest/testing.d.ts +10 -0
- package/dest/testing.d.ts.map +1 -0
- package/dest/testing.js +37 -0
- package/dest/world-state-db/index.d.ts +3 -0
- package/dest/world-state-db/index.d.ts.map +1 -0
- package/dest/world-state-db/index.js +1 -0
- package/dest/world-state-db/merkle_tree_db.d.ts +68 -0
- package/dest/world-state-db/merkle_tree_db.d.ts.map +1 -0
- package/dest/world-state-db/merkle_tree_db.js +17 -0
- package/package.json +98 -0
- package/src/index.ts +4 -0
- package/src/instrumentation/instrumentation.ts +174 -0
- package/src/native/fork_checkpoint.ts +30 -0
- package/src/native/index.ts +2 -0
- package/src/native/merkle_trees_facade.ts +331 -0
- package/src/native/message.ts +541 -0
- package/src/native/native_world_state.ts +317 -0
- package/src/native/native_world_state_instance.ts +238 -0
- package/src/native/world_state_ops_queue.ts +190 -0
- package/src/synchronizer/config.ts +68 -0
- package/src/synchronizer/factory.ts +53 -0
- package/src/synchronizer/index.ts +2 -0
- package/src/synchronizer/server_world_state_synchronizer.ts +344 -0
- package/src/test/index.ts +1 -0
- package/src/test/utils.ts +153 -0
- package/src/testing.ts +60 -0
- package/src/world-state-db/index.ts +3 -0
- package/src/world-state-db/merkle_tree_db.ts +79 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/constants';
|
|
2
|
+
import { padArrayEnd } from '@aztec/foundation/collection';
|
|
3
|
+
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
4
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
5
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
6
|
+
import type { L2Block } from '@aztec/stdlib/block';
|
|
7
|
+
import { DatabaseVersionManager } from '@aztec/stdlib/database-version';
|
|
8
|
+
import type {
|
|
9
|
+
IndexedTreeId,
|
|
10
|
+
MerkleTreeReadOperations,
|
|
11
|
+
MerkleTreeWriteOperations,
|
|
12
|
+
} from '@aztec/stdlib/interfaces/server';
|
|
13
|
+
import { MerkleTreeId, NullifierLeaf, type NullifierLeafPreimage, PublicDataTreeLeaf } from '@aztec/stdlib/trees';
|
|
14
|
+
import { BlockHeader, PartialStateReference, StateReference } from '@aztec/stdlib/tx';
|
|
15
|
+
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
16
|
+
|
|
17
|
+
import assert from 'assert/strict';
|
|
18
|
+
import { mkdtemp, rm } from 'fs/promises';
|
|
19
|
+
import { tmpdir } from 'os';
|
|
20
|
+
import { join } from 'path';
|
|
21
|
+
|
|
22
|
+
import { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
|
|
23
|
+
import type { MerkleTreeAdminDatabase as MerkleTreeDatabase } from '../world-state-db/merkle_tree_db.js';
|
|
24
|
+
import { MerkleTreesFacade, MerkleTreesForkFacade, serializeLeaf } from './merkle_trees_facade.js';
|
|
25
|
+
import {
|
|
26
|
+
WorldStateMessageType,
|
|
27
|
+
type WorldStateStatusFull,
|
|
28
|
+
type WorldStateStatusSummary,
|
|
29
|
+
blockStateReference,
|
|
30
|
+
sanitiseFullStatus,
|
|
31
|
+
sanitiseSummary,
|
|
32
|
+
treeStateReferenceToSnapshot,
|
|
33
|
+
worldStateRevision,
|
|
34
|
+
} from './message.js';
|
|
35
|
+
import { NativeWorldState } from './native_world_state_instance.js';
|
|
36
|
+
|
|
37
|
+
// The current version of the world state database schema
|
|
38
|
+
// Increment this when making incompatible changes to the database schema
|
|
39
|
+
export const WORLD_STATE_DB_VERSION = 1; // The initial version
|
|
40
|
+
|
|
41
|
+
export class NativeWorldStateService implements MerkleTreeDatabase {
|
|
42
|
+
protected initialHeader: BlockHeader | undefined;
|
|
43
|
+
// This is read heavily and only changes when data is persisted, so we cache it
|
|
44
|
+
private cachedStatusSummary: WorldStateStatusSummary | undefined;
|
|
45
|
+
|
|
46
|
+
protected constructor(
|
|
47
|
+
protected readonly instance: NativeWorldState,
|
|
48
|
+
protected readonly worldStateInstrumentation: WorldStateInstrumentation,
|
|
49
|
+
protected readonly log = createLogger('world-state:database'),
|
|
50
|
+
private readonly cleanup = () => Promise.resolve(),
|
|
51
|
+
) {}
|
|
52
|
+
|
|
53
|
+
static async new(
|
|
54
|
+
rollupAddress: EthAddress,
|
|
55
|
+
dataDir: string,
|
|
56
|
+
dbMapSizeKb: number,
|
|
57
|
+
prefilledPublicData: PublicDataTreeLeaf[] = [],
|
|
58
|
+
instrumentation = new WorldStateInstrumentation(getTelemetryClient()),
|
|
59
|
+
log = createLogger('world-state:database'),
|
|
60
|
+
cleanup = () => Promise.resolve(),
|
|
61
|
+
): Promise<NativeWorldStateService> {
|
|
62
|
+
const worldStateDirectory = join(dataDir, 'world_state');
|
|
63
|
+
// Create a version manager to handle versioning
|
|
64
|
+
const versionManager = new DatabaseVersionManager(
|
|
65
|
+
WORLD_STATE_DB_VERSION,
|
|
66
|
+
rollupAddress,
|
|
67
|
+
worldStateDirectory,
|
|
68
|
+
(dir: string) => {
|
|
69
|
+
return Promise.resolve(new NativeWorldState(dir, dbMapSizeKb, prefilledPublicData, instrumentation));
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const [instance] = await versionManager.open();
|
|
74
|
+
const worldState = new this(instance, instrumentation, log, cleanup);
|
|
75
|
+
try {
|
|
76
|
+
await worldState.init();
|
|
77
|
+
} catch (e) {
|
|
78
|
+
log.error(`Error initialising world state: ${e}`);
|
|
79
|
+
throw e;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return worldState;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static async tmp(
|
|
86
|
+
rollupAddress = EthAddress.ZERO,
|
|
87
|
+
cleanupTmpDir = true,
|
|
88
|
+
prefilledPublicData: PublicDataTreeLeaf[] = [],
|
|
89
|
+
instrumentation = new WorldStateInstrumentation(getTelemetryClient()),
|
|
90
|
+
): Promise<NativeWorldStateService> {
|
|
91
|
+
const log = createLogger('world-state:database');
|
|
92
|
+
const dataDir = await mkdtemp(join(tmpdir(), 'aztec-world-state-'));
|
|
93
|
+
const dbMapSizeKb = 10 * 1024 * 1024;
|
|
94
|
+
log.debug(`Created temporary world state database at: ${dataDir} with size: ${dbMapSizeKb}`);
|
|
95
|
+
|
|
96
|
+
// pass a cleanup callback because process.on('beforeExit', cleanup) does not work under Jest
|
|
97
|
+
const cleanup = async () => {
|
|
98
|
+
if (cleanupTmpDir) {
|
|
99
|
+
await rm(dataDir, { recursive: true, force: true, maxRetries: 3 });
|
|
100
|
+
log.debug(`Deleted temporary world state database: ${dataDir}`);
|
|
101
|
+
} else {
|
|
102
|
+
log.debug(`Leaving temporary world state database: ${dataDir}`);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return this.new(rollupAddress, dataDir, dbMapSizeKb, prefilledPublicData, instrumentation, log, cleanup);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
protected async init() {
|
|
110
|
+
const status = await this.getStatusSummary();
|
|
111
|
+
if (!status.treesAreSynched) {
|
|
112
|
+
throw new Error('World state trees are out of sync, please delete your data directory and re-sync');
|
|
113
|
+
}
|
|
114
|
+
this.initialHeader = await this.buildInitialHeader();
|
|
115
|
+
const committed = this.getCommitted();
|
|
116
|
+
|
|
117
|
+
// validate the initial state
|
|
118
|
+
const archive = await committed.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
119
|
+
if (archive.size === 0n) {
|
|
120
|
+
throw new Error("Archive tree can't be empty");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// the initial header _must_ be the first element in the archive tree
|
|
124
|
+
// if this assertion fails, check that the hashing done in Header in yarn-project matches the initial header hash done in world_state.cpp
|
|
125
|
+
const indices = await committed.findLeafIndices(MerkleTreeId.ARCHIVE, [await this.initialHeader.hash()]);
|
|
126
|
+
const initialHeaderIndex = indices[0];
|
|
127
|
+
assert.strictEqual(initialHeaderIndex, 0n, 'Invalid initial archive state');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public getCommitted(): MerkleTreeReadOperations {
|
|
131
|
+
return new MerkleTreesFacade(this.instance, this.initialHeader!, worldStateRevision(false, 0, 0));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public getSnapshot(blockNumber: number): MerkleTreeReadOperations {
|
|
135
|
+
return new MerkleTreesFacade(this.instance, this.initialHeader!, worldStateRevision(false, 0, blockNumber));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public async fork(blockNumber?: number): Promise<MerkleTreeWriteOperations> {
|
|
139
|
+
const resp = await this.instance.call(WorldStateMessageType.CREATE_FORK, {
|
|
140
|
+
latest: blockNumber === undefined,
|
|
141
|
+
blockNumber: blockNumber ?? 0,
|
|
142
|
+
canonical: true,
|
|
143
|
+
});
|
|
144
|
+
return new MerkleTreesForkFacade(this.instance, this.initialHeader!, worldStateRevision(true, resp.forkId, 0));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
public getInitialHeader(): BlockHeader {
|
|
148
|
+
return this.initialHeader!;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
public async handleL2BlockAndMessages(l2Block: L2Block, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull> {
|
|
152
|
+
// We have to pad both the values within tx effects because that's how the trees are built by circuits.
|
|
153
|
+
const paddedNoteHashes = l2Block.body.txEffects.flatMap(txEffect =>
|
|
154
|
+
padArrayEnd(txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX),
|
|
155
|
+
);
|
|
156
|
+
const paddedL1ToL2Messages = padArrayEnd(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
157
|
+
|
|
158
|
+
const paddedNullifiers = l2Block.body.txEffects
|
|
159
|
+
.flatMap(txEffect => padArrayEnd(txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX))
|
|
160
|
+
.map(nullifier => new NullifierLeaf(nullifier));
|
|
161
|
+
|
|
162
|
+
const publicDataWrites: PublicDataTreeLeaf[] = l2Block.body.txEffects.flatMap(txEffect => {
|
|
163
|
+
return txEffect.publicDataWrites.map(write => {
|
|
164
|
+
if (write.isEmpty()) {
|
|
165
|
+
throw new Error('Public data write must not be empty when syncing');
|
|
166
|
+
}
|
|
167
|
+
return new PublicDataTreeLeaf(write.leafSlot, write.value);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
return await this.instance.call(
|
|
173
|
+
WorldStateMessageType.SYNC_BLOCK,
|
|
174
|
+
{
|
|
175
|
+
blockNumber: l2Block.number,
|
|
176
|
+
blockHeaderHash: await l2Block.header.hash(),
|
|
177
|
+
paddedL1ToL2Messages: paddedL1ToL2Messages.map(serializeLeaf),
|
|
178
|
+
paddedNoteHashes: paddedNoteHashes.map(serializeLeaf),
|
|
179
|
+
paddedNullifiers: paddedNullifiers.map(serializeLeaf),
|
|
180
|
+
publicDataWrites: publicDataWrites.map(serializeLeaf),
|
|
181
|
+
blockStateRef: blockStateReference(l2Block.header.state),
|
|
182
|
+
canonical: true,
|
|
183
|
+
},
|
|
184
|
+
this.sanitiseAndCacheSummaryFromFull.bind(this),
|
|
185
|
+
this.deleteCachedSummary.bind(this),
|
|
186
|
+
);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
this.worldStateInstrumentation.incCriticalErrors('synch_pending_block');
|
|
189
|
+
throw err;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
public async close(): Promise<void> {
|
|
194
|
+
await this.instance.close();
|
|
195
|
+
await this.cleanup();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private async buildInitialHeader(): Promise<BlockHeader> {
|
|
199
|
+
const state = await this.getInitialStateReference();
|
|
200
|
+
return BlockHeader.empty({ state });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private sanitiseAndCacheSummaryFromFull(response: WorldStateStatusFull) {
|
|
204
|
+
const sanitised = sanitiseFullStatus(response);
|
|
205
|
+
this.cachedStatusSummary = { ...sanitised.summary };
|
|
206
|
+
return sanitised;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private sanitiseAndCacheSummary(response: WorldStateStatusSummary) {
|
|
210
|
+
const sanitised = sanitiseSummary(response);
|
|
211
|
+
this.cachedStatusSummary = { ...sanitised };
|
|
212
|
+
return sanitised;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private deleteCachedSummary(_: string) {
|
|
216
|
+
this.cachedStatusSummary = undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Advances the finalised block number to be the number provided
|
|
221
|
+
* @param toBlockNumber The block number that is now the tip of the finalised chain
|
|
222
|
+
* @returns The new WorldStateStatus
|
|
223
|
+
*/
|
|
224
|
+
public async setFinalised(toBlockNumber: bigint) {
|
|
225
|
+
try {
|
|
226
|
+
await this.instance.call(
|
|
227
|
+
WorldStateMessageType.FINALISE_BLOCKS,
|
|
228
|
+
{
|
|
229
|
+
toBlockNumber,
|
|
230
|
+
canonical: true,
|
|
231
|
+
},
|
|
232
|
+
this.sanitiseAndCacheSummary.bind(this),
|
|
233
|
+
this.deleteCachedSummary.bind(this),
|
|
234
|
+
);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
this.worldStateInstrumentation.incCriticalErrors('finalize_block');
|
|
237
|
+
throw err;
|
|
238
|
+
}
|
|
239
|
+
return this.getStatusSummary();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Removes all historical snapshots up to but not including the given block number
|
|
244
|
+
* @param toBlockNumber The block number of the new oldest historical block
|
|
245
|
+
* @returns The new WorldStateStatus
|
|
246
|
+
*/
|
|
247
|
+
public async removeHistoricalBlocks(toBlockNumber: bigint) {
|
|
248
|
+
try {
|
|
249
|
+
return await this.instance.call(
|
|
250
|
+
WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS,
|
|
251
|
+
{
|
|
252
|
+
toBlockNumber,
|
|
253
|
+
canonical: true,
|
|
254
|
+
},
|
|
255
|
+
this.sanitiseAndCacheSummaryFromFull.bind(this),
|
|
256
|
+
this.deleteCachedSummary.bind(this),
|
|
257
|
+
);
|
|
258
|
+
} catch (err) {
|
|
259
|
+
this.worldStateInstrumentation.incCriticalErrors('prune_historical_block');
|
|
260
|
+
throw err;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Removes all pending blocks down to but not including the given block number
|
|
266
|
+
* @param toBlockNumber The block number of the new tip of the pending chain,
|
|
267
|
+
* @returns The new WorldStateStatus
|
|
268
|
+
*/
|
|
269
|
+
public async unwindBlocks(toBlockNumber: bigint) {
|
|
270
|
+
try {
|
|
271
|
+
return await this.instance.call(
|
|
272
|
+
WorldStateMessageType.UNWIND_BLOCKS,
|
|
273
|
+
{
|
|
274
|
+
toBlockNumber,
|
|
275
|
+
canonical: true,
|
|
276
|
+
},
|
|
277
|
+
this.sanitiseAndCacheSummaryFromFull.bind(this),
|
|
278
|
+
this.deleteCachedSummary.bind(this),
|
|
279
|
+
);
|
|
280
|
+
} catch (err) {
|
|
281
|
+
this.worldStateInstrumentation.incCriticalErrors('prune_pending_block');
|
|
282
|
+
throw err;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
public async getStatusSummary() {
|
|
287
|
+
if (this.cachedStatusSummary !== undefined) {
|
|
288
|
+
return { ...this.cachedStatusSummary };
|
|
289
|
+
}
|
|
290
|
+
return await this.instance.call(
|
|
291
|
+
WorldStateMessageType.GET_STATUS,
|
|
292
|
+
{ canonical: true },
|
|
293
|
+
this.sanitiseAndCacheSummary.bind(this),
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
updateLeaf<ID extends IndexedTreeId>(
|
|
298
|
+
_treeId: ID,
|
|
299
|
+
_leaf: NullifierLeafPreimage | Buffer,
|
|
300
|
+
_index: bigint,
|
|
301
|
+
): Promise<void> {
|
|
302
|
+
return Promise.reject(new Error('Method not implemented'));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private async getInitialStateReference(): Promise<StateReference> {
|
|
306
|
+
const resp = await this.instance.call(WorldStateMessageType.GET_INITIAL_STATE_REFERENCE, { canonical: true });
|
|
307
|
+
|
|
308
|
+
return new StateReference(
|
|
309
|
+
treeStateReferenceToSnapshot(resp.state[MerkleTreeId.L1_TO_L2_MESSAGE_TREE]),
|
|
310
|
+
new PartialStateReference(
|
|
311
|
+
treeStateReferenceToSnapshot(resp.state[MerkleTreeId.NOTE_HASH_TREE]),
|
|
312
|
+
treeStateReferenceToSnapshot(resp.state[MerkleTreeId.NULLIFIER_TREE]),
|
|
313
|
+
treeStateReferenceToSnapshot(resp.state[MerkleTreeId.PUBLIC_DATA_TREE]),
|
|
314
|
+
),
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ARCHIVE_HEIGHT,
|
|
3
|
+
GeneratorIndex,
|
|
4
|
+
L1_TO_L2_MSG_TREE_HEIGHT,
|
|
5
|
+
MAX_NULLIFIERS_PER_TX,
|
|
6
|
+
MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
|
|
7
|
+
NOTE_HASH_TREE_HEIGHT,
|
|
8
|
+
NULLIFIER_TREE_HEIGHT,
|
|
9
|
+
PUBLIC_DATA_TREE_HEIGHT,
|
|
10
|
+
} from '@aztec/constants';
|
|
11
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
12
|
+
import { NativeWorldState as BaseNativeWorldState, MsgpackChannel } from '@aztec/native';
|
|
13
|
+
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
14
|
+
import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees';
|
|
15
|
+
|
|
16
|
+
import assert from 'assert';
|
|
17
|
+
import { cpus } from 'os';
|
|
18
|
+
|
|
19
|
+
import type { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
|
|
20
|
+
import {
|
|
21
|
+
WorldStateMessageType,
|
|
22
|
+
type WorldStateRequest,
|
|
23
|
+
type WorldStateRequestCategories,
|
|
24
|
+
type WorldStateResponse,
|
|
25
|
+
isWithCanonical,
|
|
26
|
+
isWithForkId,
|
|
27
|
+
isWithRevision,
|
|
28
|
+
} from './message.js';
|
|
29
|
+
import { WorldStateOpsQueue } from './world_state_ops_queue.js';
|
|
30
|
+
|
|
31
|
+
const MAX_WORLD_STATE_THREADS = +(process.env.HARDWARE_CONCURRENCY || '16');
|
|
32
|
+
|
|
33
|
+
export interface NativeWorldStateInstance {
|
|
34
|
+
call<T extends WorldStateMessageType>(
|
|
35
|
+
messageType: T,
|
|
36
|
+
body: WorldStateRequest[T] & WorldStateRequestCategories,
|
|
37
|
+
): Promise<WorldStateResponse[T]>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Strongly-typed interface to access the WorldState class in the native world_state_napi module.
|
|
42
|
+
*/
|
|
43
|
+
export class NativeWorldState implements NativeWorldStateInstance {
|
|
44
|
+
private open = true;
|
|
45
|
+
|
|
46
|
+
// We maintain a map of queue to fork
|
|
47
|
+
private queues = new Map<number, WorldStateOpsQueue>();
|
|
48
|
+
|
|
49
|
+
private instance: MsgpackChannel<WorldStateMessageType, WorldStateRequest, WorldStateResponse>;
|
|
50
|
+
|
|
51
|
+
/** Creates a new native WorldState instance */
|
|
52
|
+
constructor(
|
|
53
|
+
dataDir: string,
|
|
54
|
+
dbMapSizeKb: number,
|
|
55
|
+
prefilledPublicData: PublicDataTreeLeaf[] = [],
|
|
56
|
+
private instrumentation: WorldStateInstrumentation,
|
|
57
|
+
private log = createLogger('world-state:database'),
|
|
58
|
+
) {
|
|
59
|
+
const threads = Math.min(cpus().length, MAX_WORLD_STATE_THREADS);
|
|
60
|
+
log.info(
|
|
61
|
+
`Creating world state data store at directory ${dataDir} with map size ${dbMapSizeKb} KB and ${threads} threads.`,
|
|
62
|
+
);
|
|
63
|
+
const prefilledPublicDataBufferArray = prefilledPublicData.map(d => [d.slot.toBuffer(), d.value.toBuffer()]);
|
|
64
|
+
const ws = new BaseNativeWorldState(
|
|
65
|
+
dataDir,
|
|
66
|
+
{
|
|
67
|
+
[MerkleTreeId.NULLIFIER_TREE]: NULLIFIER_TREE_HEIGHT,
|
|
68
|
+
[MerkleTreeId.NOTE_HASH_TREE]: NOTE_HASH_TREE_HEIGHT,
|
|
69
|
+
[MerkleTreeId.PUBLIC_DATA_TREE]: PUBLIC_DATA_TREE_HEIGHT,
|
|
70
|
+
[MerkleTreeId.L1_TO_L2_MESSAGE_TREE]: L1_TO_L2_MSG_TREE_HEIGHT,
|
|
71
|
+
[MerkleTreeId.ARCHIVE]: ARCHIVE_HEIGHT,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
[MerkleTreeId.NULLIFIER_TREE]: 2 * MAX_NULLIFIERS_PER_TX,
|
|
75
|
+
[MerkleTreeId.PUBLIC_DATA_TREE]: 2 * MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
|
|
76
|
+
},
|
|
77
|
+
prefilledPublicDataBufferArray,
|
|
78
|
+
GeneratorIndex.BLOCK_HASH,
|
|
79
|
+
dbMapSizeKb,
|
|
80
|
+
threads,
|
|
81
|
+
);
|
|
82
|
+
this.instance = new MsgpackChannel(ws);
|
|
83
|
+
// Manually create the queue for the canonical fork
|
|
84
|
+
this.queues.set(0, new WorldStateOpsQueue());
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Sends a message to the native instance and returns the response.
|
|
89
|
+
* @param messageType - The type of message to send
|
|
90
|
+
* @param body - The message body
|
|
91
|
+
* @param responseHandler - A callback accepting the response, executed on the job queue
|
|
92
|
+
* @param errorHandler - A callback called on request error, executed on the job queue
|
|
93
|
+
* @returns The response to the message
|
|
94
|
+
*/
|
|
95
|
+
public async call<T extends WorldStateMessageType>(
|
|
96
|
+
messageType: T,
|
|
97
|
+
body: WorldStateRequest[T] & WorldStateRequestCategories,
|
|
98
|
+
// allows for the pre-processing of responses on the job queue before being passed back
|
|
99
|
+
responseHandler = (response: WorldStateResponse[T]): WorldStateResponse[T] => response,
|
|
100
|
+
errorHandler = (_: string) => {},
|
|
101
|
+
): Promise<WorldStateResponse[T]> {
|
|
102
|
+
// Here we determine which fork the request is being executed against and whether it requires uncommitted data
|
|
103
|
+
// We use the fork Id to select the appropriate request queue and the uncommitted data flag to pass to the queue
|
|
104
|
+
let forkId = -1;
|
|
105
|
+
// We assume it includes uncommitted unless explicitly told otherwise
|
|
106
|
+
let committedOnly = false;
|
|
107
|
+
|
|
108
|
+
// Canonical requests ALWAYS go against the canonical fork
|
|
109
|
+
// These include things like block syncs/unwinds etc
|
|
110
|
+
// These requests don't contain a fork ID
|
|
111
|
+
if (isWithCanonical(body)) {
|
|
112
|
+
forkId = 0;
|
|
113
|
+
} else if (isWithForkId(body)) {
|
|
114
|
+
forkId = body.forkId;
|
|
115
|
+
} else if (isWithRevision(body)) {
|
|
116
|
+
forkId = body.revision.forkId;
|
|
117
|
+
committedOnly = body.revision.includeUncommitted === false;
|
|
118
|
+
} else {
|
|
119
|
+
const _: never = body;
|
|
120
|
+
throw new Error(`Unable to determine forkId for message=${WorldStateMessageType[messageType]}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Get the queue or create a new one
|
|
124
|
+
let requestQueue = this.queues.get(forkId);
|
|
125
|
+
if (requestQueue === undefined) {
|
|
126
|
+
requestQueue = new WorldStateOpsQueue();
|
|
127
|
+
this.queues.set(forkId, requestQueue);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Enqueue the request and wait for the response
|
|
131
|
+
const response = await requestQueue.execute(
|
|
132
|
+
async () => {
|
|
133
|
+
assert.notEqual(messageType, WorldStateMessageType.CLOSE, 'Use close() to close the native instance');
|
|
134
|
+
assert.equal(this.open, true, 'Native instance is closed');
|
|
135
|
+
let response: WorldStateResponse[T];
|
|
136
|
+
try {
|
|
137
|
+
response = await this._sendMessage(messageType, body);
|
|
138
|
+
} catch (error: any) {
|
|
139
|
+
errorHandler(error.message);
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
return responseHandler(response);
|
|
143
|
+
},
|
|
144
|
+
messageType,
|
|
145
|
+
committedOnly,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
// If the request was to delete the fork then we clean it up here
|
|
149
|
+
if (messageType === WorldStateMessageType.DELETE_FORK) {
|
|
150
|
+
await requestQueue.stop();
|
|
151
|
+
this.queues.delete(forkId);
|
|
152
|
+
}
|
|
153
|
+
return response;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Stops the native instance.
|
|
158
|
+
*/
|
|
159
|
+
public async close(): Promise<void> {
|
|
160
|
+
if (!this.open) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
this.open = false;
|
|
164
|
+
const queue = this.queues.get(0)!;
|
|
165
|
+
|
|
166
|
+
await queue.execute(
|
|
167
|
+
async () => {
|
|
168
|
+
await this._sendMessage(WorldStateMessageType.CLOSE, { canonical: true });
|
|
169
|
+
},
|
|
170
|
+
WorldStateMessageType.CLOSE,
|
|
171
|
+
false,
|
|
172
|
+
);
|
|
173
|
+
await queue.stop();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private async _sendMessage<T extends WorldStateMessageType>(
|
|
177
|
+
messageType: T,
|
|
178
|
+
body: WorldStateRequest[T] & WorldStateRequestCategories,
|
|
179
|
+
): Promise<WorldStateResponse[T]> {
|
|
180
|
+
let logMetadata: Record<string, any> = {};
|
|
181
|
+
|
|
182
|
+
if (body) {
|
|
183
|
+
if ('treeId' in body) {
|
|
184
|
+
logMetadata['treeId'] = MerkleTreeId[body.treeId];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if ('revision' in body) {
|
|
188
|
+
logMetadata = { ...logMetadata, ...body.revision };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if ('forkId' in body) {
|
|
192
|
+
logMetadata['forkId'] = body.forkId;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if ('blockNumber' in body) {
|
|
196
|
+
logMetadata['blockNumber'] = body.blockNumber;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if ('toBlockNumber' in body) {
|
|
200
|
+
logMetadata['toBlockNumber'] = body.toBlockNumber;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if ('leafIndex' in body) {
|
|
204
|
+
logMetadata['leafIndex'] = body.leafIndex;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if ('blockHeaderHash' in body) {
|
|
208
|
+
logMetadata['blockHeaderHash'] = '0x' + body.blockHeaderHash.toString('hex');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if ('leaves' in body) {
|
|
212
|
+
logMetadata['leavesCount'] = body.leaves.length;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// sync operation
|
|
216
|
+
if ('paddedNoteHashes' in body) {
|
|
217
|
+
logMetadata['notesCount'] = body.paddedNoteHashes.length;
|
|
218
|
+
logMetadata['nullifiersCount'] = body.paddedNullifiers.length;
|
|
219
|
+
logMetadata['l1ToL2MessagesCount'] = body.paddedL1ToL2Messages.length;
|
|
220
|
+
logMetadata['publicDataWritesCount'] = body.publicDataWrites.length;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
const { duration, response } = await this.instance.sendMessage(messageType, body);
|
|
226
|
+
this.log.trace(`Call ${WorldStateMessageType[messageType]} took (ms)`, {
|
|
227
|
+
duration,
|
|
228
|
+
...logMetadata,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
this.instrumentation.recordRoundTrip(duration.totalUs, messageType);
|
|
232
|
+
return response;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
this.log.error(`Call ${WorldStateMessageType[messageType]} failed: ${error}`, error, logMetadata);
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|