UNPKG

@chainsafe/ssz

Version:

Simple Serialize

140 lines 6.22 kB
import { getNodeAtDepth, toGindexBitstring, zeroNode, } from "@chainsafe/persistent-merkle-tree"; import { isBasicType } from "../type/basic.js"; import { isCompositeType } from "../type/composite.js"; import { TreeView } from "./abstract.js"; /** * Intented usage: * * - Get initial BeaconState from disk. * - Before applying next block, switch to mutable * - Get some field, create a view in mutable mode * - Do modifications of the state in the state transition function * - When done, commit and apply new root node once to og BeaconState * - However, keep all the caches and transfer them to the new BeaconState * * Questions: * - Can the child views created in mutable mode switch to not mutable? If so, it seems that it needs to recursively * iterate the entire data structure and views * */ class ProfileTreeView extends TreeView { type; tree; constructor(type, tree) { super(); this.type = type; this.tree = tree; } get node() { return this.tree.rootNode; } } export function getProfileTreeViewClass(type) { class CustomProfileTreeView extends ProfileTreeView { } // Dynamically define prototype methods for (let index = 0; index < type.fieldsEntries.length; index++) { const { fieldName, fieldType, chunkIndex, optional } = type.fieldsEntries[index]; // If the field type is basic, the value to get and set will be the actual 'struct' value (i.e. a JS number). // The view must use the tree_getFromNode() and tree_setToNode() methods to persist the struct data to the node, // and use the cached views array to store the new node. if (isBasicType(fieldType)) { Object.defineProperty(CustomProfileTreeView.prototype, fieldName, { configurable: false, enumerable: true, // TODO: Review the memory cost of this closures get: function () { const leafNode = getNodeAtDepth(this.node, this.type.depth, chunkIndex); if (optional && leafNode === zeroNode(0)) { return null; } return fieldType.tree_getFromNode(leafNode); }, set: function (value) { if (optional && value == null) { const leafNode = zeroNode(0); this.tree.setNodeAtDepth(this.type.depth, chunkIndex, leafNode); return; } const leafNodePrev = getNodeAtDepth(this.node, this.type.depth, chunkIndex); const leafNode = leafNodePrev.clone(); fieldType.tree_setToNode(leafNode, value); this.tree.setNodeAtDepth(this.type.depth, chunkIndex, leafNode); }, }); } // If the field type is composite, the value to get and set will be another TreeView. The parent TreeView must // cache the view itself to retain the caches of the child view. To set a value the view must return a node to // set it to the parent tree in the field gindex. else if (isCompositeType(fieldType)) { Object.defineProperty(CustomProfileTreeView.prototype, fieldName, { configurable: false, enumerable: true, // Returns TreeView of fieldName get: function () { const gindex = toGindexBitstring(this.type.depth, chunkIndex); const tree = this.tree.getSubtree(gindex); if (optional && tree.rootNode === zeroNode(0)) { return null; } return fieldType.getView(tree); }, // Expects TreeView of fieldName set: function (value) { if (optional && value == null) { this.tree.setNodeAtDepth(this.type.depth, chunkIndex, zeroNode(0)); } const node = fieldType.commitView(value); this.tree.setNodeAtDepth(this.type.depth, chunkIndex, node); }, }); } // Should never happen else { /* istanbul ignore next - unreachable code */ throw Error(`Unknown fieldType ${fieldType.typeName} for fieldName ${String(fieldName)}`); } } // Change class name Object.defineProperty(CustomProfileTreeView, "name", { value: type.typeName, writable: false }); return CustomProfileTreeView; } /** * Precompute fixed and variable offsets position for faster deserialization. * @returns Does a single pass over all fields and returns: * - isFixedLen: If field index [i] is fixed length * - fieldRangesFixedLen: For fields with fixed length, their range of bytes * - variableOffsetsPosition: Position of the 4 bytes offset for variable size fields * - fixedEnd: End of the fixed size range * - offsets are relative to the start of serialized active fields, after the Bitvector[N] of optional fields */ export function computeSerdesData(optionalFields, fields) { const isFixedLen = []; const fieldRangesFixedLen = []; const variableOffsetsPosition = []; // should not be optionalFields.uint8Array.length because offsets are relative to the start of serialized active fields let pointerFixed = 0; let optionalIndex = 0; for (const { optional, fieldType } of fields) { if (optional && !optionalFields.get(optionalIndex++)) { continue; } isFixedLen.push(fieldType.fixedSize !== null); if (fieldType.fixedSize === null) { // Variable length variableOffsetsPosition.push(pointerFixed); pointerFixed += 4; } else { fieldRangesFixedLen.push({ start: pointerFixed, end: pointerFixed + fieldType.fixedSize }); pointerFixed += fieldType.fixedSize; } } return { isFixedLen, fieldRangesFixedLen, variableOffsetsPosition, fixedEnd: pointerFixed, }; } //# sourceMappingURL=profile.js.map