diff --git a/yarn-project/merkle-tree/src/index.ts b/yarn-project/merkle-tree/src/index.ts index be11945c0be..5181cecfc15 100644 --- a/yarn-project/merkle-tree/src/index.ts +++ b/yarn-project/merkle-tree/src/index.ts @@ -4,7 +4,7 @@ export * from './interfaces/merkle_tree.js'; export * from './interfaces/update_only_tree.js'; export * from './pedersen.js'; export * from './sparse_tree/sparse_tree.js'; -export * from './standard_indexed_tree/standard_indexed_tree.js'; +export { LowLeafWitnessData, StandardIndexedTree } from './standard_indexed_tree/standard_indexed_tree.js'; export * from './standard_tree/standard_tree.js'; export { INITIAL_LEAF } from './tree_base.js'; export { newTree } from './new_tree.js'; @@ -12,3 +12,4 @@ export { loadTree } from './load_tree.js'; export * from './snapshots/snapshot_builder.js'; export * from './snapshots/full_snapshot.js'; export * from './snapshots/append_only_snapshot.js'; +export * from './snapshots/indexed_tree_snapshot.js'; diff --git a/yarn-project/merkle-tree/src/snapshots/append_only_snapshot.ts b/yarn-project/merkle-tree/src/snapshots/append_only_snapshot.ts index c05241fd524..b42250eaa0e 100644 --- a/yarn-project/merkle-tree/src/snapshots/append_only_snapshot.ts +++ b/yarn-project/merkle-tree/src/snapshots/append_only_snapshot.ts @@ -5,7 +5,7 @@ import { LevelUp } from 'levelup'; import { AppendOnlyTree } from '../interfaces/append_only_tree.js'; import { SiblingPathSource } from '../interfaces/merkle_tree.js'; import { TreeBase } from '../tree_base.js'; -import { SnapshotBuilder } from './snapshot_builder.js'; +import { TreeSnapshotBuilder } from './snapshot_builder.js'; // stores the last block that modified this node const nodeModifiedAtBlockKey = (treeName: string, level: number, index: bigint) => @@ -33,7 +33,7 @@ const snapshotLeafCountKey = (treeName: string, block: number) => `snapshot:leaf * Best case: O(H) database reads + O(1) hashes * Worst case: O(H) database reads + O(H) hashes */ -export class AppendOnlySnapshotBuilder implements SnapshotBuilder { +export class AppendOnlySnapshotBuilder implements TreeSnapshotBuilder { constructor(private db: LevelUp, private tree: TreeBase & AppendOnlyTree, private hasher: Hasher) {} async getSnapshot(block: number): Promise { const leafCount = await this.#getLeafCountAtBlock(block); diff --git a/yarn-project/merkle-tree/src/snapshots/base_full_snapshot.ts b/yarn-project/merkle-tree/src/snapshots/base_full_snapshot.ts new file mode 100644 index 00000000000..3f93daa9609 --- /dev/null +++ b/yarn-project/merkle-tree/src/snapshots/base_full_snapshot.ts @@ -0,0 +1,182 @@ +import { SiblingPath } from '@aztec/types'; + +import { LevelUp, LevelUpChain } from 'levelup'; + +import { SiblingPathSource } from '../interfaces/merkle_tree.js'; +import { TreeBase } from '../tree_base.js'; +import { TreeSnapshot, TreeSnapshotBuilder } from './snapshot_builder.js'; + +// ket for a node's children +const snapshotChildKey = (node: Buffer, child: 0 | 1) => + Buffer.concat([Buffer.from('snapshot:node:'), node, Buffer.from(':' + child)]); + +// metadata for a snapshot - the root of the historical tree +const snapshotRootKey = (treeName: string, block: number) => `snapshot:root:${treeName}:${block}`; + +/** + * Builds a full snapshot of a tree. This implementation works for any Merkle tree and stores + * it in a database in a similar way to how a tree is stored in memory, using pointers. + * + * Sharing the same database between versions and trees is recommended as the trees would share + * structure. + * + * Implement the protected method `handleLeaf` to store any additional data you need for each leaf. + * + * Complexity: + * N - count of non-zero nodes in tree + * M - count of snapshots + * H - tree height + * Worst case space complexity: O(N * M) + * Sibling path access: O(H) database reads + */ +export abstract class BaseFullTreeSnapshotBuilder + implements TreeSnapshotBuilder +{ + constructor(protected db: LevelUp, protected tree: T) {} + + async snapshot(block: number): Promise { + const historicalRoot = await this.#getRootAtBlock(block); + + if (historicalRoot) { + return this.openSnapshot(historicalRoot); + } + + const batch = this.db.batch(); + const root = this.tree.getRoot(); + const depth = this.tree.getDepth(); + const queue: [Buffer, number, bigint][] = [[root, 0, 0n]]; + + // walk the tree breadth-first and store each of its nodes in the database + // for each node we save two keys + // :0 -> + // :1 -> + while (queue.length > 0) { + const [node, level, i] = queue.shift()!; + // check if the database already has a child for this tree + // if it does, then we know we've seen the whole subtree below it before + // and we don't have to traverse it anymore + // we use the left child here, but it could be anything that shows we've stored the node before + const exists: Buffer | undefined = await this.db.get(snapshotChildKey(node, 0)).catch(() => undefined); + if (exists) { + continue; + } + + if (level + 1 > depth) { + // short circuit if we've reached the leaf level + // otherwise getNode might throw if we ask for the children of a leaf + this.handleLeaf(i, node, batch); + continue; + } + + const [lhs, rhs] = await Promise.all([ + this.tree.getNode(level + 1, 2n * i), + this.tree.getNode(level + 1, 2n * i + 1n), + ]); + + // we want the zero hash at the children's level, not the node's level + const zeroHash = this.tree.getZeroHash(level + 1); + + batch.put(snapshotChildKey(node, 0), lhs ?? zeroHash); + batch.put(snapshotChildKey(node, 1), rhs ?? zeroHash); + + // enqueue the children only if they're not zero hashes + if (lhs) { + queue.push([lhs, level + 1, 2n * i]); + } + + if (rhs) { + queue.push([rhs, level + 1, 2n * i + 1n]); + } + } + + batch.put(snapshotRootKey(this.tree.getName(), block), root); + await batch.write(); + + return this.openSnapshot(root); + } + + protected handleLeaf(_index: bigint, _node: Buffer, _batch: LevelUpChain) { + return; + } + + async getSnapshot(version: number): Promise { + const historicRoot = await this.#getRootAtBlock(version); + + if (!historicRoot) { + throw new Error(`Version ${version} does not exist for tree ${this.tree.getName()}`); + } + + return this.openSnapshot(historicRoot); + } + + protected abstract openSnapshot(root: Buffer): S; + + async #getRootAtBlock(block: number): Promise { + try { + return await this.db.get(snapshotRootKey(this.tree.getName(), block)); + } catch (err) { + return undefined; + } + } +} + +/** + * A source of sibling paths from a snapshot tree + */ +export class BaseFullTreeSnapshot implements TreeSnapshot { + constructor(protected db: LevelUp, protected historicRoot: Buffer, protected tree: TreeBase) {} + + async getSiblingPath(index: bigint): Promise> { + const siblings: Buffer[] = []; + + for await (const [_node, sibling] of this.pathFromRootToLeaf(index)) { + siblings.push(sibling); + } + + // we got the siblings we were looking for, but they are in root-leaf order + // reverse them here so we have leaf-root (what SiblingPath expects) + siblings.reverse(); + + return new SiblingPath(this.tree.getDepth() as N, siblings); + } + + protected async *pathFromRootToLeaf(leafIndex: bigint) { + const root = this.historicRoot; + const pathFromRoot = this.#getPathFromRoot(leafIndex); + + let node: Buffer = root; + for (let i = 0; i < pathFromRoot.length; i++) { + // get both children. We'll need both anyway (one to keep track of, the other to walk down to) + const children: [Buffer, Buffer] = await Promise.all([ + this.db.get(snapshotChildKey(node, 0)), + this.db.get(snapshotChildKey(node, 1)), + ]).catch(() => [this.tree.getZeroHash(i + 1), this.tree.getZeroHash(i + 1)]); + const next = children[pathFromRoot[i]]; + const sibling = children[(pathFromRoot[i] + 1) % 2]; + + yield [next, sibling]; + + node = next; + } + } + + /** + * Calculates the path from the root to the target leaf. Returns an array of 0s and 1s, + * each 0 represents walking down a left child and each 1 walking down to the child on the right. + * + * @param leafIndex - The target leaf + * @returns An array of 0s and 1s + */ + #getPathFromRoot(leafIndex: bigint): ReadonlyArray<0 | 1> { + const path: Array<0 | 1> = []; + let level = this.tree.getDepth(); + while (level > 0) { + path.push(leafIndex & 0x01n ? 1 : 0); + leafIndex >>= 1n; + level--; + } + + path.reverse(); + return path; + } +} diff --git a/yarn-project/merkle-tree/src/snapshots/full_snapshot.test.ts b/yarn-project/merkle-tree/src/snapshots/full_snapshot.test.ts index 25d0680afbb..3f2cc2af791 100644 --- a/yarn-project/merkle-tree/src/snapshots/full_snapshot.test.ts +++ b/yarn-project/merkle-tree/src/snapshots/full_snapshot.test.ts @@ -2,18 +2,18 @@ import levelup, { LevelUp } from 'levelup'; import { Pedersen, StandardTree, newTree } from '../index.js'; import { createMemDown } from '../test/utils/create_mem_down.js'; -import { FullSnapshotBuilder } from './full_snapshot.js'; +import { FullTreeSnapshotBuilder } from './full_snapshot.js'; import { describeSnapshotBuilderTestSuite } from './snapshot_builder_test_suite.js'; describe('FullSnapshotBuilder', () => { let tree: StandardTree; - let snapshotBuilder: FullSnapshotBuilder; + let snapshotBuilder: FullTreeSnapshotBuilder; let db: LevelUp; beforeEach(async () => { db = levelup(createMemDown()); tree = await newTree(StandardTree, db, new Pedersen(), 'test', 4); - snapshotBuilder = new FullSnapshotBuilder(db, tree); + snapshotBuilder = new FullTreeSnapshotBuilder(db, tree); }); describeSnapshotBuilderTestSuite( diff --git a/yarn-project/merkle-tree/src/snapshots/full_snapshot.ts b/yarn-project/merkle-tree/src/snapshots/full_snapshot.ts index 52af1ed18d6..83772a4368b 100644 --- a/yarn-project/merkle-tree/src/snapshots/full_snapshot.ts +++ b/yarn-project/merkle-tree/src/snapshots/full_snapshot.ts @@ -1,17 +1,7 @@ -import { SiblingPath } from '@aztec/types'; - -import { LevelUp } from 'levelup'; - import { SiblingPathSource } from '../interfaces/merkle_tree.js'; import { TreeBase } from '../tree_base.js'; -import { SnapshotBuilder } from './snapshot_builder.js'; - -// ket for a node's children -const snapshotChildKey = (node: Buffer, child: 0 | 1) => - Buffer.concat([Buffer.from('snapshot:node:'), node, Buffer.from(':' + child)]); - -// metadata for a snapshot - the root of the historical tree -const snapshotRootKey = (treeName: string, block: number) => `snapshot:root:${treeName}:${block}`; +import { BaseFullTreeSnapshot, BaseFullTreeSnapshotBuilder } from './base_full_snapshot.js'; +import { TreeSnapshotBuilder } from './snapshot_builder.js'; /** * Builds a full snapshot of a tree. This implementation works for any Merkle tree and stores @@ -27,137 +17,11 @@ const snapshotRootKey = (treeName: string, block: number) => `snapshot:root:${tr * Worst case space complexity: O(N * M) * Sibling path access: O(H) database reads */ -export class FullSnapshotBuilder implements SnapshotBuilder { - constructor(private db: LevelUp, private tree: TreeBase) {} - - async snapshot(block: number): Promise { - const historicalRoot = await this.#getRootAtBlock(block); - - if (historicalRoot) { - return new FullSnapshot(this.db, historicalRoot, this.tree); - } - - const batch = this.db.batch(); - const root = this.tree.getRoot(false); - const depth = this.tree.getDepth(); - const queue: [Buffer, number, bigint][] = [[root, 0, 0n]]; - - // walk the tree breadth-first and store each of its nodes in the database - // for each node we save two keys - // :0 -> - // :1 -> - while (queue.length > 0) { - const [node, level, i] = queue.shift()!; - // check if the database already has a child for this tree - // if it does, then we know we've seen the whole subtree below it before - // and we don't have to traverse it anymore - // we use the left child here, but it could be anything that shows we've stored the node before - const exists: Buffer | undefined = await this.db.get(snapshotChildKey(node, 0)).catch(() => undefined); - if (exists) { - continue; - } - - if (level + 1 > depth) { - // short circuit if we've reached the leaf level - // otherwise getNode might throw if we ask for the children of a leaf - continue; - } - - const [lhs, rhs] = await Promise.all([ - this.tree.getNode(level + 1, 2n * i), - this.tree.getNode(level + 1, 2n * i + 1n), - ]); - - // we want the zero hash at the children's level, not the node's level - const zeroHash = this.tree.getZeroHash(level + 1); - - batch.put(snapshotChildKey(node, 0), lhs ?? zeroHash); - batch.put(snapshotChildKey(node, 1), rhs ?? zeroHash); - - // enqueue the children only if they're not zero hashes - if (lhs) { - queue.push([lhs, level + 1, 2n * i]); - } - - if (rhs) { - queue.push([rhs, level + 1, 2n * i + 1n]); - } - } - - batch.put(snapshotRootKey(this.tree.getName(), block), root); - await batch.write(); - - return new FullSnapshot(this.db, root, this.tree); - } - - async getSnapshot(version: number): Promise { - const historicRoot = await this.#getRootAtBlock(version); - - if (!historicRoot) { - throw new Error(`Version ${version} does not exist for tree ${this.tree.getName()}`); - } - - return new FullSnapshot(this.db, historicRoot, this.tree); - } - - async #getRootAtBlock(version: number): Promise { - try { - return await this.db.get(snapshotRootKey(this.tree.getName(), version)); - } catch (err) { - return undefined; - } - } -} - -/** - * A source of sibling paths from a snapshot tree - */ -class FullSnapshot implements SiblingPathSource { - constructor(private db: LevelUp, private historicRoot: Buffer, private tree: TreeBase) {} - - async getSiblingPath(index: bigint): Promise> { - const root = this.historicRoot; - const pathFromRoot = this.#getPathFromRoot(index); - const siblings: Buffer[] = []; - - let node: Buffer = root; - for (let i = 0; i < pathFromRoot.length; i++) { - // get both children. We'll need both anyway (one to keep track of, the other to walk down to) - const children: [Buffer, Buffer] = await Promise.all([ - this.db.get(snapshotChildKey(node, 0)), - this.db.get(snapshotChildKey(node, 1)), - ]).catch(() => [this.tree.getZeroHash(i + 1), this.tree.getZeroHash(i + 1)]); - const next = children[pathFromRoot[i]]; - const sibling = children[(pathFromRoot[i] + 1) % 2]; - - siblings.push(sibling); - node = next; - } - - // we got the siblings we were looking for, but they are in root-leaf order - // reverse them here so we have leaf-root (what SiblingPath expects) - siblings.reverse(); - - return new SiblingPath(this.tree.getDepth() as N, siblings); - } - - /** - * Calculates the path from the root to the target leaf. Returns an array of 0s and 1s, - * each 0 represents walking down a left child and each 1 walking down to the child on the right. - * - * @param leafIndex - The target leaf - * @returns An array of 0s and 1s - */ - #getPathFromRoot(leafIndex: bigint): ReadonlyArray<0 | 1> { - const path: Array<0 | 1> = []; - let level = this.tree.getDepth(); - while (level > 0) { - path.push(leafIndex & 0x01n ? 1 : 0); - leafIndex >>= 1n; - level--; - } - - path.reverse(); - return path; +export class FullTreeSnapshotBuilder + extends BaseFullTreeSnapshotBuilder + implements TreeSnapshotBuilder +{ + protected openSnapshot(root: Buffer): SiblingPathSource { + return new BaseFullTreeSnapshot(this.db, root, this.tree); } } diff --git a/yarn-project/merkle-tree/src/snapshots/indexed_tree_snapshot.test.ts b/yarn-project/merkle-tree/src/snapshots/indexed_tree_snapshot.test.ts new file mode 100644 index 00000000000..0ddb0ca7303 --- /dev/null +++ b/yarn-project/merkle-tree/src/snapshots/indexed_tree_snapshot.test.ts @@ -0,0 +1,82 @@ +import levelup, { LevelUp } from 'levelup'; + +import { Pedersen, newTree } from '../index.js'; +import { StandardIndexedTreeWithAppend } from '../standard_indexed_tree/test/standard_indexed_tree_with_append.js'; +import { createMemDown } from '../test/utils/create_mem_down.js'; +import { IndexedTreeSnapshotBuilder } from './indexed_tree_snapshot.js'; +import { describeSnapshotBuilderTestSuite } from './snapshot_builder_test_suite.js'; + +describe('IndexedTreeSnapshotBuilder', () => { + let db: LevelUp; + let tree: StandardIndexedTreeWithAppend; + let snapshotBuilder: IndexedTreeSnapshotBuilder; + + beforeEach(async () => { + db = levelup(createMemDown()); + tree = await newTree(StandardIndexedTreeWithAppend, db, new Pedersen(), 'test', 4); + snapshotBuilder = new IndexedTreeSnapshotBuilder(db, tree); + }); + + describeSnapshotBuilderTestSuite( + () => tree, + () => snapshotBuilder, + async () => { + const newLeaves = Array.from({ length: 2 }).map(() => Buffer.from(Math.random().toString())); + await tree.appendLeaves(newLeaves); + }, + ); + + describe('getSnapshot', () => { + it('returns historical leaf data', async () => { + await tree.appendLeaves([Buffer.from('a'), Buffer.from('b'), Buffer.from('c')]); + await tree.commit(); + const expectedLeavesAtBlock1 = await Promise.all([ + tree.getLatestLeafDataCopy(0, false), + tree.getLatestLeafDataCopy(1, false), + tree.getLatestLeafDataCopy(2, false), + // id'expect these to be undefined, but leaf 3 isn't? + // must be some indexed-tree quirk I don't quite understand yet + tree.getLatestLeafDataCopy(3, false), + tree.getLatestLeafDataCopy(4, false), + tree.getLatestLeafDataCopy(5, false), + ]); + + await snapshotBuilder.snapshot(1); + + await tree.appendLeaves([Buffer.from('d'), Buffer.from('e'), Buffer.from('f')]); + await tree.commit(); + const expectedLeavesAtBlock2 = await Promise.all([ + tree.getLatestLeafDataCopy(0, false), + tree.getLatestLeafDataCopy(1, false), + tree.getLatestLeafDataCopy(2, false), + tree.getLatestLeafDataCopy(3, false), + tree.getLatestLeafDataCopy(4, false), + tree.getLatestLeafDataCopy(5, false), + ]); + + await snapshotBuilder.snapshot(2); + + const snapshot1 = await snapshotBuilder.getSnapshot(1); + const actualLeavesAtBlock1 = await Promise.all([ + snapshot1.getLatestLeafDataCopy(0n), + snapshot1.getLatestLeafDataCopy(1n), + snapshot1.getLatestLeafDataCopy(2n), + snapshot1.getLatestLeafDataCopy(3n), + snapshot1.getLatestLeafDataCopy(4n), + snapshot1.getLatestLeafDataCopy(5n), + ]); + expect(actualLeavesAtBlock1).toEqual(expectedLeavesAtBlock1); + + const snapshot2 = await snapshotBuilder.getSnapshot(2); + const actualLeavesAtBlock2 = await Promise.all([ + snapshot2.getLatestLeafDataCopy(0n), + snapshot2.getLatestLeafDataCopy(1n), + snapshot2.getLatestLeafDataCopy(2n), + snapshot2.getLatestLeafDataCopy(3n), + snapshot2.getLatestLeafDataCopy(4n), + snapshot2.getLatestLeafDataCopy(5n), + ]); + expect(actualLeavesAtBlock2).toEqual(expectedLeavesAtBlock2); + }); + }); +}); diff --git a/yarn-project/merkle-tree/src/snapshots/indexed_tree_snapshot.ts b/yarn-project/merkle-tree/src/snapshots/indexed_tree_snapshot.ts new file mode 100644 index 00000000000..81151863cd4 --- /dev/null +++ b/yarn-project/merkle-tree/src/snapshots/indexed_tree_snapshot.ts @@ -0,0 +1,48 @@ +import { LevelUp, LevelUpChain } from 'levelup'; + +import { IndexedTree, LeafData } from '../interfaces/indexed_tree.js'; +import { decodeTreeValue, encodeTreeValue } from '../standard_indexed_tree/standard_indexed_tree.js'; +import { TreeBase } from '../tree_base.js'; +import { BaseFullTreeSnapshot, BaseFullTreeSnapshotBuilder } from './base_full_snapshot.js'; +import { IndexedTreeSnapshot, TreeSnapshotBuilder } from './snapshot_builder.js'; + +const snapshotLeafValue = (node: Buffer, index: bigint) => + Buffer.concat([Buffer.from('snapshot:leaf:'), node, Buffer.from(':' + index)]); + +/** a */ +export class IndexedTreeSnapshotBuilder + extends BaseFullTreeSnapshotBuilder + implements TreeSnapshotBuilder +{ + constructor(db: LevelUp, tree: IndexedTree & TreeBase) { + super(db, tree); + } + + protected openSnapshot(root: Buffer): IndexedTreeSnapshot { + return new IndexedTreeSnapshotImpl(this.db, root, this.tree); + } + + protected handleLeaf(index: bigint, node: Buffer, batch: LevelUpChain) { + const leafData = this.tree.getLatestLeafDataCopy(Number(index), false); + if (leafData) { + batch.put(snapshotLeafValue(node, index), encodeTreeValue(leafData)); + } + } +} + +/** A snapshot of an indexed tree at a particular point in time */ +class IndexedTreeSnapshotImpl extends BaseFullTreeSnapshot implements IndexedTreeSnapshot { + async getLatestLeafDataCopy(index: bigint): Promise { + let leafNode: Buffer; + for await (const [node, _sibling] of this.pathFromRootToLeaf(index)) { + leafNode = node; + } + + const leafValue = await this.db.get(snapshotLeafValue(leafNode!, index)).catch(() => undefined); + if (leafValue) { + return decodeTreeValue(leafValue); + } else { + return undefined; + } + } +} diff --git a/yarn-project/merkle-tree/src/snapshots/snapshot_builder.ts b/yarn-project/merkle-tree/src/snapshots/snapshot_builder.ts index 1fcde85e467..6735175bc64 100644 --- a/yarn-project/merkle-tree/src/snapshots/snapshot_builder.ts +++ b/yarn-project/merkle-tree/src/snapshots/snapshot_builder.ts @@ -1,18 +1,33 @@ +import { LeafData } from '../interfaces/indexed_tree.js'; import { SiblingPathSource } from '../interfaces/merkle_tree.js'; /** * An interface for a tree that can record snapshots of its contents. */ -export interface SnapshotBuilder { +export interface TreeSnapshotBuilder { /** * Creates a snapshot of the tree at the given version. * @param block - The version to snapshot the tree at. */ - snapshot(block: number): Promise; + snapshot(block: number): Promise; /** * Returns a snapshot of the tree at the given version. * @param block - The version of the snapshot to return. */ - getSnapshot(block: number): Promise; + getSnapshot(block: number): Promise; +} + +/** + * A tree snapshot + */ +export interface TreeSnapshot extends SiblingPathSource {} + +/** A snapshot of an indexed tree */ +export interface IndexedTreeSnapshot extends SiblingPathSource { + /** + * Gets the historical data for a leaf + * @param index - The index of the leaf to get the data for + */ + getLatestLeafDataCopy(index: bigint): Promise; } diff --git a/yarn-project/merkle-tree/src/snapshots/snapshot_builder_test_suite.ts b/yarn-project/merkle-tree/src/snapshots/snapshot_builder_test_suite.ts index c4f2b2891ce..f28bfea803a 100644 --- a/yarn-project/merkle-tree/src/snapshots/snapshot_builder_test_suite.ts +++ b/yarn-project/merkle-tree/src/snapshots/snapshot_builder_test_suite.ts @@ -1,8 +1,8 @@ import { TreeBase } from '../tree_base.js'; -import { SnapshotBuilder } from './snapshot_builder.js'; +import { TreeSnapshotBuilder } from './snapshot_builder.js'; /** Creates a test suit for snapshots */ -export function describeSnapshotBuilderTestSuite( +export function describeSnapshotBuilderTestSuite( getTree: () => T, getSnapshotBuilder: () => S, modifyTree: (tree: T) => Promise, diff --git a/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts b/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts index 29c2bac627c..c8eca1e3181 100644 --- a/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts +++ b/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts @@ -53,15 +53,14 @@ function getEmptyLowLeafWitness(treeHeight: N): LowLeafWitness }; } -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const encodeTreeValue = (leafData: LeafData) => { +export const encodeTreeValue = (leafData: LeafData) => { const valueAsBuffer = toBufferBE(leafData.value, 32); const indexAsBuffer = toBufferBE(leafData.nextIndex, 32); const nextValueAsBuffer = toBufferBE(leafData.nextValue, 32); return Buffer.concat([valueAsBuffer, indexAsBuffer, nextValueAsBuffer]); }; -const decodeTreeValue = (buf: Buffer) => { +export const decodeTreeValue = (buf: Buffer) => { const value = toBigIntBE(buf.subarray(0, 32)); const nextIndex = toBigIntBE(buf.subarray(32, 64)); const nextValue = toBigIntBE(buf.subarray(64, 96));