@chainsafe/ssz
Version:
Simple Serialize
161 lines • 6.18 kB
JavaScript
import { BranchNode, LeafNode, getNodesAtDepth, levelAtIndex, packedNodeRootsToBytes, packedRootsBytesToNode, } from "@chainsafe/persistent-merkle-tree";
// There's a matrix of Array-ish types that require a combination of this functions.
// Regular class extends syntax doesn't work because it can only extend a single class.
//
// Type of array: List, Vector. Changes length property
// Type of element: Basic, Composite. Changes merkelization if packing or not.
// If Composite: Fixed len, Variable len. Changes the serialization requiring offsets.
/**
* SSZ Lists (variable-length arrays) include the length of the list in the tree
* This length is always in the same index in the tree
* ```
* 1
* / \
* 2 3 // <-here
* ```
*/
export function getLengthFromRootNode(node) {
// Length is represented as a Uint32 at the start of the chunk:
// 4 = 4 bytes in Uint32
// 0 = 0 offset bytes in Node's data
return node.right.getUint(4, 0);
}
export function getChunksNodeFromRootNode(node) {
return node.left;
}
export function addLengthNode(chunksNode, length) {
return new BranchNode(chunksNode, LeafNode.fromUint32(length));
}
export function setChunksNode(rootNode, chunksNode, newLength, hcOffset = 0, hcByLevel = null) {
const lengthNode = newLength !== null
? // If newLength is set, create a new node for length
LeafNode.fromUint32(newLength)
: // else re-use existing node
rootNode.right;
const branchNode = new BranchNode(chunksNode, lengthNode);
if (hcByLevel !== null) {
levelAtIndex(hcByLevel, hcOffset).push(chunksNode, lengthNode, branchNode);
}
return branchNode;
}
/**
* @param length In List length = value.length, Vector length = fixed value
*/
export function value_serializeToBytesArrayBasic(elementType, length, output, offset, value) {
const elSize = elementType.byteLength;
for (let i = 0; i < length; i++) {
elementType.value_serializeToBytes(output, offset + i * elSize, value[i]);
}
return offset + length * elSize;
}
export function value_deserializeFromBytesArrayBasic(elementType, data, start, end, arrayProps) {
const elSize = elementType.byteLength;
// Vector + List length validation
const length = (end - start) / elSize;
assertValidArrayLength(length, arrayProps, true);
const values = new Array(length);
for (let i = 0; i < length; i++) {
// TODO: If faster, consider skipping size check for uint types
values[i] = elementType.value_deserializeFromBytes(data, start + i * elSize, start + (i + 1) * elSize);
}
return values;
}
/**
* @param length In List length = value.length, Vector length = fixed value
*/
export function tree_serializeToBytesArrayBasic(elementType, length, depth, output, offset, node, cachedNodes = null) {
const size = elementType.byteLength * length;
const chunkCount = Math.ceil(size / 32);
const nodes = cachedNodes ?? getNodesAtDepth(node, depth, 0, chunkCount);
packedNodeRootsToBytes(output.dataView, offset, size, nodes);
return offset + size;
}
// List of basic elements will pack them in merkelized form
export function tree_deserializeFromBytesArrayBasic(elementType, chunkDepth, data, start, end, arrayProps) {
// Vector + List length validation
const length = (end - start) / elementType.byteLength;
assertValidArrayLength(length, arrayProps, true);
// Abstract converting data to LeafNode to allow for custom data representation, such as the hashObject
const chunksNode = packedRootsBytesToNode(chunkDepth, data.dataView, start, end);
if (arrayProps.isList) {
return addLengthNode(chunksNode, length);
}
return chunksNode;
}
/**
* @param length In List length = undefined, Vector length = fixed value
*/
export function value_fromJsonArray(elementType, json, arrayProps) {
if (!Array.isArray(json)) {
throw Error("JSON is not an array");
}
assertValidArrayLength(json.length, arrayProps);
const value = new Array(json.length);
for (let i = 0; i < json.length; i++) {
value[i] = elementType.fromJson(json[i]);
}
return value;
}
/**
* @param length In List length = undefined, Vector length = fixed value
*/
export function value_toJsonArray(elementType, value, arrayProps) {
const length = arrayProps.isList ? value.length : arrayProps.length;
const json = new Array(length);
for (let i = 0; i < length; i++) {
json[i] = elementType.toJson(value[i]);
}
return json;
}
/**
* Clone recursively an array of basic or composite types
*/
export function value_cloneArray(elementType, value) {
const newValue = new Array(value.length);
for (let i = 0; i < value.length; i++) {
newValue[i] = elementType.clone(value[i]);
}
return newValue;
}
/**
* Check recursively if a type is structuraly equal. Returns early
*/
export function value_equals(elementType, a, b) {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (!elementType.equals(a[i], b[i])) {
return false;
}
}
return true;
}
export function value_defaultValueArray(elementType, length) {
const values = new Array(length);
for (let i = 0; i < length; i++) {
values[i] = elementType.defaultValue();
}
return values;
}
/**
* @param checkNonDecimalLength Check that length is a multiple of element size.
* Optional since it's not necessary in getOffsetsArrayComposite() fn.
*/
export function assertValidArrayLength(length, arrayProps, checkNonDecimalLength) {
if (checkNonDecimalLength && length % 1 !== 0) {
throw Error("size not multiple of element fixedSize");
}
// Vector + List length validation
if (arrayProps.isList) {
if (length > arrayProps.limit) {
throw new Error(`Invalid list length ${length} over limit ${arrayProps.limit}`);
}
}
else {
if (length !== arrayProps.length) {
throw new Error(`Incorrect vector length ${length} expected ${arrayProps.length}`);
}
}
}
//# sourceMappingURL=arrayBasic.js.map