UNPKG

@chainsafe/persistent-merkle-tree

Version:

Merkle tree implemented as a persistent datastructure

197 lines 5.87 kB
/** * Model HashComputation[] at the same level that support reusing the same memory. * Before every run, reset() should be called. * After every run, clean() should be called. */ export class HashComputationLevel { _length; _totalLength; // use LinkedList to avoid memory allocation when the list grows // always have a fixed head although length is 0 head; tail; pointer; constructor() { this._length = 0; this._totalLength = 0; this.head = { src0: null, src1: null, dest: null, next: null, }; this.tail = null; this.pointer = null; } get length() { return this._length; } get totalLength() { return this._totalLength; } /** * run before every run */ reset() { // keep this.head object, only release the data this.head.src0 = null; this.head.src1 = null; this.head.dest = null; this.tail = null; this._length = 0; // totalLength is not reset this.pointer = null; } /** * Append a new HashComputation to tail. * This will overwrite the existing HashComputation if it is not null, or grow the list if needed. */ push(src0, src1, dest) { if (this.tail !== null) { let newTail = this.tail.next; if (newTail !== null) { newTail.src0 = src0; newTail.src1 = src1; newTail.dest = dest; } else { // grow the list newTail = { src0, src1, dest, next: null }; this.tail.next = newTail; this._totalLength++; } this.tail = newTail; this._length++; return; } // first item this.head.src0 = src0; this.head.src1 = src1; this.head.dest = dest; this.tail = this.head; this._length = 1; if (this._totalLength === 0) { this._totalLength = 1; } // else _totalLength > 0, do not set } /** * run after every run * hashComps may still refer to the old Nodes, we should release them to avoid memory leak. */ clean() { let hc = this.tail?.next ?? null; while (hc !== null) { if (hc.src0 === null) { // we may have already cleaned it in the previous run, return early break; } hc.src0 = null; hc.src1 = null; hc.dest = null; hc = hc.next; } } /** * Implement Iterator for this class */ next() { if (!this.pointer || this.tail === null) { return { done: true, value: undefined }; } // never yield value beyond the tail const value = this.pointer; const isNull = value.src0 === null; this.pointer = this.pointer.next; return isNull ? { done: true, value: undefined } : { done: false, value }; } /** * This is convenient method to consume HashComputationLevel with for-of loop * See "next" method above for the actual implementation */ [Symbol.iterator]() { this.pointer = this.head; return this; } /** * Not great due to memory allocation, for testing only. * This converts all HashComputation with data to an array. */ toArray() { const hashComps = []; for (const hc of this) { hashComps.push(hc); } return hashComps; } /** * For testing only. * This dumps all backed HashComputation objects, note that some HashComputation may not have data. */ dump() { const hashComps = []; let hc = null; for (hc = this.head; hc !== null; hc = hc.next) { hashComps.push(hc); } return hashComps; } } /** * Model HashComputationLevel[] at different levels. */ export class HashComputationGroup { byLevel; constructor() { this.byLevel = []; } reset() { for (const level of this.byLevel) { level.reset(); } } clean() { for (const level of this.byLevel) { level.clean(); } } } /** * Get HashComputations from a root node all the way to the leaf nodes. * hcByLevel is the global array to store HashComputationLevel at different levels * at this ${node}, we only add more HashComputations starting from ${index} * * ╔═══ hcByLevel ══════╗ * ║ level 0 ║ * ║ level 1 ║ * ║ ... ║ * ║ ║ node * ║ ║ / \ * ║ level ${index} ║ 01 02 * ║ ║ / \ / \ * ║ level ${index + 1} ║ 03 04 05 06 * ║ ║ * ║ ... ║ * ╚════════════════════╝ */ export function getHashComputations(node, index, hcByLevel) { if (node.h0 === null) { const hashComputations = levelAtIndex(hcByLevel, index); const { left, right } = node; hashComputations.push(left, right, node); // leaf nodes should have h0 to stop the recursion getHashComputations(left, index + 1, hcByLevel); getHashComputations(right, index + 1, hcByLevel); } // else stop the recursion, node is hashed } /** * Utility to get HashComputationLevel at a specific index. */ export function levelAtIndex(hcByLevel, index) { if (hcByLevel[index] === undefined) { hcByLevel[index] = new HashComputationLevel(); } return hcByLevel[index]; } //# sourceMappingURL=hashComputation.js.map