@chainsafe/ssz
Version:
Simple Serialize
164 lines • 6.95 kB
JavaScript
import { getHashComputations, getNodeAtDepth, getNodesAtDepth, setNodesAtDepth, } from "@chainsafe/persistent-merkle-tree";
import { TreeViewDU } from "./abstract.js";
export class ArrayBasicTreeViewDU extends TreeViewDU {
type;
_rootNode;
nodes;
nodesChanged = new Set();
_length;
dirtyLength = false;
nodesPopulated;
constructor(type, _rootNode, cache) {
super();
this.type = type;
this._rootNode = _rootNode;
if (cache) {
this.nodes = cache.nodes;
this._length = cache.length;
this.nodesPopulated = cache.nodesPopulated;
}
else {
this.nodes = [];
this._length = this.type.tree_getLength(_rootNode);
this.nodesPopulated = false;
}
}
/**
* Number of elements in the array. Equal to un-commited length of the array
*/
get length() {
return this._length;
}
get node() {
return this._rootNode;
}
get cache() {
return {
nodes: this.nodes,
length: this._length,
nodesPopulated: this.nodesPopulated,
};
}
/**
* Get element at `index`. Returns the Basic element type value directly
*/
get(index) {
// First walk through the tree to get the root node for that index
const chunkIndex = Math.floor(index / this.type.itemsPerChunk);
let node = this.nodes[chunkIndex];
if (node === undefined) {
node = getNodeAtDepth(this._rootNode, this.type.depth, chunkIndex);
this.nodes[chunkIndex] = node;
}
return this.type.elementType.tree_getFromPackedNode(node, index);
}
/**
* Set Basic element type `value` at `index`
*/
set(index, value) {
if (index >= this._length) {
throw Error(`Error setting index over length ${index} > ${this._length}`);
}
const chunkIndex = Math.floor(index / this.type.itemsPerChunk);
// Create new node if current leafNode is not dirty
let nodeChanged;
if (this.nodesChanged.has(chunkIndex)) {
// TODO: This assumes that node has already been populated
nodeChanged = this.nodes[chunkIndex];
}
else {
const nodePrev = (this.nodes[chunkIndex] ??
getNodeAtDepth(this._rootNode, this.type.depth, chunkIndex));
nodeChanged = nodePrev.clone();
// Store the changed node in the nodes cache
this.nodes[chunkIndex] = nodeChanged;
this.nodesChanged.add(chunkIndex);
}
this.type.elementType.tree_setToPackedNode(nodeChanged, index, value);
}
/**
* Get all values of this array as Basic element type values, from index zero to `this.length - 1`
* @param values optional output parameter, if is provided it must be an array of the same length as this array
*/
getAll(values) {
if (values && values.length !== this._length) {
throw Error(`Expected ${this._length} values, got ${values.length}`);
}
if (!this.nodesPopulated) {
const nodesPrev = this.nodes;
const chunksNode = this.type.tree_getChunksNode(this.node);
const chunkCount = Math.ceil(this._length / this.type.itemsPerChunk);
this.nodes = getNodesAtDepth(chunksNode, this.type.chunkDepth, 0, chunkCount);
// Re-apply changed nodes
for (const index of this.nodesChanged) {
this.nodes[index] = nodesPrev[index];
}
this.nodesPopulated = true;
}
values = values ?? new Array(this._length);
const itemsPerChunk = this.type.itemsPerChunk; // Prevent many access in for loop below
const lenFullNodes = Math.floor(this._length / itemsPerChunk);
const remainder = this._length % itemsPerChunk;
// TODO Optimize: caching the variables used in the loop above it
for (let n = 0; n < lenFullNodes; n++) {
const leafNode = this.nodes[n];
// TODO: Implement add a fast bulk packed element reader in the elementType
// ```
// abstract getValuesFromPackedNode(leafNode: LeafNode, output: V[], indexOffset: number): void;
// ```
// if performance here is a problem
for (let i = 0; i < itemsPerChunk; i++) {
values[n * itemsPerChunk + i] = this.type.elementType.tree_getFromPackedNode(leafNode, i);
}
}
if (remainder > 0) {
const leafNode = this.nodes[lenFullNodes];
for (let i = 0; i < remainder; i++) {
values[lenFullNodes * itemsPerChunk + i] = this.type.elementType.tree_getFromPackedNode(leafNode, i);
}
}
return values;
}
/**
* When we need to compute HashComputations (hcByLevel != null):
* - if old _rootNode is hashed, then only need to put pending changes to hcByLevel
* - if old _rootNode is not hashed, need to traverse and put to hcByLevel
*/
commit(hcOffset = 0, hcByLevel = null) {
const isOldRootHashed = this._rootNode.h0 !== null;
if (this.nodesChanged.size === 0) {
if (!isOldRootHashed && hcByLevel !== null) {
getHashComputations(this._rootNode, hcOffset, hcByLevel);
}
return;
}
// Numerical sort ascending
const indexes = Array.from(this.nodesChanged.keys()).sort((a, b) => a - b);
const nodes = new Array(indexes.length);
for (let i = 0; i < indexes.length; i++) {
nodes[i] = this.nodes[indexes[i]];
}
const chunksNode = this.type.tree_getChunksNode(this._rootNode);
const offsetThis = hcOffset + this.type.tree_chunksNodeOffset();
const byLevelThis = hcByLevel != null && isOldRootHashed ? hcByLevel : null;
const newChunksNode = setNodesAtDepth(chunksNode, this.type.chunkDepth, indexes, nodes, offsetThis, byLevelThis);
this._rootNode = this.type.tree_setChunksNode(this._rootNode, newChunksNode, this.dirtyLength ? this._length : null, hcOffset, isOldRootHashed ? hcByLevel : null);
if (!isOldRootHashed && hcByLevel !== null) {
getHashComputations(this._rootNode, hcOffset, hcByLevel);
}
this.nodesChanged.clear();
this.dirtyLength = false;
}
clearCache() {
this.nodes = [];
this.nodesPopulated = false;
// Must clear nodesChanged, otherwise a subsequent commit call will break, because it assumes a node is there
this.nodesChanged.clear();
// Reset cached length only if it has been mutated
if (this.dirtyLength) {
this._length = this.type.tree_getLength(this._rootNode);
this.dirtyLength = false;
}
}
}
//# sourceMappingURL=arrayBasic.js.map