@chainsafe/persistent-merkle-tree
Version:
Merkle tree implemented as a persistent datastructure
197 lines • 5.87 kB
JavaScript
/**
* 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