Skip to content

Commit

Permalink
fix: implement getChunkBytes() for StableContainer & Profile
Browse files Browse the repository at this point in the history
  • Loading branch information
twoeths committed Oct 11, 2024
1 parent 7b1b3ad commit 806c4de
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 63 deletions.
3 changes: 2 additions & 1 deletion packages/ssz/src/type/composite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,8 @@ export abstract class CompositeType<V, TV, TVDU> extends Type<V> {
const merkleBytes = this.getChunkBytes(value);
merkleizeInto(merkleBytes, this.maxChunkCount, output, offset);
if (this.cachePermanentRootStruct) {
(value as ValueWithCachedPermanentRoot)[symbolCachedPermanentRoot] = output.slice(offset, offset + 32);
const cachedRoot = Uint8Array.prototype.slice.call(output, offset, offset + 32);
(value as ValueWithCachedPermanentRoot)[symbolCachedPermanentRoot] = cachedRoot;
}
}

Expand Down
34 changes: 21 additions & 13 deletions packages/ssz/src/type/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Gindex,
toGindex,
concatGindices,
merkleizeInto,
getNode,
BranchNode,
zeroHash,
Expand Down Expand Up @@ -33,6 +34,7 @@ import {Case} from "../util/strings";
import {BitArray} from "../value/bitArray";
import {mixInActiveFields, setActiveFields} from "./stableContainer";
import {NonOptionalFields, isOptionalType, toNonOptionalType} from "./optional";
import {Uint8} from "../../test/lodestarTypes/sszTypes";
/* eslint-disable @typescript-eslint/member-ordering */

type BytesRange = {start: number; end: number};
Expand Down Expand Up @@ -154,6 +156,9 @@ export class ProfileType<Fields extends Record<string, Type<unknown>>> extends C
// Refactor this constructor to allow customization without pollutin the options
this.TreeView = opts?.getProfileTreeViewClass?.(this) ?? getProfileTreeViewClass(this);
this.TreeViewDU = opts?.getProfileTreeViewDUClass?.(this) ?? getProfileTreeViewDUClass(this);
const fieldBytes = this.activeFields.bitLen * 32;
const chunkBytes = Math.ceil(fieldBytes / 64) * 64;
this.chunkBytesBuffer = new Uint8Array(chunkBytes);
}

static named<Fields extends Record<string, Type<unknown>>>(
Expand Down Expand Up @@ -361,37 +366,40 @@ export class ProfileType<Fields extends Record<string, Type<unknown>>> extends C
}

// Merkleization
hashTreeRoot(value: ValueOfFields<Fields>): Uint8Array {
// hashTreeRoot is the same to parent as it call hashTreeRootInto()
hashTreeRootInto(value: ValueOfFields<Fields>, output: Uint8Array, offset: number): void {
// Return cached mutable root if any
if (this.cachePermanentRootStruct) {
const cachedRoot = (value as ValueWithCachedPermanentRoot)[symbolCachedPermanentRoot];
if (cachedRoot) {
return cachedRoot;
output.set(cachedRoot, offset);
return;
}
}

const root = mixInActiveFields(super.hashTreeRoot(value), this.activeFields);
const merkleBytes = this.getChunkBytes(value);
const root = new Uint8Array(32);
merkleizeInto(merkleBytes, this.maxChunkCount, root, 0);
mixInActiveFields(root, this.activeFields, root, 0);
output.set(root, offset);

if (this.cachePermanentRootStruct) {
(value as ValueWithCachedPermanentRoot)[symbolCachedPermanentRoot] = root;
}

return root;
}

protected getRoots(struct: ValueOfFields<Fields>): Uint8Array[] {
const roots = new Array<Uint8Array>(this.activeFields.bitLen).fill(zeroHash(0));

// already asserted that # of active fields in bitvector === # of fields
protected getChunkBytes(struct: ValueOfFields<Fields>): Uint8Array {
this.chunkBytesBuffer.fill(0);
for (let i = 0; i < this.fieldsEntries.length; i++) {
const {fieldName, fieldType, chunkIndex, optional} = this.fieldsEntries[i];
if (optional && struct[fieldName] == null) {
continue;
this.chunkBytesBuffer.set(zeroHash(0), chunkIndex * 32);
} else {
fieldType.hashTreeRootInto(struct[fieldName], this.chunkBytesBuffer, chunkIndex * 32);
}
roots[chunkIndex] = fieldType.hashTreeRoot(struct[fieldName]);
}

return roots;
// remaining bytes are zeroed as we never write them
return this.chunkBytesBuffer;
}

// Proofs
Expand Down
63 changes: 35 additions & 28 deletions packages/ssz/src/type/stableContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,13 @@ import {
getNode,
zeroNode,
zeroHash,
merkleizeInto,
countToDepth,
getNodeH,
setNode,
setNodeWithFn,
} from "@chainsafe/persistent-merkle-tree";
import {
ValueWithCachedPermanentRoot,
hash64,
maxChunksToDepth,
merkleize,
splitIntoRootChunks,
symbolCachedPermanentRoot,
} from "../util/merkleize";
import {ValueWithCachedPermanentRoot, maxChunksToDepth, symbolCachedPermanentRoot} from "../util/merkleize";
import {Require} from "../util/types";
import {namedClass} from "../util/named";
import {JsonPath, Type, ValueOf} from "./abstract";
Expand Down Expand Up @@ -153,6 +147,9 @@ export class StableContainerType<Fields extends Record<string, Type<unknown>>> e
// Refactor this constructor to allow customization without pollutin the options
this.TreeView = opts?.getContainerTreeViewClass?.(this) ?? getContainerTreeViewClass(this);
this.TreeViewDU = opts?.getContainerTreeViewDUClass?.(this) ?? getContainerTreeViewDUClass(this);
const fieldBytes = this.fieldsEntries.length * 32;
const chunkBytes = Math.ceil(fieldBytes / 64) * 64;
this.chunkBytesBuffer = new Uint8Array(chunkBytes);
}

static named<Fields extends Record<string, Type<unknown>>>(
Expand Down Expand Up @@ -341,43 +338,45 @@ export class StableContainerType<Fields extends Record<string, Type<unknown>>> e
}

// Merkleization
hashTreeRoot(value: ValueOfFields<Fields>): Uint8Array {
// hashTreeRoot is the same to parent as it call hashTreeRootInto()
hashTreeRootInto(value: ValueOfFields<Fields>, output: Uint8Array, offset: number): void {
// Return cached mutable root if any
if (this.cachePermanentRootStruct) {
const cachedRoot = (value as ValueWithCachedPermanentRoot)[symbolCachedPermanentRoot];
if (cachedRoot) {
return cachedRoot;
output.set(cachedRoot, offset);
return;
}
}

const merkleBytes = this.getChunkBytes(value);
const root = new Uint8Array(32);
merkleizeInto(merkleBytes, this.maxChunkCount, root, 0);
// compute active field bitvector
const activeFields = BitArray.fromBoolArray([
...this.fieldsEntries.map(({fieldName}) => value[fieldName] != null),
...this.padActiveFields,
]);
const root = mixInActiveFields(super.hashTreeRoot(value), activeFields);
mixInActiveFields(root, activeFields, root, 0);
output.set(root, offset);

if (this.cachePermanentRootStruct) {
(value as ValueWithCachedPermanentRoot)[symbolCachedPermanentRoot] = root;
}

return root;
}

protected getRoots(struct: ValueOfFields<Fields>): Uint8Array[] {
const roots = new Array<Uint8Array>(this.fieldsEntries.length);

protected getChunkBytes(struct: ValueOfFields<Fields>): Uint8Array {
this.chunkBytesBuffer.fill(0);
for (let i = 0; i < this.fieldsEntries.length; i++) {
const {fieldName, fieldType, optional} = this.fieldsEntries[i];
if (optional && struct[fieldName] == null) {
roots[i] = zeroHash(0);
continue;
this.chunkBytesBuffer.set(zeroHash(0), i * 32);
} else {
fieldType.hashTreeRootInto(struct[fieldName], this.chunkBytesBuffer, i * 32);
}

roots[i] = fieldType.hashTreeRoot(struct[fieldName]);
}

return roots;
return this.chunkBytesBuffer;
}

// Proofs
Expand Down Expand Up @@ -815,15 +814,23 @@ export function setActiveField(rootNode: Node, bitLen: number, fieldIndex: numbe
return new BranchNode(rootNode.left, newActiveFieldsNode);
}

export function mixInActiveFields(root: Uint8Array, activeFields: BitArray): Uint8Array {
// This is a global buffer to avoid creating a new one for each call to getChunkBytes
const mixInActiveFieldsChunkBytes = new Uint8Array(64);
const activeFieldsSingleChunk = mixInActiveFieldsChunkBytes.subarray(32);

export function mixInActiveFields(root: Uint8Array, activeFields: BitArray, output: Uint8Array, offset: number): void {
// fast path for depth 1, the bitvector fits in one chunk
mixInActiveFieldsChunkBytes.set(root, 0);
if (activeFields.bitLen <= 256) {
const activeFieldsChunk = new Uint8Array(32);
activeFieldsChunk.set(activeFields.uint8Array);
return hash64(root, activeFieldsChunk);
activeFieldsSingleChunk.fill(0);
activeFieldsSingleChunk.set(activeFields.uint8Array);
// 1 chunk for root, 1 chunk for activeFields
merkleizeInto(mixInActiveFieldsChunkBytes, 2, output, offset);
return;
}

const activeFieldsChunks = splitIntoRootChunks(activeFields.uint8Array);
const activeFieldsRoot = merkleize(activeFieldsChunks, activeFieldsChunks.length);
return hash64(root, activeFieldsRoot);
const chunkCount = Math.ceil(activeFields.uint8Array.length / 32);
merkleizeInto(activeFields.uint8Array, chunkCount, activeFieldsSingleChunk, 0);
// 1 chunk for root, 1 chunk for activeFields
merkleizeInto(mixInActiveFieldsChunkBytes, 2, output, offset);
}
19 changes: 1 addition & 18 deletions packages/ssz/src/util/merkleize.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {hasher} from "@chainsafe/persistent-merkle-tree/lib/hasher/index";
import {zeroHash} from "@chainsafe/persistent-merkle-tree";
import {hasher, zeroHash} from "@chainsafe/persistent-merkle-tree";

/** Dedicated property to cache hashTreeRoot of immutable CompositeType values */
export const symbolCachedPermanentRoot = Symbol("ssz_cached_permanent_root");
Expand Down Expand Up @@ -43,22 +42,6 @@ export function merkleize(chunks: Uint8Array[], padFor: number): Uint8Array {
return chunks[0];
}

/**
* Split a long Uint8Array into Uint8Array of exactly 32 bytes
*/
export function splitIntoRootChunks(longChunk: Uint8Array): Uint8Array[] {
const chunkCount = Math.ceil(longChunk.length / 32);
const chunks = new Array<Uint8Array>(chunkCount);

for (let i = 0; i < chunkCount; i++) {
const chunk = new Uint8Array(32);
chunk.set(longChunk.slice(i * 32, (i + 1) * 32));
chunks[i] = chunk;
}

return chunks;
}

/** @ignore */
export function mixInLength(root: Uint8Array, length: number): Uint8Array {
const lengthBuf = Buffer.alloc(32);
Expand Down
6 changes: 3 additions & 3 deletions packages/ssz/src/viewDU/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +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);
// there's a chance the view is not changed, no need to rebind nodes in that case
if (this.nodes[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

0 comments on commit 806c4de

Please sign in to comment.