Skip to content

Commit

Permalink
feat: implement type.defaultPartialViewDU() and zeroSnapshot()
Browse files Browse the repository at this point in the history
  • Loading branch information
twoeths committed Sep 10, 2024
1 parent df36a03 commit 21e0c54
Show file tree
Hide file tree
Showing 9 changed files with 64 additions and 30 deletions.
18 changes: 13 additions & 5 deletions packages/persistent-merkle-tree/src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ type Snapshot = {
* Tree could be full tree, or partial tree. See https://github.com/ChainSafe/ssz/issues/293
*/
export function toSnapshot(rootNode: Node, depth: number, count: number): Snapshot {
const finalizedGindices = indexToFinalizedGindices(depth, count - 1);
if (count < 0) {
throw new Error(`Expect count to be non-negative, got ${count}`);
}

const finalizedGindices = count > 0 ? indexToFinalizedGindices(depth, count - 1) : [];
const finalized = finalizedGindices.map((gindex) => getNode(rootNode, gindex).root);

return {
Expand All @@ -28,15 +32,19 @@ export function toSnapshot(rootNode: Node, depth: number, count: number): Snapsh
*/
export function fromSnapshot(snapshot: Snapshot, depth: number): Node {
const tree = new Tree(zeroNode(depth));
const {count, finalized} = snapshot;
if (count < 0) {
throw new Error(`Expect count to be non-negative, got ${count}`);
}

const finalizedGindices = indexToFinalizedGindices(depth, snapshot.count - 1);
const finalizedGindices = count > 0 ? indexToFinalizedGindices(depth, count - 1) : [];

if (finalizedGindices.length !== snapshot.finalized.length) {
throw new Error(`Expected ${finalizedGindices.length} finalized gindices, got ${snapshot.finalized.length}`);
if (finalizedGindices.length !== finalized.length) {
throw new Error(`Expected ${finalizedGindices.length} finalized gindices, got ${finalized.length}`);
}

for (const [i, gindex] of finalizedGindices.entries()) {
const node = LeafNode.fromRoot(snapshot.finalized[i]);
const node = LeafNode.fromRoot(finalized[i]);
tree.setNode(gindex, node);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/persistent-merkle-tree/test/unit/snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe("toSnapshot and fromSnapshot", () => {
const depth = 4;
const maxItems = Math.pow(2, depth);

for (let count = 1; count <= maxItems; count ++) {
for (let count = 0; count <= maxItems; count ++) {
it(`toSnapshot and fromSnapshot with count ${count}`, () => {
const nodes = Array.from({length: count}, (_, i) => LeafNode.fromRoot(Buffer.alloc(32, i)));
const fullListRootNode = subtreeFillToContents(nodes, depth);
Expand Down
18 changes: 16 additions & 2 deletions packages/ssz/src/type/partialListComposite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {ListCompositeOpts, ListCompositeType} from "./listComposite";
import {PartialListCompositeTreeViewDU} from "../viewDU/partialListComposite";
import {Snapshot} from "../util/types";
import {byteArrayEquals} from "../util/byteArray";
import {zeroSnapshot} from "../util/snapshot";

/**
* Similar to ListCompositeType, this is mainly used to create a PartialListCompositeTreeViewDU from a snapshot.
Expand Down Expand Up @@ -41,15 +42,28 @@ export class PartialListCompositeType<
}
}

snapshotToViewDU(snapshot: Snapshot): PartialListCompositeTreeViewDU<ElementType> {
/**
* Create a PartialListCompositeTreeViewDU from a snapshot.
*/
toPartialViewDU(snapshot: Snapshot): PartialListCompositeTreeViewDU<ElementType> {
const chunksNode = fromSnapshot(snapshot, this.chunkDepth);
// old root node could be whatever, so leave zeroNode(0) here
const rootNode = this.tree_setChunksNode(zeroNode(0), chunksNode, snapshot.count);

if (!byteArrayEquals(rootNode.root, snapshot.root)) {
throw new Error(`Snapshot root is incorrect, expected ${snapshot.root}, got ${rootNode.root}`);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any

return new PartialListCompositeTreeViewDU(this, rootNode, snapshot);
}

/**
* Creates a PartialListCompositeTreeViewDU from a zero snapshot.
*/
defaultPartialViewDU(): PartialListCompositeTreeViewDU<ElementType> {
// old root node could be whatever, so leave zeroNode(0) here
const rootNode = this.tree_setChunksNode(zeroNode(0), zeroNode(this.chunkDepth), 0);

return new PartialListCompositeTreeViewDU(this, rootNode, zeroSnapshot(this.chunkDepth));
}
}
14 changes: 14 additions & 0 deletions packages/ssz/src/util/snapshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {zeroHash} from "@chainsafe/persistent-merkle-tree";
import {hash64} from "./merkleize";
import {Snapshot} from "./types";

/**
* Create a zero snapshot with the given chunksDepth.
*/
export function zeroSnapshot(chunkDepth: number): Snapshot {
return {
finalized: [],
count: 0,
root: hash64(zeroHash(chunkDepth), zeroHash(0)),
};
}
8 changes: 0 additions & 8 deletions packages/ssz/src/util/types.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import {zeroNode} from "@chainsafe/persistent-merkle-tree";

export type Require<T, K extends keyof T> = T & Required<Pick<T, K>>;

export type Snapshot = {
finalized: Uint8Array[];
root: Uint8Array;
count: number;
};

export const ZERO_SNAPSHOT = {
finalized: [],
root: zeroNode(0).root,
count: 0,
};
7 changes: 6 additions & 1 deletion packages/ssz/src/viewDU/listComposite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {ListCompositeType} from "../view/listComposite";
import {ArrayCompositeTreeViewDU, ArrayCompositeTreeViewDUCache} from "./arrayComposite";
import {tree_serializeToBytesArrayComposite} from "../type/arrayComposite";
import {Snapshot} from "../util/types";
import {zeroSnapshot} from "../util/snapshot";

export class ListCompositeTreeViewDU<
ElementType extends CompositeType<ValueOf<ElementType>, CompositeView<ElementType>, CompositeViewDU<ElementType>>
Expand Down Expand Up @@ -116,10 +117,14 @@ export class ListCompositeTreeViewDU<
toSnapshot(count: number): Snapshot {
// Commit before getting rootNode to ensure all pending data is in the rootNode
this.commit();
if (count <= 0 || count > this._length) {
if (count < 0 || count > this._length) {
throw Error(`Invalid count ${count}, length is ${this._length}`);
}

if (count === 0) {
return zeroSnapshot(this.type.chunkDepth);
}

// sliceTo is inclusive
const rootNode = this.sliceTo(count - 1)._rootNode;
const chunksNode = this.type.tree_getChunksNode(rootNode);
Expand Down
9 changes: 5 additions & 4 deletions packages/ssz/src/viewDU/partialListComposite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {CompositeType, CompositeView, CompositeViewDU} from "../type/composite";
import {ArrayCompositeTreeViewDUCache} from "./arrayComposite";
import {ListCompositeTreeViewDU} from "./listComposite";
import {PartialListCompositeType} from "../type/partialListComposite";
import {Snapshot, ZERO_SNAPSHOT} from "../util/types";
import {Snapshot} from "../util/types";
import {zeroSnapshot} from "../util/snapshot";

/**
* Similar to ListCompositeTreeViewDU but this is created from a snapshot so some methods are not supported
Expand Down Expand Up @@ -93,7 +94,7 @@ export class PartialListCompositeTreeViewDU<
if (index === this.snapshot.count - 1) {
// super.sliceTo() uses treeZeroAfterIndex() which does not work well in this case
// this is because treeZeroAfterIndex() requires navigating the tree to index first which we don't have in this case
return this.type.snapshotToViewDU(this.snapshot) as this;
return this.type.toPartialViewDU(this.snapshot) as this;
}

return super.sliceTo(index);
Expand Down Expand Up @@ -126,8 +127,8 @@ export class PartialListCompositeTreeViewDU<

const newRootNode = this.type.tree_setChunksNode(this._rootNode, newChunksNode, newLength);

// Use ZERO_SNAPSHOT because this is equivalent to a full tree
return new PartialListCompositeTreeViewDU(this.type, newRootNode, ZERO_SNAPSHOT) as this;
// Use zeroSnapshot because this is equivalent to a full tree
return new PartialListCompositeTreeViewDU(this.type, newRootNode, zeroSnapshot(this.type.chunkDepth)) as this;
}

serializeToBytes(): number {
Expand Down
10 changes: 5 additions & 5 deletions packages/ssz/test/unit/byType/partialListComposite/tree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe("PartialListCompositeType ViewDU", function () {
for (let snapshotCount = 1; snapshotCount <= maxItem; snapshotCount++) {
it(`snapshotCount=${snapshotCount}`, function () {
const snapshot = fullList.toSnapshot(snapshotCount);
const partialList = partialListType.snapshotToViewDU(snapshot);
const partialList = partialListType.toPartialViewDU(snapshot);
expect(partialList.hashTreeRoot()).to.deep.equal(fullList.sliceTo(snapshotCount - 1).hashTreeRoot());
expect(partialList.toValue()).to.deep.equal(new Array(snapshotCount));

Expand All @@ -52,7 +52,7 @@ describe("PartialListCompositeType ViewDU", function () {
for (let snapshotCount = 1; snapshotCount <= maxItem; snapshotCount++) {
it(`snapshotCount=${snapshotCount}`, function () {
const snapshot = fullList.toSnapshot(snapshotCount);
const partialList = partialListType.snapshotToViewDU(snapshot);
const partialList = partialListType.toPartialViewDU(snapshot);
expect(partialList.hashTreeRoot()).to.deep.equal(fullList.sliceTo(snapshotCount - 1).hashTreeRoot());
expect(partialList.toValue()).to.deep.equal(new Array(snapshotCount));

Expand All @@ -71,7 +71,7 @@ describe("PartialListCompositeType ViewDU", function () {
for (let snapshotCount = 1; snapshotCount <= maxItem; snapshotCount++) {
it(`snapshotCount=${snapshotCount}`, function () {
const snapshot = fullList.toSnapshot(snapshotCount);
const partialList = partialListType.snapshotToViewDU(snapshot);
const partialList = partialListType.toPartialViewDU(snapshot);
expect(partialList.hashTreeRoot()).to.deep.equal(fullList.sliceTo(snapshotCount - 1).hashTreeRoot());
expect(partialList.toValue()).to.deep.equal(new Array(snapshotCount));

Expand All @@ -94,7 +94,7 @@ describe("PartialListCompositeType ViewDU", function () {
for (let snapshotCount = 1; snapshotCount <= maxItem; snapshotCount++) {
it(`snapshotCount=${snapshotCount}`, function () {
const snapshot = fullList.toSnapshot(snapshotCount);
const partialList = partialListType.snapshotToViewDU(snapshot);
const partialList = partialListType.toPartialViewDU(snapshot);
expect(partialList.hashTreeRoot()).to.deep.equal(fullList.sliceTo(snapshotCount - 1).hashTreeRoot());
expect(partialList.toValue()).to.deep.equal(new Array(snapshotCount));

Expand All @@ -120,7 +120,7 @@ describe("PartialListCompositeType ViewDU", function () {
for (let snapshotCount = 1; snapshotCount <= maxItem; snapshotCount++) {
it(`snapshotCount=${snapshotCount}`, function () {
const snapshot = fullList.toSnapshot(snapshotCount);
const partialList = partialListType.snapshotToViewDU(snapshot);
const partialList = partialListType.toPartialViewDU(snapshot);
expect(partialList.hashTreeRoot()).to.deep.equal(fullList.sliceTo(snapshotCount - 1).hashTreeRoot());
expect(partialList.toValue()).to.deep.equal(new Array(snapshotCount));

Expand Down
8 changes: 4 additions & 4 deletions packages/ssz/test/unit/snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ describe("snapshot", () => {
}
fullList.commit();

for (let snapshotCount = 1; snapshotCount <= maxItems; snapshotCount++) {
for (let snapshotCount = 0; snapshotCount <= maxItems; snapshotCount++) {
// toSnapshot uses sliceTo, it's good to test sliceFrom too
it(`toSnapshot and fromSnapshot then sliceTo/sliceFrom with snapshotCount ${snapshotCount}`, () => {
const snapshot = fullList.toSnapshot(snapshotCount);
const partialList = partialListType.snapshotToViewDU(snapshot);
const partialList = partialListType.toPartialViewDU(snapshot);

// 1st step - check if the restored root node is the same
expect(partialList.hashTreeRoot()).to.deep.equal(fullList.sliceTo(snapshotCount - 1).hashTreeRoot());
Expand All @@ -48,7 +48,7 @@ describe("snapshot", () => {

it(`toSnapshot and fromSnapshot then subsequent toSnapshot calls with snapshotCount ${snapshotCount}`, () => {
const snapshot = fullList.toSnapshot(snapshotCount);
const partialList = partialListType.snapshotToViewDU(snapshot);
const partialList = partialListType.toPartialViewDU(snapshot);

// 1st step - check if the restored root node is the same
expect(partialList.hashTreeRoot()).to.deep.equal(fullList.sliceTo(snapshotCount - 1).hashTreeRoot());
Expand All @@ -61,7 +61,7 @@ describe("snapshot", () => {
// confirm toSnapshot() works
for (let j = snapshotCount; j <= i; j++) {
const snapshot2 = partialList.toSnapshot(j);
const partialList2 = partialListType.snapshotToViewDU(snapshot2);
const partialList2 = partialListType.toPartialViewDU(snapshot2);
// sliceTo() is inclusive
const fullListToJ = fullList.sliceTo(j - 1);
expect(partialList2.length).to.equal(fullListToJ.length);
Expand Down

0 comments on commit 21e0c54

Please sign in to comment.