UNPKG

@chainsafe/persistent-merkle-tree

Version:

Merkle tree implemented as a persistent datastructure

208 lines (207 loc) 7.39 kB
import { Gindex, GindexBitstring } from "./gindex.ts"; import { HashComputationLevel } from "./hashComputation.ts"; import { Node } from "./node.ts"; import { Proof, ProofInput } from "./proof/index.ts"; export type Hook = (newRootNode: Node) => void; /** * Binary merkle tree * * Wrapper around immutable `Node` to support mutability. * * Mutability between a parent tree and subtree is achieved by maintaining a `hook` callback, which updates the parent when the subtree is updated. */ export declare class Tree { private _rootNode; private hook?; constructor(node: Node, hook?: Hook); /** * The root hash of the tree */ get root(): Uint8Array; /** * The root node of the tree */ get rootNode(): Node; /** * * Setting the root node will trigger a call to the tree's `hook` if it exists. */ set rootNode(newRootNode: Node); /** * Create a `Tree` from a `Proof` object */ static createFromProof(proof: Proof): Tree; /** * Return a copy of the tree */ clone(): Tree; /** * Return the subtree at the specified gindex. * * Note: The returned subtree will have a `hook` attached to the parent tree. * Updates to the subtree will result in updates to the parent. */ getSubtree(index: Gindex | GindexBitstring): Tree; /** * Return the node at the specified gindex. */ getNode(gindex: Gindex | GindexBitstring): Node; /** * Return the node at the specified depth and index. * * Supports index up to `Number.MAX_SAFE_INTEGER`. */ getNodeAtDepth(depth: number, index: number): Node; /** * Return the hash at the specified gindex. */ getRoot(index: Gindex | GindexBitstring): Uint8Array; /** * Set the node at at the specified gindex. */ setNode(gindex: Gindex | GindexBitstring, n: Node): void; /** * Traverse to the node at the specified gindex, * then apply the function to get a new node and set the node at the specified gindex with the result. * * This is a convenient method to avoid traversing the tree 2 times to * get and set. */ setNodeWithFn(gindex: Gindex | GindexBitstring, getNewNode: (node: Node) => Node): void; /** * Set the node at the specified depth and index. * * Supports index up to `Number.MAX_SAFE_INTEGER`. */ setNodeAtDepth(depth: number, index: number, node: Node): void; /** * Set the hash at the specified gindex. * * Note: This will set a new `LeafNode` at the specified gindex. */ setRoot(index: Gindex | GindexBitstring, root: Uint8Array): void; /** * Fast read-only iteration * In-order traversal of nodes at `depth` * starting from the `startIndex`-indexed node * iterating through `count` nodes * * Supports index up to `Number.MAX_SAFE_INTEGER`. */ getNodesAtDepth(depth: number, startIndex: number, count: number): Node[]; /** * Fast read-only iteration * In-order traversal of nodes at `depth` * starting from the `startIndex`-indexed node * iterating through `count` nodes * * Supports index up to `Number.MAX_SAFE_INTEGER`. */ iterateNodesAtDepth(depth: number, startIndex: number, count: number): IterableIterator<Node>; /** * Return a merkle proof for the node at the specified gindex. */ getSingleProof(index: Gindex): Uint8Array[]; /** * Return a merkle proof for the proof input. * * This method can be used to create multiproofs. */ getProof(input: ProofInput): Proof; } /** * Return the node at the specified gindex. */ export declare function getNode(rootNode: Node, gindex: Gindex | GindexBitstring): Node; /** * Set the node at at the specified gindex. * Returns the new root node. */ export declare function setNode(rootNode: Node, gindex: Gindex | GindexBitstring, n: Node): Node; /** * Traverse to the node at the specified gindex, * then apply the function to get a new node and set the node at the specified gindex with the result. * * This is a convenient method to avoid traversing the tree 2 times to * get and set. * * Returns the new root node. */ export declare function setNodeWithFn(rootNode: Node, gindex: Gindex | GindexBitstring, getNewNode: (node: Node) => Node): Node; /** * Supports index up to `Number.MAX_SAFE_INTEGER`. */ export declare function getNodeAtDepth(rootNode: Node, depth: number, index: number): Node; /** * Supports index up to `Number.MAX_SAFE_INTEGER`. */ export declare function setNodeAtDepth(rootNode: Node, nodesDepth: number, index: number, nodeChanged: Node): Node; /** * Set multiple nodes in batch, editing and traversing nodes strictly once. * * - gindexes MUST be sorted in ascending order beforehand. * - All gindexes must be at the exact same depth. * - Depth must be > 0, if 0 just replace the root node. * * Strategy: for each gindex in `gindexes` navigate to the depth of its parent, * and create a new parent. Then calculate the closest common depth with the next * gindex and navigate upwards creating or caching nodes as necessary. Loop and repeat. * * Supports index up to `Number.MAX_SAFE_INTEGER`. * @param hcByLevel an array of HashComputation[] by level (could be from 0 to `nodesDepth - 1`) */ export declare function setNodesAtDepth(rootNode: Node, nodesDepth: number, indexes: number[], nodes: Node[], hcOffset?: number, hcByLevel?: HashComputationLevel[] | null): Node; /** * Fast read-only iteration * In-order traversal of nodes at `depth` * starting from the `startIndex`-indexed node * iterating through `count` nodes * * **Strategy** * 1. Navigate down to parentDepth storing a stack of parents * 2. At target level push current node * 3. Go up to the first level that navigated left * 4. Repeat (1) for next index */ export declare function getNodesAtDepth(rootNode: Node, depth: number, startIndex: number, count: number): Node[]; /** * @see getNodesAtDepth but instead of pushing to an array, it yields */ export declare function iterateNodesAtDepth(rootNode: Node, depth: number, startIndex: number, count: number): IterableIterator<Node>; /** * Zero's all nodes right of index with constant depth of `nodesDepth`. * * For example, zero-ing this tree at depth 2 after index 0 * ``` * X X * X X -> X 0 * X X X X X 0 0 0 * ``` * * Or, zero-ing this tree at depth 3 after index 2 * ``` * X X * X X X 0 * X X X X -> X X 0 0 * X X X X X X X X X X X 0 0 0 0 0 * ``` * * The strategy is to first navigate down to `nodesDepth` and `index` and keep a stack of parents. * Then navigate up re-binding: * - If navigated to the left rebind with zeroNode() * - If navigated to the right rebind with parent.left from the stack */ export declare function treeZeroAfterIndex(rootNode: Node, nodesDepth: number, index: number): Node; /** * depth depthi gindexes indexes * 0 1 1 0 * 1 0 2 3 0 1 * 2 - 4 5 6 7 0 1 2 3 * * **Conditions**: * - `from` and `to` must not be equal * * @param from Index * @param to Index */ export declare function findDiffDepthi(from: number, to: number): number;