diff --git a/packages/persistent-merkle-tree/package.json b/packages/persistent-merkle-tree/package.json index 4f518628..10570bb4 100644 --- a/packages/persistent-merkle-tree/package.json +++ b/packages/persistent-merkle-tree/package.json @@ -49,10 +49,5 @@ "@chainsafe/as-sha256": "0.5.0", "@chainsafe/hashtree": "1.0.1", "@noble/hashes": "^1.3.0" - }, - "peerDependencies": { - "@chainsafe/hashtree-linux-x64-gnu": "1.0.1", - "@chainsafe/hashtree-linux-arm64-gnu": "1.0.1", - "@chainsafe/hashtree-darwin-arm64": "1.0.1" } } diff --git a/packages/persistent-merkle-tree/src/subtree.ts b/packages/persistent-merkle-tree/src/subtree.ts index 65ea3f51..44dc7987 100644 --- a/packages/persistent-merkle-tree/src/subtree.ts +++ b/packages/persistent-merkle-tree/src/subtree.ts @@ -38,7 +38,6 @@ export function subtreeFillToLength(bottom: Node, depth: number, length: number) /** * WARNING: Mutates the provided nodes array. - * @param hashCompRootNode is a hacky way from ssz to set `dest` of HashComputation for BranchNodeStruct * TODO: Don't mutate the nodes array. * hcByLevel is an output parameter that will be filled with the hash computations if exists. */ diff --git a/packages/ssz/src/viewDU/abstract.ts b/packages/ssz/src/viewDU/abstract.ts index 08bd8551..4ef1124d 100644 --- a/packages/ssz/src/viewDU/abstract.ts +++ b/packages/ssz/src/viewDU/abstract.ts @@ -58,10 +58,17 @@ export abstract class TreeViewDU createPartiallyModifiedDenebState(), fn: (state: CompositeViewDU) => { @@ -37,7 +35,7 @@ describe(`BeaconState ViewDU partially modified tree vc=${vc} numModified=${numM }, }); - itBench({ + itBench.skip({ id: `BeaconState ViewDU validator tree creation vc=${numModified}`, beforeEach: () => { const state = createPartiallyModifiedDenebState(); @@ -54,17 +52,17 @@ describe(`BeaconState ViewDU partially modified tree vc=${vc} numModified=${numM const hc = new HashComputationGroup(); itBench({ - id: `BeaconState ViewDU hashTreeRoot vc=${vc}`, + id: `BeaconState ViewDU batchHashTreeRoot vc=${vc}`, beforeEach: () => createPartiallyModifiedDenebState(), fn: (state: CompositeViewDU) => { // commit() step is inside hashTreeRoot(), reuse HashComputationGroup - if (toHexString(state.hashTreeRoot(hc)) !== expectedRoot) { - throw new Error("hashTreeRoot does not match expectedRoot"); + if (toHexString(state.batchHashTreeRoot(hc)) !== expectedRoot) { + throw new Error("batchHashTreeRoot does not match expectedRoot"); } }, }); - itBench({ + itBench.skip({ id: `BeaconState ViewDU hashTreeRoot - commit step vc=${vc}`, beforeEach: () => createPartiallyModifiedDenebState(), fn: (state: CompositeViewDU) => { @@ -72,7 +70,7 @@ describe(`BeaconState ViewDU partially modified tree vc=${vc} numModified=${numM }, }); - itBench({ + itBench.skip({ id: `BeaconState ViewDU hashTreeRoot - hash step vc=${vc}`, beforeEach: () => { const state = createPartiallyModifiedDenebState(); diff --git a/packages/ssz/test/perf/eth2/hashTreeRoot.test.ts b/packages/ssz/test/perf/eth2/hashTreeRoot.test.ts index f94cc6ec..1ae6da27 100644 --- a/packages/ssz/test/perf/eth2/hashTreeRoot.test.ts +++ b/packages/ssz/test/perf/eth2/hashTreeRoot.test.ts @@ -1,5 +1,5 @@ import {itBench} from "@dapplion/benchmark"; -import {hasher, uint8ArrayToHashObject} from "@chainsafe/persistent-merkle-tree"; +import {HashComputationGroup, hasher, uint8ArrayToHashObject} from "@chainsafe/persistent-merkle-tree"; import * as sszPhase0 from "../../lodestarTypes/phase0/sszTypes"; import * as sszAltair from "../../lodestarTypes/altair/sszTypes"; import { @@ -68,6 +68,16 @@ describe("HashTreeRoot frequent eth2 objects", () => { }, }); + const hc = new HashComputationGroup(); + itBench, Uint8Array>({ + id: `BeaconState vc ${validatorCount} - batchHashTreeRoot tree`, + before: () => getStateViewDU().serialize(), + beforeEach: (bytes) => sszAltair.BeaconState.deserializeToViewDU(bytes), + fn: (state) => { + state.batchHashTreeRoot(hc); + }, + }); + for (const {fieldName, fieldType} of sszAltair.BeaconState.fieldsEntries) { // Only benchmark big data structures if (fieldType.maxSize < 10e6 || !isCompositeType(fieldType)) { diff --git a/packages/ssz/test/spec/runValidTest.ts b/packages/ssz/test/spec/runValidTest.ts index 0d12a4b6..5ea219eb 100644 --- a/packages/ssz/test/spec/runValidTest.ts +++ b/packages/ssz/test/spec/runValidTest.ts @@ -114,6 +114,20 @@ export function runValidSszTest(type: Type, testData: ValidTestCaseData assertRoot(root, "type.hashTreeRoot()"); } + if (isCompositeType(type)) { + // batchHashTreeRoot() + const root = wrapErr(() => { + const node = type.value_toTree(testDataValue); + const viewDU = type.getViewDU(node); + if (viewDU instanceof TreeViewDU) { + return viewDU.batchHashTreeRoot(); + } else { + return type.hashTreeRoot(testDataValue); + } + }, "type.hashTreeRoot()"); + assertRoot(root, "ViewDU.batchHashTreeRoot()"); + } + // value -> tree - value_toTree() const node = wrapErr(() => type.value_toTree(testDataValue), "type.value_toTree()"); assertNode(node, "type.value_toTree()"); diff --git a/packages/ssz/test/unit/byType/bitArray/tree.test.ts b/packages/ssz/test/unit/byType/bitArray/tree.test.ts index b6adf522..456243c5 100644 --- a/packages/ssz/test/unit/byType/bitArray/tree.test.ts +++ b/packages/ssz/test/unit/byType/bitArray/tree.test.ts @@ -51,19 +51,19 @@ for (const type of [new BitVectorType(4), new BitListType(4)]) { }); } -describe("BitArray batchHash", () => { +describe("BitArray batchHashTreeRoot", () => { const sszType = new BitListType(4); const value = fromNum(4, 0b0010); const expectedRoot = sszType.toView(value).hashTreeRoot(); it("fresh ViewDU", () => { - expect(sszType.toViewDU(value).hashTreeRoot()).to.be.deep.equal(expectedRoot); + expect(sszType.toViewDU(value).batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); it("set then hashTreeRoot", () => { const viewDU = sszType.toViewDU(fromNum(4, 0b0011)); viewDU.set(0, false); - expect(sszType.toViewDU(value).hashTreeRoot()).to.be.deep.equal(expectedRoot); + expect(sszType.toViewDU(value).batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); }); diff --git a/packages/ssz/test/unit/byType/bitVector/tree.test.ts b/packages/ssz/test/unit/byType/bitVector/tree.test.ts index c1a48290..04f2ee14 100644 --- a/packages/ssz/test/unit/byType/bitVector/tree.test.ts +++ b/packages/ssz/test/unit/byType/bitVector/tree.test.ts @@ -49,19 +49,19 @@ runViewTestMutation({ ], }); -describe("BitVector batchHash", () => { +describe("BitVector batchHashTreeRoot", () => { const sszType = new BitVectorType(4); const value = fromNum(4, 0b0010); const expectedRoot = sszType.toView(value).hashTreeRoot(); it("fresh ViewDU", () => { - expect(sszType.toViewDU(value).hashTreeRoot()).to.be.deep.equal(expectedRoot); + expect(sszType.toViewDU(value).batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); - it("set then hashTreeRoot", () => { + it("set then batchHashTreeRoot", () => { const viewDU = sszType.toViewDU(fromNum(4, 0b0011)); viewDU.set(0, false); - expect(sszType.toViewDU(value).hashTreeRoot()).to.be.deep.equal(expectedRoot); + expect(sszType.toViewDU(value).batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); }); diff --git a/packages/ssz/test/unit/byType/container/tree.test.ts b/packages/ssz/test/unit/byType/container/tree.test.ts index bca2fd4f..dea2f7e2 100644 --- a/packages/ssz/test/unit/byType/container/tree.test.ts +++ b/packages/ssz/test/unit/byType/container/tree.test.ts @@ -221,7 +221,7 @@ runViewTestMutation({ ], }); -describe("ContainerViewDU batchHash", function () { +describe("ContainerViewDU batchHashTreeRoot", function () { const childContainerType = new ContainerType({b0: uint64NumInfType, b1: uint64NumInfType}); const parentContainerType = new ContainerType({ // a basic type @@ -233,29 +233,29 @@ describe("ContainerViewDU batchHash", function () { const expectedRoot = parentContainerType.toView(value).hashTreeRoot(); it("fresh ViewDU", () => { - expect(parentContainerType.toViewDU(value).hashTreeRoot()).to.be.deep.equal(expectedRoot); + 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}}); - viewDU.hashTreeRoot(); + viewDU.batchHashTreeRoot(); viewDU.a += 1; - expect(viewDU.hashTreeRoot()).to.be.deep.equal(expectedRoot); + 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}}); - viewDU.hashTreeRoot(); + viewDU.batchHashTreeRoot(); viewDU.b = childContainerType.toViewDU({b0: 100, b1: 101}); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(expectedRoot); + 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}}); - viewDU.hashTreeRoot(); + viewDU.batchHashTreeRoot(); viewDU.b.b0 = 100; viewDU.b.b1 = 101; - expect(viewDU.hashTreeRoot()).to.be.deep.equal(expectedRoot); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); }); @@ -286,14 +286,14 @@ describe("ContainerNodeStruct batchHash", function () { const expectedRoot = containerType.toView(value).hashTreeRoot(); it("fresh ViewDU", () => { - expect(containerType.toViewDU(value).hashTreeRoot()).to.be.deep.equal(expectedRoot); + expect(containerType.toViewDU(value).batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); it("full hash then modify basic type", () => { const viewDU = containerType.toViewDU({...value, exitEpoch: 3}); - viewDU.hashTreeRoot(); + viewDU.batchHashTreeRoot(); viewDU.exitEpoch *= 1_000_000; - expect(viewDU.hashTreeRoot()).to.be.deep.equal(expectedRoot); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); it("modify basic type", () => { @@ -304,6 +304,6 @@ describe("ContainerNodeStruct batchHash", function () { }); viewDU.exitEpoch -= 1; viewDU.withdrawableEpoch -= 1; - expect(viewDU.hashTreeRoot()).to.be.deep.equal(expectedRoot); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); }); diff --git a/packages/ssz/test/unit/byType/listBasic/tree.test.ts b/packages/ssz/test/unit/byType/listBasic/tree.test.ts index 3924564c..3036ae33 100644 --- a/packages/ssz/test/unit/byType/listBasic/tree.test.ts +++ b/packages/ssz/test/unit/byType/listBasic/tree.test.ts @@ -241,31 +241,31 @@ describe("ListBasicType.sliceTo", () => { } }); -describe("ListBasicType batchHash", function () { +describe("ListBasicType batchHashTreeRoot", function () { const value = [1, 2, 3, 4]; const expectedRoot = ListN64Uint64NumberType.toView(value).hashTreeRoot(); it("fresh ViewDU", () => { - expect(ListN64Uint64NumberType.toViewDU(value).hashTreeRoot()).to.be.deep.equal(expectedRoot); + expect(ListN64Uint64NumberType.toViewDU(value).batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); - it("push then hashTreeRoot()", () => { + it("push then batchHashTreeRoot()", () => { const viewDU = ListN64Uint64NumberType.defaultViewDU(); viewDU.push(1); viewDU.push(2); viewDU.push(3); viewDU.push(4); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(expectedRoot); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); - it("push then modify then hashTreeRoot()", () => { + it("push then modify then batchHashTreeRoot()", () => { const viewDU = ListN64Uint64NumberType.defaultViewDU(); viewDU.push(1); viewDU.push(2); viewDU.push(3); viewDU.push(44); viewDU.set(3, 4); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(expectedRoot); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); it("full hash then modify", () => { @@ -274,10 +274,10 @@ describe("ListBasicType batchHash", function () { viewDU.push(2); viewDU.push(33); viewDU.push(44); - viewDU.hashTreeRoot(); + viewDU.batchHashTreeRoot(); viewDU.set(2, 3); viewDU.set(3, 4); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(expectedRoot); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); // similar to a fresh ViewDU but it's good to test @@ -288,7 +288,7 @@ describe("ListBasicType batchHash", function () { viewDU.push(3); viewDU.push(4); viewDU.push(5); - viewDU.hashTreeRoot(); - expect(viewDU.sliceTo(3).hashTreeRoot()).to.be.deep.equal(expectedRoot); + viewDU.batchHashTreeRoot(); + expect(viewDU.sliceTo(3).batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); }); diff --git a/packages/ssz/test/unit/byType/listComposite/tree.test.ts b/packages/ssz/test/unit/byType/listComposite/tree.test.ts index 5b738c88..2b0a6b91 100644 --- a/packages/ssz/test/unit/byType/listComposite/tree.test.ts +++ b/packages/ssz/test/unit/byType/listComposite/tree.test.ts @@ -232,7 +232,7 @@ describe("ListCompositeType.sliceFrom", () => { }); }); -describe("ListCompositeType batchHash", () => { +describe("ListCompositeType batchHashTreeRoot", () => { const value = [ {a: 1, b: 2}, {a: 3, b: 4}, @@ -240,56 +240,56 @@ describe("ListCompositeType batchHash", () => { const expectedRoot = listOfContainersType.toView(value).hashTreeRoot(); it("fresh ViewDU", () => { - expect(listOfContainersType.toViewDU(value).hashTreeRoot()).to.be.deep.equal(expectedRoot); + expect(listOfContainersType.toViewDU(value).batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); - it("push then hashTreeRoot()", () => { + 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.hashTreeRoot()).to.be.deep.equal(expectedRoot); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); 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.hashTreeRoot(); + viewDU.batchHashTreeRoot(); viewDU.set(1, containerUintsType.toViewDU({a: 3, b: 4})); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(expectedRoot); + expect(viewDU.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.hashTreeRoot(); + viewDU.batchHashTreeRoot(); const item1 = containerUintsType.toViewDU({a: 3, b: 44}); - item1.hashTreeRoot(); + item1.batchHashTreeRoot(); item1.b = 4; viewDU.set(1, item1); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(expectedRoot); + 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.hashTreeRoot(); + viewDU.batchHashTreeRoot(); const item1 = containerUintsType.toViewDU({a: 3, b: 4}); - item1.hashTreeRoot(); + item1.batchHashTreeRoot(); viewDU.set(1, item1); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(expectedRoot); + 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.hashTreeRoot(); + viewDU.batchHashTreeRoot(); viewDU.get(1).a = 3; viewDU.get(1).b = 4; - expect(viewDU.hashTreeRoot()).to.be.deep.equal(expectedRoot); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); // similar to a fresh ViewDU but it's good to test @@ -298,7 +298,7 @@ describe("ListCompositeType batchHash", () => { 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.hashTreeRoot(); - expect(viewDU.sliceTo(1).hashTreeRoot()).to.be.deep.equal(expectedRoot); + viewDU.batchHashTreeRoot(); + expect(viewDU.sliceTo(1).batchHashTreeRoot()).to.be.deep.equal(expectedRoot); }); }); diff --git a/packages/ssz/test/unit/byType/runViewTestMutation.ts b/packages/ssz/test/unit/byType/runViewTestMutation.ts index d6cae2f5..85298774 100644 --- a/packages/ssz/test/unit/byType/runViewTestMutation.ts +++ b/packages/ssz/test/unit/byType/runViewTestMutation.ts @@ -32,15 +32,22 @@ const runViewTestMutationFn = function runViewTestMutation, value: ValueOf, message: string): void { + function assertValidView(view: TreeViewDU, value: ValueOf, message: string, batchHash: boolean): void { expect(type.toJson(view.toValue())).to.deep.equal(type.toJson(value), `Wrong json - ${message}`); expect(toHexString(view.serialize())).to.equal(toHexString(type.serialize(value)), `Wrong serialized - ${message}`); - expect(toHexString(view.hashTreeRoot())).to.equal( - toHexString(type.hashTreeRoot(value)), - `Wrong hashTreeRoot - ${message}` - ); + if (batchHash) { + expect(toHexString(view.batchHashTreeRoot())).to.equal( + toHexString(type.hashTreeRoot(value)), + `Wrong batchHashTreeRoot - ${message}` + ); + } else { + expect(toHexString(view.hashTreeRoot())).to.equal( + toHexString(type.hashTreeRoot(value)), + `Wrong hashTreeRoot - ${message}` + ); + } } // eslint-disable-next-line no-only-tests/no-only-tests @@ -61,46 +68,48 @@ const runViewTestMutationFn = function runViewTestMutation) ?? tvBefore; - assertValidView(tvAfter as TreeViewDU, valueAfter, "after mutation"); + assertValidView(tvAfter as TreeViewDU, valueAfter, "after mutation", false); if (assertFn) assertFn(tvAfter as CompositeViewDU); }); } - const treeViewDUId = `${id} - TreeViewDU`; - if ((!onlyId || treeViewDUId.includes(onlyId)) && !skipTreeViewDU) { - it(treeViewDUId, () => { - const tvBefore = type.toViewDU(valueBefore) as TreeViewDU; - - // Set to mutable, and edit - const tvAfter = (fn(tvBefore as CompositeViewDU) ?? tvBefore) as CompositeViewDU; - - if (treeViewToStruct) { - const tvAfterStruct = treeViewToStruct(tvAfter); - expect(type.toJson(tvAfterStruct)).to.deep.equal( - type.toJson(valueAfter), - "Wrong value after mutation before commit" - ); - } - - if (assertFn) assertFn(tvAfter as CompositeViewDU); + for (const batchHash of [false, true]) { + const treeViewDUId = `${id} - TreeViewDU, batchHash = ${batchHash}`; + if ((!onlyId || treeViewDUId.includes(onlyId)) && !skipTreeViewDU) { + it(treeViewDUId, () => { + const tvBefore = type.toViewDU(valueBefore) as TreeViewDU; - type.commitViewDU(tvAfter); - assertValidView(tvAfter as TreeViewDU, valueAfter, "after mutation"); - - if (assertFn) assertFn(tvAfter as CompositeViewDU); - - if (!skipCloneMutabilityViewDU) { - // Ensure correct mutability of clone and caches // Set to mutable, and edit - const tvBefore2 = type.toViewDU(valueBefore) as TreeViewDU; - const tvAfter2 = (fn(tvBefore2 as CompositeViewDU) ?? tvBefore2) as CompositeViewDU; - // Drop changes - (tvAfter2 as TreeViewDU).clone(); - // Assert same value as before - assertValidView(tvAfter2 as TreeViewDU, valueBefore, "dropped mutation"); - } - }); + const tvAfter = (fn(tvBefore as CompositeViewDU) ?? tvBefore) as CompositeViewDU; + + if (treeViewToStruct) { + const tvAfterStruct = treeViewToStruct(tvAfter); + expect(type.toJson(tvAfterStruct)).to.deep.equal( + type.toJson(valueAfter), + "Wrong value after mutation before commit" + ); + } + + if (assertFn) assertFn(tvAfter as CompositeViewDU); + + type.commitViewDU(tvAfter); + assertValidView(tvAfter as TreeViewDU, valueAfter, "after mutation", batchHash); + + if (assertFn) assertFn(tvAfter as CompositeViewDU); + + if (!skipCloneMutabilityViewDU) { + // Ensure correct mutability of clone and caches + // Set to mutable, and edit + const tvBefore2 = type.toViewDU(valueBefore) as TreeViewDU; + const tvAfter2 = (fn(tvBefore2 as CompositeViewDU) ?? tvBefore2) as CompositeViewDU; + // Drop changes + (tvAfter2 as TreeViewDU).clone(); + // Assert same value as before + assertValidView(tvAfter2 as TreeViewDU, valueBefore, "dropped mutation", batchHash); + } + }); + } } } }); diff --git a/packages/ssz/test/unit/eth2/beaconState.test.ts b/packages/ssz/test/unit/eth2/beaconState.test.ts index 5c41d0ab..9b886f82 100644 --- a/packages/ssz/test/unit/eth2/beaconState.test.ts +++ b/packages/ssz/test/unit/eth2/beaconState.test.ts @@ -7,19 +7,18 @@ import {BitArray, fromHexString} from "../../../src"; const VALIDATOR_REGISTRY_LIMIT = 1099511627776; export const Balances = new ListUintNum64Type(VALIDATOR_REGISTRY_LIMIT); -// TODO - batch: mix the commit() or hashTreeRoot()? -describe("BeaconState ViewDU batch hash", function () { +describe("BeaconState ViewDU batchHashTreeRoot", function () { const view = BeaconState.defaultView(); const viewDU = BeaconState.defaultViewDU(); it("BeaconState ViewDU should have same hashTreeRoot() to View", () => { // genesisTime viewDU.genesisTime = view.genesisTime = 1e9; - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // genesisValidatorsRoot viewDU.genesisValidatorsRoot = view.genesisValidatorsRoot = Buffer.alloc(32, 1); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // fork const fork: phase0.Fork = { @@ -29,7 +28,7 @@ describe("BeaconState ViewDU batch hash", function () { }; view.fork = BeaconState.fields.fork.toView(fork); viewDU.fork = BeaconState.fields.fork.toViewDU(fork); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // latestBlockHeader const latestBlockHeader: phase0.BeaconBlockHeader = { @@ -41,21 +40,21 @@ describe("BeaconState ViewDU batch hash", function () { }; view.latestBlockHeader = BeaconState.fields.latestBlockHeader.toView(latestBlockHeader); viewDU.latestBlockHeader = BeaconState.fields.latestBlockHeader.toViewDU(latestBlockHeader); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // blockRoots const blockRoots = ssz.phase0.HistoricalBlockRoots.defaultValue(); blockRoots[0] = fromHexString("0x1234"); view.blockRoots = ssz.phase0.HistoricalBlockRoots.toView(blockRoots); viewDU.blockRoots = ssz.phase0.HistoricalBlockRoots.toViewDU(blockRoots); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // stateRoots const stateRoots = ssz.phase0.HistoricalStateRoots.defaultValue(); stateRoots[0] = fromHexString("0x5678"); view.stateRoots = ssz.phase0.HistoricalStateRoots.toView(stateRoots); viewDU.stateRoots = ssz.phase0.HistoricalStateRoots.toViewDU(stateRoots); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // historical_roots Frozen in Capella, replaced by historical_summaries // Eth1 @@ -66,19 +65,19 @@ describe("BeaconState ViewDU batch hash", function () { }; view.eth1Data = BeaconState.fields.eth1Data.toView(eth1Data); viewDU.eth1Data = BeaconState.fields.eth1Data.toViewDU(eth1Data); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // Eth1DataVotes const eth1DataVotes = ssz.phase0.Eth1DataVotes.defaultValue(); eth1DataVotes[0] = eth1Data; view.eth1DataVotes = ssz.phase0.Eth1DataVotes.toView(eth1DataVotes); viewDU.eth1DataVotes = ssz.phase0.Eth1DataVotes.toViewDU(eth1DataVotes); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // Eth1DepositIndex view.eth1DepositIndex = 1000; viewDU.eth1DepositIndex = 1000; - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // validators const validator = { @@ -93,34 +92,34 @@ describe("BeaconState ViewDU batch hash", function () { }; view.validators = BeaconState.fields.validators.toView([validator]); viewDU.validators = BeaconState.fields.validators.toViewDU([validator]); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // balances view.balances = BeaconState.fields.balances.toView([1000, 2000, 3000]); viewDU.balances = Balances.toViewDU([1000, 2000, 3000]); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // randaoMixes const randaoMixes = ssz.phase0.RandaoMixes.defaultValue(); randaoMixes[0] = fromHexString("0x1234"); view.randaoMixes = ssz.phase0.RandaoMixes.toView(randaoMixes); viewDU.randaoMixes = ssz.phase0.RandaoMixes.toViewDU(randaoMixes); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // slashings view.slashings = BeaconState.fields.slashings.toView(Array.from({length: 64}, () => BigInt(1000))); viewDU.slashings = BeaconState.fields.slashings.toViewDU(Array.from({length: 64}, () => BigInt(1000))); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // previousEpochAttestations view.previousEpochParticipation = BeaconState.fields.previousEpochParticipation.toView([1, 2, 3]); viewDU.previousEpochParticipation = BeaconState.fields.previousEpochParticipation.toViewDU([1, 2, 3]); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // currentEpochAttestations view.currentEpochParticipation = BeaconState.fields.currentEpochParticipation.toView([1, 2, 3]); viewDU.currentEpochParticipation = BeaconState.fields.currentEpochParticipation.toViewDU([1, 2, 3]); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // justificationBits view.justificationBits = BeaconState.fields.justificationBits.toView( @@ -129,7 +128,7 @@ describe("BeaconState ViewDU batch hash", function () { viewDU.justificationBits = BeaconState.fields.justificationBits.toViewDU( BitArray.fromBoolArray([true, false, true, true]) ); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // previousJustifiedCheckpoint const checkpoint: phase0.Checkpoint = { @@ -138,22 +137,22 @@ describe("BeaconState ViewDU batch hash", function () { }; view.previousJustifiedCheckpoint = BeaconState.fields.previousJustifiedCheckpoint.toView(checkpoint); viewDU.previousJustifiedCheckpoint = BeaconState.fields.previousJustifiedCheckpoint.toViewDU(checkpoint); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // currentJustifiedCheckpoint view.currentJustifiedCheckpoint = BeaconState.fields.currentJustifiedCheckpoint.toView(checkpoint); viewDU.currentJustifiedCheckpoint = BeaconState.fields.currentJustifiedCheckpoint.toViewDU(checkpoint); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // finalizedCheckpoint view.finalizedCheckpoint = BeaconState.fields.finalizedCheckpoint.toView(checkpoint); viewDU.finalizedCheckpoint = BeaconState.fields.finalizedCheckpoint.toViewDU(checkpoint); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // inactivityScores view.inactivityScores = BeaconState.fields.inactivityScores.toView([1, 2, 3]); viewDU.inactivityScores = BeaconState.fields.inactivityScores.toViewDU([1, 2, 3]); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // currentSyncCommittee const syncCommittee: altair.SyncCommittee = { @@ -162,12 +161,12 @@ describe("BeaconState ViewDU batch hash", function () { }; view.currentSyncCommittee = BeaconState.fields.currentSyncCommittee.toView(syncCommittee); viewDU.currentSyncCommittee = BeaconState.fields.currentSyncCommittee.toViewDU(syncCommittee); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // nextSyncCommittee view.nextSyncCommittee = BeaconState.fields.nextSyncCommittee.toView(syncCommittee); viewDU.nextSyncCommittee = BeaconState.fields.nextSyncCommittee.toViewDU(syncCommittee); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // latestExecutionPayloadHeader const latestExecutionPayloadHeader = BeaconState.fields.latestExecutionPayloadHeader.defaultValue(); @@ -179,15 +178,15 @@ describe("BeaconState ViewDU batch hash", function () { BeaconState.fields.latestExecutionPayloadHeader.toView(latestExecutionPayloadHeader); viewDU.latestExecutionPayloadHeader = BeaconState.fields.latestExecutionPayloadHeader.toViewDU(latestExecutionPayloadHeader); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // nextWithdrawalIndex viewDU.nextWithdrawalIndex = view.nextWithdrawalIndex = 1000; - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // nextWithdrawalValidatorIndex viewDU.nextWithdrawalValidatorIndex = view.nextWithdrawalValidatorIndex = 1000; - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); // historicalSummaries const historicalSummaries = { @@ -196,6 +195,6 @@ describe("BeaconState ViewDU batch hash", function () { }; view.historicalSummaries = BeaconState.fields.historicalSummaries.toView([historicalSummaries]); viewDU.historicalSummaries = BeaconState.fields.historicalSummaries.toViewDU([historicalSummaries]); - expect(viewDU.hashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(view.hashTreeRoot()); }); }); diff --git a/packages/ssz/test/unit/eth2/validators.test.ts b/packages/ssz/test/unit/eth2/validators.test.ts index 4d877428..1cccd691 100644 --- a/packages/ssz/test/unit/eth2/validators.test.ts +++ b/packages/ssz/test/unit/eth2/validators.test.ts @@ -2,7 +2,6 @@ import {expect} from "chai"; import {describe, it} from "mocha"; import {toHexString, ListCompositeType, ValueOf, CompositeViewDU} from "../../../src"; import {ValidatorContainer, ValidatorNodeStruct} from "../../lodestarTypes/phase0/sszTypes"; -import {HashComputationLevel} from "@chainsafe/persistent-merkle-tree"; type Validator = ValueOf; const validator: Validator = { @@ -34,6 +33,7 @@ describe("Container with BranchNodeStruct", function () { getExitEpoch: (treeBacked) => treeBacked.exitEpoch, getPubkey: (treeBacked) => toHexString(treeBacked.pubkey), hashTreeRoot: (treeBacked) => treeBacked.hashTreeRoot(), + batchHashTreeRoot: (treeBacked) => treeBacked.batchHashTreeRoot(), getProof: (treeBacked) => treeBacked.createProof(validatorProofJsonPaths), serialize: (treeBacked) => treeBacked.serialize(), }; @@ -105,61 +105,3 @@ describe("Container with BranchNodeStruct", function () { }); }); }); - -/** - * modifying any fields should result in the whole tree being recomputed - * 0 root - * / \ - * 1 10 11 - * / \ / \ - * 2 20 21 22 23 - * / \ / \ / \ / \ - * 3 pub with eff sla act act exit with - * / \ - * 4 pub0 pub1 - * This does not suport batch hash - **/ -describe.skip("getHashComputations BranchNodeStruct", function () { - const testCases: {name: string; fn: (validator: ValueOf) => void}[] = [ - {name: "modify pubkey", fn: (validator) => (validator.pubkey = Buffer.alloc(48, 0x01))}, - { - name: "modify withdrawalCredentials", - fn: (validator) => (validator.withdrawalCredentials = Buffer.alloc(32, 0x01)), - }, - {name: "modify effectiveBalance", fn: (validator) => (validator.effectiveBalance += 1e9)}, - {name: "modify slashed", fn: (validator) => (validator.slashed = true)}, - {name: "modify activationEligibilityEpoch", fn: (validator) => (validator.activationEligibilityEpoch += 1e6)}, - {name: "modify activationEpoch", fn: (validator) => (validator.activationEpoch += 1e6)}, - {name: "modify exitEpoch", fn: (validator) => (validator.exitEpoch += 1e6)}, - {name: "modify withdrawableEpoch", fn: (validator) => (validator.withdrawableEpoch += 1e6)}, - { - name: "modify all", - fn: (validator) => { - validator.pubkey = Buffer.alloc(48, 0x01); - validator.withdrawalCredentials = Buffer.alloc(32, 0x01); - validator.effectiveBalance += 1e9; - validator.slashed = true; - validator.activationEligibilityEpoch += 1e6; - validator.activationEpoch += 1e6; - validator.exitEpoch += 1e6; - validator.withdrawableEpoch += 1e6; - }, - }, - ]; - - for (const {name, fn} of testCases) { - it(name, () => { - const hcByLevel: HashComputationLevel[] = []; - const validatorViewDU = ValidatorNodeStruct.toViewDU(validator); - // cache all roots - validatorViewDU.hashTreeRoot(); - fn(validatorViewDU); - validatorViewDU.commit(0, hcByLevel); - expect(hcByLevel.length).to.be.equal(4); - expect(hcByLevel[0].length).to.be.equal(1); - expect(hcByLevel[1].length).to.be.equal(2); - expect(hcByLevel[2].length).to.be.equal(4); - expect(hcByLevel[3].length).to.be.equal(1); - }); - } -}); diff --git a/packages/ssz/test/unit/lodestarTypes/phase0/listValidator.test.ts b/packages/ssz/test/unit/lodestarTypes/phase0/listValidator.test.ts index 6602afed..ff3ae2a2 100644 --- a/packages/ssz/test/unit/lodestarTypes/phase0/listValidator.test.ts +++ b/packages/ssz/test/unit/lodestarTypes/phase0/listValidator.test.ts @@ -35,7 +35,7 @@ describe("ListValidator ssz type", function () { oldViewDU.get(i).activationEpoch = 2024; newViewDU.get(i).activationEpoch = 2024; } - expect(newViewDU.hashTreeRoot()).to.be.deep.equal(oldViewDU.hashTreeRoot()); + expect(newViewDU.batchHashTreeRoot()).to.be.deep.equal(oldViewDU.batchHashTreeRoot()); expect(newViewDU.serialize()).to.be.deep.equal(oldViewDU.serialize()); }); } @@ -54,7 +54,7 @@ describe("ListValidator ssz type", function () { oldViewDU.get(index).activationEpoch = 2024; newViewDU.get(index).activationEpoch = 2024; } - expect(newViewDU.hashTreeRoot()).to.be.deep.equal(oldViewDU.hashTreeRoot()); + expect(newViewDU.batchHashTreeRoot()).to.be.deep.equal(oldViewDU.batchHashTreeRoot()); expect(newViewDU.serialize()).to.be.deep.equal(oldViewDU.serialize()); }); } @@ -78,7 +78,7 @@ describe("ListValidator ssz type", function () { newViewDU.push(ssz.phase0.Validator.toViewDU(validator)); } oldViewDU.commit(); - expect(newViewDU.hashTreeRoot()).to.be.deep.equal(oldViewDU.node.root); + expect(newViewDU.batchHashTreeRoot()).to.be.deep.equal(oldViewDU.node.root); expect(newViewDU.serialize()).to.be.deep.equal(oldViewDU.serialize()); const allValidators = newViewDU.getAllReadonlyValues(); for (let i = 0; i < numPush; i++) { diff --git a/packages/ssz/test/unit/lodestarTypes/phase0/validator.test.ts b/packages/ssz/test/unit/lodestarTypes/phase0/validator.test.ts index cf01c2f1..6725a92b 100644 --- a/packages/ssz/test/unit/lodestarTypes/phase0/validator.test.ts +++ b/packages/ssz/test/unit/lodestarTypes/phase0/validator.test.ts @@ -35,8 +35,10 @@ describe("Validator ssz types", function () { const root = ValidatorContainer.hashTreeRoot(validator); const root2 = ssz.phase0.Validator.hashTreeRoot(validator); const root3 = ssz.phase0.Validator.toViewDU(validator).hashTreeRoot(); + const root4 = ssz.phase0.Validator.toViewDU(validator).batchHashTreeRoot(); expect(root2).to.be.deep.equal(root); expect(root3).to.be.deep.equal(root); + expect(root4).to.be.deep.equal(root); } }); }); diff --git a/packages/ssz/test/unit/regressions.test.ts b/packages/ssz/test/unit/regressions.test.ts index 4f5ecaf9..6dc0c22f 100644 --- a/packages/ssz/test/unit/regressions.test.ts +++ b/packages/ssz/test/unit/regressions.test.ts @@ -32,6 +32,8 @@ describe("Regressions / known issues", () => { const bytes = SyncCommitteeBits.serialize(bitArray); const rootByTreeBacked = SyncCommitteeBits.deserializeToViewDU(bytes).hashTreeRoot(); expect(toHexString(rootByStruct)).to.be.equal(toHexString(rootByTreeBacked), "Inconsistent hashTreeRoot"); + const rootByBatch = SyncCommitteeBits.deserializeToViewDU(bytes).batchHashTreeRoot(); + expect(toHexString(rootByStruct)).to.be.equal(toHexString(rootByBatch), "Inconsistent hashTreeRoot"); }); it("converts bit arrays to tree", function () { diff --git a/packages/ssz/test/unit/unchangedViewDUs.test.ts b/packages/ssz/test/unit/unchangedViewDUs.test.ts index 98e696c9..aa61dfe2 100644 --- a/packages/ssz/test/unit/unchangedViewDUs.test.ts +++ b/packages/ssz/test/unit/unchangedViewDUs.test.ts @@ -5,25 +5,25 @@ import {getRandomState} from "../utils/generateEth2Objs"; describe("Unchanged ViewDUs", () => { const state = sszAltair.BeaconState.toViewDU(getRandomState(100)); - it("should not recompute hashTreeRoot() when no fields is changed", () => { - const root = state.hashTreeRoot(); + it("should not recompute batchHashTreeRoot() when no fields is changed", () => { + const root = state.batchHashTreeRoot(); // this causes viewsChanged inside BeaconState container state.validators.length; state.balances.length; // but we should not recompute root, should get from cache instead - const root2 = state.hashTreeRoot(); - expect(root2).to.equal(root, "should not recompute hashTreeRoot() when no fields are changed"); + const root2 = state.batchHashTreeRoot(); + expect(root2).to.equal(root, "should not recompute batchHashTreeRoot() when no fields are changed"); }); - it("handle childViewDU.hashTreeRoot()", () => { + it("handle childViewDU.batchHashTreeRoot()", () => { const state2 = state.clone(); state2.latestBlockHeader.stateRoot = Buffer.alloc(32, 3); - const root2 = state2.hashTreeRoot(); + const root2 = state2.batchHashTreeRoot(); const state3 = state.clone(); state3.latestBlockHeader.stateRoot = Buffer.alloc(32, 3); - // hashTreeRoot() also does the commit() + // batchHashTreeRoot() also does the commit() state3.latestBlockHeader.commit(); - const root3 = state3.hashTreeRoot(); + const root3 = state3.batchHashTreeRoot(); expect(root3).to.be.deep.equal(root2); }); });