@chainsafe/ssz
Version:
Simple Serialize
211 lines • 7.48 kB
JavaScript
import { allocUnsafe } from "@chainsafe/as-sha256";
import { concatGindices, getHashComputations, getNode, merkleizeBlocksBytes, } from "@chainsafe/persistent-merkle-tree";
import { namedClass } from "../util/named.js";
import { addLengthNode, getLengthFromRootNode } from "./arrayBasic.js";
import { CompositeType, isCompositeType } from "./composite.js";
import { NoneType } from "./none.js";
const VALUE_GINDEX = BigInt(2);
const SELECTOR_GINDEX = BigInt(3);
/**
* Union: union type containing one of the given subtypes
* - Notation: Union[type_0, type_1, ...], e.g. union[None, uint64, uint32]
*/
export class UnionType extends CompositeType {
types;
typeName;
depth = 1;
maxChunkCount = 1;
fixedSize = null;
minSize;
maxSize;
isList = true;
isViewMutable = true;
mixInLengthBlockBytes = new Uint8Array(64);
mixInLengthBuffer = Buffer.from(this.mixInLengthBlockBytes.buffer, this.mixInLengthBlockBytes.byteOffset, this.mixInLengthBlockBytes.byteLength);
maxSelector;
constructor(types, opts) {
super();
this.types = types;
if (types.length >= 128) {
throw Error("Must have less than 128 types");
}
if (types.length === 0) {
throw Error("Must have at least 1 type option");
}
if (types[0] instanceof NoneType && types.length < 2) {
throw Error("Must have at least 2 type options if the first is None");
}
for (let i = 1; i < types.length; i++) {
if (types[i] instanceof NoneType) {
throw Error("None may only be the first option");
}
}
this.typeName = opts?.typeName ?? `Union[${types.map((t) => t.typeName).join(",")}]`;
const minLens = [];
const maxLens = [];
for (const _type of types) {
minLens.push(_type.minSize);
maxLens.push(_type.maxSize);
}
this.minSize = 1 + Math.min(...minLens);
this.maxSize = 1 + Math.max(...maxLens);
this.maxSelector = this.types.length - 1;
// maxChunkCount = 1 so this.blocksBuffer.length = 32 in this case
this.blocksBuffer = new Uint8Array(32);
}
static named(types, opts) {
return new (namedClass(UnionType, opts.typeName))(types, opts);
}
defaultValue() {
return {
selector: 0,
value: this.types[0].defaultValue(),
};
}
getView(tree) {
return this.tree_toValue(tree.rootNode);
}
getViewDU(node) {
return this.tree_toValue(node);
}
cacheOfViewDU() {
return;
}
commitView(view) {
return this.value_toTree(view);
}
commitViewDU(view, hcOffset = 0, hcByLevel = null) {
const node = this.value_toTree(view);
if (hcByLevel !== null && node.h0 === null) {
getHashComputations(node, hcOffset, hcByLevel);
}
return node;
}
value_serializedSize(value) {
return 1 + this.types[value.selector].value_serializedSize(value.value);
}
value_serializeToBytes(output, offset, value) {
output.uint8Array[offset] = value.selector;
return this.types[value.selector].value_serializeToBytes(output, offset + 1, value.value);
}
value_deserializeFromBytes(data, start, end) {
const selector = data.uint8Array[start];
if (selector > this.maxSelector) {
throw Error(`Invalid selector ${selector}`);
}
return {
selector,
value: this.types[selector].value_deserializeFromBytes(data, start + 1, end),
};
}
tree_serializedSize(node) {
const selector = getLengthFromRootNode(node);
const valueNode = node.left;
return 1 + this.types[selector].value_serializedSize(valueNode);
}
tree_serializeToBytes(output, offset, node) {
const selector = getLengthFromRootNode(node);
const valueNode = node.left;
output.uint8Array[offset] = selector;
return this.types[selector].tree_serializeToBytes(output, offset + 1, valueNode);
}
tree_deserializeFromBytes(data, start, end) {
const selector = data.uint8Array[start];
if (selector > this.maxSelector) {
throw Error(`Invalid selector ${selector}`);
}
const valueNode = this.types[selector].tree_deserializeFromBytes(data, start + 1, end);
return addLengthNode(valueNode, selector);
}
// Merkleization
hashTreeRoot(value) {
const root = allocUnsafe(32);
this.hashTreeRootInto(value, root, 0);
return root;
}
hashTreeRootInto(value, output, offset) {
super.hashTreeRootInto(value, this.mixInLengthBlockBytes, 0);
this.mixInLengthBuffer.writeUIntLE(value.selector, 32, 6);
const chunkCount = 2;
merkleizeBlocksBytes(this.mixInLengthBlockBytes, chunkCount, output, offset);
}
getBlocksBytes(value) {
this.types[value.selector].hashTreeRootInto(value.value, this.blocksBuffer, 0);
return this.blocksBuffer;
}
// Proofs
getPropertyGindex(prop) {
switch (prop) {
case "value":
return VALUE_GINDEX;
case "selector":
return SELECTOR_GINDEX;
default:
throw new Error(`Invalid Union type property ${prop}`);
}
}
getPropertyType() {
// a Union has multiple types
throw new Error("Not applicable for Union type");
}
getIndexProperty(index) {
if (index === 0)
return "value";
if (index === 1)
return "selector";
throw Error("Union index of out bounds");
}
tree_getLeafGindices(rootGindex, rootNode) {
if (!rootNode) {
throw Error("rootNode required");
}
const gindices = [concatGindices([rootGindex, SELECTOR_GINDEX])];
const selector = getLengthFromRootNode(rootNode);
const type = this.types[selector];
const extendedFieldGindex = concatGindices([rootGindex, VALUE_GINDEX]);
if (isCompositeType(type)) {
gindices.push(...type.tree_getLeafGindices(extendedFieldGindex, getNode(rootNode, VALUE_GINDEX)));
}
else {
gindices.push(extendedFieldGindex);
}
return gindices;
}
// JSON
fromJson(json) {
if (typeof json !== "object") {
throw new Error("JSON must be of type object");
}
const union = json;
if (typeof union.selector !== "number") {
throw new Error("Invalid JSON Union selector must be number");
}
const type = this.types[union.selector];
if (!type) {
throw new Error("Invalid JSON Union selector out of range");
}
return {
selector: union.selector,
value: type.toJson(union.value),
};
}
toJson(value) {
return {
selector: value.selector,
value: this.types[value.selector].toJson(value.value),
};
}
clone(value) {
return {
selector: value.selector,
value: this.types[value.selector].clone(value.value),
};
}
equals(a, b) {
if (a.selector !== b.selector) {
return false;
}
return this.types[a.selector].equals(a.value, b.value);
}
}
//# sourceMappingURL=union.js.map