@aztec/merkle-tree 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 +41 -0
- package/dest/hasher_with_stats.d.ts +22 -0
- package/dest/hasher_with_stats.d.ts.map +1 -0
- package/dest/hasher_with_stats.js +40 -0
- package/dest/index.d.ts +20 -0
- package/dest/index.d.ts.map +1 -0
- package/dest/index.js +19 -0
- package/dest/interfaces/append_only_tree.d.ts +16 -0
- package/dest/interfaces/append_only_tree.d.ts.map +1 -0
- package/dest/interfaces/append_only_tree.js +3 -0
- package/dest/interfaces/indexed_tree.d.ts +69 -0
- package/dest/interfaces/indexed_tree.d.ts.map +1 -0
- package/dest/interfaces/indexed_tree.js +3 -0
- package/dest/interfaces/merkle_tree.d.ts +64 -0
- package/dest/interfaces/merkle_tree.d.ts.map +1 -0
- package/dest/interfaces/merkle_tree.js +3 -0
- package/dest/interfaces/update_only_tree.d.ts +17 -0
- package/dest/interfaces/update_only_tree.d.ts.map +1 -0
- package/dest/interfaces/update_only_tree.js +3 -0
- package/dest/load_tree.d.ts +16 -0
- package/dest/load_tree.d.ts.map +1 -0
- package/dest/load_tree.js +13 -0
- package/dest/new_tree.d.ts +16 -0
- package/dest/new_tree.d.ts.map +1 -0
- package/dest/new_tree.js +14 -0
- package/dest/pedersen.d.ts +13 -0
- package/dest/pedersen.d.ts.map +1 -0
- package/dest/pedersen.js +24 -0
- package/dest/poseidon.d.ts +13 -0
- package/dest/poseidon.d.ts.map +1 -0
- package/dest/poseidon.js +24 -0
- package/dest/sha_256.d.ts +22 -0
- package/dest/sha_256.d.ts.map +1 -0
- package/dest/sha_256.js +44 -0
- package/dest/snapshots/append_only_snapshot.d.ts +32 -0
- package/dest/snapshots/append_only_snapshot.d.ts.map +1 -0
- package/dest/snapshots/append_only_snapshot.js +216 -0
- package/dest/snapshots/base_full_snapshot.d.ts +66 -0
- package/dest/snapshots/base_full_snapshot.d.ts.map +1 -0
- package/dest/snapshots/base_full_snapshot.js +201 -0
- package/dest/snapshots/full_snapshot.d.ts +27 -0
- package/dest/snapshots/full_snapshot.d.ts.map +1 -0
- package/dest/snapshots/full_snapshot.js +23 -0
- package/dest/snapshots/indexed_tree_snapshot.d.ts +16 -0
- package/dest/snapshots/indexed_tree_snapshot.d.ts.map +1 -0
- package/dest/snapshots/indexed_tree_snapshot.js +79 -0
- package/dest/snapshots/snapshot_builder.d.ts +84 -0
- package/dest/snapshots/snapshot_builder.d.ts.map +1 -0
- package/dest/snapshots/snapshot_builder.js +1 -0
- package/dest/snapshots/snapshot_builder_test_suite.d.ts +6 -0
- package/dest/snapshots/snapshot_builder_test_suite.d.ts.map +1 -0
- package/dest/snapshots/snapshot_builder_test_suite.js +167 -0
- package/dest/sparse_tree/sparse_tree.d.ts +21 -0
- package/dest/sparse_tree/sparse_tree.d.ts.map +1 -0
- package/dest/sparse_tree/sparse_tree.js +44 -0
- package/dest/standard_indexed_tree/standard_indexed_tree.d.ts +291 -0
- package/dest/standard_indexed_tree/standard_indexed_tree.d.ts.map +1 -0
- package/dest/standard_indexed_tree/standard_indexed_tree.js +478 -0
- package/dest/standard_indexed_tree/test/standard_indexed_tree_with_append.d.ts +25 -0
- package/dest/standard_indexed_tree/test/standard_indexed_tree_with_append.d.ts.map +1 -0
- package/dest/standard_indexed_tree/test/standard_indexed_tree_with_append.js +56 -0
- package/dest/standard_tree/standard_tree.d.ts +23 -0
- package/dest/standard_tree/standard_tree.d.ts.map +1 -0
- package/dest/standard_tree/standard_tree.js +47 -0
- package/dest/tree_base.d.ts +158 -0
- package/dest/tree_base.d.ts.map +1 -0
- package/dest/tree_base.js +290 -0
- package/dest/unbalanced_tree.d.ts +109 -0
- package/dest/unbalanced_tree.d.ts.map +1 -0
- package/dest/unbalanced_tree.js +208 -0
- package/package.json +85 -0
- package/src/hasher_with_stats.ts +51 -0
- package/src/index.ts +19 -0
- package/src/interfaces/append_only_tree.ts +17 -0
- package/src/interfaces/indexed_tree.ts +83 -0
- package/src/interfaces/merkle_tree.ts +70 -0
- package/src/interfaces/update_only_tree.ts +18 -0
- package/src/load_tree.ts +33 -0
- package/src/new_tree.ts +29 -0
- package/src/pedersen.ts +27 -0
- package/src/poseidon.ts +27 -0
- package/src/sha_256.ts +49 -0
- package/src/snapshots/append_only_snapshot.ts +278 -0
- package/src/snapshots/base_full_snapshot.ts +222 -0
- package/src/snapshots/full_snapshot.ts +33 -0
- package/src/snapshots/indexed_tree_snapshot.ts +108 -0
- package/src/snapshots/snapshot_builder.ts +92 -0
- package/src/snapshots/snapshot_builder_test_suite.ts +226 -0
- package/src/sparse_tree/sparse_tree.ts +56 -0
- package/src/standard_indexed_tree/standard_indexed_tree.ts +640 -0
- package/src/standard_indexed_tree/test/standard_indexed_tree_with_append.ts +81 -0
- package/src/standard_tree/standard_tree.ts +60 -0
- package/src/tree_base.ts +359 -0
- package/src/unbalanced_tree.ts +239 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import { toBufferBE } from '@aztec/foundation/bigint-buffer';
|
|
2
|
+
import { Timer } from '@aztec/foundation/timer';
|
|
3
|
+
import { SiblingPath } from '@aztec/foundation/trees';
|
|
4
|
+
import { IndexedTreeSnapshotBuilder } from '../snapshots/indexed_tree_snapshot.js';
|
|
5
|
+
import { TreeBase } from '../tree_base.js';
|
|
6
|
+
export const buildDbKeyForPreimage = (name, index)=>{
|
|
7
|
+
return `${name}:leaf_by_index:${toBufferBE(index, 32).toString('hex')}`;
|
|
8
|
+
};
|
|
9
|
+
export const buildDbKeyForLeafIndex = (name, key)=>{
|
|
10
|
+
return `${name}:leaf_index_by_leaf_key:${toBufferBE(key, 32).toString('hex')}`;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Pre-compute empty witness.
|
|
14
|
+
* @param treeHeight - Height of tree for sibling path.
|
|
15
|
+
* @returns An empty witness.
|
|
16
|
+
*/ function getEmptyLowLeafWitness(treeHeight, leafPreimageFactory) {
|
|
17
|
+
return {
|
|
18
|
+
leafPreimage: leafPreimageFactory.empty(),
|
|
19
|
+
index: 0n,
|
|
20
|
+
siblingPath: new SiblingPath(treeHeight, Array(treeHeight).fill(toBufferBE(0n, 32)))
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export const noopDeserializer = {
|
|
24
|
+
fromBuffer: (buf)=>buf
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Standard implementation of an indexed tree.
|
|
28
|
+
*/ export class StandardIndexedTree extends TreeBase {
|
|
29
|
+
leafPreimageFactory;
|
|
30
|
+
leafFactory;
|
|
31
|
+
#snapshotBuilder;
|
|
32
|
+
cachedLeafPreimages;
|
|
33
|
+
leaves;
|
|
34
|
+
leafIndex;
|
|
35
|
+
constructor(store, hasher, name, depth, size = 0n, leafPreimageFactory, leafFactory, root){
|
|
36
|
+
super(store, hasher, name, depth, size, noopDeserializer, root), this.leafPreimageFactory = leafPreimageFactory, this.leafFactory = leafFactory, this.#snapshotBuilder = new IndexedTreeSnapshotBuilder(this.store, this, this.leafPreimageFactory), this.cachedLeafPreimages = {};
|
|
37
|
+
this.leaves = store.openMap(`tree_${name}_leaves`);
|
|
38
|
+
this.leafIndex = store.openMap(`tree_${name}_leaf_index`);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Appends the given leaves to the tree.
|
|
42
|
+
* @param _leaves - The leaves to append.
|
|
43
|
+
* @returns Empty promise.
|
|
44
|
+
* @remarks Use batchInsert method instead.
|
|
45
|
+
*/ appendLeaves(_leaves) {
|
|
46
|
+
throw new Error('Not implemented');
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Commits the changes to the database.
|
|
50
|
+
* @returns Empty promise.
|
|
51
|
+
*/ async commit() {
|
|
52
|
+
await super.commit();
|
|
53
|
+
await this.commitLeaves();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Rolls back the not-yet-committed changes.
|
|
57
|
+
* @returns Empty promise.
|
|
58
|
+
*/ async rollback() {
|
|
59
|
+
await super.rollback();
|
|
60
|
+
this.clearCachedLeaves();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Gets the value of the leaf at the given index.
|
|
64
|
+
* @param index - Index of the leaf of which to obtain the value.
|
|
65
|
+
* @param includeUncommitted - Indicates whether to include uncommitted leaves in the computation.
|
|
66
|
+
* @returns The value of the leaf at the given index or undefined if the leaf is empty.
|
|
67
|
+
*/ getLeafValue(index, includeUncommitted) {
|
|
68
|
+
const preimage = this.getLatestLeafPreimageCopy(index, includeUncommitted);
|
|
69
|
+
return preimage && preimage.toBuffer();
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Finds the index of the largest leaf whose value is less than or equal to the provided value.
|
|
73
|
+
* @param newKey - The new key to be inserted into the tree.
|
|
74
|
+
* @param includeUncommitted - If true, the uncommitted changes are included in the search.
|
|
75
|
+
* @returns The found leaf index and a flag indicating if the corresponding leaf's value is equal to `newValue`.
|
|
76
|
+
*/ findIndexOfPreviousKey(newKey, includeUncommitted) {
|
|
77
|
+
let lowLeafIndex = this.getDbLowLeafIndex(newKey);
|
|
78
|
+
let lowLeafPreimage = lowLeafIndex !== undefined ? this.getDbPreimage(lowLeafIndex) : undefined;
|
|
79
|
+
if (includeUncommitted) {
|
|
80
|
+
const cachedLowLeafIndex = this.getCachedLowLeafIndex(newKey);
|
|
81
|
+
if (cachedLowLeafIndex !== undefined) {
|
|
82
|
+
const cachedLowLeafPreimage = this.getCachedPreimage(cachedLowLeafIndex);
|
|
83
|
+
if (!lowLeafPreimage || cachedLowLeafPreimage.getKey() > lowLeafPreimage.getKey()) {
|
|
84
|
+
lowLeafIndex = cachedLowLeafIndex;
|
|
85
|
+
lowLeafPreimage = cachedLowLeafPreimage;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (lowLeafIndex === undefined || !lowLeafPreimage) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
index: lowLeafIndex,
|
|
94
|
+
alreadyPresent: lowLeafPreimage.getKey() === newKey
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
getCachedLowLeafIndex(key) {
|
|
98
|
+
const indexes = Object.getOwnPropertyNames(this.cachedLeafPreimages);
|
|
99
|
+
const lowLeafIndexes = indexes.map((index)=>({
|
|
100
|
+
index: BigInt(index),
|
|
101
|
+
key: this.cachedLeafPreimages[index].getKey()
|
|
102
|
+
})).filter(({ key: candidateKey })=>candidateKey <= key).sort((a, b)=>Number(b.key - a.key));
|
|
103
|
+
return lowLeafIndexes[0]?.index;
|
|
104
|
+
}
|
|
105
|
+
getCachedLeafIndex(key) {
|
|
106
|
+
const index = Object.keys(this.cachedLeafPreimages).find((index)=>{
|
|
107
|
+
return this.cachedLeafPreimages[index].getKey() === key;
|
|
108
|
+
});
|
|
109
|
+
if (index) {
|
|
110
|
+
return BigInt(index);
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
getDbLowLeafIndex(key) {
|
|
115
|
+
const values = Array.from(this.leafIndex.values({
|
|
116
|
+
end: buildDbKeyForLeafIndex(this.getName(), key),
|
|
117
|
+
limit: 1,
|
|
118
|
+
reverse: true
|
|
119
|
+
}));
|
|
120
|
+
return values[0];
|
|
121
|
+
}
|
|
122
|
+
getDbPreimage(index) {
|
|
123
|
+
const value = this.leaves.get(buildDbKeyForPreimage(this.getName(), index));
|
|
124
|
+
return value ? this.leafPreimageFactory.fromBuffer(value) : undefined;
|
|
125
|
+
}
|
|
126
|
+
getCachedPreimage(index) {
|
|
127
|
+
return this.cachedLeafPreimages[index.toString()];
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Gets the latest LeafPreimage copy.
|
|
131
|
+
* @param index - Index of the leaf of which to obtain the LeafPreimage copy.
|
|
132
|
+
* @param includeUncommitted - If true, the uncommitted changes are included in the search.
|
|
133
|
+
* @returns A copy of the leaf preimage at the given index or undefined if the leaf was not found.
|
|
134
|
+
*/ getLatestLeafPreimageCopy(index, includeUncommitted) {
|
|
135
|
+
const preimage = !includeUncommitted ? this.getDbPreimage(index) : this.getCachedPreimage(index) ?? this.getDbPreimage(index);
|
|
136
|
+
return preimage && this.leafPreimageFactory.clone(preimage);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Returns the index of a leaf given its value, or undefined if no leaf with that value is found.
|
|
140
|
+
* @param value - The leaf value to look for.
|
|
141
|
+
* @param includeUncommitted - Indicates whether to include uncommitted data.
|
|
142
|
+
* @returns The index of the first leaf found with a given value (undefined if not found).
|
|
143
|
+
*/ findLeafIndex(value, includeUncommitted) {
|
|
144
|
+
const leaf = this.leafFactory.fromBuffer(value);
|
|
145
|
+
let index = this.leafIndex.get(buildDbKeyForLeafIndex(this.getName(), leaf.getKey()));
|
|
146
|
+
if (includeUncommitted && index === undefined) {
|
|
147
|
+
const cachedIndex = this.getCachedLeafIndex(leaf.getKey());
|
|
148
|
+
index = cachedIndex;
|
|
149
|
+
}
|
|
150
|
+
return index;
|
|
151
|
+
}
|
|
152
|
+
findLeafIndexAfter(_leaf, _startIndex, _includeUncommitted) {
|
|
153
|
+
throw new Error('Method not implemented for indexed trees');
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Initializes the tree.
|
|
157
|
+
* @param prefilledSize - A number of leaves that are prefilled with values.
|
|
158
|
+
* @returns Empty promise.
|
|
159
|
+
*
|
|
160
|
+
* @remarks Explanation of pre-filling:
|
|
161
|
+
* There needs to be an initial (0,0,0) leaf in the tree, so that when we insert the first 'proper' leaf, we can
|
|
162
|
+
* prove that any value greater than 0 doesn't exist in the tree yet. We prefill/pad the tree with "the number of
|
|
163
|
+
* leaves that are added by one block" so that the first 'proper' block can insert a full subtree.
|
|
164
|
+
*
|
|
165
|
+
* Without this padding, there would be a leaf (0,0,0) at leaf index 0, making it really difficult to insert e.g.
|
|
166
|
+
* 1024 leaves for the first block, because there's only neat space for 1023 leaves after 0. By padding with 1023
|
|
167
|
+
* more leaves, we can then insert the first block of 1024 leaves into indices 1024:2047.
|
|
168
|
+
*/ async init(prefilledSize) {
|
|
169
|
+
if (prefilledSize < 1) {
|
|
170
|
+
throw new Error(`Prefilled size must be at least 1!`);
|
|
171
|
+
}
|
|
172
|
+
const leaves = [];
|
|
173
|
+
for(let i = 0n; i < prefilledSize; i++){
|
|
174
|
+
const newLeaf = this.leafFactory.buildDummy(i);
|
|
175
|
+
const newLeafPreimage = this.leafPreimageFactory.fromLeaf(newLeaf, i + 1n, i + 1n);
|
|
176
|
+
leaves.push(newLeafPreimage);
|
|
177
|
+
}
|
|
178
|
+
// Make the last leaf point to the first leaf
|
|
179
|
+
leaves[prefilledSize - 1] = this.leafPreimageFactory.fromLeaf(leaves[prefilledSize - 1].asLeaf(), 0n, 0n);
|
|
180
|
+
this.encodeAndAppendLeaves(leaves, true);
|
|
181
|
+
await this.commit();
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Commits all the leaves to the database and removes them from a cache.
|
|
185
|
+
*/ commitLeaves() {
|
|
186
|
+
return this.store.transaction(()=>{
|
|
187
|
+
const keys = Object.getOwnPropertyNames(this.cachedLeafPreimages);
|
|
188
|
+
for (const key of keys){
|
|
189
|
+
const leaf = this.cachedLeafPreimages[key];
|
|
190
|
+
const index = BigInt(key);
|
|
191
|
+
void this.leaves.set(buildDbKeyForPreimage(this.getName(), index), leaf.toBuffer());
|
|
192
|
+
void this.leafIndex.set(buildDbKeyForLeafIndex(this.getName(), leaf.getKey()), index);
|
|
193
|
+
}
|
|
194
|
+
this.clearCachedLeaves();
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Clears the cache.
|
|
199
|
+
*/ clearCachedLeaves() {
|
|
200
|
+
this.cachedLeafPreimages = {};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Updates a leaf in the tree.
|
|
204
|
+
* @param preimage - New contents of the leaf.
|
|
205
|
+
* @param index - Index of the leaf to be updated.
|
|
206
|
+
*/ updateLeaf(preimage, index) {
|
|
207
|
+
if (index > this.maxIndex) {
|
|
208
|
+
throw Error(`Index out of bounds. Index ${index}, max index: ${this.maxIndex}.`);
|
|
209
|
+
}
|
|
210
|
+
this.cachedLeafPreimages[index.toString()] = preimage;
|
|
211
|
+
const encodedLeaf = this.encodeLeaf(preimage, true);
|
|
212
|
+
this.addLeafToCacheAndHashToRoot(encodedLeaf, index);
|
|
213
|
+
const numLeaves = this.getNumLeaves(true);
|
|
214
|
+
if (index >= numLeaves) {
|
|
215
|
+
this.cachedSize = index + 1n;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/* eslint-disable jsdoc/require-description-complete-sentence */ /* The following doc block messes up with complete-sentence, so we just disable it */ /**
|
|
219
|
+
*
|
|
220
|
+
* Each base rollup needs to provide non membership / inclusion proofs for each of the nullifier.
|
|
221
|
+
* This method will return membership proofs and perform partial node updates that will
|
|
222
|
+
* allow the circuit to incrementally update the tree and perform a batch insertion.
|
|
223
|
+
*
|
|
224
|
+
* This offers massive circuit performance savings over doing incremental insertions.
|
|
225
|
+
*
|
|
226
|
+
* WARNING: This function has side effects, it will insert values into the tree.
|
|
227
|
+
*
|
|
228
|
+
* Assumptions:
|
|
229
|
+
* 1. There are 8 nullifiers provided and they are either unique or empty. (denoted as 0)
|
|
230
|
+
* 2. If kc 0 has 1 nullifier, and kc 1 has 3 nullifiers the layout will assume to be the sparse
|
|
231
|
+
* nullifier layout: [kc0-0, 0, 0, 0, kc1-0, kc1-1, kc1-2, 0]
|
|
232
|
+
*
|
|
233
|
+
* Algorithm overview
|
|
234
|
+
*
|
|
235
|
+
* In general, if we want to batch insert items, we first need to update their low nullifier to point to them,
|
|
236
|
+
* then batch insert all of the values at once in the final step.
|
|
237
|
+
* To update a low nullifier, we provide an insertion proof that the low nullifier currently exists to the
|
|
238
|
+
* circuit, then update the low nullifier.
|
|
239
|
+
* Updating this low nullifier will in turn change the root of the tree. Therefore future low nullifier insertion proofs
|
|
240
|
+
* must be given against this new root.
|
|
241
|
+
* As a result, each low nullifier membership proof will be provided against an intermediate tree state, each with differing
|
|
242
|
+
* roots.
|
|
243
|
+
*
|
|
244
|
+
* This become tricky when two items that are being batch inserted need to update the same low nullifier, or need to use
|
|
245
|
+
* a value that is part of the same batch insertion as their low nullifier. What we do to avoid this case is to
|
|
246
|
+
* update the existing leaves in the tree with the nullifiers in high to low order, ensuring that this case never occurs.
|
|
247
|
+
* The circuit has to sort the nullifiers (or take a hint of the sorted nullifiers and prove that it's a valid permutation).
|
|
248
|
+
* Then we just batch insert the new nullifiers in the original order.
|
|
249
|
+
*
|
|
250
|
+
* The following example will illustrate attempting to insert 2,3,20,19 into a tree already containing 0,5,10,15
|
|
251
|
+
*
|
|
252
|
+
* The example will explore two cases. In each case the values low nullifier will exist within the batch insertion,
|
|
253
|
+
* One where the low nullifier comes before the item in the set (2,3), and one where it comes after (20,19).
|
|
254
|
+
*
|
|
255
|
+
* First, we sort the nullifiers high to low, that's 20,19,3,2
|
|
256
|
+
*
|
|
257
|
+
* The original tree: Pending insertion subtree
|
|
258
|
+
*
|
|
259
|
+
* index 0 1 2 3 - - - -
|
|
260
|
+
* ------------------------------------- ----------------------------
|
|
261
|
+
* val 0 5 10 15 - - - -
|
|
262
|
+
* nextIdx 1 2 3 0 - - - -
|
|
263
|
+
* nextVal 5 10 15 0 - - - -
|
|
264
|
+
*
|
|
265
|
+
*
|
|
266
|
+
* Inserting 20:
|
|
267
|
+
* 1. Find the low nullifier (3) - provide inclusion proof
|
|
268
|
+
* 2. Update its pointers
|
|
269
|
+
* 3. Insert 20 into the pending subtree
|
|
270
|
+
*
|
|
271
|
+
* index 0 1 2 3 - - 6 -
|
|
272
|
+
* ------------------------------------- ----------------------------
|
|
273
|
+
* val 0 5 10 15 - - 20 -
|
|
274
|
+
* nextIdx 1 2 3 6 - - 0 -
|
|
275
|
+
* nextVal 5 10 15 20 - - 0 -
|
|
276
|
+
*
|
|
277
|
+
* Inserting 19:
|
|
278
|
+
* 1. Find the low nullifier (3) - provide inclusion proof
|
|
279
|
+
* 2. Update its pointers
|
|
280
|
+
* 3. Insert 19 into the pending subtree
|
|
281
|
+
*
|
|
282
|
+
* index 0 1 2 3 - - 6 7
|
|
283
|
+
* ------------------------------------- ----------------------------
|
|
284
|
+
* val 0 5 10 15 - - 20 19
|
|
285
|
+
* nextIdx 1 2 3 7 - - 0 6
|
|
286
|
+
* nextVal 5 10 15 19 - - 0 20
|
|
287
|
+
*
|
|
288
|
+
* Inserting 3:
|
|
289
|
+
* 1. Find the low nullifier (0) - provide inclusion proof
|
|
290
|
+
* 2. Update its pointers
|
|
291
|
+
* 3. Insert 3 into the pending subtree
|
|
292
|
+
*
|
|
293
|
+
* index 0 1 2 3 - 5 6 7
|
|
294
|
+
* ------------------------------------- ----------------------------
|
|
295
|
+
* val 0 5 10 15 - 3 20 19
|
|
296
|
+
* nextIdx 5 2 3 7 - 1 0 6
|
|
297
|
+
* nextVal 3 10 15 19 - 5 0 20
|
|
298
|
+
*
|
|
299
|
+
* Inserting 2:
|
|
300
|
+
* 1. Find the low nullifier (0) - provide inclusion proof
|
|
301
|
+
* 2. Update its pointers
|
|
302
|
+
* 3. Insert 2 into the pending subtree
|
|
303
|
+
*
|
|
304
|
+
* index 0 1 2 3 4 5 6 7
|
|
305
|
+
* ------------------------------------- ----------------------------
|
|
306
|
+
* val 0 5 10 15 2 3 20 19
|
|
307
|
+
* nextIdx 4 2 3 7 5 1 0 6
|
|
308
|
+
* nextVal 2 10 15 19 3 5 0 20
|
|
309
|
+
*
|
|
310
|
+
* Perform subtree insertion
|
|
311
|
+
*
|
|
312
|
+
* index 0 1 2 3 4 5 6 7
|
|
313
|
+
* ---------------------------------------------------------------------
|
|
314
|
+
* val 0 5 10 15 2 3 20 19
|
|
315
|
+
* nextIdx 4 2 3 7 5 1 0 6
|
|
316
|
+
* nextVal 2 10 15 19 3 5 0 20
|
|
317
|
+
*
|
|
318
|
+
* For leaves that allow updating the process is exactly the same. When a leaf is inserted that is already present,
|
|
319
|
+
* the low leaf will be the leaf that is being updated, and it'll get updated and an empty leaf will be inserted instead.
|
|
320
|
+
* For example:
|
|
321
|
+
*
|
|
322
|
+
* Initial state:
|
|
323
|
+
*
|
|
324
|
+
* index 0 1 2 3 4 5 6 7
|
|
325
|
+
* ---------------------------------------------------------------------
|
|
326
|
+
* slot 0 0 0 0 0 0 0 0
|
|
327
|
+
* value 0 0 0 0 0 0 0 0
|
|
328
|
+
* nextIdx 0 0 0 0 0 0 0 0
|
|
329
|
+
* nextSlot 0 0 0 0 0 0 0 0.
|
|
330
|
+
*
|
|
331
|
+
*
|
|
332
|
+
* Add new value 30:5:
|
|
333
|
+
*
|
|
334
|
+
* index 0 1 2 3 4 5 6 7
|
|
335
|
+
* ---------------------------------------------------------------------
|
|
336
|
+
* slot 0 30 0 0 0 0 0 0
|
|
337
|
+
* value 0 5 0 0 0 0 0 0
|
|
338
|
+
* nextIdx 1 0 0 0 0 0 0 0
|
|
339
|
+
* nextSlot 30 0 0 0 0 0 0 0.
|
|
340
|
+
*
|
|
341
|
+
*
|
|
342
|
+
* Update the value of 30 to 10 (insert 30:10):
|
|
343
|
+
*
|
|
344
|
+
* index 0 1 2 3 4 5 6 7
|
|
345
|
+
* ---------------------------------------------------------------------
|
|
346
|
+
* slot 0 30 0 0 0 0 0 0
|
|
347
|
+
* value 0 10 0 0 0 0 0 0
|
|
348
|
+
* nextIdx 1 0 0 0 0 0 0 0
|
|
349
|
+
* nextSlot 30 0 0 0 0 0 0 0.
|
|
350
|
+
*
|
|
351
|
+
* The low leaf is 30, so we update it to 10, and insert an empty leaf at index 2.
|
|
352
|
+
*
|
|
353
|
+
* @param leaves - Values to insert into the tree.
|
|
354
|
+
* @param subtreeHeight - Height of the subtree.
|
|
355
|
+
* @returns The data for the leaves to be updated when inserting the new ones.
|
|
356
|
+
*/ async batchInsert(leaves, subtreeHeight) {
|
|
357
|
+
this.hasher.reset();
|
|
358
|
+
const timer = new Timer();
|
|
359
|
+
const insertedKeys = new Map();
|
|
360
|
+
const emptyLowLeafWitness = getEmptyLowLeafWitness(this.getDepth(), this.leafPreimageFactory);
|
|
361
|
+
// Accumulators
|
|
362
|
+
const lowLeavesWitnesses = leaves.map(()=>emptyLowLeafWitness);
|
|
363
|
+
const pendingInsertionSubtree = leaves.map(()=>this.leafPreimageFactory.empty());
|
|
364
|
+
// Start info
|
|
365
|
+
const startInsertionIndex = this.getNumLeaves(true);
|
|
366
|
+
const leavesToInsert = leaves.map((leaf)=>this.leafFactory.fromBuffer(leaf));
|
|
367
|
+
const sortedDescendingLeafTuples = leavesToInsert.map((leaf, index)=>({
|
|
368
|
+
leaf,
|
|
369
|
+
index
|
|
370
|
+
})).sort((a, b)=>Number(b.leaf.getKey() - a.leaf.getKey()));
|
|
371
|
+
const sortedDescendingLeaves = sortedDescendingLeafTuples.map((leafTuple)=>leafTuple.leaf);
|
|
372
|
+
// Get insertion path for each leaf
|
|
373
|
+
for(let i = 0; i < leavesToInsert.length; i++){
|
|
374
|
+
const newLeaf = sortedDescendingLeaves[i];
|
|
375
|
+
const originalIndex = leavesToInsert.indexOf(newLeaf);
|
|
376
|
+
if (newLeaf.isEmpty()) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (insertedKeys.has(newLeaf.getKey())) {
|
|
380
|
+
throw new Error('Cannot insert duplicated keys in the same batch');
|
|
381
|
+
} else {
|
|
382
|
+
insertedKeys.set(newLeaf.getKey(), true);
|
|
383
|
+
}
|
|
384
|
+
const indexOfPrevious = this.findIndexOfPreviousKey(newLeaf.getKey(), true);
|
|
385
|
+
if (indexOfPrevious === undefined) {
|
|
386
|
+
return {
|
|
387
|
+
lowLeavesWitnessData: undefined,
|
|
388
|
+
sortedNewLeaves: sortedDescendingLeafTuples.map((leafTuple)=>leafTuple.leaf.toBuffer()),
|
|
389
|
+
sortedNewLeavesIndexes: sortedDescendingLeafTuples.map((leafTuple)=>leafTuple.index),
|
|
390
|
+
newSubtreeSiblingPath: await this.getSubtreeSiblingPath(subtreeHeight, true)
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
const isUpdate = indexOfPrevious.alreadyPresent;
|
|
394
|
+
// get the low leaf (existence checked in getting index)
|
|
395
|
+
const lowLeafPreimage = this.getLatestLeafPreimageCopy(indexOfPrevious.index, true);
|
|
396
|
+
const siblingPath = await this.getSiblingPath(BigInt(indexOfPrevious.index), true);
|
|
397
|
+
const witness = {
|
|
398
|
+
leafPreimage: lowLeafPreimage,
|
|
399
|
+
index: BigInt(indexOfPrevious.index),
|
|
400
|
+
siblingPath
|
|
401
|
+
};
|
|
402
|
+
// Update the running paths
|
|
403
|
+
lowLeavesWitnesses[i] = witness;
|
|
404
|
+
if (isUpdate) {
|
|
405
|
+
const newLowLeaf = lowLeafPreimage.asLeaf().updateTo(newLeaf);
|
|
406
|
+
const newLowLeafPreimage = this.leafPreimageFactory.fromLeaf(newLowLeaf, lowLeafPreimage.getNextKey(), lowLeafPreimage.getNextIndex());
|
|
407
|
+
this.updateLeaf(newLowLeafPreimage, indexOfPrevious.index);
|
|
408
|
+
pendingInsertionSubtree[originalIndex] = this.leafPreimageFactory.empty();
|
|
409
|
+
} else {
|
|
410
|
+
const newLowLeafPreimage = this.leafPreimageFactory.fromLeaf(lowLeafPreimage.asLeaf(), newLeaf.getKey(), startInsertionIndex + BigInt(originalIndex));
|
|
411
|
+
this.updateLeaf(newLowLeafPreimage, indexOfPrevious.index);
|
|
412
|
+
const currentPendingPreimageLeaf = this.leafPreimageFactory.fromLeaf(newLeaf, lowLeafPreimage.getNextKey(), lowLeafPreimage.getNextIndex());
|
|
413
|
+
pendingInsertionSubtree[originalIndex] = currentPendingPreimageLeaf;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const newSubtreeSiblingPath = await this.getSubtreeSiblingPath(subtreeHeight, true);
|
|
417
|
+
// Perform batch insertion of new pending values
|
|
418
|
+
// Note: In this case we set `hash0Leaf` param to false because batch insertion algorithm use forced null leaf
|
|
419
|
+
// inclusion. See {@link encodeLeaf} for a more through param explanation.
|
|
420
|
+
this.encodeAndAppendLeaves(pendingInsertionSubtree, false);
|
|
421
|
+
this.log.debug(`Inserted ${leaves.length} leaves into ${this.getName()} tree`, {
|
|
422
|
+
eventName: 'tree-insertion',
|
|
423
|
+
duration: timer.ms(),
|
|
424
|
+
batchSize: leaves.length,
|
|
425
|
+
treeName: this.getName(),
|
|
426
|
+
treeDepth: this.getDepth(),
|
|
427
|
+
treeType: 'indexed',
|
|
428
|
+
...this.hasher.stats()
|
|
429
|
+
});
|
|
430
|
+
return {
|
|
431
|
+
lowLeavesWitnessData: lowLeavesWitnesses,
|
|
432
|
+
sortedNewLeaves: sortedDescendingLeafTuples.map((leafTuple)=>leafTuple.leaf.toBuffer()),
|
|
433
|
+
sortedNewLeavesIndexes: sortedDescendingLeafTuples.map((leafTuple)=>leafTuple.index),
|
|
434
|
+
newSubtreeSiblingPath
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
async getSubtreeSiblingPath(subtreeHeight, includeUncommitted) {
|
|
438
|
+
const nextAvailableLeafIndex = this.getNumLeaves(includeUncommitted);
|
|
439
|
+
const fullSiblingPath = await this.getSiblingPath(nextAvailableLeafIndex, includeUncommitted);
|
|
440
|
+
// Drop the first subtreeHeight items since we only care about the path to the subtree root
|
|
441
|
+
return fullSiblingPath.getSubtreeSiblingPath(subtreeHeight);
|
|
442
|
+
}
|
|
443
|
+
snapshot(blockNumber) {
|
|
444
|
+
return this.#snapshotBuilder.snapshot(blockNumber);
|
|
445
|
+
}
|
|
446
|
+
getSnapshot(blockNumber) {
|
|
447
|
+
return this.#snapshotBuilder.getSnapshot(blockNumber);
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Encodes leaves and appends them to a tree.
|
|
451
|
+
* @param preimages - Leaves to encode.
|
|
452
|
+
* @param hash0Leaf - Indicates whether 0 value leaf should be hashed. See {@link encodeLeaf}.
|
|
453
|
+
* @returns Empty promise
|
|
454
|
+
*/ encodeAndAppendLeaves(preimages, hash0Leaf) {
|
|
455
|
+
const startInsertionIndex = this.getNumLeaves(true);
|
|
456
|
+
const hashedLeaves = preimages.map((preimage, i)=>{
|
|
457
|
+
this.cachedLeafPreimages[(startInsertionIndex + BigInt(i)).toString()] = preimage;
|
|
458
|
+
return this.encodeLeaf(preimage, hash0Leaf);
|
|
459
|
+
});
|
|
460
|
+
super.appendLeaves(hashedLeaves);
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Encode a leaf into a buffer.
|
|
464
|
+
* @param leaf - Leaf to encode.
|
|
465
|
+
* @param hash0Leaf - Indicates whether 0 value leaf should be hashed. Not hashing 0 value can represent a forced
|
|
466
|
+
* null leaf insertion. Detecting this case by checking for 0 value is safe as in the case of
|
|
467
|
+
* nullifier it is improbable that a valid nullifier would be 0.
|
|
468
|
+
* @returns Leaf encoded in a buffer.
|
|
469
|
+
*/ encodeLeaf(leaf, hash0Leaf) {
|
|
470
|
+
let encodedLeaf;
|
|
471
|
+
if (!hash0Leaf && leaf.getKey() == 0n) {
|
|
472
|
+
encodedLeaf = toBufferBE(0n, 32);
|
|
473
|
+
} else {
|
|
474
|
+
encodedLeaf = this.hasher.hashInputs(leaf.toHashInputs());
|
|
475
|
+
}
|
|
476
|
+
return encodedLeaf;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
3
|
+
import { StandardIndexedTree } from '../standard_indexed_tree.js';
|
|
4
|
+
/**
|
|
5
|
+
* A testing utility which is here to store the original implementation of StandardIndexedTree.appendLeaves method
|
|
6
|
+
* that was replaced by the more efficient batchInsert method. We keep the original implementation around as it useful
|
|
7
|
+
* for testing that the more complex batchInsert method works correctly.
|
|
8
|
+
*/
|
|
9
|
+
export declare class StandardIndexedTreeWithAppend extends StandardIndexedTree {
|
|
10
|
+
/**
|
|
11
|
+
* Appends the given leaves to the tree.
|
|
12
|
+
* @param leaves - The leaves to append.
|
|
13
|
+
* @returns Empty promise.
|
|
14
|
+
* @remarks This method is inefficient and is here mostly for testing. Use batchInsert instead.
|
|
15
|
+
*/
|
|
16
|
+
appendLeaves(leaves: Buffer[]): Promise<void>;
|
|
17
|
+
private appendEmptyLeaf;
|
|
18
|
+
/**
|
|
19
|
+
* Appends the given leaf to the tree.
|
|
20
|
+
* @param leaf - The leaf to append.
|
|
21
|
+
* @returns Empty promise.
|
|
22
|
+
*/
|
|
23
|
+
private appendLeaf;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=standard_indexed_tree_with_append.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standard_indexed_tree_with_append.d.ts","sourceRoot":"","sources":["../../../src/standard_indexed_tree/test/standard_indexed_tree_with_append.ts"],"names":[],"mappings":";;AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE;;;;GAIG;AACH,qBAAa,6BAA8B,SAAQ,mBAAmB;IACpE;;;;;OAKG;IACa,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ7D,OAAO,CAAC,eAAe;IAQvB;;;;OAIG;IACH,OAAO,CAAC,UAAU;CA6CnB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { StandardIndexedTree } from '../standard_indexed_tree.js';
|
|
2
|
+
/**
|
|
3
|
+
* A testing utility which is here to store the original implementation of StandardIndexedTree.appendLeaves method
|
|
4
|
+
* that was replaced by the more efficient batchInsert method. We keep the original implementation around as it useful
|
|
5
|
+
* for testing that the more complex batchInsert method works correctly.
|
|
6
|
+
*/ export class StandardIndexedTreeWithAppend extends StandardIndexedTree {
|
|
7
|
+
/**
|
|
8
|
+
* Appends the given leaves to the tree.
|
|
9
|
+
* @param leaves - The leaves to append.
|
|
10
|
+
* @returns Empty promise.
|
|
11
|
+
* @remarks This method is inefficient and is here mostly for testing. Use batchInsert instead.
|
|
12
|
+
*/ appendLeaves(leaves) {
|
|
13
|
+
for (const leaf of leaves){
|
|
14
|
+
this.appendLeaf(leaf);
|
|
15
|
+
}
|
|
16
|
+
return Promise.resolve();
|
|
17
|
+
}
|
|
18
|
+
appendEmptyLeaf() {
|
|
19
|
+
const newSize = (this.cachedSize ?? this.size) + 1n;
|
|
20
|
+
if (newSize - 1n > this.maxIndex) {
|
|
21
|
+
throw Error(`Can't append beyond max index. Max index: ${this.maxIndex}`);
|
|
22
|
+
}
|
|
23
|
+
this.cachedSize = newSize;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Appends the given leaf to the tree.
|
|
27
|
+
* @param leaf - The leaf to append.
|
|
28
|
+
* @returns Empty promise.
|
|
29
|
+
*/ appendLeaf(leaf) {
|
|
30
|
+
const newLeaf = this.leafFactory.fromBuffer(leaf);
|
|
31
|
+
// Special case when appending zero
|
|
32
|
+
if (newLeaf.getKey() === 0n) {
|
|
33
|
+
this.appendEmptyLeaf();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const lowLeafIndex = this.findIndexOfPreviousKey(newLeaf.getKey(), true);
|
|
37
|
+
if (lowLeafIndex === undefined) {
|
|
38
|
+
throw new Error(`Previous leaf not found!`);
|
|
39
|
+
}
|
|
40
|
+
const isUpdate = lowLeafIndex.alreadyPresent;
|
|
41
|
+
const lowLeafPreimage = this.getLatestLeafPreimageCopy(lowLeafIndex.index, true);
|
|
42
|
+
const currentSize = this.getNumLeaves(true);
|
|
43
|
+
if (isUpdate) {
|
|
44
|
+
const newLowLeaf = lowLeafPreimage.asLeaf().updateTo(newLeaf);
|
|
45
|
+
const newLowLeafPreimage = this.leafPreimageFactory.fromLeaf(newLowLeaf, lowLeafPreimage.getNextKey(), lowLeafPreimage.getNextIndex());
|
|
46
|
+
this.updateLeaf(newLowLeafPreimage, BigInt(lowLeafIndex.index));
|
|
47
|
+
this.appendEmptyLeaf();
|
|
48
|
+
} else {
|
|
49
|
+
const newLeafPreimage = this.leafPreimageFactory.fromLeaf(newLeaf, lowLeafPreimage.getNextKey(), lowLeafPreimage.getNextIndex());
|
|
50
|
+
// insert a new leaf at the highest index and update the values of our previous leaf copy
|
|
51
|
+
const newLowLeafPreimage = this.leafPreimageFactory.fromLeaf(lowLeafPreimage.asLeaf(), newLeaf.getKey(), BigInt(currentSize));
|
|
52
|
+
this.updateLeaf(newLowLeafPreimage, BigInt(lowLeafIndex.index));
|
|
53
|
+
this.updateLeaf(newLeafPreimage, currentSize);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
3
|
+
import { type Bufferable } from '@aztec/foundation/serialize';
|
|
4
|
+
import type { AppendOnlyTree } from '../interfaces/append_only_tree.js';
|
|
5
|
+
import type { TreeSnapshot } from '../snapshots/snapshot_builder.js';
|
|
6
|
+
import { TreeBase } from '../tree_base.js';
|
|
7
|
+
/**
|
|
8
|
+
* A Merkle tree implementation that uses a LevelDB database to store the tree.
|
|
9
|
+
*/
|
|
10
|
+
export declare class StandardTree<T extends Bufferable = Buffer> extends TreeBase<T> implements AppendOnlyTree<T> {
|
|
11
|
+
#private;
|
|
12
|
+
/**
|
|
13
|
+
* Appends the given leaves to the tree.
|
|
14
|
+
* @param leaves - The leaves to append.
|
|
15
|
+
* @returns Empty promise.
|
|
16
|
+
*/
|
|
17
|
+
appendLeaves(leaves: T[]): Promise<void>;
|
|
18
|
+
snapshot(blockNumber: number): Promise<TreeSnapshot<T>>;
|
|
19
|
+
getSnapshot(blockNumber: number): Promise<TreeSnapshot<T>>;
|
|
20
|
+
findLeafIndex(value: T, includeUncommitted: boolean): bigint | undefined;
|
|
21
|
+
findLeafIndexAfter(value: T, startIndex: bigint, includeUncommitted: boolean): bigint | undefined;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=standard_tree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standard_tree.d.ts","sourceRoot":"","sources":["../../src/standard_tree/standard_tree.ts"],"names":[],"mappings":";;AAAA,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,6BAA6B,CAAC;AAIjF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C;;GAEG;AACH,qBAAa,YAAY,CAAC,CAAC,SAAS,UAAU,GAAG,MAAM,CAAE,SAAQ,QAAQ,CAAC,CAAC,CAAE,YAAW,cAAc,CAAC,CAAC,CAAC;;IAGvG;;;;OAIG;IACa,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBjD,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAIvD,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAI1D,aAAa,CAAC,KAAK,EAAE,CAAC,EAAE,kBAAkB,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS;IAIxE,kBAAkB,CAAC,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS;CAUzG"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { serializeToBuffer } from '@aztec/foundation/serialize';
|
|
2
|
+
import { Timer } from '@aztec/foundation/timer';
|
|
3
|
+
import { AppendOnlySnapshotBuilder } from '../snapshots/append_only_snapshot.js';
|
|
4
|
+
import { TreeBase } from '../tree_base.js';
|
|
5
|
+
/**
|
|
6
|
+
* A Merkle tree implementation that uses a LevelDB database to store the tree.
|
|
7
|
+
*/ export class StandardTree extends TreeBase {
|
|
8
|
+
#snapshotBuilder = new AppendOnlySnapshotBuilder(this.store, this, this.hasher, this.deserializer);
|
|
9
|
+
/**
|
|
10
|
+
* Appends the given leaves to the tree.
|
|
11
|
+
* @param leaves - The leaves to append.
|
|
12
|
+
* @returns Empty promise.
|
|
13
|
+
*/ appendLeaves(leaves) {
|
|
14
|
+
this.hasher.reset();
|
|
15
|
+
const timer = new Timer();
|
|
16
|
+
super.appendLeaves(leaves);
|
|
17
|
+
this.log.debug(`Inserted ${leaves.length} leaves into ${this.getName()} tree`, {
|
|
18
|
+
eventName: 'tree-insertion',
|
|
19
|
+
duration: timer.ms(),
|
|
20
|
+
batchSize: leaves.length,
|
|
21
|
+
treeName: this.getName(),
|
|
22
|
+
treeDepth: this.getDepth(),
|
|
23
|
+
treeType: 'append-only',
|
|
24
|
+
...this.hasher.stats()
|
|
25
|
+
});
|
|
26
|
+
return Promise.resolve();
|
|
27
|
+
}
|
|
28
|
+
snapshot(blockNumber) {
|
|
29
|
+
return this.#snapshotBuilder.snapshot(blockNumber);
|
|
30
|
+
}
|
|
31
|
+
getSnapshot(blockNumber) {
|
|
32
|
+
return this.#snapshotBuilder.getSnapshot(blockNumber);
|
|
33
|
+
}
|
|
34
|
+
findLeafIndex(value, includeUncommitted) {
|
|
35
|
+
return this.findLeafIndexAfter(value, 0n, includeUncommitted);
|
|
36
|
+
}
|
|
37
|
+
findLeafIndexAfter(value, startIndex, includeUncommitted) {
|
|
38
|
+
const buffer = serializeToBuffer(value);
|
|
39
|
+
for(let i = startIndex; i < this.getNumLeaves(includeUncommitted); i++){
|
|
40
|
+
const currentValue = this.getLeafValue(i, includeUncommitted);
|
|
41
|
+
if (currentValue && serializeToBuffer(currentValue).equals(buffer)) {
|
|
42
|
+
return i;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|