UNPKG

@chainsafe/ssz

Version:

Simple Serialize

187 lines 9.14 kB
import { getNodeAtDepth, zeroNode } from "@chainsafe/persistent-merkle-tree"; import { isBasicType } from "../type/basic.js"; import { isCompositeType } from "../type/composite.js"; import { computeSerdesData } from "../view/stableContainer.js"; import { BasicContainerTreeViewDU } from "./container.js"; class StableContainerTreeViewDU extends BasicContainerTreeViewDU { type; _rootNode; /** pending active fields bitvector */ activeFields; constructor(type, _rootNode, cache) { super(type, _rootNode, cache); this.type = type; this._rootNode = _rootNode; if (cache) { this.activeFields = cache.activeFields; } else { this.activeFields = type.tree_getActiveFields(_rootNode); } } get cache() { const result = super.cache; return { ...result, activeFields: this.activeFields }; } commit(hcOffset = 0, hcByLevel = null) { super.commit(hcOffset, hcByLevel); this._rootNode = this.type.tree_setActiveFields(this._rootNode, this.activeFields); if (hcByLevel !== null) { hcByLevel[hcOffset].push(this._rootNode.left, this._rootNode.right, this._rootNode); } } /** * Same method to `type/container.ts` that call ViewDU.serializeToBytes() of internal fields. */ serializeToBytes(output, offset) { this.commit(); const activeFields = this.type.tree_getActiveFields(this.node); // write active fields bitvector output.uint8Array.set(activeFields.uint8Array, offset); const { fixedEnd } = computeSerdesData(activeFields, this.type.fieldsEntries); const activeFieldsLen = activeFields.uint8Array.length; let fixedIndex = offset + activeFieldsLen; let variableIndex = offset + fixedEnd; for (let index = 0; index < this.type.fieldsEntries.length; index++) { const { fieldType, optional } = this.type.fieldsEntries[index]; if (optional && !activeFields.get(index)) { continue; } let node = this.nodes[index]; if (node === undefined) { node = getNodeAtDepth(this._rootNode, this.type.depth, index); this.nodes[index] = node; } if (fieldType.fixedSize === null) { // write offset relative to the start of serialized active fields, after the Bitvector[N] output.dataView.setUint32(fixedIndex, variableIndex - offset - activeFieldsLen, true); fixedIndex += 4; // write serialized element to variable section // basic types always have fixedSize if (isCompositeType(fieldType)) { const view = fieldType.getViewDU(node, this.caches[index]); if (view.serializeToBytes !== undefined) { variableIndex = view.serializeToBytes(output, variableIndex); } else { // some types don't define ViewDU as TreeViewDU, like the UnionType, in that case view.serializeToBytes = undefined variableIndex = fieldType.tree_serializeToBytes(output, variableIndex, node); } } } else { fixedIndex = fieldType.tree_serializeToBytes(output, fixedIndex, node); } } return variableIndex; } } export function getContainerTreeViewDUClass(type) { class CustomContainerTreeViewDU extends StableContainerTreeViewDU { } // Dynamically define prototype methods for (let index = 0; index < type.fieldsEntries.length; index++) { const { fieldName, fieldType, 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(CustomContainerTreeViewDU.prototype, fieldName, { configurable: false, enumerable: true, // TODO: Review the memory cost of this closures get: function () { if (optional && this.activeFields.get(index) === false) { return null; } // First walk through the tree to get the root node for that index let node = this.nodes[index]; if (node === undefined) { node = getNodeAtDepth(this._rootNode, this.type.depth, index); this.nodes[index] = node; } return fieldType.tree_getFromNode(node); }, set: function (value) { if (optional && value == null) { this.nodes[index] = zeroNode(0); this.nodesChanged.add(index); this.activeFields.set(index, false); return; } // Create new node if current leafNode is not dirty let nodeChanged; if (this.nodesChanged.has(index)) { // TODO: This assumes that node has already been populated nodeChanged = this.nodes[index]; } else { const nodePrev = (this.nodes[index] ?? getNodeAtDepth(this._rootNode, this.type.depth, index)); nodeChanged = nodePrev.clone(); // Store the changed node in the nodes cache this.nodes[index] = nodeChanged; this.nodesChanged.add(index); } fieldType.tree_setToNode(nodeChanged, value); this.activeFields.set(index, true); }, }); } // 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(CustomContainerTreeViewDU.prototype, fieldName, { configurable: false, enumerable: true, // Returns TreeViewDU of fieldName get: function () { if (optional && this.activeFields.get(index) === false) { return null; } const viewChanged = this.viewsChanged.get(index); if (viewChanged) { return viewChanged; } let node = this.nodes[index]; if (node === undefined) { node = getNodeAtDepth(this._rootNode, this.type.depth, index); this.nodes[index] = node; } // Keep a reference to the new view to call .commit on it latter, only if mutable const view = fieldType.getViewDU(node, this.caches[index]); if (fieldType.isViewMutable) { this.viewsChanged.set(index, view); } // No need to persist the child's view cache since a second get returns this view instance. // The cache is only persisted on commit where the viewsChanged map is dropped. return view; }, // Expects TreeViewDU of fieldName set: function (view) { if (optional && view == null) { this.nodes[index] = zeroNode(0); this.nodesChanged.add(index); this.activeFields.set(index, false); return; } // When setting a view: // - Not necessary to commit node // - Not necessary to persist cache // Just keeping a reference to the view in this.viewsChanged ensures consistency this.viewsChanged.set(index, view); this.activeFields.set(index, true); }, }); } // Should never happen else { /* istanbul ignore next - unreachable code */ throw Error(`Unknown fieldType ${fieldType.typeName} for fieldName ${String(fieldName)}`); } } // Change class name Object.defineProperty(CustomContainerTreeViewDU, "name", { value: type.typeName, writable: false }); return CustomContainerTreeViewDU; } //# sourceMappingURL=stableContainer.js.map