Skip to content

Commit

Permalink
feat: new BranchNodeStruct
Browse files Browse the repository at this point in the history
  • Loading branch information
twoeths committed Jun 14, 2024
1 parent e83ac5f commit c306a66
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 120 deletions.
16 changes: 8 additions & 8 deletions packages/persistent-merkle-tree/src/hasher/as-sha256.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,14 @@ export const hasher: Hasher = {
dest3 !== null
) {
const [o0, o1, o2, o3] = batchHash4HashObjectInputs([
src0_0,
src1_0,
src0_1,
src1_1,
src0_2,
src1_2,
src0_3,
src1_3,
src0_0.rootHashObject,
src1_0.rootHashObject,
src0_1.rootHashObject,
src1_1.rootHashObject,
src0_2.rootHashObject,
src1_2.rootHashObject,
src0_3.rootHashObject,
src1_3.rootHashObject,
]);
if (o0 == null || o1 == null || o2 == null || o3 == null) {
throw Error(`batchHash4HashObjectInputs return null or undefined at batch ${i} level ${level}`);
Expand Down
4 changes: 2 additions & 2 deletions packages/persistent-merkle-tree/src/hasher/hashtree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ export const hasher: Hasher = {
const indexInBatch = i % PARALLEL_FACTOR;
const offset = indexInBatch * 16;

hashObjectToUint32Array(src0, uint32Input, offset);
hashObjectToUint32Array(src1, uint32Input, offset + 8);
hashObjectToUint32Array(src0.rootHashObject, uint32Input, offset);
hashObjectToUint32Array(src1.rootHashObject, uint32Input, offset + 8);
destNodes.push(dest);
if (indexInBatch === PARALLEL_FACTOR - 1) {
hashInto(uint8Input, uint8Output);
Expand Down
12 changes: 9 additions & 3 deletions packages/persistent-merkle-tree/src/packedNode.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import {subtreeFillToContents} from "./subtree";
import {Node, LeafNode, getNodeH, setNodeH} from "./node";
import {Node, LeafNode, getNodeH, setNodeH, HashComputationGroup} from "./node";

const NUMBER_2_POW_32 = 2 ** 32;

export function packedRootsBytesToNode(depth: number, dataView: DataView, start: number, end: number): Node {
export function packedRootsBytesToNode(
depth: number,
dataView: DataView,
start: number,
end: number,
hashComps: HashComputationGroup | null = null
): Node {
const leafNodes = packedRootsBytesToLeafNodes(dataView, start, end);
return subtreeFillToContents(leafNodes, depth);
return subtreeFillToContents(leafNodes, depth, hashComps);
}

/**
Expand Down
39 changes: 6 additions & 33 deletions packages/ssz/src/branchNodeStruct.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import {HashObject} from "@chainsafe/as-sha256/lib/hashObject";
import {
hashObjectToUint8Array,
Node,
getHashComputations,
HashComputationGroup,
hashObjectToUint8Array,
} from "@chainsafe/persistent-merkle-tree";

export type ValueToNodeFn<T> = (
Expand All @@ -20,14 +19,9 @@ export type ValueToNodeFn<T> = (
* expensive because the tree has to be recreated every time.
*/
export class BranchNodeStruct<T> extends Node {
/**
* this represents the backed tree which is lazily computed from value
*/
private _rootNode: Node | null = null;
constructor(private readonly valueToNode: ValueToNodeFn<T>, readonly value: T) {
constructor(public readonly rootNode: Node, readonly value: T) {
// First null value is to save an extra variable to check if a node has a root or not
super(null as unknown as number, 0, 0, 0, 0, 0, 0, 0);
this._rootNode = null;
}

get rootHashObject(): HashObject {
Expand All @@ -42,7 +36,7 @@ export class BranchNodeStruct<T> extends Node {
}

isLeaf(): boolean {
return false;
return this.rootNode.isLeaf();
}

get left(): Node {
Expand All @@ -52,29 +46,8 @@ export class BranchNodeStruct<T> extends Node {
get right(): Node {
return this.rootNode.right;
}
}

getHashComputations(hashComps: HashComputationGroup): void {
if (this.h0 !== null) {
return;
}

if (this._rootNode === null) {
// set dest of HashComputation to this node
this._rootNode = this.valueToNode(this.value, hashComps, this);
} else {
// not likely to hit this path if called from ViewDU, handle just in case
getHashComputations(this, hashComps.offset, hashComps.byLevel);
}
}

/**
* Singleton implementation to make sure there is single backed tree for this node.
* This is important for batching HashComputations
*/
private get rootNode(): Node {
if (this._rootNode === null) {
this._rootNode = this.valueToNode(this.value, null, null);
}
return this._rootNode;
}
export function isBranchNodeStruct(node: Node): node is BranchNodeStruct<unknown> {
return (node as BranchNodeStruct<unknown>).value !== undefined;
}
9 changes: 7 additions & 2 deletions packages/ssz/src/type/arrayBasic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,13 @@ export function getChunksNodeFromRootNode(node: Node): Node {
return node.left;
}

export function addLengthNode(chunksNode: Node, length: number): Node {
return new BranchNode(chunksNode, LeafNode.fromUint32(length));
export function addLengthNode(chunksNode: Node, length: number, hashComps: HashComputationGroup | null = null): Node {
const lengthNode = LeafNode.fromUint32(length);
const newNode = new BranchNode(chunksNode, lengthNode);
if (hashComps !== null) {
arrayAtIndex(hashComps.byLevel, hashComps.offset).push({src0: chunksNode, src1: lengthNode, dest: newNode});
}
return newNode;
}

export function setChunksNode(
Expand Down
8 changes: 0 additions & 8 deletions packages/ssz/src/type/byteArray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,6 @@ export abstract class ByteArrayType extends CompositeType<ByteArray, ByteArray,
return this.commitViewDU(view);
}

// TODO - batch
commitViewDU(view: ByteArray, hashComps: HashComputationGroup | null = null): Node {
const uint8Array = new Uint8Array(this.value_serializedSize(view));
const dataView = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength);
this.value_serializeToBytes({uint8Array, dataView}, 0, view);
return this.tree_deserializeFromBytes({uint8Array, dataView}, 0, uint8Array.length);
}

cacheOfViewDU(): unknown {
return;
}
Expand Down
8 changes: 7 additions & 1 deletion packages/ssz/src/type/byteList.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {getNodesAtDepth, Node, packedNodeRootsToBytes, packedRootsBytesToNode} from "@chainsafe/persistent-merkle-tree";
import {getNodesAtDepth, HashComputationGroup, Node, packedNodeRootsToBytes, packedRootsBytesToNode} from "@chainsafe/persistent-merkle-tree";
import {mixInLength, maxChunksToDepth} from "../util/merkleize";
import {Require} from "../util/types";
import {namedClass} from "../util/named";
Expand Down Expand Up @@ -54,6 +54,12 @@ export class ByteListType extends ByteArrayType {

// Views: inherited from ByteArrayType

commitViewDU(view: ByteArray, hashComps: HashComputationGroup | null = null): Node {
const dataView = new DataView(view.buffer, view.byteOffset, view.byteLength);
const chunksNode = packedRootsBytesToNode(this.chunkDepth, dataView, 0, view.length, hashComps);
return addLengthNode(chunksNode, view.length, hashComps);
}

// Serialization + deserialization

value_serializedSize(value: Uint8Array): number {
Expand Down
9 changes: 7 additions & 2 deletions packages/ssz/src/type/byteVector.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {getNodesAtDepth, Node, packedNodeRootsToBytes, packedRootsBytesToNode} from "@chainsafe/persistent-merkle-tree";
import {getNodesAtDepth, HashComputationGroup, Node, packedNodeRootsToBytes, packedRootsBytesToNode} from "@chainsafe/persistent-merkle-tree";
import {maxChunksToDepth} from "../util/merkleize";
import {Require} from "../util/types";
import {namedClass} from "../util/named";
import {ByteViews} from "./composite";
import {ByteArrayType} from "./byteArray";
import {ByteArray, ByteArrayType} from "./byteArray";

export type ByteVector = Uint8Array;

Expand Down Expand Up @@ -54,6 +54,11 @@ export class ByteVectorType extends ByteArrayType {
return new (namedClass(ByteVectorType, opts.typeName))(limitBits, opts);
}

commitViewDU(view: ByteArray, hashComps: HashComputationGroup | null = null): Node {
const dataView = new DataView(view.buffer, view.byteOffset, view.byteLength);
return packedRootsBytesToNode(this.chunkDepth, dataView, 0, view.length, hashComps);
}

// Views: inherited from ByteArrayType

// Serialization + deserialization
Expand Down
34 changes: 16 additions & 18 deletions packages/ssz/src/type/containerNodeStruct.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {HashComputationGroup, Node, subtreeFillToContents} from "@chainsafe/persistent-merkle-tree";
import {Node, subtreeFillToContents} from "@chainsafe/persistent-merkle-tree";
import {Type, ByteViews} from "./abstract";
import {isCompositeType} from "./composite";
import {ContainerType, ContainerOptions, renderContainerTypeName} from "./container";
Expand Down Expand Up @@ -69,8 +69,19 @@ export class ContainerNodeStructType<Fields extends Record<string, Type<unknown>
}

tree_deserializeFromBytes(data: ByteViews, start: number, end: number): Node {
const value = this.value_deserializeFromBytes(data, start, end);
return new BranchNodeStruct(this.valueToTree.bind(this), value);
const fieldRanges = this.getFieldRanges(data.dataView, start, end);
const value = {} as {[K in keyof Fields]: unknown};
const nodes: Node[] = [];

for (let i = 0; i < this.fieldsEntries.length; i++) {
const {fieldName, fieldType} = this.fieldsEntries[i];
const fieldRange = fieldRanges[i];
value[fieldName] = fieldType.value_deserializeFromBytes(data, start + fieldRange.start, start + fieldRange.end);
nodes[i] = fieldType.value_toTree(value[fieldName]);
}

const rootNode = subtreeFillToContents(nodes, this.depth);
return new BranchNodeStruct(rootNode, value);
}

// Proofs
Expand All @@ -89,9 +100,9 @@ export class ContainerNodeStructType<Fields extends Record<string, Type<unknown>
const uint8Array = new Uint8Array(super.tree_serializedSize(node));
const dataView = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength);
super.tree_serializeToBytes({uint8Array, dataView}, 0, node);
const value = this.value_deserializeFromBytes({uint8Array, dataView}, 0, uint8Array.length);
const rootNode = this.tree_deserializeFromBytes({uint8Array, dataView}, 0, uint8Array.length);
return {
node: new BranchNodeStruct(this.valueToTree.bind(this), value),
node: rootNode,
done: true,
};
}
Expand All @@ -102,17 +113,4 @@ export class ContainerNodeStructType<Fields extends Record<string, Type<unknown>
return (node as BranchNodeStruct<ValueOfFields<Fields>>).value;
}

value_toTree(value: ValueOfFields<Fields>): Node {
return new BranchNodeStruct(this.valueToTree.bind(this), value);
}

private valueToTree(
value: ValueOfFields<Fields>,
hashComps: HashComputationGroup | null = null,
hashCompRootNode: Node | null = null
): Node {
const nodes = this.fieldsEntries.map(({fieldName, fieldType}) => fieldType.value_toTree(value[fieldName]));
const rootNode = subtreeFillToContents(nodes, this.depth, hashComps, hashCompRootNode);
return rootNode;
}
}
19 changes: 13 additions & 6 deletions packages/ssz/src/view/containerNodeStruct.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {Node, Tree} from "@chainsafe/persistent-merkle-tree";
import {LeafNode, Node, Tree, getNodeAtDepth} from "@chainsafe/persistent-merkle-tree";
import {Type, ValueOf} from "../type/abstract";
import {isCompositeType} from "../type/composite";
import {BranchNodeStruct} from "../branchNodeStruct";
import {ContainerTreeViewTypeConstructor, ContainerTypeGeneric, ValueOfFields} from "./container";
import {TreeView} from "./abstract";
import {isBasicType} from "../type/basic";

/* eslint-disable @typescript-eslint/member-ordering */

Expand Down Expand Up @@ -44,7 +45,7 @@ export function getContainerTreeViewClass<Fields extends Record<string, Type<unk
// If the field type is basic, the value to get and set will be the actual 'struct' value (i.e. a JS number).
// The view must use the tree_getFromNode() and tree_setToNode() methods to persist the struct data to the node,
// and use the cached views array to store the new node.
if (fieldType.isBasic) {
if (isBasicType(fieldType)) {
Object.defineProperty(CustomContainerTreeView.prototype, fieldName, {
configurable: false,
enumerable: true,
Expand All @@ -55,12 +56,16 @@ export function getContainerTreeViewClass<Fields extends Record<string, Type<unk
},

set: function (this: CustomContainerTreeView, value: unknown) {
const node = this.tree.rootNode as BranchNodeStruct<ValueOfFields<Fields>>;
const newNodeValue = this.type.clone(node.value);
const oldRootNode = this.tree.rootNode as BranchNodeStruct<ValueOfFields<Fields>>;
const newNodeValue = this.type.clone(oldRootNode.value);

// TODO: Should this check for valid field name? Benchmark the cost
newNodeValue[fieldName] = value as ValueOf<Fields[keyof Fields]>;
this.tree.rootNode = new BranchNodeStruct(node["valueToNode"], newNodeValue);
const leafNodePrev = getNodeAtDepth(oldRootNode, this.type.depth, index) as LeafNode;
const leafNode = leafNodePrev.clone();
fieldType.tree_setToNode(leafNode, value);
this.tree.setNodeAtDepth(this.type.depth, index, leafNode);
this.tree.rootNode = new BranchNodeStruct(this.tree.rootNode, newNodeValue);
},
});
}
Expand All @@ -86,7 +91,9 @@ export function getContainerTreeViewClass<Fields extends Record<string, Type<unk

// TODO: Should this check for valid field name? Benchmark the cost
newNodeValue[fieldName] = fieldType.toValueFromView(view) as ValueOf<Fields[keyof Fields]>;
this.tree.rootNode = new BranchNodeStruct(node["valueToNode"], newNodeValue);
const newNode = fieldType.commitView(view);
this.tree.setNodeAtDepth(this.type.depth, index, newNode);
this.tree.rootNode = new BranchNodeStruct(this.tree.rootNode, newNodeValue);
},
});
}
Expand Down
7 changes: 6 additions & 1 deletion packages/ssz/src/viewDU/abstract.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {HashComputationGroup, executeHashComputations} from "@chainsafe/persistent-merkle-tree";
import {ByteViews, CompositeType} from "../type/composite";
import {TreeView} from "../view/abstract";
import {isBranchNodeStruct} from "../branchNodeStruct";

/* eslint-disable @typescript-eslint/member-ordering */

Expand Down Expand Up @@ -61,9 +62,13 @@ export abstract class TreeViewDU<T extends CompositeType<unknown, unknown, unkno
executeHashComputations(hashComps.byLevel);

// This makes sure the root node is computed by batch
if (this.node.h0 === null) {
if (
this.node.h0 === null &&
(!isBranchNodeStruct(this.node) || (isBranchNodeStruct(this.node) && this.node.rootNode.h0 === null))
) {
throw Error("Root is not computed by batch");
}

return this.node.root;
}

Expand Down
Loading

0 comments on commit c306a66

Please sign in to comment.