Skip to content

Commit

Permalink
feat: implement sliceTo() for ListBasicTreeViewDU (#336)
Browse files Browse the repository at this point in the history
* feat: implement sliceTo() for ListBasicTreeViewDU

* chore: support index -1

* chore: drop support index -1
  • Loading branch information
twoeths authored Sep 20, 2023
1 parent d18ea08 commit e84686b
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 1 deletion.
47 changes: 46 additions & 1 deletion packages/ssz/src/viewDU/listBasic.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import {LeafNode, Node, zeroNode} from "@chainsafe/persistent-merkle-tree";
import {
LeafNode,
Node,
getNodeAtDepth,
setNodesAtDepth,
treeZeroAfterIndex,
zeroNode,
} from "@chainsafe/persistent-merkle-tree";
import {ValueOf} from "../type/abstract";
import {BasicType} from "../type/basic";
import {ListBasicType} from "../view/listBasic";
Expand Down Expand Up @@ -30,4 +37,42 @@ export class ListBasicTreeViewDU<ElementType extends BasicType<unknown>> extends

this.set(index, value);
}

/**
* Returns a new ListBasicTreeViewDU instance with the values from 0 to `index`.
* To achieve it, rebinds the underlying tree zero-ing all nodes right of `chunkIindex`.
* Also set all value right of `index` in the same chunk to 0.
*/
sliceTo(index: number): this {
if (index < 0) {
throw new Error(`Does not support sliceTo() with negative index ${index}`);
}

// Commit before getting rootNode to ensure all pending data is in the rootNode
this.commit();

// All nodes beyond length are already zero
if (index >= this._length - 1) {
return this;
}

const rootNode = this._rootNode;
const chunkIndex = Math.floor(index / this.type.itemsPerChunk);
const nodePrev = (this.nodes[chunkIndex] ?? getNodeAtDepth(rootNode, this.type.depth, chunkIndex)) as LeafNode;

const nodeChanged = nodePrev.clone();
// set remaining items in the same chunk to 0
for (let i = index + 1; i < (chunkIndex + 1) * this.type.itemsPerChunk; i++) {
this.type.elementType.tree_setToPackedNode(nodeChanged, i, 0);
}
const chunksNode = this.type.tree_getChunksNode(this._rootNode);
let newChunksNode = setNodesAtDepth(chunksNode, this.type.chunkDepth, [chunkIndex], [nodeChanged]);
// also do the treeZeroAfterIndex operation on the chunks tree
newChunksNode = treeZeroAfterIndex(newChunksNode, this.type.chunkDepth, chunkIndex);

// Must set new length and commit to tree to restore the same tree at that index
const newLength = index + 1;
const newRootNode = this.type.tree_setChunksNode(rootNode, newChunksNode, newLength);
return this.type.getViewDU(newRootNode) as this;
}
}
23 changes: 23 additions & 0 deletions packages/ssz/test/unit/byType/listBasic/tree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,26 @@ describe("ListBasicType drop caches", () => {
expect(bytesAfter).to.equal(bytesBefore, "view retained changes");
});
});

describe("ListBasicType.sliceTo", () => {
// same to BeaconState InactivityScores
it("Slice List at multiple length", () => {
const listType = new ListBasicType(new UintNumberType(8), 100);
const listView = listType.defaultViewDU();
const listRoots: string[] = [];
const listSerialized: string[] = [];

for (let i = 0; i < 16; i++) {
listView.push(i);
listSerialized[i] = toHexString(listView.serialize());
listRoots[i] = toHexString(listView.hashTreeRoot());
}

for (let i = 0; i < 16; i++) {
const listSlice = listView.sliceTo(i);
expect(listSlice.length).to.equal(i + 1, `Wrong length at .sliceTo(${i})`);
expect(toHexString(listSlice.serialize())).equals(listSerialized[i], `Wrong serialize at .sliceTo(${i})`);
expect(toHexString(listSlice.hashTreeRoot())).equals(listRoots[i], `Wrong root at .sliceTo(${i})`);
}
});
});

0 comments on commit e84686b

Please sign in to comment.