Skip to content

Commit

Permalink
fix: StableContainer BitVector[N] for view + value
Browse files Browse the repository at this point in the history
  • Loading branch information
twoeths committed Sep 25, 2024
1 parent e6a6cc6 commit 61638b3
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 22 deletions.
22 changes: 16 additions & 6 deletions packages/ssz/src/type/stableContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export class StableContainerType<Fields extends Record<string, Type<unknown>>> e
/** Cached TreeView constuctor with custom prototype for this Type's properties */
protected readonly TreeView: ContainerTreeViewTypeConstructor<Fields>;
protected readonly TreeViewDU: ContainerTreeViewDUTypeConstructor<Fields>;
private padActiveFields: boolean[];

constructor(fields: Fields, readonly maxFields: number, readonly opts?: StableContainerOptions<Fields>) {
super(opts?.cachePermanentRootStruct);
Expand Down Expand Up @@ -134,6 +135,8 @@ export class StableContainerType<Fields extends Record<string, Type<unknown>>> e
});
}

this.padActiveFields = Array.from({length: this.maxChunkCount - this.fieldsEntries.length}, () => false);

if (this.fieldsEntries.length === 0) {
throw Error("StableContainer must have > 0 fields");
}
Expand Down Expand Up @@ -227,7 +230,7 @@ export class StableContainerType<Fields extends Record<string, Type<unknown>>> e
// compute active field bitvector
const activeFields = BitArray.fromBoolArray([
...this.fieldsEntries.map(({fieldName}) => value[fieldName] != null),
...Array.from({length: this.maxChunkCount - this.fieldsEntries.length}, () => false),
...this.padActiveFields,
]);
// write active field bitvector
output.uint8Array.set(activeFields.uint8Array, offset);
Expand Down Expand Up @@ -355,7 +358,10 @@ export class StableContainerType<Fields extends Record<string, Type<unknown>>> e
}

// compute active field bitvector
const activeFields = BitArray.fromBoolArray(this.fieldsEntries.map(({fieldName}) => value[fieldName] != null));
const activeFields = BitArray.fromBoolArray([
...this.fieldsEntries.map(({fieldName}) => value[fieldName] != null),
...this.padActiveFields,
]);
const root = mixInActiveFields(super.hashTreeRoot(value), activeFields);

if (this.cachePermanentRootStruct) {
Expand Down Expand Up @@ -795,25 +801,29 @@ export function getActiveField(rootNode: Node, bitLen: number, fieldIndex: numbe
}

export function setActiveField(rootNode: Node, bitLen: number, fieldIndex: number, value: boolean): Node {
const bitIx = Math.min(fieldIndex / 8);
const byteIx = fieldIndex % 8;
const byteIx = Math.floor(fieldIndex / 8);
const bitIx = fieldIndex % 8;

// fast path for depth 1, the bitvector fits in one chunk
if (bitLen <= 256) {
const activeFieldsBuf = rootNode.right.root;
activeFieldsBuf[byteIx] |= (value ? 1 : 0) << bitIx;

return setNode(rootNode, "11", LeafNode.fromRoot(activeFieldsBuf));
const activeFieldGindex = BigInt(3);
return setNode(rootNode, activeFieldGindex, LeafNode.fromRoot(activeFieldsBuf));
}

const chunkCount = Math.ceil(bitLen / 256);
const chunkIx = bitLen % 256;
const depth = Math.ceil(Math.log2(chunkCount));
return setNodeWithFn(rootNode, BigInt(2 * depth + chunkIx), (node) => {
const activeFieldsNode = rootNode.right;
const newActiveFieldsNode = setNodeWithFn(activeFieldsNode, BigInt(2 * depth + chunkIx), (node) => {
const chunkBuf = node.root;
chunkBuf[byteIx] |= (value ? 1 : 0) << bitIx;
return LeafNode.fromRoot(chunkBuf);
});

return new BranchNode(rootNode.left, newActiveFieldsNode);
}

export function mixInActiveFields(root: Uint8Array, activeFields: BitArray): Uint8Array {
Expand Down
2 changes: 1 addition & 1 deletion packages/ssz/src/view/stableContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export function getContainerTreeViewClass<Fields extends Record<string, Type<unk
this.tree.setNodeAtDepth(this.type.depth, index, leafNode);
// only update the active field if necessary
if (!this.type.tree_getActiveField(this.tree.rootNode, index)) {
this.tree.rootNode = this.type.tree_setActiveField(this.tree.rootNode, index, false);
this.tree.rootNode = this.type.tree_setActiveField(this.tree.rootNode, index, true);
}
},
});
Expand Down
34 changes: 19 additions & 15 deletions packages/ssz/test/unit/byType/stableContainer/tree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,22 +626,26 @@ describe("StableContainer BitVector[N]", () => {
it("should have correct active_fields", () => {
for (const maxFields of [64, 256, 512, 1024]) {
const stableType = new StableContainerType({a: optionalType}, maxFields);
const view = stableType.defaultViewDU();
view.a = 1;
view.commit();
const activeFieldsDepth = Math.ceil(Math.log2(Math.ceil(maxFields / 256)));
const activeFieldsRootNodes = getNodesAtDepth(view.node.right, activeFieldsDepth, 0, 4);
let isFirst = true;
for (const node of activeFieldsRootNodes) {
if (isFirst) {
const root = node.root;
expect(root[0]).to.be.equal(1);
root[0] = 0;
expect(root).to.deep.equal(zeroHash(0));
} else {
expect(node.root).to.deep.equal(zeroHash(0));
for (const view of [stableType.defaultView(), stableType.defaultViewDU()]) {
view.a = 1;
// commit() inside ViewDU
view.hashTreeRoot();
const activeFieldsDepth = Math.ceil(Math.log2(Math.ceil(maxFields / 256)));
const activeFieldsRootNodes = getNodesAtDepth(view.node.right, activeFieldsDepth, 0, 4);
let isFirst = true;
for (const node of activeFieldsRootNodes) {
if (isFirst) {
const root = node.root;
expect(root[0]).to.be.equal(1);
root[0] = 0;
expect(root).to.deep.equal(zeroHash(0));
} else {
expect(node.root).to.deep.equal(zeroHash(0));
}
isFirst = false;
}
isFirst = false;

expect(view.node.root).to.be.deep.equal(stableType.hashTreeRoot({a: 1}));
}
}
});
Expand Down

0 comments on commit 61638b3

Please sign in to comment.