UNPKG

@chainsafe/ssz

Version:
173 lines 7.55 kB
import { allocUnsafe } from "@chainsafe/as-sha256"; import { merkleizeBlockArray, merkleizeBlocksBytes, } from "@chainsafe/persistent-merkle-tree"; import { cacheRoot, maxChunksToDepth, symbolCachedPermanentRoot, } from "../util/merkleize.js"; import { namedClass } from "../util/named.js"; import { ListCompositeTreeView } from "../view/listComposite.js"; import { ListCompositeTreeViewDU } from "../viewDU/listComposite.js"; import { ArrayType } from "./array.js"; import { addLengthNode, getLengthFromRootNode, setChunksNode } from "./arrayBasic.js"; import { maxSizeArrayComposite, tree_deserializeFromBytesArrayComposite, tree_serializeToBytesArrayComposite, tree_serializedSizeArrayComposite, value_deserializeFromBytesArrayComposite, value_serializeToBytesArrayComposite, value_serializedSizeArrayComposite, } from "./arrayComposite.js"; /** * List: ordered variable-length homogeneous collection, limited to N values * * Array of Composite type: * - Composite types always take at least one chunk * - Composite types are always returned as views */ export class ListCompositeType extends ArrayType { elementType; limit; typeName; itemsPerChunk = 1; depth; chunkDepth; maxChunkCount; fixedSize = null; minSize; maxSize; isList = true; isViewMutable = true; blockArray = []; mixInLengthBlockBytes = new Uint8Array(64); mixInLengthBuffer = Buffer.from(this.mixInLengthBlockBytes.buffer, this.mixInLengthBlockBytes.byteOffset, this.mixInLengthBlockBytes.byteLength); defaultLen = 0; constructor(elementType, limit, opts) { super(elementType, opts?.cachePermanentRootStruct); this.elementType = elementType; this.limit = limit; if (elementType.isBasic) throw Error("elementType must not be basic"); if (limit === 0) throw Error("List limit must be > 0"); this.typeName = opts?.typeName ?? `List[${elementType.typeName}, ${limit}]`; this.maxChunkCount = this.limit; this.chunkDepth = maxChunksToDepth(this.maxChunkCount); // Depth includes the extra level for the length node this.depth = this.chunkDepth + 1; this.minSize = 0; this.maxSize = maxSizeArrayComposite(elementType, this.limit); } // biome-ignore lint/suspicious/noExplicitAny: We need to use `any` here explicitly static named(elementType, limit, opts) { return new (namedClass(ListCompositeType, opts.typeName))(elementType, limit, opts); } getView(tree) { return new ListCompositeTreeView(this, tree); } getViewDU(node, cache) { // cache type should be validated (if applicate) in the view // biome-ignore lint/suspicious/noExplicitAny: We need to use `any` here explicitly return new ListCompositeTreeViewDU(this, node, cache); } commitView(view) { return view.node; } commitViewDU(view, hcOffset = 0, hcByLevel = null) { view.commit(hcOffset, hcByLevel); return view.node; } cacheOfViewDU(view) { return view.cache; } // Serialization + deserialization value_serializedSize(value) { return value_serializedSizeArrayComposite(this.elementType, value.length, value); } value_serializeToBytes(output, offset, value) { return value_serializeToBytesArrayComposite(this.elementType, value.length, output, offset, value); } value_deserializeFromBytes(data, start, end, reuseBytes) { return value_deserializeFromBytesArrayComposite(this.elementType, data, start, end, this, reuseBytes); } tree_serializedSize(node) { const chunksNode = this.tree_getChunksNode(node); const length = this.tree_getLength(node); return tree_serializedSizeArrayComposite(this.elementType, length, this.chunkDepth, chunksNode); } tree_serializeToBytes(output, offset, node) { const chunksNode = this.tree_getChunksNode(node); const length = this.tree_getLength(node); return tree_serializeToBytesArrayComposite(this.elementType, length, this.chunkDepth, chunksNode, output, offset); } tree_deserializeFromBytes(data, start, end) { return tree_deserializeFromBytesArrayComposite(this.elementType, this.chunkDepth, data, start, end, this); } // Helpers for TreeView tree_getLength(node) { return getLengthFromRootNode(node); } tree_setLength(tree, length) { tree.rootNode = addLengthNode(tree.rootNode.left, length); } tree_getChunksNode(node) { return node.left; } tree_chunksNodeOffset() { // one more level for length, see setChunksNode below return 1; } tree_setChunksNode(rootNode, chunksNode, newLength, hcOffset = 0, hcByLevel = null) { return setChunksNode(rootNode, chunksNode, newLength, hcOffset, hcByLevel); } // Merkleization hashTreeRoot(value) { // Return cached mutable root if any if (this.cachePermanentRootStruct) { const cachedRoot = value[symbolCachedPermanentRoot]; if (cachedRoot) { return cachedRoot; } } const root = allocUnsafe(32); const safeCache = true; this.hashTreeRootInto(value, root, 0, safeCache); // hashTreeRootInto will cache the root if cachePermanentRootStruct is true return root; } hashTreeRootInto(value, output, offset, safeCache = false) { if (this.cachePermanentRootStruct) { const cachedRoot = value[symbolCachedPermanentRoot]; if (cachedRoot) { output.set(cachedRoot, offset); return; } } // should not call super.hashTreeRootInto() here // use merkleizeBlockArray() instead of merkleizeBlocksBytes() to avoid big memory allocation // reallocate this.blockArray if needed if (value.length > this.blockArray.length) { const blockDiff = value.length - this.blockArray.length; const newBlocksBytes = new Uint8Array(blockDiff * 64); for (let i = 0; i < blockDiff; i++) { this.blockArray.push(newBlocksBytes.subarray(i * 64, (i + 1) * 64)); } } // populate this.blockArray for (let i = 0; i < value.length; i++) { // 2 values share a block const block = this.blockArray[Math.floor(i / 2)]; const offset = i % 2 === 0 ? 0 : 32; this.elementType.hashTreeRootInto(value[i], block, offset); } const blockLimit = Math.ceil(value.length / 2); // zero out the last block if needed if (value.length % 2 === 1) { this.blockArray[blockLimit - 1].fill(0, 32); } // compute hashTreeRoot merkleizeBlockArray(this.blockArray, blockLimit, this.maxChunkCount, this.mixInLengthBlockBytes, 0); // mixInLength this.mixInLengthBuffer.writeUIntLE(value.length, 32, 6); // one for hashTreeRoot(value), one for length const chunkCount = 2; merkleizeBlocksBytes(this.mixInLengthBlockBytes, chunkCount, output, offset); if (this.cachePermanentRootStruct) { cacheRoot(value, output, offset, safeCache); } } getBlocksBytes() { // we use merkleizeBlockArray for hashTreeRoot() computation throw Error("getBlockBytes should not be called for ListCompositeType"); } } //# sourceMappingURL=listComposite.js.map