@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,216 @@
|
|
|
1
|
+
import { serializeToBuffer } from '@aztec/foundation/serialize';
|
|
2
|
+
import { SiblingPath } from '@aztec/foundation/trees';
|
|
3
|
+
// stores the last block that modified this node
|
|
4
|
+
const nodeModifiedAtBlockKey = (level, index)=>`node:${level}:${index}:modifiedAtBlock`;
|
|
5
|
+
// stores the value of the node at the above block
|
|
6
|
+
const historicalNodeKey = (level, index)=>`node:${level}:${index}:value`;
|
|
7
|
+
/**
|
|
8
|
+
* A more space-efficient way of storing snapshots of AppendOnlyTrees that trades space need for slower
|
|
9
|
+
* sibling path reads.
|
|
10
|
+
*
|
|
11
|
+
* Complexity:
|
|
12
|
+
*
|
|
13
|
+
* N - count of non-zero nodes in tree
|
|
14
|
+
* M - count of snapshots
|
|
15
|
+
* H - tree height
|
|
16
|
+
*
|
|
17
|
+
* Space complexity: O(N + M) (N nodes - stores the last snapshot for each node and M - ints, for each snapshot stores up to which leaf its written to)
|
|
18
|
+
* Sibling path access:
|
|
19
|
+
* Best case: O(H) database reads + O(1) hashes
|
|
20
|
+
* Worst case: O(H) database reads + O(H) hashes
|
|
21
|
+
*/ export class AppendOnlySnapshotBuilder {
|
|
22
|
+
db;
|
|
23
|
+
tree;
|
|
24
|
+
hasher;
|
|
25
|
+
deserializer;
|
|
26
|
+
#nodeValue;
|
|
27
|
+
#nodeLastModifiedByBlock;
|
|
28
|
+
#snapshotMetadata;
|
|
29
|
+
constructor(db, tree, hasher, deserializer){
|
|
30
|
+
this.db = db;
|
|
31
|
+
this.tree = tree;
|
|
32
|
+
this.hasher = hasher;
|
|
33
|
+
this.deserializer = deserializer;
|
|
34
|
+
const treeName = tree.getName();
|
|
35
|
+
this.#nodeValue = db.openMap(`append_only_snapshot:${treeName}:node`);
|
|
36
|
+
this.#nodeLastModifiedByBlock = db.openMap(`append_ony_snapshot:${treeName}:block`);
|
|
37
|
+
this.#snapshotMetadata = db.openMap(`append_only_snapshot:${treeName}:snapshot_metadata`);
|
|
38
|
+
}
|
|
39
|
+
getSnapshot(block) {
|
|
40
|
+
const meta = this.#getSnapshotMeta(block);
|
|
41
|
+
if (typeof meta === 'undefined') {
|
|
42
|
+
return Promise.reject(new Error(`Snapshot for tree ${this.tree.getName()} at block ${block} does not exist`));
|
|
43
|
+
}
|
|
44
|
+
return Promise.resolve(new AppendOnlySnapshot(this.#nodeValue, this.#nodeLastModifiedByBlock, block, meta.numLeaves, meta.root, this.tree, this.hasher, this.deserializer));
|
|
45
|
+
}
|
|
46
|
+
snapshot(block) {
|
|
47
|
+
return this.db.transaction(()=>{
|
|
48
|
+
const meta = this.#getSnapshotMeta(block);
|
|
49
|
+
if (typeof meta !== 'undefined') {
|
|
50
|
+
// no-op, we already have a snapshot
|
|
51
|
+
return new AppendOnlySnapshot(this.#nodeValue, this.#nodeLastModifiedByBlock, block, meta.numLeaves, meta.root, this.tree, this.hasher, this.deserializer);
|
|
52
|
+
}
|
|
53
|
+
const root = this.tree.getRoot(false);
|
|
54
|
+
const depth = this.tree.getDepth();
|
|
55
|
+
const queue = [
|
|
56
|
+
[
|
|
57
|
+
root,
|
|
58
|
+
0,
|
|
59
|
+
0n
|
|
60
|
+
]
|
|
61
|
+
];
|
|
62
|
+
// walk the tree in BF and store latest nodes
|
|
63
|
+
while(queue.length > 0){
|
|
64
|
+
const [node, level, index] = queue.shift();
|
|
65
|
+
const historicalValue = this.#nodeValue.get(historicalNodeKey(level, index));
|
|
66
|
+
if (!historicalValue || !node.equals(historicalValue)) {
|
|
67
|
+
// we've never seen this node before or it's different than before
|
|
68
|
+
// update the historical tree and tag it with the block that modified it
|
|
69
|
+
void this.#nodeLastModifiedByBlock.set(nodeModifiedAtBlockKey(level, index), block);
|
|
70
|
+
void this.#nodeValue.set(historicalNodeKey(level, index), node);
|
|
71
|
+
} else {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (level + 1 > depth) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
// these could be undefined because zero hashes aren't stored in the tree
|
|
78
|
+
const [lhs, rhs] = [
|
|
79
|
+
this.tree.getNode(level + 1, 2n * index),
|
|
80
|
+
this.tree.getNode(level + 1, 2n * index + 1n)
|
|
81
|
+
];
|
|
82
|
+
if (lhs) {
|
|
83
|
+
queue.push([
|
|
84
|
+
lhs,
|
|
85
|
+
level + 1,
|
|
86
|
+
2n * index
|
|
87
|
+
]);
|
|
88
|
+
}
|
|
89
|
+
if (rhs) {
|
|
90
|
+
queue.push([
|
|
91
|
+
rhs,
|
|
92
|
+
level + 1,
|
|
93
|
+
2n * index + 1n
|
|
94
|
+
]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const numLeaves = this.tree.getNumLeaves(false);
|
|
98
|
+
void this.#snapshotMetadata.set(block, {
|
|
99
|
+
numLeaves,
|
|
100
|
+
root
|
|
101
|
+
});
|
|
102
|
+
return new AppendOnlySnapshot(this.#nodeValue, this.#nodeLastModifiedByBlock, block, numLeaves, root, this.tree, this.hasher, this.deserializer);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
#getSnapshotMeta(block) {
|
|
106
|
+
return this.#snapshotMetadata.get(block);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* a
|
|
111
|
+
*/ class AppendOnlySnapshot {
|
|
112
|
+
nodes;
|
|
113
|
+
nodeHistory;
|
|
114
|
+
block;
|
|
115
|
+
leafCount;
|
|
116
|
+
historicalRoot;
|
|
117
|
+
tree;
|
|
118
|
+
hasher;
|
|
119
|
+
deserializer;
|
|
120
|
+
constructor(nodes, nodeHistory, block, leafCount, historicalRoot, tree, hasher, deserializer){
|
|
121
|
+
this.nodes = nodes;
|
|
122
|
+
this.nodeHistory = nodeHistory;
|
|
123
|
+
this.block = block;
|
|
124
|
+
this.leafCount = leafCount;
|
|
125
|
+
this.historicalRoot = historicalRoot;
|
|
126
|
+
this.tree = tree;
|
|
127
|
+
this.hasher = hasher;
|
|
128
|
+
this.deserializer = deserializer;
|
|
129
|
+
}
|
|
130
|
+
getSiblingPath(index) {
|
|
131
|
+
const path = [];
|
|
132
|
+
const depth = this.tree.getDepth();
|
|
133
|
+
let level = depth;
|
|
134
|
+
while(level > 0){
|
|
135
|
+
const isRight = index & 0x01n;
|
|
136
|
+
const siblingIndex = isRight ? index - 1n : index + 1n;
|
|
137
|
+
const sibling = this.#getHistoricalNodeValue(level, siblingIndex);
|
|
138
|
+
path.push(sibling);
|
|
139
|
+
level -= 1;
|
|
140
|
+
index >>= 1n;
|
|
141
|
+
}
|
|
142
|
+
return new SiblingPath(depth, path);
|
|
143
|
+
}
|
|
144
|
+
getDepth() {
|
|
145
|
+
return this.tree.getDepth();
|
|
146
|
+
}
|
|
147
|
+
getNumLeaves() {
|
|
148
|
+
return this.leafCount;
|
|
149
|
+
}
|
|
150
|
+
getRoot() {
|
|
151
|
+
// we could recompute it, but it's way cheaper to just store the root
|
|
152
|
+
return this.historicalRoot;
|
|
153
|
+
}
|
|
154
|
+
getLeafValue(index) {
|
|
155
|
+
const leafLevel = this.getDepth();
|
|
156
|
+
const blockNumber = this.#getBlockNumberThatModifiedNode(leafLevel, index);
|
|
157
|
+
// leaf hasn't been set yet
|
|
158
|
+
if (typeof blockNumber === 'undefined') {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
// leaf was set some time in the past
|
|
162
|
+
if (blockNumber <= this.block) {
|
|
163
|
+
const val = this.nodes.get(historicalNodeKey(leafLevel, index));
|
|
164
|
+
return val ? this.deserializer.fromBuffer(val) : undefined;
|
|
165
|
+
}
|
|
166
|
+
// leaf has been set but in a block in the future
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
#getHistoricalNodeValue(level, index) {
|
|
170
|
+
const blockNumber = this.#getBlockNumberThatModifiedNode(level, index);
|
|
171
|
+
// node has never been set
|
|
172
|
+
if (typeof blockNumber === 'undefined') {
|
|
173
|
+
return this.tree.getZeroHash(level);
|
|
174
|
+
}
|
|
175
|
+
// node was set some time in the past
|
|
176
|
+
if (blockNumber <= this.block) {
|
|
177
|
+
return this.nodes.get(historicalNodeKey(level, index));
|
|
178
|
+
}
|
|
179
|
+
// the node has been modified since this snapshot was taken
|
|
180
|
+
// because we're working with an AppendOnly tree, historical leaves never change
|
|
181
|
+
// so what we do instead is rebuild this Merkle path up using zero hashes as needed
|
|
182
|
+
// worst case this will do O(H) hashes
|
|
183
|
+
//
|
|
184
|
+
// we first check if this subtree was touched by the block
|
|
185
|
+
// compare how many leaves this block added to the leaf interval of this subtree
|
|
186
|
+
// if they don't intersect then the whole subtree was a hash of zero
|
|
187
|
+
// if they do then we need to rebuild the merkle tree
|
|
188
|
+
const depth = this.tree.getDepth();
|
|
189
|
+
const leafStart = index * 2n ** BigInt(depth - level);
|
|
190
|
+
if (leafStart >= this.leafCount) {
|
|
191
|
+
return this.tree.getZeroHash(level);
|
|
192
|
+
}
|
|
193
|
+
const [lhs, rhs] = [
|
|
194
|
+
this.#getHistoricalNodeValue(level + 1, 2n * index),
|
|
195
|
+
this.#getHistoricalNodeValue(level + 1, 2n * index + 1n)
|
|
196
|
+
];
|
|
197
|
+
return this.hasher.hash(lhs, rhs);
|
|
198
|
+
}
|
|
199
|
+
#getBlockNumberThatModifiedNode(level, index) {
|
|
200
|
+
return this.nodeHistory.get(nodeModifiedAtBlockKey(level, index));
|
|
201
|
+
}
|
|
202
|
+
findLeafIndex(value) {
|
|
203
|
+
return this.findLeafIndexAfter(value, 0n);
|
|
204
|
+
}
|
|
205
|
+
findLeafIndexAfter(value, startIndex) {
|
|
206
|
+
const valueBuffer = serializeToBuffer(value);
|
|
207
|
+
const numLeaves = this.getNumLeaves();
|
|
208
|
+
for(let i = startIndex; i < numLeaves; i++){
|
|
209
|
+
const currentValue = this.getLeafValue(i);
|
|
210
|
+
if (currentValue && serializeToBuffer(currentValue).equals(valueBuffer)) {
|
|
211
|
+
return i;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
3
|
+
import { type Bufferable, type FromBuffer } from '@aztec/foundation/serialize';
|
|
4
|
+
import { SiblingPath } from '@aztec/foundation/trees';
|
|
5
|
+
import type { AztecKVStore, AztecMap } from '@aztec/kv-store';
|
|
6
|
+
import type { TreeBase } from '../tree_base.js';
|
|
7
|
+
import type { TreeSnapshot, TreeSnapshotBuilder } from './snapshot_builder.js';
|
|
8
|
+
/**
|
|
9
|
+
* Metadata for a snapshot, per block
|
|
10
|
+
*/
|
|
11
|
+
type SnapshotMetadata = {
|
|
12
|
+
/** The tree root at the time */
|
|
13
|
+
root: Buffer;
|
|
14
|
+
/** The number of filled leaves */
|
|
15
|
+
numLeaves: bigint;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Builds a full snapshot of a tree. This implementation works for any Merkle tree and stores
|
|
19
|
+
* it in a database in a similar way to how a tree is stored in memory, using pointers.
|
|
20
|
+
*
|
|
21
|
+
* Sharing the same database between versions and trees is recommended as the trees would share
|
|
22
|
+
* structure.
|
|
23
|
+
*
|
|
24
|
+
* Implement the protected method `handleLeaf` to store any additional data you need for each leaf.
|
|
25
|
+
*
|
|
26
|
+
* Complexity:
|
|
27
|
+
* N - count of non-zero nodes in tree
|
|
28
|
+
* M - count of snapshots
|
|
29
|
+
* H - tree height
|
|
30
|
+
* Worst case space complexity: O(N * M)
|
|
31
|
+
* Sibling path access: O(H) database reads
|
|
32
|
+
*/
|
|
33
|
+
export declare abstract class BaseFullTreeSnapshotBuilder<T extends TreeBase<Bufferable>, S extends TreeSnapshot<Bufferable>> implements TreeSnapshotBuilder<S> {
|
|
34
|
+
#private;
|
|
35
|
+
protected db: AztecKVStore;
|
|
36
|
+
protected tree: T;
|
|
37
|
+
protected nodes: AztecMap<string, [Buffer, Buffer]>;
|
|
38
|
+
protected snapshotMetadata: AztecMap<number, SnapshotMetadata>;
|
|
39
|
+
constructor(db: AztecKVStore, tree: T);
|
|
40
|
+
snapshot(block: number): Promise<S>;
|
|
41
|
+
protected handleLeaf(_index: bigint, _node: Buffer): void;
|
|
42
|
+
getSnapshot(version: number): Promise<S>;
|
|
43
|
+
protected abstract openSnapshot(root: Buffer, numLeaves: bigint): S;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A source of sibling paths from a snapshot tree
|
|
47
|
+
*/
|
|
48
|
+
export declare class BaseFullTreeSnapshot<T extends Bufferable> implements TreeSnapshot<T> {
|
|
49
|
+
#private;
|
|
50
|
+
protected db: AztecMap<string, [Buffer, Buffer]>;
|
|
51
|
+
protected historicRoot: Buffer;
|
|
52
|
+
protected numLeaves: bigint;
|
|
53
|
+
protected tree: TreeBase<T>;
|
|
54
|
+
protected deserializer: FromBuffer<T>;
|
|
55
|
+
constructor(db: AztecMap<string, [Buffer, Buffer]>, historicRoot: Buffer, numLeaves: bigint, tree: TreeBase<T>, deserializer: FromBuffer<T>);
|
|
56
|
+
getSiblingPath<N extends number>(index: bigint): SiblingPath<N>;
|
|
57
|
+
getLeafValue(index: bigint): T | undefined;
|
|
58
|
+
getDepth(): number;
|
|
59
|
+
getRoot(): Buffer;
|
|
60
|
+
getNumLeaves(): bigint;
|
|
61
|
+
protected pathFromRootToLeaf(leafIndex: bigint): Generator<Buffer[], void, unknown>;
|
|
62
|
+
findLeafIndex(value: T): bigint | undefined;
|
|
63
|
+
findLeafIndexAfter(value: T, startIndex: bigint): bigint | undefined;
|
|
64
|
+
}
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=base_full_snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base_full_snapshot.d.ts","sourceRoot":"","sources":["../../src/snapshots/base_full_snapshot.ts"],"names":[],"mappings":";;AAAA,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAqB,MAAM,6BAA6B,CAAC;AAClG,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE/E;;GAEG;AACH,KAAK,gBAAgB,GAAG;IACtB,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,8BAAsB,2BAA2B,CAAC,CAAC,SAAS,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,SAAS,YAAY,CAAC,UAAU,CAAC,CAClH,YAAW,mBAAmB,CAAC,CAAC,CAAC;;IAKrB,SAAS,CAAC,EAAE,EAAE,YAAY;IAAE,SAAS,CAAC,IAAI,EAAE,CAAC;IAHzD,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,SAAS,CAAC,gBAAgB,EAAE,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;gBAEzC,EAAE,EAAE,YAAY,EAAY,IAAI,EAAE,CAAC;IAKzD,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAwDnC,SAAS,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAEzD,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAUxC,SAAS,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,CAAC;CAKpE;AAED;;GAEG;AACH,qBAAa,oBAAoB,CAAC,CAAC,SAAS,UAAU,CAAE,YAAW,YAAY,CAAC,CAAC,CAAC;;IAE9E,SAAS,CAAC,EAAE,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD,SAAS,CAAC,YAAY,EAAE,MAAM;IAC9B,SAAS,CAAC,SAAS,EAAE,MAAM;IAC3B,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC3B,SAAS,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC,CAAC;gBAJ3B,EAAE,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACtC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EACjB,YAAY,EAAE,UAAU,CAAC,CAAC,CAAC;IAGvC,cAAc,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC;IAc/D,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAS1C,QAAQ,IAAI,MAAM;IAIlB,OAAO,IAAI,MAAM;IAIjB,YAAY,IAAI,MAAM;IAItB,SAAS,CAAE,kBAAkB,CAAC,SAAS,EAAE,MAAM;IAwC/C,aAAa,CAAC,KAAK,EAAE,CAAC,GAAG,MAAM,GAAG,SAAS;IAIpC,kBAAkB,CAAC,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;CAW5E"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { serializeToBuffer } from '@aztec/foundation/serialize';
|
|
2
|
+
import { SiblingPath } from '@aztec/foundation/trees';
|
|
3
|
+
/**
|
|
4
|
+
* Builds a full snapshot of a tree. This implementation works for any Merkle tree and stores
|
|
5
|
+
* it in a database in a similar way to how a tree is stored in memory, using pointers.
|
|
6
|
+
*
|
|
7
|
+
* Sharing the same database between versions and trees is recommended as the trees would share
|
|
8
|
+
* structure.
|
|
9
|
+
*
|
|
10
|
+
* Implement the protected method `handleLeaf` to store any additional data you need for each leaf.
|
|
11
|
+
*
|
|
12
|
+
* Complexity:
|
|
13
|
+
* N - count of non-zero nodes in tree
|
|
14
|
+
* M - count of snapshots
|
|
15
|
+
* H - tree height
|
|
16
|
+
* Worst case space complexity: O(N * M)
|
|
17
|
+
* Sibling path access: O(H) database reads
|
|
18
|
+
*/ export class BaseFullTreeSnapshotBuilder {
|
|
19
|
+
db;
|
|
20
|
+
tree;
|
|
21
|
+
nodes;
|
|
22
|
+
snapshotMetadata;
|
|
23
|
+
constructor(db, tree){
|
|
24
|
+
this.db = db;
|
|
25
|
+
this.tree = tree;
|
|
26
|
+
this.nodes = db.openMap(`full_snapshot:${tree.getName()}:node`);
|
|
27
|
+
this.snapshotMetadata = db.openMap(`full_snapshot:${tree.getName()}:metadata`);
|
|
28
|
+
}
|
|
29
|
+
snapshot(block) {
|
|
30
|
+
return this.db.transaction(()=>{
|
|
31
|
+
const snapshotMetadata = this.#getSnapshotMeta(block);
|
|
32
|
+
if (snapshotMetadata) {
|
|
33
|
+
return this.openSnapshot(snapshotMetadata.root, snapshotMetadata.numLeaves);
|
|
34
|
+
}
|
|
35
|
+
const root = this.tree.getRoot(false);
|
|
36
|
+
const numLeaves = this.tree.getNumLeaves(false);
|
|
37
|
+
const depth = this.tree.getDepth();
|
|
38
|
+
const queue = [
|
|
39
|
+
[
|
|
40
|
+
root,
|
|
41
|
+
0,
|
|
42
|
+
0n
|
|
43
|
+
]
|
|
44
|
+
];
|
|
45
|
+
// walk the tree breadth-first and store each of its nodes in the database
|
|
46
|
+
// for each node we save two keys
|
|
47
|
+
// <node hash>:0 -> <left child's hash>
|
|
48
|
+
// <node hash>:1 -> <right child's hash>
|
|
49
|
+
while(queue.length > 0){
|
|
50
|
+
const [node, level, i] = queue.shift();
|
|
51
|
+
const nodeKey = node.toString('hex');
|
|
52
|
+
// check if the database already has a child for this tree
|
|
53
|
+
// if it does, then we know we've seen the whole subtree below it before
|
|
54
|
+
// and we don't have to traverse it anymore
|
|
55
|
+
// we use the left child here, but it could be anything that shows we've stored the node before
|
|
56
|
+
if (this.nodes.has(nodeKey)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (level + 1 > depth) {
|
|
60
|
+
// short circuit if we've reached the leaf level
|
|
61
|
+
// otherwise getNode might throw if we ask for the children of a leaf
|
|
62
|
+
this.handleLeaf(i, node);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const [lhs, rhs] = [
|
|
66
|
+
this.tree.getNode(level + 1, 2n * i),
|
|
67
|
+
this.tree.getNode(level + 1, 2n * i + 1n)
|
|
68
|
+
];
|
|
69
|
+
// we want the zero hash at the children's level, not the node's level
|
|
70
|
+
const zeroHash = this.tree.getZeroHash(level + 1);
|
|
71
|
+
void this.nodes.set(nodeKey, [
|
|
72
|
+
lhs ?? zeroHash,
|
|
73
|
+
rhs ?? zeroHash
|
|
74
|
+
]);
|
|
75
|
+
// enqueue the children only if they're not zero hashes
|
|
76
|
+
if (lhs) {
|
|
77
|
+
queue.push([
|
|
78
|
+
lhs,
|
|
79
|
+
level + 1,
|
|
80
|
+
2n * i
|
|
81
|
+
]);
|
|
82
|
+
}
|
|
83
|
+
if (rhs) {
|
|
84
|
+
queue.push([
|
|
85
|
+
rhs,
|
|
86
|
+
level + 1,
|
|
87
|
+
2n * i + 1n
|
|
88
|
+
]);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
void this.snapshotMetadata.set(block, {
|
|
92
|
+
root,
|
|
93
|
+
numLeaves
|
|
94
|
+
});
|
|
95
|
+
return this.openSnapshot(root, numLeaves);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
handleLeaf(_index, _node) {}
|
|
99
|
+
getSnapshot(version) {
|
|
100
|
+
const snapshotMetadata = this.#getSnapshotMeta(version);
|
|
101
|
+
if (!snapshotMetadata) {
|
|
102
|
+
return Promise.reject(new Error(`Version ${version} does not exist for tree ${this.tree.getName()}`));
|
|
103
|
+
}
|
|
104
|
+
return Promise.resolve(this.openSnapshot(snapshotMetadata.root, snapshotMetadata.numLeaves));
|
|
105
|
+
}
|
|
106
|
+
#getSnapshotMeta(block) {
|
|
107
|
+
return this.snapshotMetadata.get(block);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* A source of sibling paths from a snapshot tree
|
|
112
|
+
*/ export class BaseFullTreeSnapshot {
|
|
113
|
+
db;
|
|
114
|
+
historicRoot;
|
|
115
|
+
numLeaves;
|
|
116
|
+
tree;
|
|
117
|
+
deserializer;
|
|
118
|
+
constructor(db, historicRoot, numLeaves, tree, deserializer){
|
|
119
|
+
this.db = db;
|
|
120
|
+
this.historicRoot = historicRoot;
|
|
121
|
+
this.numLeaves = numLeaves;
|
|
122
|
+
this.tree = tree;
|
|
123
|
+
this.deserializer = deserializer;
|
|
124
|
+
}
|
|
125
|
+
getSiblingPath(index) {
|
|
126
|
+
const siblings = [];
|
|
127
|
+
for (const [_node, sibling] of this.pathFromRootToLeaf(index)){
|
|
128
|
+
siblings.push(sibling);
|
|
129
|
+
}
|
|
130
|
+
// we got the siblings we were looking for, but they are in root-leaf order
|
|
131
|
+
// reverse them here so we have leaf-root (what SiblingPath expects)
|
|
132
|
+
siblings.reverse();
|
|
133
|
+
return new SiblingPath(this.tree.getDepth(), siblings);
|
|
134
|
+
}
|
|
135
|
+
getLeafValue(index) {
|
|
136
|
+
let leafNode = undefined;
|
|
137
|
+
for (const [node, _sibling] of this.pathFromRootToLeaf(index)){
|
|
138
|
+
leafNode = node;
|
|
139
|
+
}
|
|
140
|
+
return leafNode ? this.deserializer.fromBuffer(leafNode) : undefined;
|
|
141
|
+
}
|
|
142
|
+
getDepth() {
|
|
143
|
+
return this.tree.getDepth();
|
|
144
|
+
}
|
|
145
|
+
getRoot() {
|
|
146
|
+
return this.historicRoot;
|
|
147
|
+
}
|
|
148
|
+
getNumLeaves() {
|
|
149
|
+
return this.numLeaves;
|
|
150
|
+
}
|
|
151
|
+
*pathFromRootToLeaf(leafIndex) {
|
|
152
|
+
const root = this.historicRoot;
|
|
153
|
+
const pathFromRoot = this.#getPathFromRoot(leafIndex);
|
|
154
|
+
let node = root;
|
|
155
|
+
for(let i = 0; i < pathFromRoot.length; i++){
|
|
156
|
+
// get both children. We'll need both anyway (one to keep track of, the other to walk down to)
|
|
157
|
+
const children = this.db.get(node.toString('hex')) ?? [
|
|
158
|
+
this.tree.getZeroHash(i + 1),
|
|
159
|
+
this.tree.getZeroHash(i + 1)
|
|
160
|
+
];
|
|
161
|
+
const next = children[pathFromRoot[i]];
|
|
162
|
+
const sibling = children[(pathFromRoot[i] + 1) % 2];
|
|
163
|
+
yield [
|
|
164
|
+
next,
|
|
165
|
+
sibling
|
|
166
|
+
];
|
|
167
|
+
node = next;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Calculates the path from the root to the target leaf. Returns an array of 0s and 1s,
|
|
172
|
+
* each 0 represents walking down a left child and each 1 walking down to the child on the right.
|
|
173
|
+
*
|
|
174
|
+
* @param leafIndex - The target leaf
|
|
175
|
+
* @returns An array of 0s and 1s
|
|
176
|
+
*/ #getPathFromRoot(leafIndex) {
|
|
177
|
+
const path = [];
|
|
178
|
+
let level = this.tree.getDepth();
|
|
179
|
+
while(level > 0){
|
|
180
|
+
path.push(leafIndex & 0x01n ? 1 : 0);
|
|
181
|
+
leafIndex >>= 1n;
|
|
182
|
+
level--;
|
|
183
|
+
}
|
|
184
|
+
path.reverse();
|
|
185
|
+
return path;
|
|
186
|
+
}
|
|
187
|
+
findLeafIndex(value) {
|
|
188
|
+
return this.findLeafIndexAfter(value, 0n);
|
|
189
|
+
}
|
|
190
|
+
findLeafIndexAfter(value, startIndex) {
|
|
191
|
+
const numLeaves = this.getNumLeaves();
|
|
192
|
+
const buffer = serializeToBuffer(value);
|
|
193
|
+
for(let i = startIndex; i < numLeaves; i++){
|
|
194
|
+
const currentValue = this.getLeafValue(i);
|
|
195
|
+
if (currentValue && serializeToBuffer(currentValue).equals(buffer)) {
|
|
196
|
+
return i;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
3
|
+
import type { Bufferable, FromBuffer } from '@aztec/foundation/serialize';
|
|
4
|
+
import type { AztecKVStore } from '@aztec/kv-store';
|
|
5
|
+
import type { TreeBase } from '../tree_base.js';
|
|
6
|
+
import { BaseFullTreeSnapshotBuilder } from './base_full_snapshot.js';
|
|
7
|
+
import type { TreeSnapshot, TreeSnapshotBuilder } from './snapshot_builder.js';
|
|
8
|
+
/**
|
|
9
|
+
* Builds a full snapshot of a tree. This implementation works for any Merkle tree and stores
|
|
10
|
+
* it in a database in a similar way to how a tree is stored in memory, using pointers.
|
|
11
|
+
*
|
|
12
|
+
* Sharing the same database between versions and trees is recommended as the trees would share
|
|
13
|
+
* structure.
|
|
14
|
+
*
|
|
15
|
+
* Complexity:
|
|
16
|
+
* N - count of non-zero nodes in tree
|
|
17
|
+
* M - count of snapshots
|
|
18
|
+
* H - tree height
|
|
19
|
+
* Worst case space complexity: O(N * M)
|
|
20
|
+
* Sibling path access: O(H) database reads
|
|
21
|
+
*/
|
|
22
|
+
export declare class FullTreeSnapshotBuilder<T extends Bufferable> extends BaseFullTreeSnapshotBuilder<TreeBase<T>, TreeSnapshot<T>> implements TreeSnapshotBuilder<TreeSnapshot<T>> {
|
|
23
|
+
private deserializer;
|
|
24
|
+
constructor(db: AztecKVStore, tree: TreeBase<T>, deserializer: FromBuffer<T>);
|
|
25
|
+
protected openSnapshot(root: Buffer, numLeaves: bigint): TreeSnapshot<T>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=full_snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"full_snapshot.d.ts","sourceRoot":"","sources":["../../src/snapshots/full_snapshot.ts"],"names":[],"mappings":";;AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAwB,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAC5F,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE/E;;;;;;;;;;;;;GAaG;AACH,qBAAa,uBAAuB,CAAC,CAAC,SAAS,UAAU,CACvD,SAAQ,2BAA2B,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAChE,YAAW,mBAAmB,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAEE,OAAO,CAAC,YAAY;gBAAzD,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAU,YAAY,EAAE,UAAU,CAAC,CAAC,CAAC;IAIpF,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC;CAGzE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BaseFullTreeSnapshot, BaseFullTreeSnapshotBuilder } from './base_full_snapshot.js';
|
|
2
|
+
/**
|
|
3
|
+
* Builds a full snapshot of a tree. This implementation works for any Merkle tree and stores
|
|
4
|
+
* it in a database in a similar way to how a tree is stored in memory, using pointers.
|
|
5
|
+
*
|
|
6
|
+
* Sharing the same database between versions and trees is recommended as the trees would share
|
|
7
|
+
* structure.
|
|
8
|
+
*
|
|
9
|
+
* Complexity:
|
|
10
|
+
* N - count of non-zero nodes in tree
|
|
11
|
+
* M - count of snapshots
|
|
12
|
+
* H - tree height
|
|
13
|
+
* Worst case space complexity: O(N * M)
|
|
14
|
+
* Sibling path access: O(H) database reads
|
|
15
|
+
*/ export class FullTreeSnapshotBuilder extends BaseFullTreeSnapshotBuilder {
|
|
16
|
+
deserializer;
|
|
17
|
+
constructor(db, tree, deserializer){
|
|
18
|
+
super(db, tree), this.deserializer = deserializer;
|
|
19
|
+
}
|
|
20
|
+
openSnapshot(root, numLeaves) {
|
|
21
|
+
return new BaseFullTreeSnapshot(this.nodes, root, numLeaves, this.tree, this.deserializer);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
3
|
+
import type { AztecKVStore, AztecMap } from '@aztec/kv-store';
|
|
4
|
+
import type { IndexedTree, PreimageFactory } from '../interfaces/indexed_tree.js';
|
|
5
|
+
import type { TreeBase } from '../tree_base.js';
|
|
6
|
+
import { BaseFullTreeSnapshotBuilder } from './base_full_snapshot.js';
|
|
7
|
+
import type { IndexedTreeSnapshot, TreeSnapshotBuilder } from './snapshot_builder.js';
|
|
8
|
+
/** a */
|
|
9
|
+
export declare class IndexedTreeSnapshotBuilder extends BaseFullTreeSnapshotBuilder<IndexedTree & TreeBase<Buffer>, IndexedTreeSnapshot> implements TreeSnapshotBuilder<IndexedTreeSnapshot> {
|
|
10
|
+
private leafPreimageBuilder;
|
|
11
|
+
leaves: AztecMap<string, Buffer>;
|
|
12
|
+
constructor(store: AztecKVStore, tree: IndexedTree & TreeBase<Buffer>, leafPreimageBuilder: PreimageFactory);
|
|
13
|
+
protected openSnapshot(root: Buffer, numLeaves: bigint): IndexedTreeSnapshot;
|
|
14
|
+
protected handleLeaf(index: bigint, node: Buffer): void;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=indexed_tree_snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexed_tree_snapshot.d.ts","sourceRoot":"","sources":["../../src/snapshots/indexed_tree_snapshot.ts"],"names":[],"mappings":";;AACA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE9D,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAClF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAwB,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAC5F,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAItF,QAAQ;AACR,qBAAa,0BACX,SAAQ,2BAA2B,CAAC,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAE,mBAAmB,CACvF,YAAW,mBAAmB,CAAC,mBAAmB,CAAC;IAGoB,OAAO,CAAC,mBAAmB;IADlG,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBACrB,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAU,mBAAmB,EAAE,eAAe;IAKnH,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,mBAAmB;cAIzD,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAM1D"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { BaseFullTreeSnapshot, BaseFullTreeSnapshotBuilder } from './base_full_snapshot.js';
|
|
2
|
+
const snapshotLeafValue = (node, index)=>'snapshot:leaf:' + node.toString('hex') + ':' + index;
|
|
3
|
+
/** a */ export class IndexedTreeSnapshotBuilder extends BaseFullTreeSnapshotBuilder {
|
|
4
|
+
leafPreimageBuilder;
|
|
5
|
+
leaves;
|
|
6
|
+
constructor(store, tree, leafPreimageBuilder){
|
|
7
|
+
super(store, tree), this.leafPreimageBuilder = leafPreimageBuilder;
|
|
8
|
+
this.leaves = store.openMap('indexed_tree_snapshot:' + tree.getName());
|
|
9
|
+
}
|
|
10
|
+
openSnapshot(root, numLeaves) {
|
|
11
|
+
return new IndexedTreeSnapshotImpl(this.nodes, this.leaves, root, numLeaves, this.tree, this.leafPreimageBuilder);
|
|
12
|
+
}
|
|
13
|
+
handleLeaf(index, node) {
|
|
14
|
+
const leafPreimage = this.tree.getLatestLeafPreimageCopy(index, false);
|
|
15
|
+
if (leafPreimage) {
|
|
16
|
+
void this.leaves.set(snapshotLeafValue(node, index), leafPreimage.toBuffer());
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** A snapshot of an indexed tree at a particular point in time */ class IndexedTreeSnapshotImpl extends BaseFullTreeSnapshot {
|
|
21
|
+
leaves;
|
|
22
|
+
leafPreimageBuilder;
|
|
23
|
+
constructor(db, leaves, historicRoot, numLeaves, tree, leafPreimageBuilder){
|
|
24
|
+
super(db, historicRoot, numLeaves, tree, {
|
|
25
|
+
fromBuffer: (buf)=>buf
|
|
26
|
+
}), this.leaves = leaves, this.leafPreimageBuilder = leafPreimageBuilder;
|
|
27
|
+
}
|
|
28
|
+
getLeafValue(index) {
|
|
29
|
+
const leafPreimage = this.getLatestLeafPreimageCopy(index);
|
|
30
|
+
return leafPreimage?.toBuffer();
|
|
31
|
+
}
|
|
32
|
+
getLatestLeafPreimageCopy(index) {
|
|
33
|
+
const leafNode = super.getLeafValue(index);
|
|
34
|
+
const leafValue = this.leaves.get(snapshotLeafValue(leafNode, index));
|
|
35
|
+
if (leafValue) {
|
|
36
|
+
return this.leafPreimageBuilder.fromBuffer(leafValue);
|
|
37
|
+
} else {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
findIndexOfPreviousKey(newValue) {
|
|
42
|
+
const numLeaves = this.getNumLeaves();
|
|
43
|
+
const diff = [];
|
|
44
|
+
for(let i = 0; i < numLeaves; i++){
|
|
45
|
+
// this is very inefficient
|
|
46
|
+
const storedLeaf = this.getLatestLeafPreimageCopy(BigInt(i));
|
|
47
|
+
// The stored leaf can be undefined if it addresses an empty leaf
|
|
48
|
+
// If the leaf is empty we do the same as if the leaf was larger
|
|
49
|
+
if (storedLeaf === undefined) {
|
|
50
|
+
diff.push(newValue);
|
|
51
|
+
} else if (storedLeaf.getKey() > newValue) {
|
|
52
|
+
diff.push(newValue);
|
|
53
|
+
} else if (storedLeaf.getKey() === newValue) {
|
|
54
|
+
return {
|
|
55
|
+
index: BigInt(i),
|
|
56
|
+
alreadyPresent: true
|
|
57
|
+
};
|
|
58
|
+
} else {
|
|
59
|
+
diff.push(newValue - storedLeaf.getKey());
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
let minIndex = 0;
|
|
63
|
+
for(let i = 1; i < diff.length; i++){
|
|
64
|
+
if (diff[i] < diff[minIndex]) {
|
|
65
|
+
minIndex = i;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
index: BigInt(minIndex),
|
|
70
|
+
alreadyPresent: false
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
findLeafIndex(value) {
|
|
74
|
+
const index = this.tree.findLeafIndex(value, false);
|
|
75
|
+
if (index !== undefined && index < this.getNumLeaves()) {
|
|
76
|
+
return index;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|