@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.
Files changed (74) hide show
  1. package/README.md +40 -0
  2. package/dest/index.d.ts +5 -0
  3. package/dest/index.d.ts.map +1 -0
  4. package/dest/index.js +4 -0
  5. package/dest/instrumentation/instrumentation.d.ts +22 -0
  6. package/dest/instrumentation/instrumentation.d.ts.map +1 -0
  7. package/dest/instrumentation/instrumentation.js +117 -0
  8. package/dest/native/fork_checkpoint.d.ts +10 -0
  9. package/dest/native/fork_checkpoint.d.ts.map +1 -0
  10. package/dest/native/fork_checkpoint.js +26 -0
  11. package/dest/native/index.d.ts +3 -0
  12. package/dest/native/index.d.ts.map +1 -0
  13. package/dest/native/index.js +2 -0
  14. package/dest/native/merkle_trees_facade.d.ts +42 -0
  15. package/dest/native/merkle_trees_facade.d.ts.map +1 -0
  16. package/dest/native/merkle_trees_facade.js +240 -0
  17. package/dest/native/message.d.ts +331 -0
  18. package/dest/native/message.d.ts.map +1 -0
  19. package/dest/native/message.js +192 -0
  20. package/dest/native/native_world_state.d.ts +57 -0
  21. package/dest/native/native_world_state.d.ts.map +1 -0
  22. package/dest/native/native_world_state.js +229 -0
  23. package/dest/native/native_world_state_instance.d.ts +33 -0
  24. package/dest/native/native_world_state_instance.d.ts.map +1 -0
  25. package/dest/native/native_world_state_instance.js +164 -0
  26. package/dest/native/world_state_ops_queue.d.ts +19 -0
  27. package/dest/native/world_state_ops_queue.d.ts.map +1 -0
  28. package/dest/native/world_state_ops_queue.js +146 -0
  29. package/dest/synchronizer/config.d.ts +23 -0
  30. package/dest/synchronizer/config.d.ts.map +1 -0
  31. package/dest/synchronizer/config.js +39 -0
  32. package/dest/synchronizer/factory.d.ts +12 -0
  33. package/dest/synchronizer/factory.d.ts.map +1 -0
  34. package/dest/synchronizer/factory.js +24 -0
  35. package/dest/synchronizer/index.d.ts +3 -0
  36. package/dest/synchronizer/index.d.ts.map +1 -0
  37. package/dest/synchronizer/index.js +2 -0
  38. package/dest/synchronizer/server_world_state_synchronizer.d.ts +79 -0
  39. package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -0
  40. package/dest/synchronizer/server_world_state_synchronizer.js +277 -0
  41. package/dest/test/index.d.ts +2 -0
  42. package/dest/test/index.d.ts.map +1 -0
  43. package/dest/test/index.js +1 -0
  44. package/dest/test/utils.d.ts +19 -0
  45. package/dest/test/utils.d.ts.map +1 -0
  46. package/dest/test/utils.js +99 -0
  47. package/dest/testing.d.ts +10 -0
  48. package/dest/testing.d.ts.map +1 -0
  49. package/dest/testing.js +37 -0
  50. package/dest/world-state-db/index.d.ts +3 -0
  51. package/dest/world-state-db/index.d.ts.map +1 -0
  52. package/dest/world-state-db/index.js +1 -0
  53. package/dest/world-state-db/merkle_tree_db.d.ts +68 -0
  54. package/dest/world-state-db/merkle_tree_db.d.ts.map +1 -0
  55. package/dest/world-state-db/merkle_tree_db.js +17 -0
  56. package/package.json +98 -0
  57. package/src/index.ts +4 -0
  58. package/src/instrumentation/instrumentation.ts +174 -0
  59. package/src/native/fork_checkpoint.ts +30 -0
  60. package/src/native/index.ts +2 -0
  61. package/src/native/merkle_trees_facade.ts +331 -0
  62. package/src/native/message.ts +541 -0
  63. package/src/native/native_world_state.ts +317 -0
  64. package/src/native/native_world_state_instance.ts +238 -0
  65. package/src/native/world_state_ops_queue.ts +190 -0
  66. package/src/synchronizer/config.ts +68 -0
  67. package/src/synchronizer/factory.ts +53 -0
  68. package/src/synchronizer/index.ts +2 -0
  69. package/src/synchronizer/server_world_state_synchronizer.ts +344 -0
  70. package/src/test/index.ts +1 -0
  71. package/src/test/utils.ts +153 -0
  72. package/src/testing.ts +60 -0
  73. package/src/world-state-db/index.ts +3 -0
  74. package/src/world-state-db/merkle_tree_db.ts +79 -0
@@ -0,0 +1,229 @@
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 { DatabaseVersionManager } from '@aztec/stdlib/database-version';
7
+ import { MerkleTreeId, NullifierLeaf, PublicDataTreeLeaf } from '@aztec/stdlib/trees';
8
+ import { BlockHeader, PartialStateReference, StateReference } from '@aztec/stdlib/tx';
9
+ import { getTelemetryClient } from '@aztec/telemetry-client';
10
+ import assert from 'assert/strict';
11
+ import { mkdtemp, rm } from 'fs/promises';
12
+ import { tmpdir } from 'os';
13
+ import { join } from 'path';
14
+ import { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
15
+ import { MerkleTreesFacade, MerkleTreesForkFacade, serializeLeaf } from './merkle_trees_facade.js';
16
+ import { WorldStateMessageType, blockStateReference, sanitiseFullStatus, sanitiseSummary, treeStateReferenceToSnapshot, worldStateRevision } from './message.js';
17
+ import { NativeWorldState } from './native_world_state_instance.js';
18
+ // The current version of the world state database schema
19
+ // Increment this when making incompatible changes to the database schema
20
+ export const WORLD_STATE_DB_VERSION = 1; // The initial version
21
+ export class NativeWorldStateService {
22
+ instance;
23
+ worldStateInstrumentation;
24
+ log;
25
+ cleanup;
26
+ initialHeader;
27
+ // This is read heavily and only changes when data is persisted, so we cache it
28
+ cachedStatusSummary;
29
+ constructor(instance, worldStateInstrumentation, log = createLogger('world-state:database'), cleanup = ()=>Promise.resolve()){
30
+ this.instance = instance;
31
+ this.worldStateInstrumentation = worldStateInstrumentation;
32
+ this.log = log;
33
+ this.cleanup = cleanup;
34
+ }
35
+ static async new(rollupAddress, dataDir, dbMapSizeKb, prefilledPublicData = [], instrumentation = new WorldStateInstrumentation(getTelemetryClient()), log = createLogger('world-state:database'), cleanup = ()=>Promise.resolve()) {
36
+ const worldStateDirectory = join(dataDir, 'world_state');
37
+ // Create a version manager to handle versioning
38
+ const versionManager = new DatabaseVersionManager(WORLD_STATE_DB_VERSION, rollupAddress, worldStateDirectory, (dir)=>{
39
+ return Promise.resolve(new NativeWorldState(dir, dbMapSizeKb, prefilledPublicData, instrumentation));
40
+ });
41
+ const [instance] = await versionManager.open();
42
+ const worldState = new this(instance, instrumentation, log, cleanup);
43
+ try {
44
+ await worldState.init();
45
+ } catch (e) {
46
+ log.error(`Error initialising world state: ${e}`);
47
+ throw e;
48
+ }
49
+ return worldState;
50
+ }
51
+ static async tmp(rollupAddress = EthAddress.ZERO, cleanupTmpDir = true, prefilledPublicData = [], instrumentation = new WorldStateInstrumentation(getTelemetryClient())) {
52
+ const log = createLogger('world-state:database');
53
+ const dataDir = await mkdtemp(join(tmpdir(), 'aztec-world-state-'));
54
+ const dbMapSizeKb = 10 * 1024 * 1024;
55
+ log.debug(`Created temporary world state database at: ${dataDir} with size: ${dbMapSizeKb}`);
56
+ // pass a cleanup callback because process.on('beforeExit', cleanup) does not work under Jest
57
+ const cleanup = async ()=>{
58
+ if (cleanupTmpDir) {
59
+ await rm(dataDir, {
60
+ recursive: true,
61
+ force: true,
62
+ maxRetries: 3
63
+ });
64
+ log.debug(`Deleted temporary world state database: ${dataDir}`);
65
+ } else {
66
+ log.debug(`Leaving temporary world state database: ${dataDir}`);
67
+ }
68
+ };
69
+ return this.new(rollupAddress, dataDir, dbMapSizeKb, prefilledPublicData, instrumentation, log, cleanup);
70
+ }
71
+ async init() {
72
+ const status = await this.getStatusSummary();
73
+ if (!status.treesAreSynched) {
74
+ throw new Error('World state trees are out of sync, please delete your data directory and re-sync');
75
+ }
76
+ this.initialHeader = await this.buildInitialHeader();
77
+ const committed = this.getCommitted();
78
+ // validate the initial state
79
+ const archive = await committed.getTreeInfo(MerkleTreeId.ARCHIVE);
80
+ if (archive.size === 0n) {
81
+ throw new Error("Archive tree can't be empty");
82
+ }
83
+ // the initial header _must_ be the first element in the archive tree
84
+ // if this assertion fails, check that the hashing done in Header in yarn-project matches the initial header hash done in world_state.cpp
85
+ const indices = await committed.findLeafIndices(MerkleTreeId.ARCHIVE, [
86
+ await this.initialHeader.hash()
87
+ ]);
88
+ const initialHeaderIndex = indices[0];
89
+ assert.strictEqual(initialHeaderIndex, 0n, 'Invalid initial archive state');
90
+ }
91
+ getCommitted() {
92
+ return new MerkleTreesFacade(this.instance, this.initialHeader, worldStateRevision(false, 0, 0));
93
+ }
94
+ getSnapshot(blockNumber) {
95
+ return new MerkleTreesFacade(this.instance, this.initialHeader, worldStateRevision(false, 0, blockNumber));
96
+ }
97
+ async fork(blockNumber) {
98
+ const resp = await this.instance.call(WorldStateMessageType.CREATE_FORK, {
99
+ latest: blockNumber === undefined,
100
+ blockNumber: blockNumber ?? 0,
101
+ canonical: true
102
+ });
103
+ return new MerkleTreesForkFacade(this.instance, this.initialHeader, worldStateRevision(true, resp.forkId, 0));
104
+ }
105
+ getInitialHeader() {
106
+ return this.initialHeader;
107
+ }
108
+ async handleL2BlockAndMessages(l2Block, l1ToL2Messages) {
109
+ // We have to pad both the values within tx effects because that's how the trees are built by circuits.
110
+ const paddedNoteHashes = l2Block.body.txEffects.flatMap((txEffect)=>padArrayEnd(txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX));
111
+ const paddedL1ToL2Messages = padArrayEnd(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
112
+ const paddedNullifiers = l2Block.body.txEffects.flatMap((txEffect)=>padArrayEnd(txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX)).map((nullifier)=>new NullifierLeaf(nullifier));
113
+ const publicDataWrites = l2Block.body.txEffects.flatMap((txEffect)=>{
114
+ return txEffect.publicDataWrites.map((write)=>{
115
+ if (write.isEmpty()) {
116
+ throw new Error('Public data write must not be empty when syncing');
117
+ }
118
+ return new PublicDataTreeLeaf(write.leafSlot, write.value);
119
+ });
120
+ });
121
+ try {
122
+ return await this.instance.call(WorldStateMessageType.SYNC_BLOCK, {
123
+ blockNumber: l2Block.number,
124
+ blockHeaderHash: await l2Block.header.hash(),
125
+ paddedL1ToL2Messages: paddedL1ToL2Messages.map(serializeLeaf),
126
+ paddedNoteHashes: paddedNoteHashes.map(serializeLeaf),
127
+ paddedNullifiers: paddedNullifiers.map(serializeLeaf),
128
+ publicDataWrites: publicDataWrites.map(serializeLeaf),
129
+ blockStateRef: blockStateReference(l2Block.header.state),
130
+ canonical: true
131
+ }, this.sanitiseAndCacheSummaryFromFull.bind(this), this.deleteCachedSummary.bind(this));
132
+ } catch (err) {
133
+ this.worldStateInstrumentation.incCriticalErrors('synch_pending_block');
134
+ throw err;
135
+ }
136
+ }
137
+ async close() {
138
+ await this.instance.close();
139
+ await this.cleanup();
140
+ }
141
+ async buildInitialHeader() {
142
+ const state = await this.getInitialStateReference();
143
+ return BlockHeader.empty({
144
+ state
145
+ });
146
+ }
147
+ sanitiseAndCacheSummaryFromFull(response) {
148
+ const sanitised = sanitiseFullStatus(response);
149
+ this.cachedStatusSummary = {
150
+ ...sanitised.summary
151
+ };
152
+ return sanitised;
153
+ }
154
+ sanitiseAndCacheSummary(response) {
155
+ const sanitised = sanitiseSummary(response);
156
+ this.cachedStatusSummary = {
157
+ ...sanitised
158
+ };
159
+ return sanitised;
160
+ }
161
+ deleteCachedSummary(_) {
162
+ this.cachedStatusSummary = undefined;
163
+ }
164
+ /**
165
+ * Advances the finalised block number to be the number provided
166
+ * @param toBlockNumber The block number that is now the tip of the finalised chain
167
+ * @returns The new WorldStateStatus
168
+ */ async setFinalised(toBlockNumber) {
169
+ try {
170
+ await this.instance.call(WorldStateMessageType.FINALISE_BLOCKS, {
171
+ toBlockNumber,
172
+ canonical: true
173
+ }, this.sanitiseAndCacheSummary.bind(this), this.deleteCachedSummary.bind(this));
174
+ } catch (err) {
175
+ this.worldStateInstrumentation.incCriticalErrors('finalize_block');
176
+ throw err;
177
+ }
178
+ return this.getStatusSummary();
179
+ }
180
+ /**
181
+ * Removes all historical snapshots up to but not including the given block number
182
+ * @param toBlockNumber The block number of the new oldest historical block
183
+ * @returns The new WorldStateStatus
184
+ */ async removeHistoricalBlocks(toBlockNumber) {
185
+ try {
186
+ return await this.instance.call(WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS, {
187
+ toBlockNumber,
188
+ canonical: true
189
+ }, this.sanitiseAndCacheSummaryFromFull.bind(this), this.deleteCachedSummary.bind(this));
190
+ } catch (err) {
191
+ this.worldStateInstrumentation.incCriticalErrors('prune_historical_block');
192
+ throw err;
193
+ }
194
+ }
195
+ /**
196
+ * Removes all pending blocks down to but not including the given block number
197
+ * @param toBlockNumber The block number of the new tip of the pending chain,
198
+ * @returns The new WorldStateStatus
199
+ */ async unwindBlocks(toBlockNumber) {
200
+ try {
201
+ return await this.instance.call(WorldStateMessageType.UNWIND_BLOCKS, {
202
+ toBlockNumber,
203
+ canonical: true
204
+ }, this.sanitiseAndCacheSummaryFromFull.bind(this), this.deleteCachedSummary.bind(this));
205
+ } catch (err) {
206
+ this.worldStateInstrumentation.incCriticalErrors('prune_pending_block');
207
+ throw err;
208
+ }
209
+ }
210
+ async getStatusSummary() {
211
+ if (this.cachedStatusSummary !== undefined) {
212
+ return {
213
+ ...this.cachedStatusSummary
214
+ };
215
+ }
216
+ return await this.instance.call(WorldStateMessageType.GET_STATUS, {
217
+ canonical: true
218
+ }, this.sanitiseAndCacheSummary.bind(this));
219
+ }
220
+ updateLeaf(_treeId, _leaf, _index) {
221
+ return Promise.reject(new Error('Method not implemented'));
222
+ }
223
+ async getInitialStateReference() {
224
+ const resp = await this.instance.call(WorldStateMessageType.GET_INITIAL_STATE_REFERENCE, {
225
+ canonical: true
226
+ });
227
+ return new StateReference(treeStateReferenceToSnapshot(resp.state[MerkleTreeId.L1_TO_L2_MESSAGE_TREE]), new PartialStateReference(treeStateReferenceToSnapshot(resp.state[MerkleTreeId.NOTE_HASH_TREE]), treeStateReferenceToSnapshot(resp.state[MerkleTreeId.NULLIFIER_TREE]), treeStateReferenceToSnapshot(resp.state[MerkleTreeId.PUBLIC_DATA_TREE])));
228
+ }
229
+ }
@@ -0,0 +1,33 @@
1
+ import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees';
2
+ import type { WorldStateInstrumentation } from '../instrumentation/instrumentation.js';
3
+ import { WorldStateMessageType, type WorldStateRequest, type WorldStateRequestCategories, type WorldStateResponse } from './message.js';
4
+ export interface NativeWorldStateInstance {
5
+ call<T extends WorldStateMessageType>(messageType: T, body: WorldStateRequest[T] & WorldStateRequestCategories): Promise<WorldStateResponse[T]>;
6
+ }
7
+ /**
8
+ * Strongly-typed interface to access the WorldState class in the native world_state_napi module.
9
+ */
10
+ export declare class NativeWorldState implements NativeWorldStateInstance {
11
+ private instrumentation;
12
+ private log;
13
+ private open;
14
+ private queues;
15
+ private instance;
16
+ /** Creates a new native WorldState instance */
17
+ constructor(dataDir: string, dbMapSizeKb: number, prefilledPublicData: PublicDataTreeLeaf[] | undefined, instrumentation: WorldStateInstrumentation, log?: import("@aztec/foundation/log").Logger);
18
+ /**
19
+ * Sends a message to the native instance and returns the response.
20
+ * @param messageType - The type of message to send
21
+ * @param body - The message body
22
+ * @param responseHandler - A callback accepting the response, executed on the job queue
23
+ * @param errorHandler - A callback called on request error, executed on the job queue
24
+ * @returns The response to the message
25
+ */
26
+ call<T extends WorldStateMessageType>(messageType: T, body: WorldStateRequest[T] & WorldStateRequestCategories, responseHandler?: (response: WorldStateResponse[T]) => WorldStateResponse[T], errorHandler?: (_: string) => void): Promise<WorldStateResponse[T]>;
27
+ /**
28
+ * Stops the native instance.
29
+ */
30
+ close(): Promise<void>;
31
+ private _sendMessage;
32
+ }
33
+ //# sourceMappingURL=native_world_state_instance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native_world_state_instance.d.ts","sourceRoot":"","sources":["../../src/native/native_world_state_instance.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAK9D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAC;AACvF,OAAO,EACL,qBAAqB,EACrB,KAAK,iBAAiB,EACtB,KAAK,2BAA2B,EAChC,KAAK,kBAAkB,EAIxB,MAAM,cAAc,CAAC;AAKtB,MAAM,WAAW,wBAAwB;IACvC,IAAI,CAAC,CAAC,SAAS,qBAAqB,EAClC,WAAW,EAAE,CAAC,EACd,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,2BAA2B,GACvD,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,qBAAa,gBAAiB,YAAW,wBAAwB;IAa7D,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,GAAG;IAbb,OAAO,CAAC,IAAI,CAAQ;IAGpB,OAAO,CAAC,MAAM,CAAyC;IAEvD,OAAO,CAAC,QAAQ,CAA+E;IAE/F,+CAA+C;gBAE7C,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,mBAAmB,kCAA2B,EACtC,eAAe,EAAE,yBAAyB,EAC1C,GAAG,yCAAuC;IA8BpD;;;;;;;OAOG;IACU,IAAI,CAAC,CAAC,SAAS,qBAAqB,EAC/C,WAAW,EAAE,CAAC,EACd,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,2BAA2B,EAExD,eAAe,cAAc,kBAAkB,CAAC,CAAC,CAAC,KAAG,kBAAkB,CAAC,CAAC,CAAa,EACtF,YAAY,OAAO,MAAM,SAAO,GAC/B,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC;IAuDjC;;OAEG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAiBrB,YAAY;CA8D3B"}
@@ -0,0 +1,164 @@
1
+ import { ARCHIVE_HEIGHT, GeneratorIndex, L1_TO_L2_MSG_TREE_HEIGHT, MAX_NULLIFIERS_PER_TX, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NOTE_HASH_TREE_HEIGHT, NULLIFIER_TREE_HEIGHT, PUBLIC_DATA_TREE_HEIGHT } from '@aztec/constants';
2
+ import { createLogger } from '@aztec/foundation/log';
3
+ import { NativeWorldState as BaseNativeWorldState, MsgpackChannel } from '@aztec/native';
4
+ import { MerkleTreeId } from '@aztec/stdlib/trees';
5
+ import assert from 'assert';
6
+ import { cpus } from 'os';
7
+ import { WorldStateMessageType, isWithCanonical, isWithForkId, isWithRevision } from './message.js';
8
+ import { WorldStateOpsQueue } from './world_state_ops_queue.js';
9
+ const MAX_WORLD_STATE_THREADS = +(process.env.HARDWARE_CONCURRENCY || '16');
10
+ /**
11
+ * Strongly-typed interface to access the WorldState class in the native world_state_napi module.
12
+ */ export class NativeWorldState {
13
+ instrumentation;
14
+ log;
15
+ open;
16
+ // We maintain a map of queue to fork
17
+ queues;
18
+ instance;
19
+ /** Creates a new native WorldState instance */ constructor(dataDir, dbMapSizeKb, prefilledPublicData = [], instrumentation, log = createLogger('world-state:database')){
20
+ this.instrumentation = instrumentation;
21
+ this.log = log;
22
+ this.open = true;
23
+ this.queues = new Map();
24
+ const threads = Math.min(cpus().length, MAX_WORLD_STATE_THREADS);
25
+ log.info(`Creating world state data store at directory ${dataDir} with map size ${dbMapSizeKb} KB and ${threads} threads.`);
26
+ const prefilledPublicDataBufferArray = prefilledPublicData.map((d)=>[
27
+ d.slot.toBuffer(),
28
+ d.value.toBuffer()
29
+ ]);
30
+ const ws = new BaseNativeWorldState(dataDir, {
31
+ [MerkleTreeId.NULLIFIER_TREE]: NULLIFIER_TREE_HEIGHT,
32
+ [MerkleTreeId.NOTE_HASH_TREE]: NOTE_HASH_TREE_HEIGHT,
33
+ [MerkleTreeId.PUBLIC_DATA_TREE]: PUBLIC_DATA_TREE_HEIGHT,
34
+ [MerkleTreeId.L1_TO_L2_MESSAGE_TREE]: L1_TO_L2_MSG_TREE_HEIGHT,
35
+ [MerkleTreeId.ARCHIVE]: ARCHIVE_HEIGHT
36
+ }, {
37
+ [MerkleTreeId.NULLIFIER_TREE]: 2 * MAX_NULLIFIERS_PER_TX,
38
+ [MerkleTreeId.PUBLIC_DATA_TREE]: 2 * MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX
39
+ }, prefilledPublicDataBufferArray, GeneratorIndex.BLOCK_HASH, dbMapSizeKb, threads);
40
+ this.instance = new MsgpackChannel(ws);
41
+ // Manually create the queue for the canonical fork
42
+ this.queues.set(0, new WorldStateOpsQueue());
43
+ }
44
+ /**
45
+ * Sends a message to the native instance and returns the response.
46
+ * @param messageType - The type of message to send
47
+ * @param body - The message body
48
+ * @param responseHandler - A callback accepting the response, executed on the job queue
49
+ * @param errorHandler - A callback called on request error, executed on the job queue
50
+ * @returns The response to the message
51
+ */ async call(messageType, body, // allows for the pre-processing of responses on the job queue before being passed back
52
+ responseHandler = (response)=>response, errorHandler = (_)=>{}) {
53
+ // Here we determine which fork the request is being executed against and whether it requires uncommitted data
54
+ // We use the fork Id to select the appropriate request queue and the uncommitted data flag to pass to the queue
55
+ let forkId = -1;
56
+ // We assume it includes uncommitted unless explicitly told otherwise
57
+ let committedOnly = false;
58
+ // Canonical requests ALWAYS go against the canonical fork
59
+ // These include things like block syncs/unwinds etc
60
+ // These requests don't contain a fork ID
61
+ if (isWithCanonical(body)) {
62
+ forkId = 0;
63
+ } else if (isWithForkId(body)) {
64
+ forkId = body.forkId;
65
+ } else if (isWithRevision(body)) {
66
+ forkId = body.revision.forkId;
67
+ committedOnly = body.revision.includeUncommitted === false;
68
+ } else {
69
+ const _ = body;
70
+ throw new Error(`Unable to determine forkId for message=${WorldStateMessageType[messageType]}`);
71
+ }
72
+ // Get the queue or create a new one
73
+ let requestQueue = this.queues.get(forkId);
74
+ if (requestQueue === undefined) {
75
+ requestQueue = new WorldStateOpsQueue();
76
+ this.queues.set(forkId, requestQueue);
77
+ }
78
+ // Enqueue the request and wait for the response
79
+ const response = await requestQueue.execute(async ()=>{
80
+ assert.notEqual(messageType, WorldStateMessageType.CLOSE, 'Use close() to close the native instance');
81
+ assert.equal(this.open, true, 'Native instance is closed');
82
+ let response;
83
+ try {
84
+ response = await this._sendMessage(messageType, body);
85
+ } catch (error) {
86
+ errorHandler(error.message);
87
+ throw error;
88
+ }
89
+ return responseHandler(response);
90
+ }, messageType, committedOnly);
91
+ // If the request was to delete the fork then we clean it up here
92
+ if (messageType === WorldStateMessageType.DELETE_FORK) {
93
+ await requestQueue.stop();
94
+ this.queues.delete(forkId);
95
+ }
96
+ return response;
97
+ }
98
+ /**
99
+ * Stops the native instance.
100
+ */ async close() {
101
+ if (!this.open) {
102
+ return;
103
+ }
104
+ this.open = false;
105
+ const queue = this.queues.get(0);
106
+ await queue.execute(async ()=>{
107
+ await this._sendMessage(WorldStateMessageType.CLOSE, {
108
+ canonical: true
109
+ });
110
+ }, WorldStateMessageType.CLOSE, false);
111
+ await queue.stop();
112
+ }
113
+ async _sendMessage(messageType, body) {
114
+ let logMetadata = {};
115
+ if (body) {
116
+ if ('treeId' in body) {
117
+ logMetadata['treeId'] = MerkleTreeId[body.treeId];
118
+ }
119
+ if ('revision' in body) {
120
+ logMetadata = {
121
+ ...logMetadata,
122
+ ...body.revision
123
+ };
124
+ }
125
+ if ('forkId' in body) {
126
+ logMetadata['forkId'] = body.forkId;
127
+ }
128
+ if ('blockNumber' in body) {
129
+ logMetadata['blockNumber'] = body.blockNumber;
130
+ }
131
+ if ('toBlockNumber' in body) {
132
+ logMetadata['toBlockNumber'] = body.toBlockNumber;
133
+ }
134
+ if ('leafIndex' in body) {
135
+ logMetadata['leafIndex'] = body.leafIndex;
136
+ }
137
+ if ('blockHeaderHash' in body) {
138
+ logMetadata['blockHeaderHash'] = '0x' + body.blockHeaderHash.toString('hex');
139
+ }
140
+ if ('leaves' in body) {
141
+ logMetadata['leavesCount'] = body.leaves.length;
142
+ }
143
+ // sync operation
144
+ if ('paddedNoteHashes' in body) {
145
+ logMetadata['notesCount'] = body.paddedNoteHashes.length;
146
+ logMetadata['nullifiersCount'] = body.paddedNullifiers.length;
147
+ logMetadata['l1ToL2MessagesCount'] = body.paddedL1ToL2Messages.length;
148
+ logMetadata['publicDataWritesCount'] = body.publicDataWrites.length;
149
+ }
150
+ }
151
+ try {
152
+ const { duration, response } = await this.instance.sendMessage(messageType, body);
153
+ this.log.trace(`Call ${WorldStateMessageType[messageType]} took (ms)`, {
154
+ duration,
155
+ ...logMetadata
156
+ });
157
+ this.instrumentation.recordRoundTrip(duration.totalUs, messageType);
158
+ return response;
159
+ } catch (error) {
160
+ this.log.error(`Call ${WorldStateMessageType[messageType]} failed: ${error}`, error, logMetadata);
161
+ throw error;
162
+ }
163
+ }
164
+ }
@@ -0,0 +1,19 @@
1
+ import { WorldStateMessageType } from './message.js';
2
+ export declare const MUTATING_MSG_TYPES: Set<WorldStateMessageType>;
3
+ export declare class WorldStateOpsQueue {
4
+ private requests;
5
+ private inFlightMutatingCount;
6
+ private inFlightCount;
7
+ private stopPromise?;
8
+ private stopResolve?;
9
+ private requestId;
10
+ private ops;
11
+ execute(request: () => Promise<any>, messageType: WorldStateMessageType, committedOnly: boolean): Promise<any>;
12
+ private executeMutating;
13
+ private executeNonMutatingUncommitted;
14
+ private executeNonMutatingCommitted;
15
+ private checkAndEnqueue;
16
+ private sendEnqueuedRequest;
17
+ stop(): Promise<void>;
18
+ }
19
+ //# sourceMappingURL=world_state_ops_queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"world_state_ops_queue.d.ts","sourceRoot":"","sources":["../../src/native/world_state_ops_queue.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAyBrD,eAAO,MAAM,kBAAkB,4BAgB7B,CAAC;AAGH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,qBAAqB,CAAK;IAClC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,WAAW,CAAC,CAAgB;IACpC,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,GAAG,CAAwC;IAI5C,OAAO,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE,WAAW,EAAE,qBAAqB,EAAE,aAAa,EAAE,OAAO;IAyBtG,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,6BAA6B;IAYrC,OAAO,CAAC,2BAA2B;IAUnC,OAAO,CAAC,eAAe;IAyCvB,OAAO,CAAC,mBAAmB;IAgBpB,IAAI;CAiBZ"}
@@ -0,0 +1,146 @@
1
+ import { promiseWithResolvers } from '@aztec/foundation/promise';
2
+ import { WorldStateMessageType } from './message.js';
3
+ // These are the set of message types that implement mutating operations
4
+ // Messages of these types require exclusive access to their given forks
5
+ export const MUTATING_MSG_TYPES = new Set([
6
+ WorldStateMessageType.APPEND_LEAVES,
7
+ WorldStateMessageType.BATCH_INSERT,
8
+ WorldStateMessageType.SEQUENTIAL_INSERT,
9
+ WorldStateMessageType.UPDATE_ARCHIVE,
10
+ WorldStateMessageType.COMMIT,
11
+ WorldStateMessageType.ROLLBACK,
12
+ WorldStateMessageType.SYNC_BLOCK,
13
+ WorldStateMessageType.CREATE_FORK,
14
+ WorldStateMessageType.DELETE_FORK,
15
+ WorldStateMessageType.FINALISE_BLOCKS,
16
+ WorldStateMessageType.UNWIND_BLOCKS,
17
+ WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS,
18
+ WorldStateMessageType.CREATE_CHECKPOINT,
19
+ WorldStateMessageType.COMMIT_CHECKPOINT,
20
+ WorldStateMessageType.REVERT_CHECKPOINT
21
+ ]);
22
+ // This class implements the per-fork operation queue
23
+ export class WorldStateOpsQueue {
24
+ requests = [];
25
+ inFlightMutatingCount = 0;
26
+ inFlightCount = 0;
27
+ stopPromise;
28
+ stopResolve;
29
+ requestId = 0;
30
+ ops = new Map();
31
+ // The primary public api, this is where an operation is queued
32
+ // We return a promise that will ultimately be resolved/rejected with the response/error generated by the 'request' argument
33
+ execute(request, messageType, committedOnly) {
34
+ if (this.stopResolve !== undefined) {
35
+ throw new Error('Unable to send request to world state, queue already stopped');
36
+ }
37
+ const op = {
38
+ requestId: this.requestId++,
39
+ mutating: MUTATING_MSG_TYPES.has(messageType),
40
+ request,
41
+ promise: promiseWithResolvers()
42
+ };
43
+ this.ops.set(op.requestId, op);
44
+ // Perform the appropriate action based upon the queueing rules
45
+ if (op.mutating) {
46
+ this.executeMutating(op);
47
+ } else if (committedOnly === false) {
48
+ this.executeNonMutatingUncommitted(op);
49
+ } else {
50
+ this.executeNonMutatingCommitted(op);
51
+ }
52
+ return op.promise.promise;
53
+ }
54
+ // Mutating requests need exclusive access
55
+ executeMutating(op) {
56
+ // If nothing is in flight then we send the request immediately
57
+ // Otherwise add to the queue
58
+ if (this.inFlightCount === 0) {
59
+ this.sendEnqueuedRequest(op);
60
+ } else {
61
+ this.requests.push(op);
62
+ }
63
+ }
64
+ // Non mutating requests including uncommitted state
65
+ executeNonMutatingUncommitted(op) {
66
+ // If there are no mutating requests in flight and there is nothing queued
67
+ // then send the request immediately
68
+ // If a mutating request is in flight then we must wait
69
+ // If a mutating request is not in flight but something is queued then it must be a mutating request
70
+ if (this.inFlightMutatingCount == 0 && this.requests.length == 0) {
71
+ this.sendEnqueuedRequest(op);
72
+ } else {
73
+ this.requests.push(op);
74
+ }
75
+ }
76
+ executeNonMutatingCommitted(op) {
77
+ // This is a non-mutating request for committed data
78
+ // It can always be sent
79
+ op.request().then(op.promise.resolve, op.promise.reject).finally(()=>{
80
+ this.ops.delete(op.requestId);
81
+ });
82
+ }
83
+ checkAndEnqueue(completedOp) {
84
+ // As request has completed
85
+ // First we decrements the relevant in flight counters
86
+ if (completedOp.mutating) {
87
+ --this.inFlightMutatingCount;
88
+ }
89
+ --this.inFlightCount;
90
+ // If there are still requests in flight then do nothing further
91
+ if (this.inFlightCount != 0) {
92
+ return;
93
+ }
94
+ // No requests in flight, send next queued requests
95
+ // We loop and send:
96
+ // 1 mutating request if it is next in the queue
97
+ // As many non-mutating requests as we encounter until
98
+ // we exhaust the queue or we reach a mutating request
99
+ while(this.requests.length > 0){
100
+ const next = this.requests[0];
101
+ if (next.mutating) {
102
+ if (this.inFlightCount == 0) {
103
+ // send the mutating request
104
+ this.requests.shift();
105
+ this.sendEnqueuedRequest(next);
106
+ }
107
+ break;
108
+ } else {
109
+ // not mutating, send and go round again
110
+ this.requests.shift();
111
+ this.sendEnqueuedRequest(next);
112
+ }
113
+ }
114
+ // If the queue is empty, there is nothing in flight and we have been told to stop, then resolve the stop promise
115
+ if (this.inFlightCount == 0 && this.stopResolve !== undefined) {
116
+ this.stopResolve();
117
+ }
118
+ }
119
+ sendEnqueuedRequest(op) {
120
+ // Here we increment the in flight counts before sending
121
+ ++this.inFlightCount;
122
+ if (op.mutating) {
123
+ ++this.inFlightMutatingCount;
124
+ }
125
+ // Make the request and pass the response/error through to the stored promise
126
+ op.request().then(op.promise.resolve, op.promise.reject).finally(()=>{
127
+ this.checkAndEnqueue(op);
128
+ this.ops.delete(op.requestId);
129
+ });
130
+ }
131
+ stop() {
132
+ // If there is already a stop promise then return it
133
+ if (this.stopPromise) {
134
+ return this.stopPromise;
135
+ }
136
+ // Otherwise create a new one and capture the resolve method
137
+ this.stopPromise = new Promise((resolve)=>{
138
+ this.stopResolve = resolve;
139
+ });
140
+ // If no outstanding requests then immediately resolve the promise
141
+ if (this.requests.length == 0 && this.inFlightCount == 0 && this.stopResolve !== undefined) {
142
+ this.stopResolve();
143
+ }
144
+ return this.stopPromise;
145
+ }
146
+ }
@@ -0,0 +1,23 @@
1
+ import { type ConfigMappingsType } from '@aztec/foundation/config';
2
+ /** World State synchronizer configuration values. */
3
+ export interface WorldStateConfig {
4
+ /** The frequency in which to check. */
5
+ worldStateBlockCheckIntervalMS: number;
6
+ /** Whether to follow only the proven chain. */
7
+ worldStateProvenBlocksOnly: boolean;
8
+ /** Size of the batch for each get-blocks request from the synchronizer to the archiver. */
9
+ worldStateBlockRequestBatchSize?: number;
10
+ /** The map size to be provided to LMDB for each world state tree DB, optional, will inherit from the general dataStoreMapSizeKB if not specified*/
11
+ worldStateDbMapSizeKb?: number;
12
+ /** Optional directory for the world state DB, if unspecified will default to the general data directory */
13
+ worldStateDataDirectory?: string;
14
+ /** The number of historic blocks to maintain */
15
+ worldStateBlockHistory: number;
16
+ }
17
+ export declare const worldStateConfigMappings: ConfigMappingsType<WorldStateConfig>;
18
+ /**
19
+ * Returns the configuration values for the world state synchronizer.
20
+ * @returns The configuration values for the world state synchronizer.
21
+ */
22
+ export declare function getWorldStateConfigFromEnv(): WorldStateConfig;
23
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/synchronizer/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,kBAAkB,EAIxB,MAAM,0BAA0B,CAAC;AAElC,qDAAqD;AACrD,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,8BAA8B,EAAE,MAAM,CAAC;IAEvC,+CAA+C;IAC/C,0BAA0B,EAAE,OAAO,CAAC;IAEpC,2FAA2F;IAC3F,+BAA+B,CAAC,EAAE,MAAM,CAAC;IAEzC,mJAAmJ;IACnJ,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B,2GAA2G;IAC3G,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAEjC,gDAAgD;IAChD,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED,eAAO,MAAM,wBAAwB,EAAE,kBAAkB,CAAC,gBAAgB,CA+BzE,CAAC;AAEF;;;GAGG;AACH,wBAAgB,0BAA0B,IAAI,gBAAgB,CAE7D"}
@@ -0,0 +1,39 @@
1
+ import { booleanConfigHelper, getConfigFromMappings, numberConfigHelper } from '@aztec/foundation/config';
2
+ export const worldStateConfigMappings = {
3
+ worldStateBlockCheckIntervalMS: {
4
+ env: 'WS_BLOCK_CHECK_INTERVAL_MS',
5
+ parseEnv: (val)=>+val,
6
+ defaultValue: 100,
7
+ description: 'The frequency in which to check.'
8
+ },
9
+ worldStateProvenBlocksOnly: {
10
+ env: 'WS_PROVEN_BLOCKS_ONLY',
11
+ description: 'Whether to follow only the proven chain.',
12
+ ...booleanConfigHelper()
13
+ },
14
+ worldStateBlockRequestBatchSize: {
15
+ env: 'WS_BLOCK_REQUEST_BATCH_SIZE',
16
+ parseEnv: (val)=>val ? +val : undefined,
17
+ description: 'Size of the batch for each get-blocks request from the synchronizer to the archiver.'
18
+ },
19
+ worldStateDbMapSizeKb: {
20
+ env: 'WS_DB_MAP_SIZE_KB',
21
+ parseEnv: (val)=>val ? +val : undefined,
22
+ description: 'The maximum possible size of the world state DB'
23
+ },
24
+ worldStateDataDirectory: {
25
+ env: 'WS_DATA_DIRECTORY',
26
+ description: 'Optional directory for the world state database'
27
+ },
28
+ worldStateBlockHistory: {
29
+ env: 'WS_NUM_HISTORIC_BLOCKS',
30
+ description: 'The number of historic blocks to maintain. Values less than 1 mean all history is maintained',
31
+ ...numberConfigHelper(64)
32
+ }
33
+ };
34
+ /**
35
+ * Returns the configuration values for the world state synchronizer.
36
+ * @returns The configuration values for the world state synchronizer.
37
+ */ export function getWorldStateConfigFromEnv() {
38
+ return getConfigFromMappings(worldStateConfigMappings);
39
+ }