diff --git a/packages/ssz/src/type/arrayBasic.ts b/packages/ssz/src/type/arrayBasic.ts index 4119c26b..2ad5d3c3 100644 --- a/packages/ssz/src/type/arrayBasic.ts +++ b/packages/ssz/src/type/arrayBasic.ts @@ -5,8 +5,8 @@ import { getNodesAtDepth, packedNodeRootsToBytes, packedRootsBytesToNode, - levelAtIndex, HashComputationLevel, + levelAtIndex, } from "@chainsafe/persistent-merkle-tree"; import {Type, ValueOf, ByteViews} from "./abstract"; import {BasicType} from "./basic"; diff --git a/packages/ssz/src/type/bitArray.ts b/packages/ssz/src/type/bitArray.ts index 0a65d11b..d485de27 100644 --- a/packages/ssz/src/type/bitArray.ts +++ b/packages/ssz/src/type/bitArray.ts @@ -1,4 +1,4 @@ -import {concatGindices, Gindex, HashComputationLevel, Node, toGindex, Tree} from "@chainsafe/persistent-merkle-tree"; +import {concatGindices, Gindex, Node, toGindex, Tree, HashComputationLevel} from "@chainsafe/persistent-merkle-tree"; import {fromHexString, toHexString, byteArrayEquals} from "../util/byteArray"; import {CompositeType, LENGTH_GINDEX} from "./composite"; import {BitArray} from "../value/bitArray"; diff --git a/packages/ssz/src/type/byteArray.ts b/packages/ssz/src/type/byteArray.ts index c8469b74..78e6ae30 100644 --- a/packages/ssz/src/type/byteArray.ts +++ b/packages/ssz/src/type/byteArray.ts @@ -1,4 +1,12 @@ -import {concatGindices, Gindex, Node, toGindex, Tree} from "@chainsafe/persistent-merkle-tree"; +import { + concatGindices, + Gindex, + Node, + toGindex, + Tree, + HashComputationLevel, + getHashComputations, +} from "@chainsafe/persistent-merkle-tree"; import {fromHexString, toHexString, byteArrayEquals} from "../util/byteArray"; import {ByteViews} from "./abstract"; import {CompositeType, LENGTH_GINDEX} from "./composite"; @@ -36,12 +44,16 @@ export abstract class ByteArrayType extends CompositeType> extends CompositeTy } // TODO add an OptionalViewDU - // TODO - batch - commitViewDU(view: ValueOfType): Node { - return this.value_toTree(view); + commitViewDU(view: ValueOfType, hcOffset = 0, hcByLevel: HashComputationLevel[] | null = null): Node { + const node = this.value_toTree(view); + if (hcByLevel !== null && node.h0 === null) { + getHashComputations(node, hcOffset, hcByLevel); + } + return node; } // TODO add an OptionalViewDU diff --git a/packages/ssz/src/type/union.ts b/packages/ssz/src/type/union.ts index 97049eb1..97b5672a 100644 --- a/packages/ssz/src/type/union.ts +++ b/packages/ssz/src/type/union.ts @@ -1,4 +1,13 @@ -import {concatGindices, getNode, Gindex, Node, Tree, merkleizeInto} from "@chainsafe/persistent-merkle-tree"; +import { + concatGindices, + getNode, + Gindex, + Node, + Tree, + merkleizeInto, + getHashComputations, + HashComputationLevel, +} from "@chainsafe/persistent-merkle-tree"; import {Require} from "../util/types"; import {namedClass} from "../util/named"; import {Type, ByteViews} from "./abstract"; @@ -112,9 +121,12 @@ export class UnionType[]> extends CompositeType< return this.value_toTree(view); } - // TODO - batch - commitViewDU(view: ValueOfTypes): Node { - return this.value_toTree(view); + commitViewDU(view: ValueOfTypes, hcOffset = 0, hcByLevel: HashComputationLevel[] | null = null): Node { + const node = this.value_toTree(view); + if (hcByLevel !== null && node.h0 === null) { + getHashComputations(node, hcOffset, hcByLevel); + } + return node; } value_serializedSize(value: ValueOfTypes): number { diff --git a/packages/ssz/src/type/vectorComposite.ts b/packages/ssz/src/type/vectorComposite.ts index 73e17373..28990c43 100644 --- a/packages/ssz/src/type/vectorComposite.ts +++ b/packages/ssz/src/type/vectorComposite.ts @@ -1,4 +1,4 @@ -import {HashComputationLevel, Node, Tree} from "@chainsafe/persistent-merkle-tree"; +import {Node, Tree, HashComputationLevel} from "@chainsafe/persistent-merkle-tree"; import {maxChunksToDepth} from "../util/merkleize"; import {Require} from "../util/types"; import {namedClass} from "../util/named"; diff --git a/packages/ssz/src/view/arrayBasic.ts b/packages/ssz/src/view/arrayBasic.ts index 5997e14c..a18a841b 100644 --- a/packages/ssz/src/view/arrayBasic.ts +++ b/packages/ssz/src/view/arrayBasic.ts @@ -1,4 +1,4 @@ -import {getNodesAtDepth, HashComputationLevel, LeafNode, Node, Tree} from "@chainsafe/persistent-merkle-tree"; +import {getNodesAtDepth, LeafNode, Node, Tree, HashComputationLevel} from "@chainsafe/persistent-merkle-tree"; import {ValueOf} from "../type/abstract"; import {BasicType} from "../type/basic"; import {CompositeType} from "../type/composite"; diff --git a/packages/ssz/src/view/arrayComposite.ts b/packages/ssz/src/view/arrayComposite.ts index 97168645..87f4a3ea 100644 --- a/packages/ssz/src/view/arrayComposite.ts +++ b/packages/ssz/src/view/arrayComposite.ts @@ -1,4 +1,4 @@ -import {getNodesAtDepth, HashComputationLevel, Node, toGindexBitstring, Tree} from "@chainsafe/persistent-merkle-tree"; +import {getNodesAtDepth, Node, toGindexBitstring, Tree, HashComputationLevel} from "@chainsafe/persistent-merkle-tree"; import {ValueOf} from "../type/abstract"; import {CompositeType, CompositeView, CompositeViewDU} from "../type/composite"; import {TreeView} from "./abstract"; diff --git a/packages/ssz/src/viewDU/abstract.ts b/packages/ssz/src/viewDU/abstract.ts index 4ef1124d..144268f5 100644 --- a/packages/ssz/src/viewDU/abstract.ts +++ b/packages/ssz/src/viewDU/abstract.ts @@ -65,7 +65,7 @@ export abstract class TreeViewDU>; - this._rootNode.rootHashObject; - } + commit(_?: number, hcByLevel: HashComputationLevel[] | null = null): void { + if (this.valueChanged !== null) { + const value = this.valueChanged; + this.valueChanged = null; - /** - * Same to commit() without hash, allow to do the batch hash at consumer side, like in ListValidatorViewDU - * of ethereum consensus node. - */ - commitNoHash(): void { - if (this.valueChanged === null) { - return; + this._rootNode = this.type.value_toTree(value) as BranchNodeStruct>; } - const value = this.valueChanged; - this.valueChanged = null; - - this._rootNode = this.type.value_toTree(value) as BranchNodeStruct>; + if (this._rootNode.h0 === null && hcByLevel !== null) { + // consumer is batchHashTreeRoot() + this._rootNode.rootHashObject; + } } protected clearCache(): void { diff --git a/packages/ssz/test/lodestarTypes/phase0/viewDU/listValidator.ts b/packages/ssz/test/lodestarTypes/phase0/viewDU/listValidator.ts index 6536748b..de3fcf38 100644 --- a/packages/ssz/test/lodestarTypes/phase0/viewDU/listValidator.ts +++ b/packages/ssz/test/lodestarTypes/phase0/viewDU/listValidator.ts @@ -94,7 +94,8 @@ export class ListValidatorTreeViewDU extends ListCompositeTreeViewDU = { + a: 10, + b: true, + c: {selector: 1, value: 100}, + d: Buffer.alloc(64, 2), + e: Buffer.alloc(64, 1), + f: {f0: 100, f1: 101}, + g: {g0: 100, g1: 101}, + h: [1, 2], + i: [1, 2], + j: [{f0: 1, f1: 2}], + k: [{f0: 1, f1: 2}], + l: BitArray.fromSingleBit(64, 5), + m: BitArray.fromSingleBit(4, 1), + }; const expectedRoot = parentContainerType.toView(value).hashTreeRoot(); it("fresh ViewDU", () => { expect(parentContainerType.toViewDU(value).batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); - it("full hash then modify basic type", () => { - const viewDU = parentContainerType.toViewDU({a: 9, b: {b0: 100, b1: 101}}); + it("full hash then modify Number type", () => { + const viewDU = parentContainerType.toViewDU({...value, a: 9}); viewDU.batchHashTreeRoot(); viewDU.a += 1; expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.a = 10; + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify BooleanType", () => { + const viewDU = parentContainerType.toViewDU({...value, b: false}); + viewDU.batchHashTreeRoot(); + viewDU.b = true; + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.b = true; + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify UnionType", () => { + const viewDU = parentContainerType.toViewDU({...value, c: {selector: 1, value: 101}}); + viewDU.batchHashTreeRoot(); + viewDU.c = unionType.toViewDU({selector: 1, value: 100}); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.c = unionType.toViewDU({selector: 1, value: 100}); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify ByteVectorType", () => { + const viewDU = parentContainerType.toViewDU(value); + viewDU.batchHashTreeRoot(); + // this takes more than 1 chunk so the resulting node is a branch node + viewDU.e = viewDU.e.slice(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.e = viewDU.e.slice(); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify ByteListType", () => { + const viewDU = parentContainerType.toViewDU(value); + viewDU.batchHashTreeRoot(); + // this takes more than 1 chunk so the resulting node is a branch node + viewDU.d = viewDU.d.slice(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.d = viewDU.d.slice(); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); it("full hash then modify full child container", () => { - const viewDU = parentContainerType.toViewDU({a: 10, b: {b0: 99, b1: 999}}); + const viewDU = parentContainerType.toViewDU({...value, f: {f0: 99, f1: 999}}); viewDU.batchHashTreeRoot(); - viewDU.b = childContainerType.toViewDU({b0: 100, b1: 101}); + viewDU.f = childContainerType.toViewDU({f0: 100, f1: 101}); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.f = childContainerType.toViewDU({f0: 100, f1: 101}); + viewDU.commit(); expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); it("full hash then modify partial child container", () => { - const viewDU = parentContainerType.toViewDU({a: 10, b: {b0: 99, b1: 999}}); + const viewDU = parentContainerType.toViewDU({...value, f: {f0: 99, f1: 999}}); + viewDU.batchHashTreeRoot(); + viewDU.f.f0 = 100; + viewDU.f.f1 = 101; + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.f.f0 = 100; + viewDU.f.f1 = 101; + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify ContainerNodeStructType", () => { + const viewDU = parentContainerType.toViewDU({...value, g: {g0: 99, g1: 999}}); + viewDU.batchHashTreeRoot(); + viewDU.g = childContainerStruct.toViewDU({g0: 100, g1: 101}); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.g = childContainerStruct.toViewDU({g0: 100, g1: 101}); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify partial ContainerNodeStructType", () => { + const viewDU = parentContainerType.toViewDU({...value, g: {g0: 99, g1: 999}}); + viewDU.batchHashTreeRoot(); + viewDU.g.g0 = 100; + viewDU.g.g1 = 101; + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.g.g0 = 100; + viewDU.g.g1 = 101; + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify ListBasicType", () => { + const viewDU = parentContainerType.toViewDU({...value, h: []}); + viewDU.batchHashTreeRoot(); + viewDU.h = listBasicType.toViewDU([1, 2]); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.h = listBasicType.toViewDU([1, 2]); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then push 1 item to ListBasicType", () => { + const viewDU = parentContainerType.toViewDU({...value, h: [1]}); + viewDU.batchHashTreeRoot(); + viewDU.h.push(2); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.h = listBasicType.toViewDU([1, 2]); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify 1 item of ListBasicType", () => { + const viewDU = parentContainerType.toViewDU({...value, h: [1, 3]}); + viewDU.batchHashTreeRoot(); + viewDU.h.set(1, 2); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.h.set(1, 2); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify VectorBasicType", () => { + const viewDU = parentContainerType.toViewDU({...value, i: []}); + viewDU.batchHashTreeRoot(); + viewDU.i = vectorBasicType.toViewDU([1, 2]); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.i = vectorBasicType.toViewDU([1, 2]); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify 1 item of VectorBasicType", () => { + const viewDU = parentContainerType.toViewDU({...value, i: [1, 3]}); + viewDU.batchHashTreeRoot(); + viewDU.i.set(1, 2); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.i.set(1, 2); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify ListCompositeType", () => { + const viewDU = parentContainerType.toViewDU({...value, j: []}); + viewDU.batchHashTreeRoot(); + viewDU.j = listCompositeType.toViewDU([{f0: 1, f1: 2}]); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.j = listCompositeType.toViewDU([{f0: 1, f1: 2}]); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then push 1 item to ListCompositeType", () => { + const viewDU = parentContainerType.toViewDU({...value, j: []}); + viewDU.batchHashTreeRoot(); + viewDU.j.push(childContainerType.toViewDU({f0: 1, f1: 2})); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.j = listCompositeType.toViewDU([{f0: 1, f1: 2}]); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify 1 item of ListCompositeType", () => { + const viewDU = parentContainerType.toViewDU({...value, j: [{f0: 1, f1: 3}]}); + viewDU.batchHashTreeRoot(); + viewDU.j.set(0, childContainerType.toViewDU({f0: 1, f1: 2})); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.j.set(0, childContainerType.toViewDU({f0: 1, f1: 2})); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify 1 field of 1 item of ListCompositeType", () => { + const viewDU = parentContainerType.toViewDU({...value, j: [{f0: 1, f1: 3}]}); + viewDU.batchHashTreeRoot(); + viewDU.j.get(0).f1 = 2; + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.j.get(0).f1 = 2; + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify VectorCompositeType", () => { + const viewDU = parentContainerType.toViewDU({...value, k: [{f0: 9, f1: 9}]}); + viewDU.batchHashTreeRoot(); + viewDU.k = vectorCompositeType.toViewDU([{f0: 1, f1: 2}]); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.k = vectorCompositeType.toViewDU([{f0: 1, f1: 2}]); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify 1 item of VectorCompositeType", () => { + const viewDU = parentContainerType.toViewDU({...value, k: [{f0: 1, f1: 3}]}); + viewDU.batchHashTreeRoot(); + viewDU.k.set(0, childContainerType.toViewDU({f0: 1, f1: 2})); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.k.set(0, childContainerType.toViewDU({f0: 1, f1: 2})); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify 1 field 1 item of VectorCompositeType", () => { + const viewDU = parentContainerType.toViewDU({...value, k: [{f0: 1, f1: 3}]}); viewDU.batchHashTreeRoot(); - viewDU.b.b0 = 100; - viewDU.b.b1 = 101; + viewDU.k.get(0).f1 = 2; + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.k.get(0).f1 = 2; + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify BitVectorType", () => { + const viewDU = parentContainerType.toViewDU({...value, l: BitArray.fromSingleBit(64, 4)}); + viewDU.batchHashTreeRoot(); + viewDU.l = bitVectorType.toViewDU(BitArray.fromSingleBit(64, 5)); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.l = bitVectorType.toViewDU(BitArray.fromSingleBit(64, 5)); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify BitVectorType bit", () => { + const viewDU = parentContainerType.toViewDU({...value, l: BitArray.fromSingleBit(64, 4)}); + viewDU.batchHashTreeRoot(); + viewDU.l.set(4, false); + viewDU.l.set(5, true); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.l.set(4, false); + viewDU.l.set(5, true); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify BitListType", () => { + const viewDU = parentContainerType.toViewDU({...value, m: BitArray.fromSingleBit(4, 0)}); + viewDU.batchHashTreeRoot(); + viewDU.m = bitListType.toViewDU(BitArray.fromSingleBit(4, 1)); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.m = bitListType.toViewDU(BitArray.fromSingleBit(4, 1)); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify BitListType bit", () => { + const viewDU = parentContainerType.toViewDU({...value, m: BitArray.fromSingleBit(4, 0)}); + viewDU.batchHashTreeRoot(); + viewDU.m.set(0, false); + viewDU.m.set(1, true); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.m.set(0, false); + viewDU.m.set(1, true); + viewDU.commit(); expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); }); -describe("ContainerNodeStruct batchHash", function () { +describe("ContainerNodeStruct batchHashTreeRoot", function () { const EpochInf = new UintNumberType(8, {clipInfinity: true}); // Ethereum consensus validator type diff --git a/packages/ssz/test/unit/byType/listBasic/tree.test.ts b/packages/ssz/test/unit/byType/listBasic/tree.test.ts index 3036ae33..22b5f50d 100644 --- a/packages/ssz/test/unit/byType/listBasic/tree.test.ts +++ b/packages/ssz/test/unit/byType/listBasic/tree.test.ts @@ -256,6 +256,14 @@ describe("ListBasicType batchHashTreeRoot", function () { viewDU.push(3); viewDU.push(4); expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign the same value again, commit() then batchHashTreeRoot() + viewDU.set(0, 1); + viewDU.set(1, 2); + viewDU.set(2, 3); + viewDU.set(3, 4); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); it("push then modify then batchHashTreeRoot()", () => { @@ -266,6 +274,12 @@ describe("ListBasicType batchHashTreeRoot", function () { viewDU.push(44); viewDU.set(3, 4); expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign the same value again, commit() then batchHashTreeRoot() + viewDU.set(3, 44); + viewDU.set(3, 4); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); it("full hash then modify", () => { @@ -278,6 +292,15 @@ describe("ListBasicType batchHashTreeRoot", function () { viewDU.set(2, 3); viewDU.set(3, 4); expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign the same value again, commit() then batchHashTreeRoot() + viewDU.set(2, 33); + viewDU.set(3, 44); + viewDU.commit(); + viewDU.set(2, 3); + viewDU.set(3, 4); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); // similar to a fresh ViewDU but it's good to test diff --git a/packages/ssz/test/unit/byType/listComposite/tree.test.ts b/packages/ssz/test/unit/byType/listComposite/tree.test.ts index 2b0a6b91..9f5e5f66 100644 --- a/packages/ssz/test/unit/byType/listComposite/tree.test.ts +++ b/packages/ssz/test/unit/byType/listComposite/tree.test.ts @@ -1,6 +1,7 @@ import {expect} from "chai"; import { CompositeView, + ContainerNodeStructType, ContainerType, ListCompositeType, ReusableListIterator, @@ -17,7 +18,7 @@ const containerUintsType = new ContainerType( {a: uint64NumInfType, b: uint64NumInfType}, {typeName: "Container(uint64)"} ); -const listOfContainersType = new ListCompositeType(containerUintsType, 4); +const listOfContainersType = new ListCompositeType(containerUintsType, 4, {typeName: "ListCompositeType(Container)"}); runViewTestMutation({ type: listOfContainersType, @@ -237,68 +238,109 @@ describe("ListCompositeType batchHashTreeRoot", () => { {a: 1, b: 2}, {a: 3, b: 4}, ]; - const expectedRoot = listOfContainersType.toView(value).hashTreeRoot(); - - it("fresh ViewDU", () => { - expect(listOfContainersType.toViewDU(value).batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + const containerStructUintsType = new ContainerNodeStructType( + {a: uint64NumInfType, b: uint64NumInfType}, + {typeName: "ContainerNodeStruct(uint64)"} + ); + const listOfContainersType2 = new ListCompositeType(containerStructUintsType, 4, { + typeName: "ListCompositeType(ContainerNodeStructType)", }); - it("push then batchHashTreeRoot()", () => { - const viewDU = listOfContainersType.defaultViewDU(); - viewDU.push(containerUintsType.toViewDU({a: 1, b: 2})); - viewDU.push(containerUintsType.toViewDU({a: 3, b: 4})); - expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); - }); + for (const list of [listOfContainersType, listOfContainersType2]) { + const typeName = list.typeName; + const expectedRoot = list.toView(value).hashTreeRoot(); - it("full hash then modify full non-hashed child element", () => { - const viewDU = listOfContainersType.defaultViewDU(); - viewDU.push(containerUintsType.toViewDU({a: 1, b: 2})); - viewDU.push(containerUintsType.toViewDU({a: 33, b: 44})); - viewDU.batchHashTreeRoot(); - viewDU.set(1, containerUintsType.toViewDU({a: 3, b: 4})); - expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); - }); + it(`${typeName} - fresh ViewDU`, () => { + expect(listOfContainersType.toViewDU(value).batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); - it("full hash then modify partially hashed child element", () => { - const viewDU = listOfContainersType.defaultViewDU(); - viewDU.push(containerUintsType.toViewDU({a: 1, b: 2})); - viewDU.push(containerUintsType.toViewDU({a: 33, b: 44})); - viewDU.batchHashTreeRoot(); - const item1 = containerUintsType.toViewDU({a: 3, b: 44}); - item1.batchHashTreeRoot(); - item1.b = 4; - viewDU.set(1, item1); - expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); - }); + it(`${typeName} - push then batchHashTreeRoot()`, () => { + const viewDU = listOfContainersType.defaultViewDU(); + viewDU.push(containerUintsType.toViewDU({a: 1, b: 2})); + viewDU.push(containerUintsType.toViewDU({a: 3, b: 4})); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); - it("full hash then modify full hashed child element", () => { - const viewDU = listOfContainersType.defaultViewDU(); - viewDU.push(containerUintsType.toViewDU({a: 1, b: 2})); - viewDU.push(containerUintsType.toViewDU({a: 33, b: 44})); - viewDU.batchHashTreeRoot(); - const item1 = containerUintsType.toViewDU({a: 3, b: 4}); - item1.batchHashTreeRoot(); - viewDU.set(1, item1); - expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); - }); + // assign again, commit() then batchHashTreeRoot() + viewDU.set(0, containerUintsType.toViewDU({a: 1, b: 2})); + viewDU.set(1, containerUintsType.toViewDU({a: 3, b: 4})); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); - it("full hash then modify partial child element", () => { - const viewDU = listOfContainersType.defaultViewDU(); - viewDU.push(containerUintsType.toViewDU({a: 1, b: 2})); - viewDU.push(containerUintsType.toViewDU({a: 33, b: 44})); - viewDU.batchHashTreeRoot(); - viewDU.get(1).a = 3; - viewDU.get(1).b = 4; - expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); - }); + it(`${typeName} - full hash then modify full non-hashed child element`, () => { + const viewDU = listOfContainersType.defaultViewDU(); + viewDU.push(containerUintsType.toViewDU({a: 1, b: 2})); + viewDU.push(containerUintsType.toViewDU({a: 33, b: 44})); + viewDU.batchHashTreeRoot(); + viewDU.set(1, containerUintsType.toViewDU({a: 3, b: 4})); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); - // similar to a fresh ViewDU but it's good to test - it("sliceTo()", () => { - const viewDU = listOfContainersType.defaultViewDU(); - viewDU.push(containerUintsType.toViewDU({a: 1, b: 2})); - viewDU.push(containerUintsType.toViewDU({a: 3, b: 4})); - viewDU.push(containerUintsType.toViewDU({a: 5, b: 6})); - viewDU.batchHashTreeRoot(); - expect(viewDU.sliceTo(1).batchHashTreeRoot()).to.be.deep.equal(expectedRoot); - }); + // assign the same value again, commit() then batchHashTreeRoot() + viewDU.set(1, containerUintsType.toViewDU({a: 3, b: 4})); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it(`${typeName} - full hash then modify partially hashed child element`, () => { + const viewDU = listOfContainersType.defaultViewDU(); + viewDU.push(containerUintsType.toViewDU({a: 1, b: 2})); + viewDU.push(containerUintsType.toViewDU({a: 33, b: 44})); + viewDU.batchHashTreeRoot(); + const item1 = containerUintsType.toViewDU({a: 3, b: 44}); + item1.batchHashTreeRoot(); + item1.b = 4; + viewDU.set(1, item1); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign the same value again, commit() then batchHashTreeRoot() + const item2 = viewDU.get(1); + item2.a = 3; + item2.b = 4; + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it(`${typeName} - full hash then modify full hashed child element`, () => { + const viewDU = listOfContainersType.defaultViewDU(); + viewDU.push(containerUintsType.toViewDU({a: 1, b: 2})); + viewDU.push(containerUintsType.toViewDU({a: 33, b: 44})); + viewDU.batchHashTreeRoot(); + const item1 = containerUintsType.toViewDU({a: 3, b: 4}); + item1.batchHashTreeRoot(); + viewDU.set(1, item1); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign the same value again, commit() then batchHashTreeRoot() + const newItem = containerUintsType.toViewDU({a: 3, b: 4}); + viewDU.set(1, newItem); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it(`${typeName} - full hash then modify partial child element`, () => { + const viewDU = listOfContainersType.defaultViewDU(); + viewDU.push(containerUintsType.toViewDU({a: 1, b: 2})); + viewDU.push(containerUintsType.toViewDU({a: 33, b: 44})); + viewDU.batchHashTreeRoot(); + viewDU.get(1).a = 3; + viewDU.get(1).b = 4; + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign the same value again, commit() then batchHashTreeRoot() + viewDU.get(1).a = 3; + viewDU.get(1).b = 4; + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + // similar to a fresh ViewDU but it's good to test + it(`${typeName} - sliceTo()`, () => { + const viewDU = listOfContainersType.defaultViewDU(); + viewDU.push(containerUintsType.toViewDU({a: 1, b: 2})); + viewDU.push(containerUintsType.toViewDU({a: 3, b: 4})); + viewDU.push(containerUintsType.toViewDU({a: 5, b: 6})); + viewDU.batchHashTreeRoot(); + expect(viewDU.sliceTo(1).batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + } }); diff --git a/packages/ssz/test/unit/byType/optional/tree.test.ts b/packages/ssz/test/unit/byType/optional/tree.test.ts index c61b9478..5b69d17a 100644 --- a/packages/ssz/test/unit/byType/optional/tree.test.ts +++ b/packages/ssz/test/unit/byType/optional/tree.test.ts @@ -8,7 +8,7 @@ const SimpleObject = new ContainerType({ }); describe("Optional view tests", () => { - // unimplemented + // TODO: implement // eslint-disable-next-line @typescript-eslint/no-unsafe-call it.skip("optional simple type", () => { const type = new OptionalType(byteType); @@ -22,7 +22,7 @@ describe("Optional view tests", () => { expect(toHexString(type.commitViewDU(viewDU).root)).equals(toHexString(root)); }); - // unimplemented + // TODO: implement // eslint-disable-next-line @typescript-eslint/no-unsafe-call it.skip("optional composite type", () => { const type = new OptionalType(SimpleObject); diff --git a/packages/ssz/test/unit/byType/vectorBasic/tree.test.ts b/packages/ssz/test/unit/byType/vectorBasic/tree.test.ts new file mode 100644 index 00000000..69cebbd8 --- /dev/null +++ b/packages/ssz/test/unit/byType/vectorBasic/tree.test.ts @@ -0,0 +1,67 @@ +import {expect} from "chai"; +import {UintNumberType, VectorBasicType} from "../../../../src"; +import {runViewTestMutation} from "../runViewTestMutation"; + +const uint64NumInf = new UintNumberType(8, {clipInfinity: true}); +const vectorType = new VectorBasicType(uint64NumInf, 8); + +runViewTestMutation({ + type: vectorType, + mutations: [ + { + id: "set basic", + valueBefore: [1, 2, 3, 4, 5, 6, 7, 8], + valueAfter: [0, 1, 2, 3, 4, 5, 6, 7], + fn: (tv) => { + tv.set(0, 0); + tv.set(1, 1); + tv.set(2, 2); + tv.set(3, 3); + tv.set(4, 4); + tv.set(5, 5); + tv.set(6, 6); + tv.set(7, 7); + }, + }, + { + id: "swap two indices", + valueBefore: [1, 2, 3, 4, 5, 6, 7, 8], + valueAfter: [8, 2, 3, 4, 5, 6, 7, 1], + fn: (tv) => { + const i0 = tv.get(0); + const i7 = tv.get(7); + tv.set(0, i7); + tv.set(7, i0); + }, + }, + ], +}); + +describe("VectorBasicType batchHashTreeRoot", () => { + const value = [0, 1, 2, 3, 4, 5, 6, 7, 8]; + const expectedRoot = vectorType.hashTreeRoot(value); + + it("fresh ViewDU", () => { + expect(vectorType.toViewDU(value).batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it("full hash then modify", () => { + const viewDU = vectorType.defaultViewDU(); + viewDU.hashTreeRoot(); + viewDU.set(0, 0); + viewDU.set(1, 1); + viewDU.set(2, 2); + viewDU.set(3, 3); + viewDU.set(4, 4); + viewDU.set(5, 5); + viewDU.set(6, 6); + viewDU.set(7, 7); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign the same value again, commit() then batchHashTreeRoot() + viewDU.set(0, 0); + viewDU.set(7, 7); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); +}); diff --git a/packages/ssz/test/unit/byType/vectorComposite/tree.test.ts b/packages/ssz/test/unit/byType/vectorComposite/tree.test.ts new file mode 100644 index 00000000..b013cae5 --- /dev/null +++ b/packages/ssz/test/unit/byType/vectorComposite/tree.test.ts @@ -0,0 +1,121 @@ +import {expect} from "chai"; +import {ContainerNodeStructType, ContainerType, UintNumberType, ValueOf, VectorCompositeType} from "../../../../src"; +import {runViewTestMutation} from "../runViewTestMutation"; + +const uint64NumInfType = new UintNumberType(8, {clipInfinity: true}); +const containerUintsType = new ContainerType( + {a: uint64NumInfType, b: uint64NumInfType}, + {typeName: "Container(uint64)"} +); +const vectorOfContainersType = new VectorCompositeType(containerUintsType, 2, {typeName: "VectorComposite(Container)"}); + +runViewTestMutation({ + type: vectorOfContainersType, + treeViewToStruct: (tv) => { + const arr: ValueOf = []; + for (let i = 0; i < tv.length; i++) { + const item = tv.get(i); + arr.push({a: item.a, b: item.b}); + } + return arr; + }, + mutations: [ + { + id: "set", + valueBefore: [ + {a: 1, b: 2}, + {a: 3, b: 4}, + ], + valueAfter: [ + {a: 5, b: 6}, + {a: 7, b: 8}, + ], + fn: (tv) => { + tv.set(0, containerUintsType.toViewDU({a: 5, b: 6})); + tv.set(1, containerUintsType.toViewDU({a: 7, b: 8})); + }, + }, + { + id: "set child properties", + valueBefore: [ + {a: 1, b: 2}, + {a: 3, b: 4}, + ], + valueAfter: [ + {a: 5, b: 2}, + {a: 3, b: 8}, + ], + fn: (tv) => { + tv.get(0).a = 5; + tv.get(1).b = 8; + }, + }, + { + id: "swap indices", + valueBefore: [ + {a: 1, b: 2}, + {a: 3, b: 4}, + ], + valueAfter: [ + {a: 3, b: 4}, + {a: 1, b: 2}, + ], + fn: (tv) => { + const item0 = tv.get(0); + const item1 = tv.get(1); + tv.set(0, item1); + tv.set(1, item0); + }, + }, + ], +}); + +describe("VectorCompositeType batchHashTreeRoot", () => { + const value = [ + {a: 1, b: 2}, + {a: 3, b: 4}, + ]; + const containerUintsType = new ContainerNodeStructType( + {a: uint64NumInfType, b: uint64NumInfType}, + {typeName: "ContainerNodeStruct(uint64)"} + ); + const vectorOfContainersType2 = new VectorCompositeType(containerUintsType, 2, { + typeName: "VectorComposite(ContainerNodeStruct)", + }); + for (const vector of [vectorOfContainersType, vectorOfContainersType2]) { + const typeName = vector.typeName; + const expectedRoot = vectorOfContainersType.toView(value).hashTreeRoot(); + + it(`${typeName} - fresh ViewDU`, () => { + expect(vectorOfContainersType.toViewDU(value).batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it(`${typeName} - modify 1 full element`, () => { + const viewDU = vectorOfContainersType.toViewDU([ + {a: 1, b: 2}, + {a: 0, b: 0}, + ]); + viewDU.set(1, containerUintsType.toViewDU({a: 3, b: 4})); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign the same value again, commit() then batchHashTreeRoot(); + viewDU.set(1, containerUintsType.toViewDU({a: 3, b: 4})); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + it(`${typeName} - modify 1 property of 1 element`, () => { + const viewDU = vectorOfContainersType.toViewDU([ + {a: 1, b: 2}, + {a: 3, b: 0}, + ]); + viewDU.get(1).b = 4; + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign the same value again, commit() then batchHashTreeRoot(); + viewDU.get(1).b = 4; + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + } +});