@chainsafe/persistent-merkle-tree
Version:
Merkle tree implemented as a persistent datastructure
119 lines • 5.4 kB
JavaScript
import { LeafNode, getNodeH, setNodeH } from "./node.js";
import { subtreeFillToContents } from "./subtree.js";
const NUMBER_2_POW_32 = 2 ** 32;
export function packedRootsBytesToNode(depth, dataView, start, end) {
const leafNodes = packedRootsBytesToLeafNodes(dataView, start, end);
return subtreeFillToContents(leafNodes, depth);
}
/**
* Pack a list of uint64 numbers into a list of LeafNodes.
* Each value is UintNum64, which is 8 bytes long, which is 2 h values.
* Each 4 of them forms a LeafNode.
*
* v0 v1 v2 v3
* |-------------|-------------|-------------|-------------|
*
* h0 h1 h2 h3 h4 h5 h6 h7
* |------|------|------|------|------|------|------|------|
*/
export function packedUintNum64sToLeafNodes(values) {
const leafNodes = new Array(Math.ceil(values.length / 4));
for (let i = 0; i < values.length; i++) {
const nodeIndex = Math.floor(i / 4);
const leafNode = leafNodes[nodeIndex] ?? new LeafNode(0, 0, 0, 0, 0, 0, 0, 0);
const vIndex = i % 4;
const hIndex = 2 * vIndex;
const value = values[i];
// same logic to UintNumberType.value_serializeToBytes() for 8 bytes
if (value === Infinity) {
setNodeH(leafNode, hIndex, 0xffffffff);
setNodeH(leafNode, hIndex + 1, 0xffffffff);
}
else {
setNodeH(leafNode, hIndex, value & 0xffffffff);
setNodeH(leafNode, hIndex + 1, (value / NUMBER_2_POW_32) & 0xffffffff);
}
leafNodes[nodeIndex] = leafNode;
}
return leafNodes;
}
/**
* Optimized deserialization of linear bytes to consecutive leaf nodes
*/
export function packedRootsBytesToLeafNodes(dataView, start, end) {
const size = end - start;
// If the offset in data is not a multiple of 4, Uint32Array can't be used
// > start offset of Uint32Array should be a multiple of 4
// NOTE: Performance tests show that using a DataView is as fast as Uint32Array
const fullNodeCount = Math.floor(size / 32);
const leafNodes = new Array(Math.ceil(size / 32));
// Efficiently construct the tree writing to hashObjects directly
// TODO: Optimize, with this approach each h property is written twice
for (let i = 0; i < fullNodeCount; i++) {
const offset = start + i * 32;
leafNodes[i] = new LeafNode(dataView.getInt32(offset + 0, true), dataView.getInt32(offset + 4, true), dataView.getInt32(offset + 8, true), dataView.getInt32(offset + 12, true), dataView.getInt32(offset + 16, true), dataView.getInt32(offset + 20, true), dataView.getInt32(offset + 24, true), dataView.getInt32(offset + 28, true));
}
// Consider that the last node may only include partial data
const remainderBytes = size % 32;
// Last node
if (remainderBytes > 0) {
const node = new LeafNode(0, 0, 0, 0, 0, 0, 0, 0);
leafNodes[fullNodeCount] = node;
// Loop to dynamically copy the full h values
const fullHCount = Math.floor(remainderBytes / 4);
for (let h = 0; h < fullHCount; h++) {
setNodeH(node, h, dataView.getInt32(start + fullNodeCount * 32 + h * 4, true));
}
const remainderUint32 = size % 4;
if (remainderUint32 > 0) {
let h = 0;
for (let i = 0; i < remainderUint32; i++) {
h |= dataView.getUint8(start + size - remainderUint32 + i) << (i * 8);
}
setNodeH(node, fullHCount, h);
}
}
return leafNodes;
}
/**
* Optimized serialization of consecutive leave nodes to linear bytes
*/
export function packedNodeRootsToBytes(dataView, start, size, nodes) {
// If the offset in data is not a multiple of 4, Uint32Array can't be used
// > start offset of Uint32Array should be a multiple of 4
// NOTE: Performance tests show that using a DataView is as fast as Uint32Array
// Consider that the last node may only include partial data
const remainderBytes = size % 32;
// Full nodes
// Efficiently get hashObjects data into data
const fullNodeCount = Math.floor(size / 32);
for (let i = 0; i < fullNodeCount; i++) {
const node = nodes[i];
const offset = start + i * 32;
dataView.setInt32(offset + 0, node.h0, true);
dataView.setInt32(offset + 4, node.h1, true);
dataView.setInt32(offset + 8, node.h2, true);
dataView.setInt32(offset + 12, node.h3, true);
dataView.setInt32(offset + 16, node.h4, true);
dataView.setInt32(offset + 20, node.h5, true);
dataView.setInt32(offset + 24, node.h6, true);
dataView.setInt32(offset + 28, node.h7, true);
}
// Last node
if (remainderBytes > 0) {
const node = nodes[fullNodeCount];
// Loop to dynamically copy the full h values
const fullHCount = Math.floor(remainderBytes / 4);
for (let h = 0; h < fullHCount; h++) {
dataView.setInt32(start + fullNodeCount * 32 + h * 4, getNodeH(node, h), true);
}
const remainderUint32 = size % 4;
if (remainderUint32 > 0) {
const h = getNodeH(node, fullHCount);
for (let i = 0; i < remainderUint32; i++) {
dataView.setUint8(start + size - remainderUint32 + i, (h >> (i * 8)) & 0xff);
}
}
}
}
//# sourceMappingURL=packedNode.js.map