Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: getAll() apis to support output parameters #417

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions packages/ssz/src/view/arrayBasic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,18 @@ export class ArrayBasicTreeView<ElementType extends BasicType<unknown>> extends

/**
* Get all values of this array as Basic element type values, from index zero to `this.length - 1`
* @param values optional output parameter, if is provided it must be an array of the same length as this array
*/
getAll(): ValueOf<ElementType>[] {
getAll(values?: ValueOf<ElementType>[]): ValueOf<ElementType>[] {
if (values && values.length !== this.length) {
throw Error(`Expected ${this.length} values, got ${values.length}`);
}
const length = this.length;
const chunksNode = this.type.tree_getChunksNode(this.node);
const chunkCount = Math.ceil(length / this.type.itemsPerChunk);
const leafNodes = getNodesAtDepth(chunksNode, this.type.chunkDepth, 0, chunkCount) as LeafNode[];

const values = new Array<ValueOf<ElementType>>(length);
values = values ?? new Array<ValueOf<ElementType>>(length);
const itemsPerChunk = this.type.itemsPerChunk; // Prevent many access in for loop below
const lenFullNodes = Math.floor(length / itemsPerChunk);
const remainder = length % itemsPerChunk;
Expand Down
16 changes: 12 additions & 4 deletions packages/ssz/src/view/arrayComposite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,16 @@ export class ArrayCompositeTreeView<
* Returns an array of views of all elements in the array, from index zero to `this.length - 1`.
* The returned views don't have a parent hook to this View's Tree, so changes in the returned views won't be
* propagated upwards. To get linked element Views use `this.get()`
* @param views optional output parameter, if is provided it must be an array of the same length as this array
*/
getAllReadonly(): CompositeView<ElementType>[] {
getAllReadonly(views?: CompositeView<ElementType>[]): CompositeView<ElementType>[] {
if (views && views.length !== this.length) {
throw Error(`Expected ${this.length} views, got ${views.length}`);
}
const length = this.length;
const chunksNode = this.type.tree_getChunksNode(this.node);
const nodes = getNodesAtDepth(chunksNode, this.type.chunkDepth, 0, length);
const views = new Array<CompositeView<ElementType>>(length);
views = views ?? new Array<CompositeView<ElementType>>(length);
for (let i = 0; i < length; i++) {
// TODO: Optimize
views[i] = this.type.elementType.getView(new Tree(nodes[i]));
Expand All @@ -90,12 +94,16 @@ export class ArrayCompositeTreeView<
* Returns an array of values of all elements in the array, from index zero to `this.length - 1`.
* The returned values are not Views so any changes won't be propagated upwards.
* To get linked element Views use `this.get()`
* @param values optional output parameter, if is provided it must be an array of the same length as this array
*/
getAllReadonlyValues(): ValueOf<ElementType>[] {
getAllReadonlyValues(values?: ValueOf<ElementType>[]): ValueOf<ElementType>[] {
if (values && values.length !== this.length) {
throw Error(`Expected ${this.length} values, got ${values.length}`);
}
const length = this.length;
const chunksNode = this.type.tree_getChunksNode(this.node);
const nodes = getNodesAtDepth(chunksNode, this.type.chunkDepth, 0, length);
const values = new Array<ValueOf<ElementType>>(length);
values = values ?? new Array<ValueOf<ElementType>>(length);
for (let i = 0; i < length; i++) {
values[i] = this.type.elementType.tree_toValue(nodes[i]);
}
Expand Down
8 changes: 6 additions & 2 deletions packages/ssz/src/viewDU/arrayBasic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,12 @@ export class ArrayBasicTreeViewDU<ElementType extends BasicType<unknown>> extend

/**
* Get all values of this array as Basic element type values, from index zero to `this.length - 1`
* @param values optional output parameter, if is provided it must be an array of the same length as this array
*/
getAll(): ValueOf<ElementType>[] {
getAll(values?: ValueOf<ElementType>[]): ValueOf<ElementType>[] {
if (values && values.length !== this._length) {
throw Error(`Expected ${this._length} values, got ${values.length}`);
}
if (!this.nodesPopulated) {
const nodesPrev = this.nodes;
const chunksNode = this.type.tree_getChunksNode(this.node);
Expand All @@ -125,7 +129,7 @@ export class ArrayBasicTreeViewDU<ElementType extends BasicType<unknown>> extend
this.nodesPopulated = true;
}

const values = new Array<ValueOf<ElementType>>(this._length);
values = values ?? new Array<ValueOf<ElementType>>(this._length);
const itemsPerChunk = this.type.itemsPerChunk; // Prevent many access in for loop below
const lenFullNodes = Math.floor(this._length / itemsPerChunk);
const remainder = this._length % itemsPerChunk;
Expand Down
45 changes: 38 additions & 7 deletions packages/ssz/src/viewDU/arrayComposite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,30 +146,58 @@ export class ArrayCompositeTreeViewDU<

/**
* WARNING: Returns all commited changes, if there are any pending changes commit them beforehand
* @param views optional output parameter, if is provided it must be an array of the same length as this array
*/
getAllReadonly(): CompositeViewDU<ElementType>[] {
getAllReadonly(views?: CompositeViewDU<ElementType>[]): CompositeViewDU<ElementType>[] {
if (views && views.length !== this._length) {
throw Error(`Expected ${this._length} views, got ${views.length}`);
}
this.populateAllNodes();

const views = new Array<CompositeViewDU<ElementType>>(this._length);
views = views ?? new Array<CompositeViewDU<ElementType>>(this._length);
for (let i = 0; i < this._length; i++) {
views[i] = this.type.elementType.getViewDU(this.nodes[i], this.caches[i]);
}
return views;
}

/**
* Apply `fn` to each ViewDU in the array
*/
forEach(fn: (viewDU: CompositeViewDU<ElementType>, index: number) => void): void {
this.populateAllNodes();
for (let i = 0; i < this._length; i++) {
fn(this.type.elementType.getViewDU(this.nodes[i], this.caches[i]), i);
}
}

/**
* WARNING: Returns all commited changes, if there are any pending changes commit them beforehand
* @param values optional output parameter, if is provided it must be an array of the same length as this array
*/
getAllReadonlyValues(): ValueOf<ElementType>[] {
getAllReadonlyValues(values?: ValueOf<ElementType>[]): ValueOf<ElementType>[] {
if (values && values.length !== this._length) {
throw Error(`Expected ${this._length} values, got ${values.length}`);
}
this.populateAllNodes();

const values = new Array<ValueOf<ElementType>>(this._length);
values = values ?? new Array<ValueOf<ElementType>>(this._length);
for (let i = 0; i < this._length; i++) {
values[i] = this.type.elementType.tree_toValue(this.nodes[i]);
}
return values;
}

/**
* Apply `fn` to each value in the array
*/
forEachValue(fn: (value: ValueOf<ElementType>, index: number) => void): void {
this.populateAllNodes();
for (let i = 0; i < this._length; i++) {
fn(this.type.elementType.tree_toValue(this.nodes[i]), i);
}
}

/**
* When we need to compute HashComputations (hcByLevel != null):
* - if old _rootNode is hashed, then only need to put pending changes to hcByLevel
Expand All @@ -193,9 +221,12 @@ export class ArrayCompositeTreeViewDU<

for (const [index, view] of this.viewsChanged) {
const node = this.type.elementType.commitViewDU(view, offsetView, byLevelView);
// 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});
// there's a chance the view is not changed, no need to rebind nodes in that case
if (this.nodes[index] !== node) {
// 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 = this.type.elementType.cacheOfViewDU(view);
Expand Down
9 changes: 6 additions & 3 deletions packages/ssz/src/viewDU/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,12 @@ export class BasicContainerTreeViewDU<Fields extends Record<string, Type<unknown
for (const [index, view] of this.viewsChanged) {
const fieldType = this.type.fieldsEntries[index].fieldType as unknown as CompositeTypeAny;
const node = fieldType.commitViewDU(view, offsetView, byLevelView);
// 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});
// there's a chance the view is not changed, no need to rebind nodes in that case
if (this.nodes[index] !== node) {
// 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);
Expand Down
112 changes: 112 additions & 0 deletions packages/ssz/test/unit/byType/listComposite/tree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,3 +337,115 @@ describe("ListCompositeType batchHashTreeRoot", () => {
});
}
});

describe("ListCompositeType batchHashTreeRoot", () => {
const value = [
{a: 1, b: 2},
{a: 3, b: 4},
];
const containerStructUintsType = new ContainerNodeStructType(
{a: uint64NumInfType, b: uint64NumInfType},
{typeName: "ContainerNodeStruct(uint64)"}
);
const listOfContainersType2 = new ListCompositeType(containerStructUintsType, 4, {
typeName: "ListCompositeType(ContainerNodeStructType)",
});

for (const list of [listOfContainersType, listOfContainersType2]) {
const typeName = list.typeName;
const expectedRoot = list.toView(value).hashTreeRoot();

it(`${typeName} - fresh ViewDU`, () => {
expect(listOfContainersType.toViewDU(value).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);

// 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(`${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);

// 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);
});
}
});
2 changes: 1 addition & 1 deletion packages/ssz/test/unit/unchangedViewDUs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {getRandomState} from "../utils/generateEth2Objs";
describe("Unchanged ViewDUs", () => {
const state = sszAltair.BeaconState.toViewDU(getRandomState(100));

it.skip("should not recompute batchHashTreeRoot() when no fields is changed", () => {
it("should not recompute batchHashTreeRoot() when no fields is changed", () => {
const root = state.batchHashTreeRoot();
// this causes viewsChanged inside BeaconState container
state.validators.length;
Expand Down
Loading