@aztec/merkle-tree 0.30.1 → 0.32.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 (64) hide show
  1. package/dest/interfaces/append_only_tree.d.ts +4 -3
  2. package/dest/interfaces/append_only_tree.d.ts.map +1 -1
  3. package/dest/interfaces/indexed_tree.d.ts +3 -1
  4. package/dest/interfaces/indexed_tree.d.ts.map +1 -1
  5. package/dest/interfaces/merkle_tree.d.ts +12 -3
  6. package/dest/interfaces/merkle_tree.d.ts.map +1 -1
  7. package/dest/interfaces/update_only_tree.d.ts +4 -3
  8. package/dest/interfaces/update_only_tree.d.ts.map +1 -1
  9. package/dest/load_tree.d.ts +2 -1
  10. package/dest/load_tree.d.ts.map +1 -1
  11. package/dest/load_tree.js +3 -3
  12. package/dest/new_tree.d.ts +2 -1
  13. package/dest/new_tree.d.ts.map +1 -1
  14. package/dest/new_tree.js +3 -3
  15. package/dest/sha_256.d.ts +9 -0
  16. package/dest/sha_256.d.ts.map +1 -1
  17. package/dest/sha_256.js +23 -1
  18. package/dest/snapshots/append_only_snapshot.d.ts +6 -4
  19. package/dest/snapshots/append_only_snapshot.d.ts.map +1 -1
  20. package/dest/snapshots/append_only_snapshot.js +17 -9
  21. package/dest/snapshots/base_full_snapshot.d.ts +9 -6
  22. package/dest/snapshots/base_full_snapshot.d.ts.map +1 -1
  23. package/dest/snapshots/base_full_snapshot.js +11 -5
  24. package/dest/snapshots/full_snapshot.d.ts +6 -2
  25. package/dest/snapshots/full_snapshot.d.ts.map +1 -1
  26. package/dest/snapshots/full_snapshot.js +6 -2
  27. package/dest/snapshots/indexed_tree_snapshot.d.ts +2 -2
  28. package/dest/snapshots/indexed_tree_snapshot.d.ts.map +1 -1
  29. package/dest/snapshots/indexed_tree_snapshot.js +2 -2
  30. package/dest/snapshots/snapshot_builder.d.ts +13 -5
  31. package/dest/snapshots/snapshot_builder.d.ts.map +1 -1
  32. package/dest/snapshots/snapshot_builder_test_suite.d.ts +3 -2
  33. package/dest/snapshots/snapshot_builder_test_suite.d.ts.map +1 -1
  34. package/dest/snapshots/snapshot_builder_test_suite.js +5 -2
  35. package/dest/sparse_tree/sparse_tree.d.ts +7 -6
  36. package/dest/sparse_tree/sparse_tree.d.ts.map +1 -1
  37. package/dest/sparse_tree/sparse_tree.js +9 -4
  38. package/dest/standard_indexed_tree/standard_indexed_tree.d.ts +4 -1
  39. package/dest/standard_indexed_tree/standard_indexed_tree.d.ts.map +1 -1
  40. package/dest/standard_indexed_tree/standard_indexed_tree.js +8 -2
  41. package/dest/standard_tree/standard_tree.d.ts +7 -5
  42. package/dest/standard_tree/standard_tree.d.ts.map +1 -1
  43. package/dest/standard_tree/standard_tree.js +9 -4
  44. package/dest/tree_base.d.ts +22 -5
  45. package/dest/tree_base.d.ts.map +1 -1
  46. package/dest/tree_base.js +20 -3
  47. package/package.json +5 -5
  48. package/src/interfaces/append_only_tree.ts +7 -3
  49. package/src/interfaces/indexed_tree.ts +6 -1
  50. package/src/interfaces/merkle_tree.ts +13 -3
  51. package/src/interfaces/update_only_tree.ts +7 -3
  52. package/src/load_tree.ts +13 -3
  53. package/src/new_tree.ts +5 -3
  54. package/src/sha_256.ts +24 -0
  55. package/src/snapshots/append_only_snapshot.ts +27 -11
  56. package/src/snapshots/base_full_snapshot.ts +15 -8
  57. package/src/snapshots/full_snapshot.ts +12 -5
  58. package/src/snapshots/indexed_tree_snapshot.ts +5 -5
  59. package/src/snapshots/snapshot_builder.ts +14 -5
  60. package/src/snapshots/snapshot_builder_test_suite.ts +13 -7
  61. package/src/sparse_tree/sparse_tree.ts +14 -7
  62. package/src/standard_indexed_tree/standard_indexed_tree.ts +11 -2
  63. package/src/standard_tree/standard_tree.ts +14 -8
  64. package/src/tree_base.ts +31 -5
@@ -1,14 +1,18 @@
1
1
  import { randomBigInt } from '@aztec/foundation/crypto';
2
+ import { Bufferable } from '@aztec/foundation/serialize';
3
+
4
+ import { jest } from '@jest/globals';
2
5
 
3
6
  import { TreeBase } from '../tree_base.js';
4
- import { TreeSnapshotBuilder } from './snapshot_builder.js';
7
+ import { TreeSnapshot, TreeSnapshotBuilder } from './snapshot_builder.js';
8
+
9
+ jest.setTimeout(50_000);
5
10
 
6
11
  /** Creates a test suit for snapshots */
7
- export function describeSnapshotBuilderTestSuite<T extends TreeBase, S extends TreeSnapshotBuilder>(
8
- getTree: () => T,
9
- getSnapshotBuilder: () => S,
10
- modifyTree: (tree: T) => Promise<void>,
11
- ) {
12
+ export function describeSnapshotBuilderTestSuite<
13
+ T extends TreeBase<Bufferable>,
14
+ S extends TreeSnapshotBuilder<TreeSnapshot<Bufferable>>,
15
+ >(getTree: () => T, getSnapshotBuilder: () => S, modifyTree: (tree: T) => Promise<void>) {
12
16
  describe('SnapshotBuilder', () => {
13
17
  let tree: T;
14
18
  let snapshotBuilder: S;
@@ -34,7 +38,9 @@ export function describeSnapshotBuilderTestSuite<T extends TreeBase, S extends T
34
38
 
35
39
  const block = 1;
36
40
  const snapshot = await snapshotBuilder.snapshot(block);
37
- await expect(snapshotBuilder.snapshot(block)).resolves.toEqual(snapshot);
41
+ const newSnapshot = await snapshotBuilder.snapshot(block);
42
+
43
+ expect(newSnapshot.getRoot()).toEqual(snapshot.getRoot());
38
44
  });
39
45
 
40
46
  it('returns the same path if tree has not diverged', async () => {
@@ -1,3 +1,5 @@
1
+ import { Bufferable, serializeToBuffer } from '@aztec/foundation/serialize';
2
+
1
3
  import { UpdateOnlyTree } from '../interfaces/update_only_tree.js';
2
4
  import { FullTreeSnapshotBuilder } from '../snapshots/full_snapshot.js';
3
5
  import { TreeSnapshot } from '../snapshots/snapshot_builder.js';
@@ -6,20 +8,21 @@ import { INITIAL_LEAF, TreeBase } from '../tree_base.js';
6
8
  /**
7
9
  * A Merkle tree implementation that uses a LevelDB database to store the tree.
8
10
  */
9
- export class SparseTree extends TreeBase implements UpdateOnlyTree {
10
- #snapshotBuilder = new FullTreeSnapshotBuilder(this.store, this);
11
+ export class SparseTree<T extends Bufferable> extends TreeBase<T> implements UpdateOnlyTree<T> {
12
+ #snapshotBuilder = new FullTreeSnapshotBuilder(this.store, this, this.deserializer);
11
13
  /**
12
14
  * Updates a leaf in the tree.
13
15
  * @param leaf - New contents of the leaf.
14
16
  * @param index - Index of the leaf to be updated.
15
17
  */
16
- public updateLeaf(leaf: Buffer, index: bigint): Promise<void> {
18
+ public updateLeaf(value: T, index: bigint): Promise<void> {
17
19
  if (index > this.maxIndex) {
18
20
  throw Error(`Index out of bounds. Index ${index}, max index: ${this.maxIndex}.`);
19
21
  }
20
22
 
23
+ const leaf = serializeToBuffer(value);
21
24
  const insertingZeroElement = leaf.equals(INITIAL_LEAF);
22
- const originallyZeroElement = this.getLeafValue(index, true)?.equals(INITIAL_LEAF);
25
+ const originallyZeroElement = this.getLeafBuffer(index, true)?.equals(INITIAL_LEAF);
23
26
  if (insertingZeroElement && originallyZeroElement) {
24
27
  return Promise.resolve();
25
28
  }
@@ -35,15 +38,19 @@ export class SparseTree extends TreeBase implements UpdateOnlyTree {
35
38
  return Promise.resolve();
36
39
  }
37
40
 
38
- public snapshot(block: number): Promise<TreeSnapshot> {
41
+ public snapshot(block: number): Promise<TreeSnapshot<T>> {
39
42
  return this.#snapshotBuilder.snapshot(block);
40
43
  }
41
44
 
42
- public getSnapshot(block: number): Promise<TreeSnapshot> {
45
+ public getSnapshot(block: number): Promise<TreeSnapshot<T>> {
43
46
  return this.#snapshotBuilder.getSnapshot(block);
44
47
  }
45
48
 
46
- public findLeafIndex(_value: Buffer, _includeUncommitted: boolean): bigint | undefined {
49
+ public findLeafIndex(_value: T, _includeUncommitted: boolean): bigint | undefined {
50
+ throw new Error('Finding leaf index is not supported for sparse trees');
51
+ }
52
+
53
+ public findLeafIndexAfter(_value: T, _startIndex: bigint, _includeUncommitted: boolean): bigint | undefined {
47
54
  throw new Error('Finding leaf index is not supported for sparse trees');
48
55
  }
49
56
  }
@@ -1,6 +1,7 @@
1
1
  import { SiblingPath } from '@aztec/circuit-types';
2
2
  import { TreeInsertionStats } from '@aztec/circuit-types/stats';
3
3
  import { toBufferBE } from '@aztec/foundation/bigint-buffer';
4
+ import { FromBuffer } from '@aztec/foundation/serialize';
4
5
  import { Timer } from '@aztec/foundation/timer';
5
6
  import { IndexedTreeLeaf, IndexedTreeLeafPreimage } from '@aztec/foundation/trees';
6
7
  import { AztecKVStore, AztecMap } from '@aztec/kv-store';
@@ -51,10 +52,14 @@ function getEmptyLowLeafWitness<N extends number>(
51
52
  };
52
53
  }
53
54
 
55
+ export const noopDeserializer: FromBuffer<Buffer> = {
56
+ fromBuffer: (buf: Buffer) => buf,
57
+ };
58
+
54
59
  /**
55
60
  * Standard implementation of an indexed tree.
56
61
  */
57
- export class StandardIndexedTree extends TreeBase implements IndexedTree {
62
+ export class StandardIndexedTree extends TreeBase<Buffer> implements IndexedTree {
58
63
  #snapshotBuilder = new IndexedTreeSnapshotBuilder(this.store, this, this.leafPreimageFactory);
59
64
 
60
65
  protected cachedLeafPreimages: { [key: string]: IndexedTreeLeafPreimage } = {};
@@ -71,7 +76,7 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree {
71
76
  protected leafFactory: LeafFactory,
72
77
  root?: Buffer,
73
78
  ) {
74
- super(store, hasher, name, depth, size, root);
79
+ super(store, hasher, name, depth, size, noopDeserializer, root);
75
80
  this.leaves = store.openMap(`tree_${name}_leaves`);
76
81
  this.leafIndex = store.openMap(`tree_${name}_leaf_index`);
77
82
  }
@@ -234,6 +239,10 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree {
234
239
  return index;
235
240
  }
236
241
 
242
+ public findLeafIndexAfter(_leaf: Buffer, _startIndex: bigint, _includeUncommitted: boolean): bigint | undefined {
243
+ throw new Error('Method not implemented for indexed trees');
244
+ }
245
+
237
246
  /**
238
247
  * Initializes the tree.
239
248
  * @param prefilledSize - A number of leaves that are prefilled with values.
@@ -1,4 +1,5 @@
1
1
  import { TreeInsertionStats } from '@aztec/circuit-types/stats';
2
+ import { Bufferable, serializeToBuffer } from '@aztec/foundation/serialize';
2
3
  import { Timer } from '@aztec/foundation/timer';
3
4
 
4
5
  import { AppendOnlyTree } from '../interfaces/append_only_tree.js';
@@ -9,15 +10,15 @@ import { TreeBase } from '../tree_base.js';
9
10
  /**
10
11
  * A Merkle tree implementation that uses a LevelDB database to store the tree.
11
12
  */
12
- export class StandardTree extends TreeBase implements AppendOnlyTree {
13
- #snapshotBuilder = new AppendOnlySnapshotBuilder(this.store, this, this.hasher);
13
+ export class StandardTree<T extends Bufferable = Buffer> extends TreeBase<T> implements AppendOnlyTree<T> {
14
+ #snapshotBuilder = new AppendOnlySnapshotBuilder(this.store, this, this.hasher, this.deserializer);
14
15
 
15
16
  /**
16
17
  * Appends the given leaves to the tree.
17
18
  * @param leaves - The leaves to append.
18
19
  * @returns Empty promise.
19
20
  */
20
- public appendLeaves(leaves: Buffer[]): Promise<void> {
21
+ public appendLeaves(leaves: T[]): Promise<void> {
21
22
  this.hasher.reset();
22
23
  const timer = new Timer();
23
24
  super.appendLeaves(leaves);
@@ -34,18 +35,23 @@ export class StandardTree extends TreeBase implements AppendOnlyTree {
34
35
  return Promise.resolve();
35
36
  }
36
37
 
37
- public snapshot(blockNumber: number): Promise<TreeSnapshot> {
38
+ public snapshot(blockNumber: number): Promise<TreeSnapshot<T>> {
38
39
  return this.#snapshotBuilder.snapshot(blockNumber);
39
40
  }
40
41
 
41
- public getSnapshot(blockNumber: number): Promise<TreeSnapshot> {
42
+ public getSnapshot(blockNumber: number): Promise<TreeSnapshot<T>> {
42
43
  return this.#snapshotBuilder.getSnapshot(blockNumber);
43
44
  }
44
45
 
45
- public findLeafIndex(value: Buffer, includeUncommitted: boolean): bigint | undefined {
46
- for (let i = 0n; i < this.getNumLeaves(includeUncommitted); i++) {
46
+ public findLeafIndex(value: T, includeUncommitted: boolean): bigint | undefined {
47
+ return this.findLeafIndexAfter(value, 0n, includeUncommitted);
48
+ }
49
+
50
+ public findLeafIndexAfter(value: T, startIndex: bigint, includeUncommitted: boolean): bigint | undefined {
51
+ const buffer = serializeToBuffer(value);
52
+ for (let i = startIndex; i < this.getNumLeaves(includeUncommitted); i++) {
47
53
  const currentValue = this.getLeafValue(i, includeUncommitted);
48
- if (currentValue && currentValue.equals(value)) {
54
+ if (currentValue && serializeToBuffer(currentValue).equals(buffer)) {
49
55
  return i;
50
56
  }
51
57
  }
package/src/tree_base.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { SiblingPath } from '@aztec/circuit-types';
2
2
  import { toBigIntLE, toBufferLE } from '@aztec/foundation/bigint-buffer';
3
3
  import { DebugLogger, createDebugLogger } from '@aztec/foundation/log';
4
+ import { Bufferable, FromBuffer, serializeToBuffer } from '@aztec/foundation/serialize';
4
5
  import { AztecKVStore, AztecMap, AztecSingleton } from '@aztec/kv-store';
5
6
  import { Hasher } from '@aztec/types/interfaces';
6
7
 
@@ -44,7 +45,7 @@ export const INITIAL_LEAF = Buffer.from('000000000000000000000000000000000000000
44
45
  /**
45
46
  * A Merkle tree implementation that uses a LevelDB database to store the tree.
46
47
  */
47
- export abstract class TreeBase implements MerkleTree {
48
+ export abstract class TreeBase<T extends Bufferable> implements MerkleTree<T> {
48
49
  protected readonly maxIndex: bigint;
49
50
  protected cachedSize?: bigint;
50
51
  private root!: Buffer;
@@ -62,6 +63,7 @@ export abstract class TreeBase implements MerkleTree {
62
63
  private name: string,
63
64
  private depth: number,
64
65
  protected size: bigint = 0n,
66
+ protected deserializer: FromBuffer<T>,
65
67
  root?: Buffer,
66
68
  ) {
67
69
  if (!(depth >= 1 && depth <= MAX_DEPTH)) {
@@ -173,7 +175,22 @@ export abstract class TreeBase implements MerkleTree {
173
175
  * @param includeUncommitted - Indicates whether to include uncommitted changes.
174
176
  * @returns Leaf value at the given index or undefined.
175
177
  */
176
- public getLeafValue(index: bigint, includeUncommitted: boolean): Buffer | undefined {
178
+ public getLeafValue(index: bigint, includeUncommitted: boolean): T | undefined {
179
+ const buf = this.getLatestValueAtIndex(this.depth, index, includeUncommitted);
180
+ if (buf) {
181
+ return this.deserializer.fromBuffer(buf);
182
+ } else {
183
+ return undefined;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Gets the value at the given index.
189
+ * @param index - The index of the leaf.
190
+ * @param includeUncommitted - Indicates whether to include uncommitted changes.
191
+ * @returns Leaf value at the given index or undefined.
192
+ */
193
+ public getLeafBuffer(index: bigint, includeUncommitted: boolean): Buffer | undefined {
177
194
  return this.getLatestValueAtIndex(this.depth, index, includeUncommitted);
178
195
  }
179
196
 
@@ -292,7 +309,7 @@ export abstract class TreeBase implements MerkleTree {
292
309
  * `getLatestValueAtIndex` will return a value from cache (because at least one of the 2 children was
293
310
  * touched in previous iteration).
294
311
  */
295
- protected appendLeaves(leaves: Buffer[]): void {
312
+ protected appendLeaves(leaves: T[]): void {
296
313
  const numLeaves = this.getNumLeaves(true);
297
314
  if (numLeaves + BigInt(leaves.length) - 1n > this.maxIndex) {
298
315
  throw Error(`Can't append beyond max index. Max index: ${this.maxIndex}`);
@@ -303,7 +320,7 @@ export abstract class TreeBase implements MerkleTree {
303
320
  let level = this.depth;
304
321
  for (let i = 0; i < leaves.length; i++) {
305
322
  const cacheKey = indexToKeyHash(this.name, level, firstIndex + BigInt(i));
306
- this.cache[cacheKey] = leaves[i];
323
+ this.cache[cacheKey] = serializeToBuffer(leaves[i]);
307
324
  }
308
325
 
309
326
  let lastIndex = firstIndex + BigInt(leaves.length);
@@ -330,5 +347,14 @@ export abstract class TreeBase implements MerkleTree {
330
347
  * @param includeUncommitted - Indicates whether to include uncommitted data.
331
348
  * @returns The index of the first leaf found with a given value (undefined if not found).
332
349
  */
333
- abstract findLeafIndex(value: Buffer, includeUncommitted: boolean): bigint | undefined;
350
+ abstract findLeafIndex(value: T, includeUncommitted: boolean): bigint | undefined;
351
+
352
+ /**
353
+ * Returns the first index containing a leaf value after `startIndex`.
354
+ * @param leaf - The leaf value to look for.
355
+ * @param startIndex - The index to start searching from (used when skipping nullified messages)
356
+ * @param includeUncommitted - Indicates whether to include uncommitted data.
357
+ * @returns The index of the first leaf found with a given value (undefined if not found).
358
+ */
359
+ abstract findLeafIndexAfter(leaf: T, startIndex: bigint, includeUncommitted: boolean): bigint | undefined;
334
360
  }