Skip to content

Commit

Permalink
fix: cache same root instance from CompositeType.hashTreeRoot()
Browse files Browse the repository at this point in the history
  • Loading branch information
twoeths committed Oct 15, 2024
1 parent d03c720 commit c7e03b8
Show file tree
Hide file tree
Showing 7 changed files with 45 additions and 29 deletions.
11 changes: 5 additions & 6 deletions packages/ssz/src/type/composite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
HashComputationLevel,
} from "@chainsafe/persistent-merkle-tree";
import {byteArrayEquals} from "../util/byteArray";
import {symbolCachedPermanentRoot, ValueWithCachedPermanentRoot} from "../util/merkleize";
import {cacheRoot, symbolCachedPermanentRoot, ValueWithCachedPermanentRoot} from "../util/merkleize";
import {treePostProcessFromProofNode} from "../util/proof/treePostProcessFromProofNode";
import {Type, ByteViews, JsonPath, JsonPathProp} from "./abstract";
export {ByteViews};
Expand Down Expand Up @@ -220,14 +220,15 @@ export abstract class CompositeType<V, TV, TVDU> extends Type<V> {
}

const root = allocUnsafe(32);
this.hashTreeRootInto(value, root, 0);
const safeCache = true;
this.hashTreeRootInto(value, root, 0, safeCache);

// hashTreeRootInto will cache the root if cachePermanentRootStruct is true

return root;
}

hashTreeRootInto(value: V, output: Uint8Array, offset: number): void {
hashTreeRootInto(value: V, output: Uint8Array, offset: number, safeCache = false): void {
// Return cached mutable root if any
if (this.cachePermanentRootStruct) {
const cachedRoot = (value as ValueWithCachedPermanentRoot)[symbolCachedPermanentRoot];
Expand All @@ -240,9 +241,7 @@ export abstract class CompositeType<V, TV, TVDU> extends Type<V> {
const merkleBytes = this.getChunkBytes(value);
merkleizeInto(merkleBytes, this.maxChunkCount, output, offset);
if (this.cachePermanentRootStruct) {
// Buffer.prototype.slice does not copy memory, Enforce Uint8Array usage https://github.com/nodejs/node/issues/28087
const cachedRoot = Uint8Array.prototype.slice.call(output, offset, offset + 32);
(value as ValueWithCachedPermanentRoot)[symbolCachedPermanentRoot] = cachedRoot;
cacheRoot(value as ValueWithCachedPermanentRoot, output, offset, safeCache);
}
}

Expand Down
9 changes: 5 additions & 4 deletions packages/ssz/src/type/listBasic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
addLengthNode,
setChunksNode,
} from "./arrayBasic";
import {maxChunksToDepth, symbolCachedPermanentRoot, ValueWithCachedPermanentRoot} from "../util/merkleize";
import {cacheRoot, maxChunksToDepth, symbolCachedPermanentRoot, ValueWithCachedPermanentRoot} from "../util/merkleize";
import {Require} from "../util/types";
import {namedClass} from "../util/named";
import {ArrayBasicType} from "../view/arrayBasic";
Expand Down Expand Up @@ -176,14 +176,15 @@ export class ListBasicType<ElementType extends BasicType<unknown>>
}

const root = allocUnsafe(32);
this.hashTreeRootInto(value, root, 0);
const safeCache = true;
this.hashTreeRootInto(value, root, 0, safeCache);

// hashTreeRootInto will cache the root if cachePermanentRootStruct is true

return root;
}

hashTreeRootInto(value: ValueOf<ElementType>[], output: Uint8Array, offset: number): void {
hashTreeRootInto(value: ValueOf<ElementType>[], output: Uint8Array, offset: number, safeCache = false): void {
if (this.cachePermanentRootStruct) {
const cachedRoot = (value as ValueWithCachedPermanentRoot)[symbolCachedPermanentRoot];
if (cachedRoot) {
Expand All @@ -200,7 +201,7 @@ export class ListBasicType<ElementType extends BasicType<unknown>>
merkleizeInto(this.mixInLengthChunkBytes, chunkCount, output, offset);

if (this.cachePermanentRootStruct) {
(value as ValueWithCachedPermanentRoot)[symbolCachedPermanentRoot] = output.subarray(offset, offset + 32).slice();
cacheRoot(value as ValueWithCachedPermanentRoot, output, offset, safeCache);
}
}

Expand Down
14 changes: 5 additions & 9 deletions packages/ssz/src/type/listComposite.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {HashComputationLevel, Node, Tree, merkleizeInto} from "@chainsafe/persistent-merkle-tree";
import {maxChunksToDepth, symbolCachedPermanentRoot, ValueWithCachedPermanentRoot} from "../util/merkleize";
import {cacheRoot, maxChunksToDepth, symbolCachedPermanentRoot, ValueWithCachedPermanentRoot} from "../util/merkleize";
import {Require} from "../util/types";
import {namedClass} from "../util/named";
import {ValueOf, ByteViews} from "./abstract";
Expand Down Expand Up @@ -183,14 +183,15 @@ export class ListCompositeType<
}

const root = allocUnsafe(32);
this.hashTreeRootInto(value, root, 0);
const safeCache = true;
this.hashTreeRootInto(value, root, 0, safeCache);

// hashTreeRootInto will cache the root if cachePermanentRootStruct is true

return root;
}

hashTreeRootInto(value: ValueOf<ElementType>[], output: Uint8Array, offset: number): void {
hashTreeRootInto(value: ValueOf<ElementType>[], output: Uint8Array, offset: number, safeCache = false): void {
if (this.cachePermanentRootStruct) {
const cachedRoot = (value as ValueWithCachedPermanentRoot)[symbolCachedPermanentRoot];
if (cachedRoot) {
Expand All @@ -207,12 +208,7 @@ export class ListCompositeType<
merkleizeInto(this.mixInLengthChunkBytes, chunkCount, output, offset);

if (this.cachePermanentRootStruct) {
// Buffer.prototype.slice does not copy memory, Enforce Uint8Array usage https://github.com/nodejs/node/issues/28087
(value as ValueWithCachedPermanentRoot)[symbolCachedPermanentRoot] = Uint8Array.prototype.slice.call(
output,
offset,
offset + 32
);
cacheRoot(value as ValueWithCachedPermanentRoot, output, offset, safeCache);
}
}

Expand Down
6 changes: 3 additions & 3 deletions packages/ssz/src/type/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
zeroHash,
zeroNode,
} from "@chainsafe/persistent-merkle-tree";
import {ValueWithCachedPermanentRoot, maxChunksToDepth, symbolCachedPermanentRoot} from "../util/merkleize";
import {ValueWithCachedPermanentRoot, cacheRoot, maxChunksToDepth, symbolCachedPermanentRoot} from "../util/merkleize";
import {Require} from "../util/types";
import {namedClass} from "../util/named";
import {Type, ValueOf} from "./abstract";
Expand Down Expand Up @@ -368,7 +368,7 @@ export class ProfileType<Fields extends Record<string, Type<unknown>>> extends C

// Merkleization
// hashTreeRoot is the same to parent as it call hashTreeRootInto()
hashTreeRootInto(value: ValueOfFields<Fields>, output: Uint8Array, offset: number): void {
hashTreeRootInto(value: ValueOfFields<Fields>, output: Uint8Array, offset: number, safeCache = false): void {
// Return cached mutable root if any
if (this.cachePermanentRootStruct) {
const cachedRoot = (value as ValueWithCachedPermanentRoot)[symbolCachedPermanentRoot];
Expand All @@ -383,7 +383,7 @@ export class ProfileType<Fields extends Record<string, Type<unknown>>> extends C
mixInActiveFields(this.tempRoot, this.activeFields, output, offset);

if (this.cachePermanentRootStruct) {
(value as ValueWithCachedPermanentRoot)[symbolCachedPermanentRoot] = this.tempRoot.slice();
cacheRoot(value as ValueWithCachedPermanentRoot, output, offset, safeCache);
}
}

Expand Down
6 changes: 3 additions & 3 deletions packages/ssz/src/type/stableContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
setNode,
setNodeWithFn,
} from "@chainsafe/persistent-merkle-tree";
import {ValueWithCachedPermanentRoot, maxChunksToDepth, symbolCachedPermanentRoot} from "../util/merkleize";
import {ValueWithCachedPermanentRoot, cacheRoot, 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 @@ -341,7 +341,7 @@ export class StableContainerType<Fields extends Record<string, Type<unknown>>> e

// Merkleization
// hashTreeRoot is the same to parent as it call hashTreeRootInto()
hashTreeRootInto(value: ValueOfFields<Fields>, output: Uint8Array, offset: number): void {
hashTreeRootInto(value: ValueOfFields<Fields>, output: Uint8Array, offset: number, safeCache = false): void {
// Return cached mutable root if any
if (this.cachePermanentRootStruct) {
const cachedRoot = (value as ValueWithCachedPermanentRoot)[symbolCachedPermanentRoot];
Expand All @@ -361,7 +361,7 @@ export class StableContainerType<Fields extends Record<string, Type<unknown>>> e
mixInActiveFields(this.tempRoot, activeFields, output, offset);

if (this.cachePermanentRootStruct) {
(value as ValueWithCachedPermanentRoot)[symbolCachedPermanentRoot] = this.tempRoot.slice();
cacheRoot(value as ValueWithCachedPermanentRoot, output, offset, safeCache);
}
}

Expand Down
22 changes: 22 additions & 0 deletions packages/ssz/src/util/merkleize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,28 @@ export type ValueWithCachedPermanentRoot = {
[symbolCachedPermanentRoot]?: Uint8Array;
};

/**
* Cache a root for a ValueWithCachedPermanentRoot instance
* - if safeCache is true and output is 32 bytes and offset is 0, use output directly
* - if safeCache, use output subarray
* - otherwise, need to clone the root at output offset
*/
export function cacheRoot(
value: ValueWithCachedPermanentRoot,
output: Uint8Array,
offset: number,
safeCache: boolean
): void {
const cachedRoot =
safeCache && output.length === 32 && offset === 0
? output
: safeCache
? output.subarray(offset, offset + 32)
: // Buffer.prototype.slice does not copy memory, Enforce Uint8Array usage https://github.com/nodejs/node/issues/28087
Uint8Array.prototype.slice.call(output, offset, offset + 32);
value[symbolCachedPermanentRoot] = cachedRoot;
}

export function hash64(bytes32A: Uint8Array, bytes32B: Uint8Array): Uint8Array {
return hasher.digest64(bytes32A, bytes32B);
}
Expand Down
6 changes: 2 additions & 4 deletions packages/ssz/test/unit/cachePermanentRootStruct.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ describe("cachePermanentRootStruct", () => {

const root = type.hashTreeRoot(value);
const root2 = type.hashTreeRoot(value);
// previously this is the same reference, since we move to merkleizeInto() it is not anymore
// this should not be an issue anyway
expect(root).to.deep.equal(root2, "Second hashTreeRoot should return the same Uint8Array");
expect(root).to.equal(root2, "Second hashTreeRoot should return the same Uint8Array");

expect(type["getCachedPermanentRoot"](value)).to.deep.equal(root, "Should have cached root");
expect(type["getCachedPermanentRoot"](value)).to.equal(root, "Should have cached root");
});
});

0 comments on commit c7e03b8

Please sign in to comment.