@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.
Files changed (94) hide show
  1. package/README.md +41 -0
  2. package/dest/hasher_with_stats.d.ts +22 -0
  3. package/dest/hasher_with_stats.d.ts.map +1 -0
  4. package/dest/hasher_with_stats.js +40 -0
  5. package/dest/index.d.ts +20 -0
  6. package/dest/index.d.ts.map +1 -0
  7. package/dest/index.js +19 -0
  8. package/dest/interfaces/append_only_tree.d.ts +16 -0
  9. package/dest/interfaces/append_only_tree.d.ts.map +1 -0
  10. package/dest/interfaces/append_only_tree.js +3 -0
  11. package/dest/interfaces/indexed_tree.d.ts +69 -0
  12. package/dest/interfaces/indexed_tree.d.ts.map +1 -0
  13. package/dest/interfaces/indexed_tree.js +3 -0
  14. package/dest/interfaces/merkle_tree.d.ts +64 -0
  15. package/dest/interfaces/merkle_tree.d.ts.map +1 -0
  16. package/dest/interfaces/merkle_tree.js +3 -0
  17. package/dest/interfaces/update_only_tree.d.ts +17 -0
  18. package/dest/interfaces/update_only_tree.d.ts.map +1 -0
  19. package/dest/interfaces/update_only_tree.js +3 -0
  20. package/dest/load_tree.d.ts +16 -0
  21. package/dest/load_tree.d.ts.map +1 -0
  22. package/dest/load_tree.js +13 -0
  23. package/dest/new_tree.d.ts +16 -0
  24. package/dest/new_tree.d.ts.map +1 -0
  25. package/dest/new_tree.js +14 -0
  26. package/dest/pedersen.d.ts +13 -0
  27. package/dest/pedersen.d.ts.map +1 -0
  28. package/dest/pedersen.js +24 -0
  29. package/dest/poseidon.d.ts +13 -0
  30. package/dest/poseidon.d.ts.map +1 -0
  31. package/dest/poseidon.js +24 -0
  32. package/dest/sha_256.d.ts +22 -0
  33. package/dest/sha_256.d.ts.map +1 -0
  34. package/dest/sha_256.js +44 -0
  35. package/dest/snapshots/append_only_snapshot.d.ts +32 -0
  36. package/dest/snapshots/append_only_snapshot.d.ts.map +1 -0
  37. package/dest/snapshots/append_only_snapshot.js +216 -0
  38. package/dest/snapshots/base_full_snapshot.d.ts +66 -0
  39. package/dest/snapshots/base_full_snapshot.d.ts.map +1 -0
  40. package/dest/snapshots/base_full_snapshot.js +201 -0
  41. package/dest/snapshots/full_snapshot.d.ts +27 -0
  42. package/dest/snapshots/full_snapshot.d.ts.map +1 -0
  43. package/dest/snapshots/full_snapshot.js +23 -0
  44. package/dest/snapshots/indexed_tree_snapshot.d.ts +16 -0
  45. package/dest/snapshots/indexed_tree_snapshot.d.ts.map +1 -0
  46. package/dest/snapshots/indexed_tree_snapshot.js +79 -0
  47. package/dest/snapshots/snapshot_builder.d.ts +84 -0
  48. package/dest/snapshots/snapshot_builder.d.ts.map +1 -0
  49. package/dest/snapshots/snapshot_builder.js +1 -0
  50. package/dest/snapshots/snapshot_builder_test_suite.d.ts +6 -0
  51. package/dest/snapshots/snapshot_builder_test_suite.d.ts.map +1 -0
  52. package/dest/snapshots/snapshot_builder_test_suite.js +167 -0
  53. package/dest/sparse_tree/sparse_tree.d.ts +21 -0
  54. package/dest/sparse_tree/sparse_tree.d.ts.map +1 -0
  55. package/dest/sparse_tree/sparse_tree.js +44 -0
  56. package/dest/standard_indexed_tree/standard_indexed_tree.d.ts +291 -0
  57. package/dest/standard_indexed_tree/standard_indexed_tree.d.ts.map +1 -0
  58. package/dest/standard_indexed_tree/standard_indexed_tree.js +478 -0
  59. package/dest/standard_indexed_tree/test/standard_indexed_tree_with_append.d.ts +25 -0
  60. package/dest/standard_indexed_tree/test/standard_indexed_tree_with_append.d.ts.map +1 -0
  61. package/dest/standard_indexed_tree/test/standard_indexed_tree_with_append.js +56 -0
  62. package/dest/standard_tree/standard_tree.d.ts +23 -0
  63. package/dest/standard_tree/standard_tree.d.ts.map +1 -0
  64. package/dest/standard_tree/standard_tree.js +47 -0
  65. package/dest/tree_base.d.ts +158 -0
  66. package/dest/tree_base.d.ts.map +1 -0
  67. package/dest/tree_base.js +290 -0
  68. package/dest/unbalanced_tree.d.ts +109 -0
  69. package/dest/unbalanced_tree.d.ts.map +1 -0
  70. package/dest/unbalanced_tree.js +208 -0
  71. package/package.json +85 -0
  72. package/src/hasher_with_stats.ts +51 -0
  73. package/src/index.ts +19 -0
  74. package/src/interfaces/append_only_tree.ts +17 -0
  75. package/src/interfaces/indexed_tree.ts +83 -0
  76. package/src/interfaces/merkle_tree.ts +70 -0
  77. package/src/interfaces/update_only_tree.ts +18 -0
  78. package/src/load_tree.ts +33 -0
  79. package/src/new_tree.ts +29 -0
  80. package/src/pedersen.ts +27 -0
  81. package/src/poseidon.ts +27 -0
  82. package/src/sha_256.ts +49 -0
  83. package/src/snapshots/append_only_snapshot.ts +278 -0
  84. package/src/snapshots/base_full_snapshot.ts +222 -0
  85. package/src/snapshots/full_snapshot.ts +33 -0
  86. package/src/snapshots/indexed_tree_snapshot.ts +108 -0
  87. package/src/snapshots/snapshot_builder.ts +92 -0
  88. package/src/snapshots/snapshot_builder_test_suite.ts +226 -0
  89. package/src/sparse_tree/sparse_tree.ts +56 -0
  90. package/src/standard_indexed_tree/standard_indexed_tree.ts +640 -0
  91. package/src/standard_indexed_tree/test/standard_indexed_tree_with_append.ts +81 -0
  92. package/src/standard_tree/standard_tree.ts +60 -0
  93. package/src/tree_base.ts +359 -0
  94. 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
+ }