UNPKG

@chainsafe/ssz

Version:

Simple Serialize

159 lines 6.78 kB
import { allocUnsafe } from "@chainsafe/as-sha256"; import { merkleizeBlocksBytes } from "@chainsafe/persistent-merkle-tree"; import { cacheRoot, maxChunksToDepth, symbolCachedPermanentRoot, } from "../util/merkleize.js"; import { namedClass } from "../util/named.js"; import { ListBasicTreeView } from "../view/listBasic.js"; import { ListBasicTreeViewDU } from "../viewDU/listBasic.js"; import { ArrayType } from "./array.js"; import { addLengthNode, setChunksNode, tree_deserializeFromBytesArrayBasic, tree_serializeToBytesArrayBasic, value_deserializeFromBytesArrayBasic, value_serializeToBytesArrayBasic, } from "./arrayBasic.js"; /** * List: ordered variable-length homogeneous collection, limited to N values * * Array of Basic type: * - Basic types are max 32 bytes long so multiple values may be packed in the same node. * - Basic types are never returned in a view wrapper, but their value representation */ export class ListBasicType extends ArrayType { elementType; limit; typeName; itemsPerChunk; depth; chunkDepth; maxChunkCount; fixedSize = null; minSize; maxSize; isList = true; isViewMutable = true; 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 be basic"); if (limit === 0) throw Error("List limit must be > 0"); this.typeName = opts?.typeName ?? `List[${elementType.typeName}, ${limit}]`; // TODO Check that itemsPerChunk is an integer this.itemsPerChunk = 32 / elementType.byteLength; this.maxChunkCount = Math.ceil((this.limit * elementType.byteLength) / 32); this.chunkDepth = maxChunksToDepth(this.maxChunkCount); // Depth includes the extra level for the length node this.depth = this.chunkDepth + 1; this.minSize = 0; this.maxSize = this.limit * elementType.maxSize; } static named(elementType, limit, opts) { return new (namedClass(ListBasicType, opts.typeName))(elementType, limit, opts); } getView(tree) { return new ListBasicTreeView(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 ListBasicTreeViewDU(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.length * this.elementType.byteLength; } value_serializeToBytes(output, offset, value) { return value_serializeToBytesArrayBasic(this.elementType, value.length, output, offset, value); } value_deserializeFromBytes(data, start, end) { return value_deserializeFromBytesArrayBasic(this.elementType, data, start, end, this); } tree_serializedSize(node) { return this.tree_getLength(node) * this.elementType.byteLength; } tree_serializeToBytes(output, offset, node) { const chunksNode = this.tree_getChunksNode(node); const length = this.tree_getLength(node); return tree_serializeToBytesArrayBasic(this.elementType, length, this.chunkDepth, output, offset, chunksNode); } tree_deserializeFromBytes(data, start, end) { return tree_deserializeFromBytesArrayBasic(this.elementType, this.chunkDepth, data, start, end, this); } // Helpers for TreeView tree_getLength(node) { return node.right.getUint(4, 0); } 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; } } super.hashTreeRootInto(value, 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(value) { const byteLen = this.value_serializedSize(value); const blockByteLen = Math.ceil(byteLen / 64) * 64; // reallocate this.blocksBuffer if needed if (byteLen > this.blocksBuffer.length) { // pad 1 chunk if maxChunkCount is not even this.blocksBuffer = new Uint8Array(blockByteLen); } const blockBytes = this.blocksBuffer.subarray(0, blockByteLen); const uint8Array = blockBytes.subarray(0, byteLen); const dataView = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength); value_serializeToBytesArrayBasic(this.elementType, value.length, { uint8Array, dataView }, 0, value); // all padding bytes must be zero, this is similar to set zeroHash(0) this.blocksBuffer.subarray(byteLen, blockByteLen).fill(0); return blockBytes; } } //# sourceMappingURL=listBasic.js.map