@aztec/world-state 0.22.0 → 0.24.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/package.json +7 -7
- package/src/index.ts +3 -0
- package/src/synchronizer/config.ts +27 -0
- package/src/synchronizer/index.ts +2 -0
- package/src/synchronizer/server_world_state_synchronizer.ts +215 -0
- package/src/synchronizer/world_state_synchronizer.ts +73 -0
- package/src/world-state-db/index.ts +3 -0
- package/src/world-state-db/merkle_tree_db.ts +50 -0
- package/src/world-state-db/merkle_tree_operations.ts +178 -0
- package/src/world-state-db/merkle_tree_operations_facade.ts +182 -0
- package/src/world-state-db/merkle_tree_snapshot_operations_facade.ts +157 -0
- package/src/world-state-db/merkle_trees.ts +574 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/world-state",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": "./dest/index.js",
|
|
6
6
|
"typedocOptions": {
|
|
@@ -30,12 +30,12 @@
|
|
|
30
30
|
"rootDir": "./src"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@aztec/circuit-types": "0.
|
|
34
|
-
"@aztec/circuits.js": "0.
|
|
35
|
-
"@aztec/foundation": "0.
|
|
36
|
-
"@aztec/kv-store": "0.
|
|
37
|
-
"@aztec/merkle-tree": "0.
|
|
38
|
-
"@aztec/types": "0.
|
|
33
|
+
"@aztec/circuit-types": "0.24.0",
|
|
34
|
+
"@aztec/circuits.js": "0.24.0",
|
|
35
|
+
"@aztec/foundation": "0.24.0",
|
|
36
|
+
"@aztec/kv-store": "0.24.0",
|
|
37
|
+
"@aztec/merkle-tree": "0.24.0",
|
|
38
|
+
"@aztec/types": "0.24.0",
|
|
39
39
|
"tslib": "^2.4.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* World State synchronizer configuration values.
|
|
3
|
+
*/
|
|
4
|
+
export interface WorldStateConfig {
|
|
5
|
+
/**
|
|
6
|
+
* The frequency in which to check.
|
|
7
|
+
*/
|
|
8
|
+
worldStateBlockCheckIntervalMS: number;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Size of queue of L2 blocks to store.
|
|
12
|
+
*/
|
|
13
|
+
l2QueueSize: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns the configuration values for the world state synchronizer.
|
|
18
|
+
* @returns The configuration values for the world state synchronizer.
|
|
19
|
+
*/
|
|
20
|
+
export function getConfigEnvVars(): WorldStateConfig {
|
|
21
|
+
const { WS_BLOCK_CHECK_INTERVAL_MS, WS_L2_BLOCK_QUEUE_SIZE } = process.env;
|
|
22
|
+
const envVars: WorldStateConfig = {
|
|
23
|
+
worldStateBlockCheckIntervalMS: WS_BLOCK_CHECK_INTERVAL_MS ? +WS_BLOCK_CHECK_INTERVAL_MS : 100,
|
|
24
|
+
l2QueueSize: WS_L2_BLOCK_QUEUE_SIZE ? +WS_L2_BLOCK_QUEUE_SIZE : 1000,
|
|
25
|
+
};
|
|
26
|
+
return envVars;
|
|
27
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { L2Block, L2BlockDownloader, L2BlockSource } from '@aztec/circuit-types';
|
|
2
|
+
import { L2BlockHandledStats } from '@aztec/circuit-types/stats';
|
|
3
|
+
import { SerialQueue } from '@aztec/foundation/fifo';
|
|
4
|
+
import { createDebugLogger } from '@aztec/foundation/log';
|
|
5
|
+
import { elapsed } from '@aztec/foundation/timer';
|
|
6
|
+
import { AztecKVStore, AztecSingleton } from '@aztec/kv-store';
|
|
7
|
+
|
|
8
|
+
import { HandleL2BlockResult, MerkleTreeOperations, MerkleTrees } from '../world-state-db/index.js';
|
|
9
|
+
import { MerkleTreeOperationsFacade } from '../world-state-db/merkle_tree_operations_facade.js';
|
|
10
|
+
import { MerkleTreeSnapshotOperationsFacade } from '../world-state-db/merkle_tree_snapshot_operations_facade.js';
|
|
11
|
+
import { WorldStateConfig } from './config.js';
|
|
12
|
+
import { WorldStateRunningState, WorldStateStatus, WorldStateSynchronizer } from './world_state_synchronizer.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Synchronizes the world state with the L2 blocks from a L2BlockSource.
|
|
16
|
+
* The synchronizer will download the L2 blocks from the L2BlockSource and insert the new commitments into the merkle
|
|
17
|
+
* tree.
|
|
18
|
+
*/
|
|
19
|
+
export class ServerWorldStateSynchronizer implements WorldStateSynchronizer {
|
|
20
|
+
private latestBlockNumberAtStart = 0;
|
|
21
|
+
|
|
22
|
+
private l2BlockDownloader: L2BlockDownloader;
|
|
23
|
+
private syncPromise: Promise<void> = Promise.resolve();
|
|
24
|
+
private syncResolve?: () => void = undefined;
|
|
25
|
+
private jobQueue = new SerialQueue();
|
|
26
|
+
private stopping = false;
|
|
27
|
+
private runningPromise: Promise<void> = Promise.resolve();
|
|
28
|
+
private currentState: WorldStateRunningState = WorldStateRunningState.IDLE;
|
|
29
|
+
private blockNumber: AztecSingleton<number>;
|
|
30
|
+
|
|
31
|
+
constructor(
|
|
32
|
+
store: AztecKVStore,
|
|
33
|
+
private merkleTreeDb: MerkleTrees,
|
|
34
|
+
private l2BlockSource: L2BlockSource,
|
|
35
|
+
config: WorldStateConfig,
|
|
36
|
+
private log = createDebugLogger('aztec:world_state'),
|
|
37
|
+
) {
|
|
38
|
+
this.blockNumber = store.openSingleton('world_state_synch_last_block_number');
|
|
39
|
+
this.l2BlockDownloader = new L2BlockDownloader(
|
|
40
|
+
l2BlockSource,
|
|
41
|
+
config.l2QueueSize,
|
|
42
|
+
config.worldStateBlockCheckIntervalMS,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public getLatest(): MerkleTreeOperations {
|
|
47
|
+
return new MerkleTreeOperationsFacade(this.merkleTreeDb, true);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public getCommitted(): MerkleTreeOperations {
|
|
51
|
+
return new MerkleTreeOperationsFacade(this.merkleTreeDb, false);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public getSnapshot(blockNumber: number): MerkleTreeOperations {
|
|
55
|
+
return new MerkleTreeSnapshotOperationsFacade(this.merkleTreeDb, blockNumber);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public async start() {
|
|
59
|
+
if (this.currentState === WorldStateRunningState.STOPPED) {
|
|
60
|
+
throw new Error('Synchronizer already stopped');
|
|
61
|
+
}
|
|
62
|
+
if (this.currentState !== WorldStateRunningState.IDLE) {
|
|
63
|
+
return this.syncPromise;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// get the current latest block number
|
|
67
|
+
this.latestBlockNumberAtStart = await this.l2BlockSource.getBlockNumber();
|
|
68
|
+
|
|
69
|
+
const blockToDownloadFrom = this.currentL2BlockNum + 1;
|
|
70
|
+
|
|
71
|
+
// if there are blocks to be retrieved, go to a synching state
|
|
72
|
+
if (blockToDownloadFrom <= this.latestBlockNumberAtStart) {
|
|
73
|
+
this.setCurrentState(WorldStateRunningState.SYNCHING);
|
|
74
|
+
this.syncPromise = new Promise(resolve => {
|
|
75
|
+
this.syncResolve = resolve;
|
|
76
|
+
});
|
|
77
|
+
this.log(`Starting sync from ${blockToDownloadFrom}, latest block ${this.latestBlockNumberAtStart}`);
|
|
78
|
+
} else {
|
|
79
|
+
// if no blocks to be retrieved, go straight to running
|
|
80
|
+
this.setCurrentState(WorldStateRunningState.RUNNING);
|
|
81
|
+
this.syncPromise = Promise.resolve();
|
|
82
|
+
this.log(`Next block ${blockToDownloadFrom} already beyond latest block at ${this.latestBlockNumberAtStart}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// start looking for further blocks
|
|
86
|
+
const blockProcess = async () => {
|
|
87
|
+
while (!this.stopping) {
|
|
88
|
+
await this.jobQueue.put(() => this.collectAndProcessBlocks());
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
this.jobQueue.start();
|
|
92
|
+
this.runningPromise = blockProcess();
|
|
93
|
+
this.l2BlockDownloader.start(blockToDownloadFrom);
|
|
94
|
+
this.log(`Started block downloader from block ${blockToDownloadFrom}`);
|
|
95
|
+
return this.syncPromise;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public async stop() {
|
|
99
|
+
this.log('Stopping world state...');
|
|
100
|
+
this.stopping = true;
|
|
101
|
+
await this.l2BlockDownloader.stop();
|
|
102
|
+
this.log('Cancelling job queue...');
|
|
103
|
+
await this.jobQueue.cancel();
|
|
104
|
+
this.log('Stopping Merkle trees');
|
|
105
|
+
await this.merkleTreeDb.stop();
|
|
106
|
+
this.log('Awaiting promise');
|
|
107
|
+
await this.runningPromise;
|
|
108
|
+
this.setCurrentState(WorldStateRunningState.STOPPED);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private get currentL2BlockNum(): number {
|
|
112
|
+
return this.blockNumber.get() ?? 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public status(): Promise<WorldStateStatus> {
|
|
116
|
+
const status = {
|
|
117
|
+
syncedToL2Block: this.currentL2BlockNum,
|
|
118
|
+
state: this.currentState,
|
|
119
|
+
} as WorldStateStatus;
|
|
120
|
+
return Promise.resolve(status);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Forces an immediate sync
|
|
125
|
+
* @param minBlockNumber - The minimum block number that we must sync to
|
|
126
|
+
* @returns A promise that resolves with the block number the world state was synced to
|
|
127
|
+
*/
|
|
128
|
+
public async syncImmediate(minBlockNumber?: number): Promise<number> {
|
|
129
|
+
if (this.currentState !== WorldStateRunningState.RUNNING) {
|
|
130
|
+
throw new Error(`World State is not running, unable to perform sync`);
|
|
131
|
+
}
|
|
132
|
+
// If we have been given a block number to sync to and we have reached that number
|
|
133
|
+
// then return.
|
|
134
|
+
if (minBlockNumber !== undefined && minBlockNumber <= this.currentL2BlockNum) {
|
|
135
|
+
return this.currentL2BlockNum;
|
|
136
|
+
}
|
|
137
|
+
const blockToSyncTo = minBlockNumber === undefined ? 'latest' : `${minBlockNumber}`;
|
|
138
|
+
this.log(`World State at block ${this.currentL2BlockNum}, told to sync to block ${blockToSyncTo}...`);
|
|
139
|
+
// ensure any outstanding block updates are completed first.
|
|
140
|
+
await this.jobQueue.syncPoint();
|
|
141
|
+
while (true) {
|
|
142
|
+
// Check the block number again
|
|
143
|
+
if (minBlockNumber !== undefined && minBlockNumber <= this.currentL2BlockNum) {
|
|
144
|
+
return this.currentL2BlockNum;
|
|
145
|
+
}
|
|
146
|
+
// Poll for more blocks
|
|
147
|
+
const numBlocks = await this.l2BlockDownloader.pollImmediate();
|
|
148
|
+
this.log(`Block download immediate poll yielded ${numBlocks} blocks`);
|
|
149
|
+
if (numBlocks) {
|
|
150
|
+
// More blocks were received, process them and go round again
|
|
151
|
+
await this.jobQueue.put(() => this.collectAndProcessBlocks());
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
// No blocks are available, if we have been given a block number then we can't achieve it
|
|
155
|
+
if (minBlockNumber !== undefined) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`Unable to sync to block number ${minBlockNumber}, currently synced to block ${this.currentL2BlockNum}`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return this.currentL2BlockNum;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Checks for the availability of new blocks and processes them.
|
|
166
|
+
*/
|
|
167
|
+
private async collectAndProcessBlocks() {
|
|
168
|
+
// This request for blocks will timeout after 1 second if no blocks are received
|
|
169
|
+
const blocks = await this.l2BlockDownloader.getBlocks(1);
|
|
170
|
+
await this.handleL2Blocks(blocks);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Handles a list of L2 blocks (i.e. Inserts the new commitments into the merkle tree).
|
|
175
|
+
* @param l2Blocks - The L2 blocks to handle.
|
|
176
|
+
* @returns Whether the block handled was produced by this same node.
|
|
177
|
+
*/
|
|
178
|
+
private async handleL2Blocks(l2Blocks: L2Block[]) {
|
|
179
|
+
for (const l2Block of l2Blocks) {
|
|
180
|
+
const [duration, result] = await elapsed(() => this.handleL2Block(l2Block));
|
|
181
|
+
this.log(`Handled new L2 block`, {
|
|
182
|
+
eventName: 'l2-block-handled',
|
|
183
|
+
duration,
|
|
184
|
+
isBlockOurs: result.isBlockOurs,
|
|
185
|
+
...l2Block.getStats(),
|
|
186
|
+
} satisfies L2BlockHandledStats);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Handles a single L2 block (i.e. Inserts the new commitments into the merkle tree).
|
|
192
|
+
* @param l2Block - The L2 block to handle.
|
|
193
|
+
*/
|
|
194
|
+
private async handleL2Block(l2Block: L2Block): Promise<HandleL2BlockResult> {
|
|
195
|
+
const result = await this.merkleTreeDb.handleL2Block(l2Block);
|
|
196
|
+
await this.blockNumber.set(l2Block.number);
|
|
197
|
+
|
|
198
|
+
if (this.currentState === WorldStateRunningState.SYNCHING && l2Block.number >= this.latestBlockNumberAtStart) {
|
|
199
|
+
this.setCurrentState(WorldStateRunningState.RUNNING);
|
|
200
|
+
if (this.syncResolve !== undefined) {
|
|
201
|
+
this.syncResolve();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Method to set the value of the current state.
|
|
209
|
+
* @param newState - New state value.
|
|
210
|
+
*/
|
|
211
|
+
private setCurrentState(newState: WorldStateRunningState) {
|
|
212
|
+
this.currentState = newState;
|
|
213
|
+
this.log(`Moved to state ${WorldStateRunningState[this.currentState]}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { MerkleTreeOperations } from '../world-state-db/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Defines the possible states of the world state synchronizer.
|
|
5
|
+
*/
|
|
6
|
+
export enum WorldStateRunningState {
|
|
7
|
+
IDLE,
|
|
8
|
+
SYNCHING,
|
|
9
|
+
RUNNING,
|
|
10
|
+
STOPPED,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Defines the status of the world state synchronizer.
|
|
15
|
+
*/
|
|
16
|
+
export interface WorldStateStatus {
|
|
17
|
+
/**
|
|
18
|
+
* The current state of the world state synchronizer.
|
|
19
|
+
*/
|
|
20
|
+
state: WorldStateRunningState;
|
|
21
|
+
/**
|
|
22
|
+
* The block number that the world state synchronizer is synced to.
|
|
23
|
+
*/
|
|
24
|
+
syncedToL2Block: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Defines the interface for a world state synchronizer.
|
|
29
|
+
*/
|
|
30
|
+
export interface WorldStateSynchronizer {
|
|
31
|
+
/**
|
|
32
|
+
* Starts the synchronizer.
|
|
33
|
+
* @returns A promise that resolves once the initial sync is completed.
|
|
34
|
+
*/
|
|
35
|
+
start(): void;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns the current status of the synchronizer.
|
|
39
|
+
* @returns The current status of the synchronizer.
|
|
40
|
+
*/
|
|
41
|
+
status(): Promise<WorldStateStatus>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Stops the synchronizer.
|
|
45
|
+
*/
|
|
46
|
+
stop(): Promise<void>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Forces an immediate sync to an optionally provided minimum block number
|
|
50
|
+
* @param minBlockNumber - The minimum block number that we must sync to
|
|
51
|
+
* @returns A promise that resolves with the block number the world state was synced to
|
|
52
|
+
*/
|
|
53
|
+
syncImmediate(minBlockNumber?: number): Promise<number>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Returns an instance of MerkleTreeOperations that will include uncommitted data.
|
|
57
|
+
* @returns An instance of MerkleTreeOperations that will include uncommitted data.
|
|
58
|
+
*/
|
|
59
|
+
getLatest(): MerkleTreeOperations;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns an instance of MerkleTreeOperations that will not include uncommitted data.
|
|
63
|
+
* @returns An instance of MerkleTreeOperations that will not include uncommitted data.
|
|
64
|
+
*/
|
|
65
|
+
getCommitted(): MerkleTreeOperations;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Returns a readonly instance of MerkleTreeOperations where the state is as it was at the given block number
|
|
69
|
+
* @param block - The block number to look at
|
|
70
|
+
* @returns An instance of MerkleTreeOperations
|
|
71
|
+
*/
|
|
72
|
+
getSnapshot(block: number): MerkleTreeOperations;
|
|
73
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { MAX_NEW_NULLIFIERS_PER_TX, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX } from '@aztec/circuits.js';
|
|
2
|
+
import { IndexedTreeSnapshot, TreeSnapshot } from '@aztec/merkle-tree';
|
|
3
|
+
|
|
4
|
+
import { MerkleTreeOperations } from './merkle_tree_operations.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @remarks Short explanation:
|
|
9
|
+
* The nullifier tree must be initially padded as the pre-populated 0 index prevents efficient subtree insertion.
|
|
10
|
+
* Padding with some values solves this issue.
|
|
11
|
+
*
|
|
12
|
+
* @remarks Thorough explanation:
|
|
13
|
+
* There needs to be an initial (0,0,0) leaf in the tree, so that when we insert the first 'proper' leaf, we can
|
|
14
|
+
* prove that any value greater than 0 doesn't exist in the tree yet. We prefill/pad the tree with "the number of
|
|
15
|
+
* leaves that are added by one block" so that the first 'proper' block can insert a full subtree.
|
|
16
|
+
*
|
|
17
|
+
* Without this padding, there would be a leaf (0,0,0) at leaf index 0, making it really difficult to insert e.g.
|
|
18
|
+
* 1024 leaves for the first block, because there's only neat space for 1023 leaves after 0. By padding with 1023
|
|
19
|
+
* more leaves, we can then insert the first block of 1024 leaves into indices 1024:2047.
|
|
20
|
+
*/
|
|
21
|
+
export const INITIAL_NULLIFIER_TREE_SIZE = 2 * MAX_NEW_NULLIFIERS_PER_TX;
|
|
22
|
+
|
|
23
|
+
export const INITIAL_PUBLIC_DATA_TREE_SIZE = 2 * MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Adds a last boolean flag in each function on the type.
|
|
27
|
+
*/
|
|
28
|
+
type WithIncludeUncommitted<F> = F extends (...args: [...infer Rest]) => infer Return
|
|
29
|
+
? (...args: [...Rest, boolean]) => Return
|
|
30
|
+
: F;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Defines the names of the setters on Merkle Trees.
|
|
34
|
+
*/
|
|
35
|
+
type MerkleTreeSetters = 'appendLeaves' | 'updateLeaf' | 'commit' | 'rollback' | 'handleL2Block' | 'batchInsert';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Defines the interface for operations on a set of Merkle Trees configuring whether to return committed or uncommitted data.
|
|
39
|
+
*/
|
|
40
|
+
export type MerkleTreeDb = {
|
|
41
|
+
[Property in keyof MerkleTreeOperations as Exclude<Property, MerkleTreeSetters>]: WithIncludeUncommitted<
|
|
42
|
+
MerkleTreeOperations[Property]
|
|
43
|
+
>;
|
|
44
|
+
} & Pick<MerkleTreeOperations, MerkleTreeSetters> & {
|
|
45
|
+
/**
|
|
46
|
+
* Returns a snapshot of the current state of the trees.
|
|
47
|
+
* @param block - The block number to take the snapshot at.
|
|
48
|
+
*/
|
|
49
|
+
getSnapshot(block: number): Promise<ReadonlyArray<TreeSnapshot | IndexedTreeSnapshot>>;
|
|
50
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { L2Block, MerkleTreeId, SiblingPath } from '@aztec/circuit-types';
|
|
2
|
+
import { Header, NullifierLeafPreimage, StateReference } from '@aztec/circuits.js';
|
|
3
|
+
import { createDebugLogger } from '@aztec/foundation/log';
|
|
4
|
+
import { IndexedTreeLeafPreimage } from '@aztec/foundation/trees';
|
|
5
|
+
import { BatchInsertionResult } from '@aztec/merkle-tree';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Type alias for the nullifier tree ID.
|
|
9
|
+
*/
|
|
10
|
+
export type IndexedTreeId = MerkleTreeId.NULLIFIER_TREE | MerkleTreeId.PUBLIC_DATA_TREE;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Defines tree information.
|
|
14
|
+
*/
|
|
15
|
+
export interface TreeInfo {
|
|
16
|
+
/**
|
|
17
|
+
* The tree ID.
|
|
18
|
+
*/
|
|
19
|
+
treeId: MerkleTreeId;
|
|
20
|
+
/**
|
|
21
|
+
* The tree root.
|
|
22
|
+
*/
|
|
23
|
+
root: Buffer;
|
|
24
|
+
/**
|
|
25
|
+
* The number of leaves in the tree.
|
|
26
|
+
*/
|
|
27
|
+
size: bigint;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The depth of the tree.
|
|
31
|
+
*/
|
|
32
|
+
depth: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Defines the interface for operations on a set of Merkle Trees.
|
|
37
|
+
*/
|
|
38
|
+
export interface MerkleTreeOperations {
|
|
39
|
+
/**
|
|
40
|
+
* Appends leaves to a given tree.
|
|
41
|
+
* @param treeId - The tree to be updated.
|
|
42
|
+
* @param leaves - The set of leaves to be appended.
|
|
43
|
+
*/
|
|
44
|
+
appendLeaves(treeId: MerkleTreeId, leaves: Buffer[]): Promise<void>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns information about the given tree.
|
|
48
|
+
* @param treeId - The tree to be queried.
|
|
49
|
+
*/
|
|
50
|
+
getTreeInfo(treeId: MerkleTreeId): Promise<TreeInfo>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Gets the current state reference.
|
|
54
|
+
*/
|
|
55
|
+
getStateReference(): Promise<StateReference>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Builds the initial header.
|
|
59
|
+
*/
|
|
60
|
+
buildInitialHeader(): Promise<Header>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Gets sibling path for a leaf.
|
|
64
|
+
* @param treeId - The tree to be queried for a sibling path.
|
|
65
|
+
* @param index - The index of the leaf for which a sibling path should be returned.
|
|
66
|
+
*/
|
|
67
|
+
getSiblingPath<N extends number>(treeId: MerkleTreeId, index: bigint): Promise<SiblingPath<N>>;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Returns the previous index for a given value in an indexed tree.
|
|
71
|
+
* @param treeId - The tree for which the previous value index is required.
|
|
72
|
+
* @param value - The value to be queried.
|
|
73
|
+
*/
|
|
74
|
+
getPreviousValueIndex(
|
|
75
|
+
treeId: IndexedTreeId,
|
|
76
|
+
value: bigint,
|
|
77
|
+
): Promise<
|
|
78
|
+
| {
|
|
79
|
+
/**
|
|
80
|
+
* The index of the found leaf.
|
|
81
|
+
*/
|
|
82
|
+
index: bigint;
|
|
83
|
+
/**
|
|
84
|
+
* A flag indicating if the corresponding leaf's value is equal to `newValue`.
|
|
85
|
+
*/
|
|
86
|
+
alreadyPresent: boolean;
|
|
87
|
+
}
|
|
88
|
+
| undefined
|
|
89
|
+
>;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Returns the data at a specific leaf.
|
|
93
|
+
* @param treeId - The tree for which leaf data should be returned.
|
|
94
|
+
* @param index - The index of the leaf required.
|
|
95
|
+
*/
|
|
96
|
+
getLeafPreimage(treeId: IndexedTreeId, index: bigint): Promise<IndexedTreeLeafPreimage | undefined>;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Update the leaf data at the given index.
|
|
100
|
+
* @param treeId - The tree for which leaf data should be edited.
|
|
101
|
+
* @param leaf - The updated leaf value.
|
|
102
|
+
* @param index - The index of the leaf to be updated.
|
|
103
|
+
*/
|
|
104
|
+
updateLeaf(treeId: IndexedTreeId, leaf: NullifierLeafPreimage | Buffer, index: bigint): Promise<void>;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Returns the index containing a leaf value.
|
|
108
|
+
* @param treeId - The tree for which the index should be returned.
|
|
109
|
+
* @param value - The value to search for in the tree.
|
|
110
|
+
*/
|
|
111
|
+
findLeafIndex(treeId: MerkleTreeId, value: Buffer): Promise<bigint | undefined>;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Gets the value for a leaf in the tree.
|
|
115
|
+
* @param treeId - The tree for which the index should be returned.
|
|
116
|
+
* @param index - The index of the leaf.
|
|
117
|
+
*/
|
|
118
|
+
getLeafValue(treeId: MerkleTreeId, index: bigint): Promise<Buffer | undefined>;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Inserts the block hash into the archive.
|
|
122
|
+
* This includes all of the current roots of all of the data trees and the current blocks global vars.
|
|
123
|
+
* @param header - The header to insert into the archive.
|
|
124
|
+
*/
|
|
125
|
+
updateArchive(header: Header): Promise<void>;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Batch insert multiple leaves into the tree.
|
|
129
|
+
* @param leaves - Leaves to insert into the tree.
|
|
130
|
+
* @param treeId - The tree on which to insert.
|
|
131
|
+
* @param subtreeHeight - Height of the subtree.
|
|
132
|
+
* @returns The witness data for the leaves to be updated when inserting the new ones.
|
|
133
|
+
*/
|
|
134
|
+
batchInsert<TreeHeight extends number, SubtreeSiblingPathHeight extends number>(
|
|
135
|
+
treeId: MerkleTreeId,
|
|
136
|
+
leaves: Buffer[],
|
|
137
|
+
subtreeHeight: number,
|
|
138
|
+
): Promise<BatchInsertionResult<TreeHeight, SubtreeSiblingPathHeight>>;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Handles a single L2 block (i.e. Inserts the new commitments into the merkle tree).
|
|
142
|
+
* @param block - The L2 block to handle.
|
|
143
|
+
*/
|
|
144
|
+
handleL2Block(block: L2Block): Promise<HandleL2BlockResult>;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Commits pending changes to the underlying store.
|
|
148
|
+
*/
|
|
149
|
+
commit(): Promise<void>;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Rolls back pending changes.
|
|
153
|
+
*/
|
|
154
|
+
rollback(): Promise<void>;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Return type for handleL2Block */
|
|
158
|
+
export type HandleL2BlockResult = {
|
|
159
|
+
/** Whether the block processed was emitted by our sequencer */ isBlockOurs: boolean;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Outputs a tree leaves using for debugging purposes.
|
|
164
|
+
*/
|
|
165
|
+
export async function inspectTree(
|
|
166
|
+
db: MerkleTreeOperations,
|
|
167
|
+
treeId: MerkleTreeId,
|
|
168
|
+
log = createDebugLogger('aztec:inspect-tree'),
|
|
169
|
+
) {
|
|
170
|
+
const info = await db.getTreeInfo(treeId);
|
|
171
|
+
const output = [`Tree id=${treeId} size=${info.size} root=0x${info.root.toString('hex')}`];
|
|
172
|
+
for (let i = 0; i < info.size; i++) {
|
|
173
|
+
output.push(
|
|
174
|
+
` Leaf ${i}: ${await db.getLeafValue(treeId, BigInt(i)).then(x => x?.toString('hex') ?? '[undefined]')}`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
log(output.join('\n'));
|
|
178
|
+
}
|