Skip to content

Commit

Permalink
fix: more tests for Profile and fix bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
twoeths committed Sep 23, 2024
1 parent 624f6b4 commit 148e162
Show file tree
Hide file tree
Showing 7 changed files with 689 additions and 101 deletions.
8 changes: 4 additions & 4 deletions packages/ssz/src/type/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,10 @@ export class ProfileType<Fields extends Record<string, Type<unknown>>> 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;
}
Expand Down Expand Up @@ -603,7 +603,7 @@ export function precomputeJsonKey<Fields extends Record<string, Type<unknown>>>(
*/
export function renderContainerTypeName<Fields extends Record<string, Type<unknown>>>(
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(", ");
Expand Down
2 changes: 1 addition & 1 deletion packages/ssz/src/view/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export function getContainerTreeViewClass<Fields extends Record<string, Type<unk
const leafNodePrev = getNodeAtDepth(this.node, this.type.depth, chunkIndex) as LeafNode;
const leafNode = leafNodePrev.clone();
fieldType.tree_setToNode(leafNode, value);
this.tree.setNodeAtDepth(this.type.depth, index, leafNode);
this.tree.setNodeAtDepth(this.type.depth, chunkIndex, leafNode);
},
});
}
Expand Down
13 changes: 10 additions & 3 deletions packages/ssz/src/viewDU/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export type ContainerTreeViewDUTypeConstructor<Fields extends Record<string, Typ
new (type: ContainerTypeGeneric<Fields>, node: Node, cache?: unknown): ContainerTreeViewDUType<Fields>;
};

export type ChangedNode = {index: number; node: Node};

type ContainerTreeViewDUCache = {
nodes: Node[];
caches: unknown[];
Expand Down Expand Up @@ -94,7 +96,7 @@ export class BasicContainerTreeViewDU<Fields extends Record<string, Type<unknown
// if old root is not hashed, no need to pass hcByLevel to child view bc we need to do full traversal here
const byLevelView = hcByLevel != null && isOldRootHashed ? hcByLevel : null;

const nodesChanged: {index: number; node: Node}[] = [];
const nodesChanged: ChangedNode[] = [];

for (const [index, view] of this.viewsChanged) {
const fieldType = this.type.fieldsEntries[index].fieldType as unknown as CompositeTypeAny;
Expand All @@ -114,8 +116,7 @@ export class BasicContainerTreeViewDU<Fields extends Record<string, Type<unknown

// 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);
const {indexes, nodes} = this.parseNodesChanged(nodesChangedSorted);

this._rootNode = setNodesAtDepth(
this._rootNode,
Expand All @@ -135,6 +136,12 @@ export class BasicContainerTreeViewDU<Fields extends Record<string, Type<unknown
this.viewsChanged.clear();
}

protected parseNodesChanged(nodes: ChangedNode[]): {indexes: number[]; nodes: Node[]} {
const indexes = nodes.map((entry) => entry.index);
const nodesArray = nodes.map((entry) => entry.node);
return {indexes, nodes: nodesArray};
}

protected clearCache(): void {
this.nodes = [];
this.caches = [];
Expand Down
123 changes: 35 additions & 88 deletions packages/ssz/src/viewDU/profile.ts
Original file line number Diff line number Diff line change
@@ -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 Type<unknown>> = T extends CompositeType<unknown, unknown, infer TVDU>
? // If composite, return view. MAY propagate changes updwards
TVDU
: // If basic, return struct value. Will NOT propagate changes upwards
T extends BasicType<infer V>
? V
: never;

export type OptionalViewDUValue<T extends Type<unknown>> = T extends CompositeType<unknown, unknown, infer TVDU>
? // If composite, return view. MAY propagate changes updwards
TVDU | null | undefined
: // If basic, return struct value. Will NOT propagate changes upwards
T extends BasicType<infer V>
? V | null | undefined
: never;

export type FieldsViewDU<Fields extends Record<string, Type<unknown>>> = {
[K in keyof Fields]: Fields[K] extends CompositeType<unknown, unknown, infer TVDU>
? // If composite, return view. MAY propagate changes updwards
TVDU
: // If basic, return struct value. Will NOT propagate changes upwards
Fields[K] extends BasicType<infer V>
? V
: never;
[K in keyof Fields]: Fields[K] extends OptionalType<infer U> ? OptionalViewDUValue<U> : ViewDUValue<Fields[K]>;
};

export type ContainerTreeViewDUType<Fields extends Record<string, Type<unknown>>> = FieldsViewDU<Fields> &
Expand All @@ -29,94 +41,29 @@ type ContainerTreeViewDUCache = {
nodesPopulated: boolean;
};

class ContainerTreeViewDU<Fields extends Record<string, Type<unknown>>> extends TreeViewDU<
ContainerTypeGeneric<Fields>
> {
protected nodes: Node[] = [];
protected caches: unknown[];
protected readonly nodesChanged = new Set<number>();
protected readonly viewsChanged = new Map<number, unknown>();
private nodesPopulated: boolean;

class ProfileTreeViewDU<Fields extends Record<string, Type<unknown>>> extends BasicContainerTreeViewDU<Fields> {
constructor(
readonly type: ContainerTypeGeneric<Fields>,
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<number>(nodesArray.length);
const nodes = new Array<Node>(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();
Expand Down Expand Up @@ -157,7 +104,7 @@ class ContainerTreeViewDU<Fields extends Record<string, Type<unknown>>> extends
export function getContainerTreeViewDUClass<Fields extends Record<string, Type<unknown>>>(
type: ContainerTypeGeneric<Fields>
): ContainerTreeViewDUTypeConstructor<Fields> {
class CustomContainerTreeViewDU extends ContainerTreeViewDU<Fields> {}
class CustomContainerTreeViewDU extends ProfileTreeViewDU<Fields> {}

// Dynamically define prototype methods
for (let index = 0; index < type.fieldsEntries.length; index++) {
Expand Down Expand Up @@ -222,7 +169,7 @@ export function getContainerTreeViewDUClass<Fields extends Record<string, Type<u

let node = this.nodes[index];
if (node === undefined) {
node = getNodeAtDepth(this._rootNode, this.type.depth, index);
node = getNodeAtDepth(this._rootNode, this.type.depth, chunkIndex);
this.nodes[index] = node;
}

Expand Down
Loading

0 comments on commit 148e162

Please sign in to comment.