diff --git a/packages/ssz/src/type/profile.ts b/packages/ssz/src/type/profile.ts index 1c7676f2..42d00185 100644 --- a/packages/ssz/src/type/profile.ts +++ b/packages/ssz/src/type/profile.ts @@ -253,10 +253,10 @@ export class ProfileType>> extends C tree_serializedSize(node: Node): number { let totalSize = 0; - const nodes = getNodesAtDepth(node, this.depth, 0, this.fieldsEntries.length) as Node[]; + const nodes = getNodesAtDepth(node, this.depth, 0, this.activeFields.bitLen) as Node[]; for (let i = 0; i < this.fieldsEntries.length; i++) { - const {fieldType} = this.fieldsEntries[i]; - const node = nodes[i]; + const {fieldType, chunkIndex} = this.fieldsEntries[i]; + const node = nodes[chunkIndex]; // Offset (4 bytes) + size totalSize += fieldType.fixedSize === null ? 4 + fieldType.tree_serializedSize(node) : fieldType.fixedSize; } @@ -603,7 +603,7 @@ export function precomputeJsonKey>>( */ export function renderContainerTypeName>>( fields: Fields, - prefix = "Container" + prefix = "Profile" ): string { const fieldNames = Object.keys(fields) as (keyof Fields)[]; const fieldTypeNames = fieldNames.map((fieldName) => `${fieldName}: ${fields[fieldName].typeName}`).join(", "); diff --git a/packages/ssz/src/view/profile.ts b/packages/ssz/src/view/profile.ts index 36afe1a8..b6680ca5 100644 --- a/packages/ssz/src/view/profile.ts +++ b/packages/ssz/src/view/profile.ts @@ -96,7 +96,7 @@ export function getContainerTreeViewClass, node: Node, cache?: unknown): ContainerTreeViewDUType; }; +export type ChangedNode = {index: number; node: Node}; + type ContainerTreeViewDUCache = { nodes: Node[]; caches: unknown[]; @@ -94,7 +96,7 @@ export class BasicContainerTreeViewDU a.index - b.index); - const indexes = nodesChangedSorted.map((entry) => entry.index); - const nodes = nodesChangedSorted.map((entry) => entry.node); + const {indexes, nodes} = this.parseNodesChanged(nodesChangedSorted); this._rootNode = setNodesAtDepth( this._rootNode, @@ -135,6 +136,12 @@ export class BasicContainerTreeViewDU entry.index); + const nodesArray = nodes.map((entry) => entry.node); + return {indexes, nodes: nodesArray}; + } + protected clearCache(): void { this.nodes = []; this.caches = []; diff --git a/packages/ssz/src/viewDU/profile.ts b/packages/ssz/src/viewDU/profile.ts index 5afa08e5..21267827 100644 --- a/packages/ssz/src/viewDU/profile.ts +++ b/packages/ssz/src/viewDU/profile.ts @@ -1,20 +1,32 @@ -import {getNodeAtDepth, LeafNode, Node, setNodesAtDepth} from "@chainsafe/persistent-merkle-tree"; +import {getNodeAtDepth, LeafNode, Node} from "@chainsafe/persistent-merkle-tree"; import {ByteViews, Type} from "../type/abstract"; import {BasicType, isBasicType} from "../type/basic"; -import {CompositeType, isCompositeType, CompositeTypeAny} from "../type/composite"; +import {CompositeType, isCompositeType} from "../type/composite"; import {ContainerTypeGeneric} from "../view/profile"; import {TreeViewDU} from "./abstract"; +import {BasicContainerTreeViewDU, ChangedNode} from "./container"; +import {OptionalType} from "../type/optional"; /* eslint-disable @typescript-eslint/member-ordering */ +export type ViewDUValue> = T extends CompositeType + ? // If composite, return view. MAY propagate changes updwards + TVDU + : // If basic, return struct value. Will NOT propagate changes upwards + T extends BasicType + ? V + : never; + +export type OptionalViewDUValue> = T extends CompositeType + ? // If composite, return view. MAY propagate changes updwards + TVDU | null | undefined + : // If basic, return struct value. Will NOT propagate changes upwards + T extends BasicType + ? V | null | undefined + : never; + export type FieldsViewDU>> = { - [K in keyof Fields]: Fields[K] extends CompositeType - ? // If composite, return view. MAY propagate changes updwards - TVDU - : // If basic, return struct value. Will NOT propagate changes upwards - Fields[K] extends BasicType - ? V - : never; + [K in keyof Fields]: Fields[K] extends OptionalType ? OptionalViewDUValue : ViewDUValue; }; export type ContainerTreeViewDUType>> = FieldsViewDU & @@ -29,94 +41,29 @@ type ContainerTreeViewDUCache = { nodesPopulated: boolean; }; -class ContainerTreeViewDU>> extends TreeViewDU< - ContainerTypeGeneric -> { - protected nodes: Node[] = []; - protected caches: unknown[]; - protected readonly nodesChanged = new Set(); - protected readonly viewsChanged = new Map(); - private nodesPopulated: boolean; - +class ProfileTreeViewDU>> extends BasicContainerTreeViewDU { constructor( readonly type: ContainerTypeGeneric, protected _rootNode: Node, cache?: ContainerTreeViewDUCache ) { - super(); - - if (cache) { - this.nodes = cache.nodes; - this.caches = cache.caches; - this.nodesPopulated = cache.nodesPopulated; - } else { - this.nodes = []; - this.caches = []; - this.nodesPopulated = false; - } - } - - get node(): Node { - return this._rootNode; + super(type, _rootNode, cache); } - get cache(): ContainerTreeViewDUCache { - return { - nodes: this.nodes, - caches: this.caches, - nodesPopulated: this.nodesPopulated, - }; - } - - commit(): void { - if (this.nodesChanged.size === 0 && this.viewsChanged.size === 0) { - return; - } - - const nodesChanged: {index: number; node: Node}[] = []; - - for (const [index, view] of this.viewsChanged) { - const fieldType = this.type.fieldsEntries[index].fieldType as unknown as CompositeTypeAny; - const node = fieldType.commitViewDU(view); - // Set new node in nodes array to ensure data represented in the tree and fast nodes access is equal - this.nodes[index] = node; - nodesChanged.push({index, node}); - - // Cache the view's caches to preserve it's data after 'this.viewsChanged.clear()' - const cache = fieldType.cacheOfViewDU(view); - if (cache) this.caches[index] = cache; - } - - for (const index of this.nodesChanged) { - nodesChanged.push({index, node: this.nodes[index]}); + protected parseNodesChanged(nodesArray: ChangedNode[]): {indexes: number[]; nodes: Node[]} { + const indexes = new Array(nodesArray.length); + const nodes = new Array(nodesArray.length); + for (const [i, change] of nodesArray.entries()) { + const {index, node} = change; + const chunkIndex = this.type.fieldsEntries[index].chunkIndex; + indexes[i] = chunkIndex; + nodes[i] = node; } - - // TODO: Optimize to loop only once, Numerical sort ascending - const nodesChangedSorted = nodesChanged.sort((a, b) => a.index - b.index); - const indexes = nodesChangedSorted.map((entry) => entry.index); - const nodes = nodesChangedSorted.map((entry) => entry.node); - - this._rootNode = setNodesAtDepth(this._rootNode, this.type.depth, indexes, nodes); - - this.nodesChanged.clear(); - this.viewsChanged.clear(); - } - - protected clearCache(): void { - this.nodes = []; - this.caches = []; - this.nodesPopulated = false; - - // Must clear nodesChanged, otherwise a subsequent commit call will break, because it assumes a node is there - this.nodesChanged.clear(); - - // It's not necessary to clear this.viewsChanged since they have no effect on the cache. - // However preserving _SOME_ caches results in a very unpredictable experience. - this.viewsChanged.clear(); + return {indexes, nodes}; } /** - * Same method to `type/container.ts` that call ViewDU.serializeToBytes() of internal fields. + * Same method to `type/profile.ts` that call ViewDU.serializeToBytes() of internal fields. */ serializeToBytes(output: ByteViews, offset: number): number { this.commit(); @@ -157,7 +104,7 @@ class ContainerTreeViewDU>> extends export function getContainerTreeViewDUClass>>( type: ContainerTypeGeneric ): ContainerTreeViewDUTypeConstructor { - class CustomContainerTreeViewDU extends ContainerTreeViewDU {} + class CustomContainerTreeViewDU extends ProfileTreeViewDU {} // Dynamically define prototype methods for (let index = 0; index < type.fieldsEntries.length; index++) { @@ -222,7 +169,7 @@ export function getContainerTreeViewDUClass { + tv.a = 10; + }, + }, + { + id: "set basic x2", + valueBefore: {a: 1, b: 2}, + valueAfter: {a: 10, b: 20}, + fn: (tv) => { + tv.a = 10; + tv.b = 20; + }, + }, + // Test that reading a uin64 value that spans two hashObject h values works + // the same with Number64UintType and NumberUintType + { + id: "swap props", + valueBefore: {a: 0xffffffff + 1, b: 0xffffffff + 2}, + valueAfter: {a: 0xffffffff + 2, b: 0xffffffff + 1}, + fn: (tv) => { + const a = tv.a; + const b = tv.b; + tv.a = b; + tv.b = a; + }, + }, + ], +}); + +const containerUintsType = new ProfileType( + {a: uint64NumInfType, b: uint64NumInfType}, + BitArray.fromBoolArray([false, true, true, false]) +); + +runViewTestMutation({ + type: containerUintsType, + treeViewToStruct: (tv) => ({a: tv.a, b: tv.b}), + mutations: [ + { + id: "set all properties", + valueBefore: {a: 1, b: 2}, + valueAfter: {a: 10, b: 20}, + fn: (tv) => { + tv.a = 10; + tv.b = 21; + // Change twice on purpose to trigger a branch in set basic + tv.b = 20; + }, + }, + ], +}); + +const byte32 = new ByteVectorType(32); +const containerBytesType = new ProfileType({a: byte32, b: byte32}, BitArray.fromBoolArray([false, true, true, false])); +const rootOf = (i: number): Buffer => Buffer.alloc(32, i); + +runViewTestMutation({ + type: containerBytesType, + treeViewToStruct: (tv) => ({a: tv.a, b: tv.b}), + mutations: [ + { + id: "set all properties", + valueBefore: {a: rootOf(1), b: rootOf(2)}, + valueAfter: {a: rootOf(3), b: rootOf(4)}, + fn: (tv) => { + tv.a = rootOf(3); + tv.b = rootOf(4); + }, + }, + ], +}); + +const profileUint64 = new ProfileType({a: uint64NumType}, BitArray.fromBoolArray([false, true, false, false])); + +describe(`${profileUint64.typeName} drop caches`, () => { + it("Make some changes then get previous value", () => { + const view = profileUint64.defaultViewDU(); + const bytesBefore = toHexString(view.serialize()); + + // Make changes to view and clone them to new view + view.a = 1; + view.clone(); + + const bytesAfter = toHexString(view.serialize()); + expect(bytesAfter).to.equal(bytesBefore, "view retained changes"); + }); +}); + +// Test only ContainerType if +// - Some fields are mutable + +const list8Uint64NumInfType = new ListBasicType(uint64NumInfType, 8); + +runViewTestMutation({ + type: new ProfileType( + {a: uint64NumInfType, b: uint64NumInfType, list: list8Uint64NumInfType}, + BitArray.fromBoolArray([false, true, true, true]) + ), + mutations: [ + { + id: "set composite entire list", + valueBefore: {a: 1, b: 2, list: []}, + valueAfter: {a: 1, b: 2, list: [10, 20]}, + fn: (tv) => { + tv.list = list8Uint64NumInfType.toViewDU([10, 20]); + }, + }, + { + id: "set composite list with push", + valueBefore: {a: 1, b: 2, list: []}, + valueAfter: {a: 1, b: 2, list: [10, 20]}, + fn: (tv) => { + tv.list.push(10); + tv.list.push(20); + }, + }, + // Test that keeping a reference to `list` and pushing twice mutates the original tv value + { + id: "set composite list with push and reference", + valueBefore: {a: 1, b: 2, list: []}, + valueAfter: {a: 1, b: 2, list: [10, 20]}, + fn: (tv) => { + const list = tv.list; + list.push(10); + list.push(20); + }, + }, + ], +}); + +const containerUint64 = new ProfileType({a: uint64NumType}, BitArray.fromBoolArray([false, true, false, false])); +const listOfContainers = new ListCompositeType(containerUint64, 4); + +runViewTestMutation({ + // Ensure mutations of child array are commited + type: new ContainerType({list: listOfContainers}), + treeViewToStruct: (tv) => { + const listArr: ValueOf = []; + for (let i = 0; i < tv.list.length; i++) { + const item = tv.list.get(i); + listArr.push({a: item.a}); + } + return {list: listArr}; + }, + mutations: [ + { + id: "Push two values", + valueBefore: {list: []}, + valueAfter: {list: [{a: 1}, {a: 2}]}, + fn: (tv) => { + tv.list.push(containerUint64.toViewDU({a: 1})); + tv.list.push(containerUint64.toViewDU({a: 2})); + }, + }, + ], +}); + +// to test new the VietDU.serialize() implementation for different types +const mixedContainer = new ProfileType( + { + // a basic type + a: uint64NumType, + // a list basic type + b: new ListBasicType(uint64NumType, 10), + // a list composite type + c: new ListCompositeType(new ContainerType({a: uint64NumInfType, b: uint64NumInfType}), 10), + // embedded container type + d: new ContainerType({a: uint64NumInfType}), + // a union type, cannot mutate through this test + e: new UnionType([new NoneType(), uint64NumInfType]), + }, + BitArray.fromBoolArray([false, true, true, false, true, true, true, false]) +); + +runViewTestMutation({ + type: mixedContainer, + mutations: [ + { + id: "increase by 1", + valueBefore: {a: 10, b: [0, 1], c: [{a: 100, b: 101}], d: {a: 1000}, e: {selector: 1, value: 2000}}, + // View/ViewDU of Union is a value so we cannot mutate + valueAfter: {a: 11, b: [1, 2], c: [{a: 101, b: 102}], d: {a: 1001}, e: {selector: 1, value: 2000}}, + fn: (tv) => { + tv.a += 1; + const b = tv.b; + for (let i = 0; i < b.length; i++) { + b.set(i, b.get(i) + 1); + } + const c = tv.c; + for (let i = 0; i < c.length; i++) { + const item = c.get(i); + item.a += 1; + item.b += 1; + } + tv.d.a += 1; + // does not affect anyway, leaving here to make it explicit + tv.e = {selector: 1, value: tv.e.value ?? 0 + 1}; + }, + }, + ], +}); + +describe("ProfileViewDU batchHashTreeRoot", function () { + const childContainerType = new ContainerType({f0: uint64NumInfType, f1: uint64NumInfType}); + const unionType = new UnionType([new NoneType(), uint64NumType]); + const listBasicType = new ListBasicType(uint64NumType, 10); + const vectorBasicType = new VectorBasicType(uint64NumType, 2); + const listCompositeType = new ListCompositeType(childContainerType, 10); + const vectorCompositeType = new VectorCompositeType(childContainerType, 1); + const bitVectorType = new BitVectorType(64); + const bitListType = new BitListType(4); + const childContainerStruct = new ContainerNodeStructType({g0: uint64NumInfType, g1: uint64NumInfType}); + const optionalType = new OptionalType(listBasicType); + const parentContainerType = new ProfileType( + { + a: uint64NumType, + b: new BooleanType(), + c: unionType, + d: new ByteListType(64), + e: new ByteVectorType(64), + // a child container type + f: childContainerType, + g: childContainerStruct, + h: listBasicType, + i: vectorBasicType, + j: listCompositeType, + k: vectorCompositeType, + l: bitVectorType, + m: bitListType, + n: optionalType, + }, + BitArray.fromBoolArray([ + false, + true, + true, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + ]) + ); + + const value: ValueOf = { + 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), + n: [1, 2], + }; + const expectedRoot = parentContainerType.hashTreeRoot(value); + + it("fresh ViewDU", () => { + expect(parentContainerType.toViewDU(value).batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); + + 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({...value, f: {f0: 99, f1: 999}}); + viewDU.batchHashTreeRoot(); + 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({...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.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); + }); + + // TODO: Profile is not working with OptionalType + it.skip("full hash then modify OptionalType", () => { + const viewDU = parentContainerType.toViewDU({...value, n: null}); + viewDU.batchHashTreeRoot(); + viewDU.n = listBasicType.toViewDU([1, 2]); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + + // assign again but commit before batchHashTreeRoot() + viewDU.n = listBasicType.toViewDU([1, 2]); + viewDU.commit(); + expect(viewDU.batchHashTreeRoot()).to.be.deep.equal(expectedRoot); + }); +}); diff --git a/packages/ssz/test/unit/byType/profile/valid.test.ts b/packages/ssz/test/unit/byType/profile/valid.test.ts index 1ecc6929..9a1f67e2 100644 --- a/packages/ssz/test/unit/byType/profile/valid.test.ts +++ b/packages/ssz/test/unit/byType/profile/valid.test.ts @@ -6,7 +6,7 @@ import {runTypeTestValid} from "../runTypeTestValid"; const uint16 = new UintNumberType(2); const byteType = new UintNumberType(1); -const Circle1 = new ProfileType( +const Square = new ProfileType( { side: uint16, color: byteType, @@ -14,7 +14,7 @@ const Circle1 = new ProfileType( BitArray.fromBoolArray([true, true, false, false]) ); -const Square1 = new ProfileType( +const Circle = new ProfileType( { color: byteType, radius: uint16, @@ -23,7 +23,7 @@ const Square1 = new ProfileType( ); runTypeTestValid({ - type: Circle1, + type: Square, defaultValue: {side: 0, color: 0}, values: [ { @@ -36,7 +36,7 @@ runTypeTestValid({ }); runTypeTestValid({ - type: Square1, + type: Circle, defaultValue: {color: 0, radius: 0}, values: [ { diff --git a/packages/ssz/test/unit/byType/stableContainer/tree.test.ts b/packages/ssz/test/unit/byType/stableContainer/tree.test.ts index 180793e2..7cdedd45 100644 --- a/packages/ssz/test/unit/byType/stableContainer/tree.test.ts +++ b/packages/ssz/test/unit/byType/stableContainer/tree.test.ts @@ -25,6 +25,8 @@ import {runViewTestMutation} from "../runViewTestMutation"; // Test both ContainerType, ContainerNodeStructType only if // - All fields are immutable +// TODO: test different number of fields to test the serialization + runViewTestMutation({ // Use Number64UintType and NumberUintType to test they work the same type: new StableContainerType({a: uint64NumInfType, b: uint64NumInfType}, 8), @@ -257,7 +259,6 @@ describe("StableContainerViewDU batchHashTreeRoot", function () { l: bitVectorType, m: bitListType, n: optionalType, - // TODO: add more tests when OptionalType is implemented }, 64 );