@chainsafe/ssz
Version:
Simple Serialize
140 lines • 6.22 kB
JavaScript
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