@aztec/world-state 0.0.1-commit.993d240 → 0.0.1-commit.9a89641
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/index.d.ts +2 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -0
- package/dest/instrumentation/instrumentation.d.ts +4 -3
- package/dest/instrumentation/instrumentation.d.ts.map +1 -1
- package/dest/instrumentation/instrumentation.js +4 -9
- package/dest/native/index.d.ts +2 -1
- package/dest/native/index.d.ts.map +1 -1
- package/dest/native/index.js +1 -0
- package/dest/native/ipc_world_state_instance.d.ts +61 -22
- package/dest/native/ipc_world_state_instance.d.ts.map +1 -1
- package/dest/native/ipc_world_state_instance.js +530 -488
- package/dest/native/merkle_trees_facade.d.ts +4 -3
- package/dest/native/merkle_trees_facade.d.ts.map +1 -1
- package/dest/native/merkle_trees_facade.js +37 -109
- package/dest/native/message.d.ts +14 -229
- package/dest/native/message.d.ts.map +1 -1
- package/dest/native/message.js +0 -42
- package/dest/native/native_world_state.d.ts +14 -28
- package/dest/native/native_world_state.d.ts.map +1 -1
- package/dest/native/native_world_state.js +108 -99
- package/dest/native/native_world_state_instance.d.ts +56 -43
- package/dest/native/native_world_state_instance.d.ts.map +1 -1
- package/dest/native/native_world_state_instance.js +1 -210
- package/dest/native/world_state_operation.d.ts +7 -0
- package/dest/native/world_state_operation.d.ts.map +1 -0
- package/dest/native/world_state_operation.js +5 -0
- package/dest/synchronizer/errors.d.ts +8 -1
- package/dest/synchronizer/errors.d.ts.map +1 -1
- package/dest/synchronizer/errors.js +8 -1
- package/dest/synchronizer/factory.d.ts +1 -1
- package/dest/synchronizer/factory.d.ts.map +1 -1
- package/dest/synchronizer/factory.js +1 -1
- package/dest/synchronizer/index.d.ts +2 -1
- package/dest/synchronizer/index.d.ts.map +1 -1
- package/dest/synchronizer/index.js +1 -0
- package/dest/synchronizer/server_world_state_synchronizer.d.ts +11 -6
- package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
- package/dest/synchronizer/server_world_state_synchronizer.js +55 -43
- package/dest/test/utils.d.ts +1 -1
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +6 -1
- package/dest/testing.d.ts +2 -2
- package/dest/testing.d.ts.map +1 -1
- package/dest/testing.js +15 -4
- package/package.json +11 -10
- package/src/index.ts +1 -0
- package/src/instrumentation/instrumentation.ts +6 -18
- package/src/native/index.ts +1 -0
- package/src/native/ipc_world_state_instance.ts +625 -495
- package/src/native/merkle_trees_facade.ts +59 -104
- package/src/native/message.ts +13 -296
- package/src/native/native_world_state.ts +136 -153
- package/src/native/native_world_state_instance.ts +94 -291
- package/src/native/world_state_operation.ts +33 -0
- package/src/synchronizer/errors.ts +8 -0
- package/src/synchronizer/factory.ts +0 -1
- package/src/synchronizer/index.ts +1 -0
- package/src/synchronizer/server_world_state_synchronizer.ts +63 -30
- package/src/test/utils.ts +10 -1
- package/src/testing.ts +14 -4
- package/dest/native/world_state_ops_queue.d.ts +0 -19
- package/dest/native/world_state_ops_queue.d.ts.map +0 -1
- package/dest/native/world_state_ops_queue.js +0 -146
- package/src/native/world_state_ops_queue.ts +0 -190
|
@@ -1,301 +1,104 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import { cpus } from 'os';
|
|
18
|
-
|
|
19
|
-
import type { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
|
|
20
|
-
import type { WorldStateTreeMapSizes } from '../synchronizer/factory.js';
|
|
21
|
-
import {
|
|
22
|
-
WorldStateMessageType,
|
|
23
|
-
type WorldStateRequest,
|
|
24
|
-
type WorldStateRequestCategories,
|
|
25
|
-
type WorldStateResponse,
|
|
26
|
-
isWithCanonical,
|
|
27
|
-
isWithForkId,
|
|
28
|
-
isWithRevision,
|
|
1
|
+
import type { BlockNumber } from '@aztec/foundation/branded-types';
|
|
2
|
+
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
3
|
+
import type { IndexedTreeId, TreeInfo } from '@aztec/stdlib/interfaces/server';
|
|
4
|
+
import type { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
5
|
+
import type { WorldStateRevision } from '@aztec/stdlib/world-state';
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
BlockStateReference,
|
|
9
|
+
SerializedBatchInsertionResult,
|
|
10
|
+
SerializedIndexedLeaf,
|
|
11
|
+
SerializedLeafValue,
|
|
12
|
+
SerializedSequentialInsertionResult,
|
|
13
|
+
SerializedSiblingPathAndIndex,
|
|
14
|
+
TreeStateReference,
|
|
15
|
+
WorldStateStatusFull,
|
|
16
|
+
WorldStateStatusSummary,
|
|
29
17
|
} from './message.js';
|
|
30
|
-
import { WorldStateOpsQueue } from './world_state_ops_queue.js';
|
|
31
|
-
|
|
32
|
-
const MAX_WORLD_STATE_THREADS = +(process.env.HARDWARE_CONCURRENCY || '16');
|
|
33
18
|
|
|
19
|
+
/** Backend-agnostic handle to a running aztec-wsdb world state, accessed by the TS layer. */
|
|
34
20
|
export interface NativeWorldStateInstance {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return this.dataDir;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
public clone() {
|
|
110
|
-
return new NativeWorldState(
|
|
111
|
-
this.dataDir,
|
|
112
|
-
this.wsTreeMapSizes,
|
|
113
|
-
this.genesis,
|
|
114
|
-
this.instrumentation,
|
|
115
|
-
this.log.getBindings(),
|
|
116
|
-
this.log,
|
|
117
|
-
this.ephemeral,
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Gets the native WorldState handle from the underlying native instance.
|
|
123
|
-
* We call the getHandle() method on the native WorldState to get a NAPI External
|
|
124
|
-
* that wraps the underlying C++ WorldState pointer.
|
|
125
|
-
* @returns The NAPI External handle to the native WorldState instance,since
|
|
126
|
-
* the NAPI external type is opaque, we return any (we could also use an opaque symbol type)
|
|
127
|
-
*/
|
|
128
|
-
public getHandle(): any {
|
|
129
|
-
const worldStateWrapper = (this.instance as any).dest;
|
|
130
|
-
|
|
131
|
-
if (!worldStateWrapper) {
|
|
132
|
-
throw new Error('No WorldStateWrapper found');
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (typeof worldStateWrapper.getHandle !== 'function') {
|
|
136
|
-
throw new Error('WorldStateWrapper does not have getHandle method');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Call getHandle() to get the NAPI External
|
|
140
|
-
try {
|
|
141
|
-
return worldStateWrapper.getHandle();
|
|
142
|
-
} catch (error) {
|
|
143
|
-
this.log.error('Failed to get native WorldState handle', error);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
21
|
+
getTreeInfo(treeId: MerkleTreeId, revision: WorldStateRevision): Promise<TreeInfo>;
|
|
22
|
+
getStateReference(revision: WorldStateRevision): Promise<Record<number, TreeStateReference>>;
|
|
23
|
+
getInitialStateReference(): Promise<Record<number, TreeStateReference>>;
|
|
24
|
+
getLeafValue(
|
|
25
|
+
treeId: MerkleTreeId,
|
|
26
|
+
revision: WorldStateRevision,
|
|
27
|
+
leafIndex: bigint,
|
|
28
|
+
): Promise<SerializedLeafValue | undefined>;
|
|
29
|
+
getLeafPreimage(
|
|
30
|
+
treeId: IndexedTreeId,
|
|
31
|
+
revision: WorldStateRevision,
|
|
32
|
+
leafIndex: bigint,
|
|
33
|
+
): Promise<SerializedIndexedLeaf | undefined>;
|
|
34
|
+
getSiblingPath(treeId: MerkleTreeId, revision: WorldStateRevision, leafIndex: bigint): Promise<Buffer[]>;
|
|
35
|
+
getBlockNumbersForLeafIndices(
|
|
36
|
+
treeId: MerkleTreeId,
|
|
37
|
+
revision: WorldStateRevision,
|
|
38
|
+
leafIndices: bigint[],
|
|
39
|
+
): Promise<(bigint | undefined)[]>;
|
|
40
|
+
findLeafIndices(
|
|
41
|
+
treeId: MerkleTreeId,
|
|
42
|
+
revision: WorldStateRevision,
|
|
43
|
+
leaves: SerializedLeafValue[],
|
|
44
|
+
startIndex: bigint,
|
|
45
|
+
): Promise<(bigint | undefined)[]>;
|
|
46
|
+
findLowLeaf(
|
|
47
|
+
treeId: IndexedTreeId,
|
|
48
|
+
revision: WorldStateRevision,
|
|
49
|
+
key: Fr,
|
|
50
|
+
): Promise<{ index: bigint; alreadyPresent: boolean }>;
|
|
51
|
+
findSiblingPaths(
|
|
52
|
+
treeId: MerkleTreeId,
|
|
53
|
+
revision: WorldStateRevision,
|
|
54
|
+
leaves: SerializedLeafValue[],
|
|
55
|
+
): Promise<(SerializedSiblingPathAndIndex | undefined)[]>;
|
|
56
|
+
updateArchive(forkId: number, blockStateRef: BlockStateReference, blockHeaderHash: Buffer): Promise<void>;
|
|
57
|
+
appendLeaves(treeId: MerkleTreeId, forkId: number, leaves: SerializedLeafValue[]): Promise<void>;
|
|
58
|
+
batchInsert(
|
|
59
|
+
treeId: IndexedTreeId,
|
|
60
|
+
forkId: number,
|
|
61
|
+
leaves: SerializedLeafValue[],
|
|
62
|
+
subtreeDepth: number,
|
|
63
|
+
): Promise<SerializedBatchInsertionResult>;
|
|
64
|
+
sequentialInsert(
|
|
65
|
+
treeId: IndexedTreeId,
|
|
66
|
+
forkId: number,
|
|
67
|
+
leaves: SerializedLeafValue[],
|
|
68
|
+
): Promise<SerializedSequentialInsertionResult>;
|
|
69
|
+
syncBlock(input: {
|
|
70
|
+
blockNumber: BlockNumber;
|
|
71
|
+
blockStateRef: BlockStateReference;
|
|
72
|
+
blockHeaderHash: Buffer;
|
|
73
|
+
expectedArchiveRoot: Buffer;
|
|
74
|
+
expectedPreviousArchiveRoot: Buffer;
|
|
75
|
+
paddedNoteHashes: readonly SerializedLeafValue[];
|
|
76
|
+
paddedL1ToL2Messages: readonly SerializedLeafValue[];
|
|
77
|
+
paddedNullifiers: readonly SerializedLeafValue[];
|
|
78
|
+
publicDataWrites: readonly SerializedLeafValue[];
|
|
79
|
+
}): Promise<WorldStateStatusFull>;
|
|
80
|
+
createFork(input: { latest: boolean; blockNumber: BlockNumber }): Promise<number>;
|
|
81
|
+
deleteFork(forkId: number): Promise<void>;
|
|
82
|
+
finalizeBlocks(toBlockNumber: BlockNumber): Promise<WorldStateStatusSummary>;
|
|
83
|
+
unwindBlocks(toBlockNumber: BlockNumber): Promise<WorldStateStatusFull>;
|
|
84
|
+
removeHistoricalBlocks(toBlockNumber: BlockNumber): Promise<WorldStateStatusFull>;
|
|
85
|
+
getStatus(): Promise<WorldStateStatusSummary>;
|
|
86
|
+
createCheckpoint(forkId: number): Promise<number>;
|
|
87
|
+
commitCheckpoint(forkId: number): Promise<void>;
|
|
88
|
+
revertCheckpoint(forkId: number): Promise<void>;
|
|
89
|
+
commitAllCheckpoints(forkId: number, depth: number): Promise<void>;
|
|
90
|
+
revertAllCheckpoints(forkId: number, depth: number): Promise<void>;
|
|
91
|
+
copyStores(dstPath: string, compact: boolean): Promise<void>;
|
|
146
92
|
|
|
147
93
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* @param body - The message body
|
|
151
|
-
* @param responseHandler - A callback accepting the response, executed on the job queue
|
|
152
|
-
* @param errorHandler - A callback called on request error, executed on the job queue
|
|
153
|
-
* @returns The response to the message
|
|
94
|
+
* IPC path the underlying aztec-wsdb process listens on. The C++ AVM uses this to attach to the
|
|
95
|
+
* same world state instance the TS layer is using.
|
|
154
96
|
*/
|
|
155
|
-
|
|
156
|
-
messageType: T,
|
|
157
|
-
body: WorldStateRequest[T] & WorldStateRequestCategories,
|
|
158
|
-
// allows for the pre-processing of responses on the job queue before being passed back
|
|
159
|
-
responseHandler = (response: WorldStateResponse[T]): WorldStateResponse[T] => response,
|
|
160
|
-
errorHandler = (_: string) => {},
|
|
161
|
-
): Promise<WorldStateResponse[T]> {
|
|
162
|
-
// Here we determine which fork the request is being executed against and whether it requires uncommitted data
|
|
163
|
-
// We use the fork Id to select the appropriate request queue and the uncommitted data flag to pass to the queue
|
|
164
|
-
let forkId = -1;
|
|
165
|
-
// We assume it includes uncommitted unless explicitly told otherwise
|
|
166
|
-
let committedOnly = false;
|
|
167
|
-
|
|
168
|
-
// Canonical requests ALWAYS go against the canonical fork
|
|
169
|
-
// These include things like block syncs/unwinds etc
|
|
170
|
-
// These requests don't contain a fork ID
|
|
171
|
-
if (isWithCanonical(body)) {
|
|
172
|
-
forkId = 0;
|
|
173
|
-
} else if (isWithForkId(body)) {
|
|
174
|
-
forkId = body.forkId;
|
|
175
|
-
} else if (isWithRevision(body)) {
|
|
176
|
-
forkId = body.revision.forkId;
|
|
177
|
-
committedOnly = body.revision.includeUncommitted === false;
|
|
178
|
-
} else {
|
|
179
|
-
const _: never = body;
|
|
180
|
-
throw new Error(`Unable to determine forkId for message=${WorldStateMessageType[messageType]}`);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Get the queue or create a new one
|
|
184
|
-
let requestQueue = this.queues.get(forkId);
|
|
185
|
-
if (requestQueue === undefined) {
|
|
186
|
-
requestQueue = new WorldStateOpsQueue();
|
|
187
|
-
this.queues.set(forkId, requestQueue);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Enqueue the request and wait for the response. The per-fork queue is cleaned up in `finally` even on
|
|
191
|
-
// error, so the JS-side queues map cannot outlive the native fork (e.g. when the native fork was already
|
|
192
|
-
// destroyed by an unwind/historical-prune and DELETE_FORK rejects with "Fork not found").
|
|
193
|
-
try {
|
|
194
|
-
const response = await requestQueue.execute(
|
|
195
|
-
async () => {
|
|
196
|
-
assert.notEqual(messageType, WorldStateMessageType.CLOSE, 'Use close() to close the native instance');
|
|
197
|
-
assert.equal(this.open, true, 'Native instance is closed');
|
|
198
|
-
let response: WorldStateResponse[T];
|
|
199
|
-
try {
|
|
200
|
-
response = await this._sendMessage(messageType, body);
|
|
201
|
-
} catch (error: any) {
|
|
202
|
-
errorHandler(error.message);
|
|
203
|
-
throw error;
|
|
204
|
-
}
|
|
205
|
-
return responseHandler(response);
|
|
206
|
-
},
|
|
207
|
-
messageType,
|
|
208
|
-
committedOnly,
|
|
209
|
-
);
|
|
210
|
-
return response;
|
|
211
|
-
} finally {
|
|
212
|
-
if (messageType === WorldStateMessageType.DELETE_FORK) {
|
|
213
|
-
await requestQueue.stop();
|
|
214
|
-
this.queues.delete(forkId);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
97
|
+
getIpcPath(): string;
|
|
218
98
|
|
|
219
99
|
/**
|
|
220
|
-
*
|
|
100
|
+
* Shut down the world state instance. Cancels any in-flight queues, closes the IPC channel, and
|
|
101
|
+
* terminates the underlying aztec-wsdb process. Idempotent.
|
|
221
102
|
*/
|
|
222
|
-
|
|
223
|
-
if (!this.open) {
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
this.open = false;
|
|
227
|
-
const queue = this.queues.get(0)!;
|
|
228
|
-
|
|
229
|
-
await queue.execute(
|
|
230
|
-
async () => {
|
|
231
|
-
await this._sendMessage(WorldStateMessageType.CLOSE, { canonical: true });
|
|
232
|
-
},
|
|
233
|
-
WorldStateMessageType.CLOSE,
|
|
234
|
-
false,
|
|
235
|
-
);
|
|
236
|
-
await queue.stop();
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
private async _sendMessage<T extends WorldStateMessageType>(
|
|
240
|
-
messageType: T,
|
|
241
|
-
body: WorldStateRequest[T] & WorldStateRequestCategories,
|
|
242
|
-
): Promise<WorldStateResponse[T]> {
|
|
243
|
-
let logMetadata: Record<string, any> = {};
|
|
244
|
-
|
|
245
|
-
if (body) {
|
|
246
|
-
if ('treeId' in body) {
|
|
247
|
-
logMetadata['treeId'] = MerkleTreeId[body.treeId];
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if ('revision' in body) {
|
|
251
|
-
logMetadata = { ...logMetadata, ...body.revision };
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if ('forkId' in body) {
|
|
255
|
-
logMetadata['forkId'] = body.forkId;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if ('blockNumber' in body) {
|
|
259
|
-
logMetadata['blockNumber'] = body.blockNumber;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if ('toBlockNumber' in body) {
|
|
263
|
-
logMetadata['toBlockNumber'] = body.toBlockNumber;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if ('leafIndex' in body) {
|
|
267
|
-
logMetadata['leafIndex'] = body.leafIndex;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if ('blockHeaderHash' in body) {
|
|
271
|
-
logMetadata['blockHeaderHash'] = '0x' + body.blockHeaderHash.toString('hex');
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if ('leaves' in body) {
|
|
275
|
-
logMetadata['leavesCount'] = body.leaves.length;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// sync operation
|
|
279
|
-
if ('paddedNoteHashes' in body) {
|
|
280
|
-
logMetadata['notesCount'] = body.paddedNoteHashes.length;
|
|
281
|
-
logMetadata['nullifiersCount'] = body.paddedNullifiers.length;
|
|
282
|
-
logMetadata['l1ToL2MessagesCount'] = body.paddedL1ToL2Messages.length;
|
|
283
|
-
logMetadata['publicDataWritesCount'] = body.publicDataWrites.length;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
try {
|
|
288
|
-
const { duration, response } = await this.instance.sendMessage(messageType, body);
|
|
289
|
-
this.log.trace(`Call ${WorldStateMessageType[messageType]} took (ms)`, {
|
|
290
|
-
duration,
|
|
291
|
-
...logMetadata,
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
this.instrumentation.recordRoundTrip(duration.totalUs, messageType);
|
|
295
|
-
return response;
|
|
296
|
-
} catch (error) {
|
|
297
|
-
this.log.error(`Call ${WorldStateMessageType[messageType]} failed: ${error}`, error, logMetadata);
|
|
298
|
-
throw error;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
103
|
+
close(): Promise<void>;
|
|
301
104
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Names of the world-state operations, used as labels for instrumentation and
|
|
3
|
+
* logging. (Read/write ordering is enforced server-side by the wsdb scheduler,
|
|
4
|
+
* so there is no longer a client-side operation queue.)
|
|
5
|
+
*/
|
|
6
|
+
export type WorldStateOperationName =
|
|
7
|
+
| 'getTreeInfo'
|
|
8
|
+
| 'getStateReference'
|
|
9
|
+
| 'getInitialStateReference'
|
|
10
|
+
| 'getLeafValue'
|
|
11
|
+
| 'getLeafPreimage'
|
|
12
|
+
| 'getSiblingPath'
|
|
13
|
+
| 'getBlockNumbersForLeafIndices'
|
|
14
|
+
| 'findLeafIndices'
|
|
15
|
+
| 'findLowLeaf'
|
|
16
|
+
| 'findSiblingPaths'
|
|
17
|
+
| 'appendLeaves'
|
|
18
|
+
| 'batchInsert'
|
|
19
|
+
| 'sequentialInsert'
|
|
20
|
+
| 'updateArchive'
|
|
21
|
+
| 'syncBlock'
|
|
22
|
+
| 'createFork'
|
|
23
|
+
| 'deleteFork'
|
|
24
|
+
| 'finalizeBlocks'
|
|
25
|
+
| 'unwindBlocks'
|
|
26
|
+
| 'removeHistoricalBlocks'
|
|
27
|
+
| 'getStatus'
|
|
28
|
+
| 'createCheckpoint'
|
|
29
|
+
| 'commitCheckpoint'
|
|
30
|
+
| 'revertCheckpoint'
|
|
31
|
+
| 'commitAllCheckpoints'
|
|
32
|
+
| 'revertAllCheckpoints'
|
|
33
|
+
| 'copyStores';
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by {@link ServerWorldStateSynchronizer.syncImmediate} when world state cannot be synced to the requested
|
|
3
|
+
* target: either the block is not available from the block source (`block_not_available`, e.g. it was pruned away)
|
|
4
|
+
* or the synced block does not match the requested hash (`block_hash_mismatch`, i.e. a reorg). Both causes are
|
|
5
|
+
* transient from the caller's perspective: re-resolving the query against the current chain and retrying may succeed
|
|
6
|
+
* or produce a more precise error.
|
|
7
|
+
*/
|
|
1
8
|
export class WorldStateSynchronizerError extends Error {
|
|
2
9
|
constructor(message: string, options?: ErrorOptions) {
|
|
3
10
|
super(message, options);
|
|
11
|
+
this.name = 'WorldStateSynchronizerError';
|
|
4
12
|
}
|
|
5
13
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { INITIAL_CHECKPOINT_NUMBER } from '@aztec/constants';
|
|
2
1
|
import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
3
2
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
3
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
@@ -6,15 +5,13 @@ import { promiseWithResolvers } from '@aztec/foundation/promise';
|
|
|
6
5
|
import { elapsed } from '@aztec/foundation/timer';
|
|
7
6
|
import {
|
|
8
7
|
type BlockHash,
|
|
9
|
-
|
|
8
|
+
EventDrivenL2BlockStream,
|
|
10
9
|
type L2Block,
|
|
11
|
-
type L2BlockId,
|
|
12
10
|
type L2BlockSource,
|
|
13
|
-
L2BlockStream,
|
|
14
11
|
type L2BlockStreamEvent,
|
|
15
12
|
type L2BlockStreamEventHandler,
|
|
16
13
|
type L2BlockStreamLocalDataProvider,
|
|
17
|
-
type
|
|
14
|
+
type LocalChainTips,
|
|
18
15
|
} from '@aztec/stdlib/block';
|
|
19
16
|
import {
|
|
20
17
|
WorldStateRunningState,
|
|
@@ -51,7 +48,7 @@ export class ServerWorldStateSynchronizer
|
|
|
51
48
|
private currentState: WorldStateRunningState = WorldStateRunningState.IDLE;
|
|
52
49
|
|
|
53
50
|
private syncPromise = promiseWithResolvers<void>();
|
|
54
|
-
protected blockStream:
|
|
51
|
+
protected blockStream: EventDrivenL2BlockStream | undefined;
|
|
55
52
|
|
|
56
53
|
// WorldState doesn't track the proven block number, it only tracks the latest tips of the pending chain and the finalized chain
|
|
57
54
|
// store the proven block number here, in the synchronizer, so that we don't end up spamming the logs with 'chain-proved' events
|
|
@@ -81,6 +78,46 @@ export class ServerWorldStateSynchronizer
|
|
|
81
78
|
return this.merkleTreeDb.getSnapshot(blockNumber);
|
|
82
79
|
}
|
|
83
80
|
|
|
81
|
+
public async getVerifiedSnapshot(blockNumber: BlockNumber, blockHash: BlockHash): Promise<MerkleTreeReadOperations> {
|
|
82
|
+
const snapshot = this.merkleTreeDb.getSnapshot(blockNumber);
|
|
83
|
+
// Block 0's snapshot is the pre-genesis archive view (size 0), so archive leaf 0 is not visible from it;
|
|
84
|
+
// verify against the initial header hash instead. For later blocks, read archive leaf `blockNumber` from the
|
|
85
|
+
// snapshot's own view so the exact handle we return is validated against the requested fork.
|
|
86
|
+
const actualHash =
|
|
87
|
+
blockNumber === BlockNumber.ZERO
|
|
88
|
+
? (await this.merkleTreeCommitted.getInitialHeader().hash()).toString()
|
|
89
|
+
: (await snapshot.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber)))?.toString();
|
|
90
|
+
|
|
91
|
+
if (actualHash === undefined) {
|
|
92
|
+
// A missing archive leaf means either the block's history has been pruned away (permanent: the block predates
|
|
93
|
+
// the oldest historical block kept by world state) or a reorg flipped the fork between the sync and this read
|
|
94
|
+
// (transient). Only the latter is worth retrying, so it alone surfaces as WorldStateSynchronizerError.
|
|
95
|
+
const { oldestHistoricalBlock } = await this.merkleTreeDb.getStatusSummary();
|
|
96
|
+
if (blockNumber < oldestHistoricalBlock) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Unable to find leaf for block ${blockNumber} in the archive tree: world state history has been pruned to block ${oldestHistoricalBlock}`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
throw new WorldStateSynchronizerError(`Unable to read block hash at block ${blockNumber} to verify snapshot`, {
|
|
102
|
+
cause: { reason: 'block_not_available', targetBlockNumber: blockNumber },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (actualHash !== blockHash.toString()) {
|
|
106
|
+
throw new WorldStateSynchronizerError(
|
|
107
|
+
`Block hash mismatch at block ${blockNumber} (expected ${blockHash} but got ${actualHash})`,
|
|
108
|
+
{
|
|
109
|
+
cause: {
|
|
110
|
+
reason: 'block_hash_mismatch',
|
|
111
|
+
targetBlockNumber: blockNumber,
|
|
112
|
+
expectedHash: blockHash.toString(),
|
|
113
|
+
actualHash,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return snapshot;
|
|
119
|
+
}
|
|
120
|
+
|
|
84
121
|
public fork(blockNumber?: BlockNumber, opts?: { closeDelayMs?: number }): Promise<MerkleTreeWriteOperations> {
|
|
85
122
|
return this.merkleTreeDb.fork(blockNumber, opts);
|
|
86
123
|
}
|
|
@@ -123,9 +160,9 @@ export class ServerWorldStateSynchronizer
|
|
|
123
160
|
return this.syncPromise.promise;
|
|
124
161
|
}
|
|
125
162
|
|
|
126
|
-
protected createBlockStream():
|
|
163
|
+
protected createBlockStream(): EventDrivenL2BlockStream {
|
|
127
164
|
const logger = createLogger('world-state:block_stream');
|
|
128
|
-
return new
|
|
165
|
+
return new EventDrivenL2BlockStream(this.l2BlockSource, this, this, logger, {
|
|
129
166
|
pollIntervalMS: this.config.worldStateBlockCheckIntervalMS,
|
|
130
167
|
batchSize: this.config.worldStateBlockRequestBatchSize,
|
|
131
168
|
ignoreCheckpoints: true,
|
|
@@ -266,8 +303,12 @@ export class ServerWorldStateSynchronizer
|
|
|
266
303
|
return this.merkleTreeCommitted.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(number)).then(leaf => leaf?.toString());
|
|
267
304
|
}
|
|
268
305
|
|
|
269
|
-
/**
|
|
270
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Returns the proposed, proven, and finalized block tips of the chain. World state drives its block stream with
|
|
308
|
+
* `ignoreCheckpoints`, so it does not track checkpointed blocks or checkpoints and omits `checkpointed` from the tips
|
|
309
|
+
* it reports.
|
|
310
|
+
*/
|
|
311
|
+
public async getL2Tips(): Promise<LocalChainTips> {
|
|
271
312
|
const status = await this.merkleTreeDb.getStatusSummary();
|
|
272
313
|
const unfinalizedBlockHashPromise = this.getL2BlockHash(status.unfinalizedBlockNumber);
|
|
273
314
|
const finalizedBlockHashPromise = this.getL2BlockHash(status.finalizedBlockNumber);
|
|
@@ -281,30 +322,13 @@ export class ServerWorldStateSynchronizer
|
|
|
281
322
|
finalizedBlockHashPromise,
|
|
282
323
|
provenBlockHashPromise,
|
|
283
324
|
]);
|
|
284
|
-
const latestBlockId: L2BlockId = { number: status.unfinalizedBlockNumber, hash: unfinalizedBlockHash! };
|
|
285
|
-
|
|
286
|
-
// World state doesn't track checkpointed blocks or checkpoints themselves.
|
|
287
|
-
// but we use a block stream so we need to provide 'local' L2Tips.
|
|
288
|
-
// We configure the block stream to ignore checkpoints and set checkpoint values to genesis here.
|
|
289
|
-
const genesisCheckpointHeaderHash = GENESIS_CHECKPOINT_HEADER_HASH.toString();
|
|
290
|
-
const initialBlockHash = (await this.merkleTreeCommitted.getInitialHeader().hash()).toString();
|
|
291
325
|
return {
|
|
292
|
-
proposed:
|
|
293
|
-
checkpointed: {
|
|
294
|
-
block: { number: BlockNumber.ZERO, hash: initialBlockHash },
|
|
295
|
-
checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
|
|
296
|
-
},
|
|
297
|
-
proposedCheckpoint: {
|
|
298
|
-
block: { number: BlockNumber.ZERO, hash: initialBlockHash },
|
|
299
|
-
checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
|
|
300
|
-
},
|
|
326
|
+
proposed: { number: status.unfinalizedBlockNumber, hash: unfinalizedBlockHash },
|
|
301
327
|
finalized: {
|
|
302
|
-
block: { number: status.finalizedBlockNumber, hash: finalizedBlockHash
|
|
303
|
-
checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
|
|
328
|
+
block: { number: status.finalizedBlockNumber, hash: finalizedBlockHash },
|
|
304
329
|
},
|
|
305
330
|
proven: {
|
|
306
|
-
block: { number: provenBlockNumber, hash: provenBlockHash
|
|
307
|
-
checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash },
|
|
331
|
+
block: { number: provenBlockNumber, hash: provenBlockHash },
|
|
308
332
|
},
|
|
309
333
|
};
|
|
310
334
|
}
|
|
@@ -324,6 +348,15 @@ export class ServerWorldStateSynchronizer
|
|
|
324
348
|
case 'chain-finalized':
|
|
325
349
|
await this.handleChainFinalized(event.block.number);
|
|
326
350
|
break;
|
|
351
|
+
// World state runs in block mode with ignoreCheckpoints: it tracks tips via blocks-added/pruned/proven/finalized
|
|
352
|
+
// and ignores the thin tip events (it never anchors on them).
|
|
353
|
+
case 'chain-proposed':
|
|
354
|
+
case 'chain-checkpointed':
|
|
355
|
+
break;
|
|
356
|
+
default: {
|
|
357
|
+
const _: never = event;
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
327
360
|
}
|
|
328
361
|
}
|
|
329
362
|
|
package/src/test/utils.ts
CHANGED
|
@@ -60,7 +60,16 @@ export async function updateBlockState(block: L2Block, l1ToL2Messages: Fr[], for
|
|
|
60
60
|
await Promise.all([publicDataInsert, nullifierInsert, noteHashInsert, messageInsert]);
|
|
61
61
|
|
|
62
62
|
const state = await fork.getStateReference();
|
|
63
|
-
|
|
63
|
+
|
|
64
|
+
// Capture the archive root *before* appending this block so the header's lastArchive chains off the previous block.
|
|
65
|
+
// sync_block now verifies lastArchive against the committed archive root, so a block carrying an unchained value
|
|
66
|
+
// (e.g. the random lastArchive from L2Block.random) would be rejected as a divergence from the canonical chain.
|
|
67
|
+
const previousArchive = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
68
|
+
block.header = BlockHeader.from({
|
|
69
|
+
...block.header,
|
|
70
|
+
state,
|
|
71
|
+
lastArchive: new AppendOnlyTreeSnapshot(Fr.fromBuffer(previousArchive.root), Number(previousArchive.size)),
|
|
72
|
+
});
|
|
64
73
|
await fork.updateArchive(block.header);
|
|
65
74
|
|
|
66
75
|
const archiveState = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|