@lodestar/config
Version:
Chain configuration required for lodestar
176 lines (140 loc) • 4.95 kB
text/typescript
import {fromHex, toHex} from "@lodestar/utils";
import {validateBlobSchedule} from "../utils/validateBlobSchedule.js";
import {
BlobSchedule,
BlobScheduleEntry,
ChainConfig,
SpecJson,
SpecValue,
SpecValueTypeName,
chainConfigTypes,
isBlobSchedule,
} from "./types.js";
const MAX_UINT64_JSON = "18446744073709551615";
export function chainConfigToJson(config: ChainConfig): SpecJson {
const json: SpecJson = {};
for (const key of Object.keys(chainConfigTypes) as (keyof ChainConfig)[]) {
const value = config[key];
if (value !== undefined) {
json[key] = serializeSpecValue(value, chainConfigTypes[key]);
}
}
return json;
}
export function chainConfigFromJson(json: Record<string, unknown>): ChainConfig {
const config = {} as ChainConfig;
for (const key of Object.keys(chainConfigTypes) as (keyof ChainConfig)[]) {
const value = json[key];
if (value !== undefined) {
config[key] = deserializeSpecValue(json[key], chainConfigTypes[key], key) as never;
}
}
return config;
}
export function specValuesToJson(spec: Record<string, SpecValue>): SpecJson {
const json: SpecJson = {};
for (const key of Object.keys(spec)) {
json[key] = serializeSpecValue(spec[key], toSpecValueTypeName(spec[key]));
}
return json;
}
/** Automatic inference of typeName. For critical variables define type names, else infer */
export function toSpecValueTypeName(value: SpecValue): SpecValueTypeName {
if (value instanceof Uint8Array) return "bytes";
if (typeof value === "number") return "number";
if (typeof value === "bigint") return "bigint";
if (typeof value === "string") return "string";
if (isBlobSchedule(value)) return "blob_schedule";
throw Error(`Unknown value type ${value}`);
}
export function serializeSpecValue(
value: SpecValue,
typeName: SpecValueTypeName
): string | Record<keyof BlobScheduleEntry, string>[] {
switch (typeName) {
case "number":
if (typeof value !== "number") {
throw Error(`Invalid value ${value.toString()} expected number`);
}
if (value === Infinity) {
return MAX_UINT64_JSON;
}
return value.toString(10);
case "bigint":
if (typeof value !== "bigint") {
throw Error(`Invalid value ${value.toString()} expected bigint`);
}
return value.toString(10);
case "bytes":
if (!(value instanceof Uint8Array)) {
throw Error(`Invalid value ${value.toString()} expected Uint8Array`);
}
return toHex(value);
case "string":
if (typeof value !== "string") {
throw Error(`Invalid value ${value.toString()} expected string`);
}
return value;
case "blob_schedule":
if (!isBlobSchedule(value)) {
throw Error(`Invalid value ${value.toString()} expected BlobSchedule`);
}
return value.map(({EPOCH, MAX_BLOBS_PER_BLOCK}) => ({
EPOCH: EPOCH === Infinity ? MAX_UINT64_JSON : EPOCH.toString(10),
MAX_BLOBS_PER_BLOCK: MAX_BLOBS_PER_BLOCK === Infinity ? MAX_UINT64_JSON : MAX_BLOBS_PER_BLOCK.toString(10),
}));
}
}
export function deserializeSpecValue(valueStr: unknown, typeName: SpecValueTypeName, keyName: string): SpecValue {
if (typeName === "blob_schedule") {
return deserializeBlobSchedule(valueStr);
}
if (typeof valueStr !== "string") {
throw Error(`Invalid ${keyName} value ${valueStr} expected string`);
}
switch (typeName) {
case "number":
if (valueStr === MAX_UINT64_JSON) {
return Infinity;
}
return parseInt(valueStr, 10);
case "bigint":
return BigInt(valueStr);
case "bytes":
return fromHex(valueStr);
case "string":
return valueStr;
}
}
export function deserializeBlobSchedule(input: unknown): BlobSchedule {
if (!Array.isArray(input)) {
throw Error(`Invalid BLOB_SCHEDULE value ${input} expected array`);
}
const blobSchedule = input.map((entry, i) => {
if (typeof entry !== "object" || entry === null) {
throw Error(`Invalid BLOB_SCHEDULE[${i}] entry ${entry} expected object`);
}
const out = {} as BlobScheduleEntry;
for (const key of ["EPOCH", "MAX_BLOBS_PER_BLOCK"] as Array<keyof BlobScheduleEntry>) {
const value = entry[key];
if (value === undefined) {
throw Error(`Invalid BLOB_SCHEDULE[${i}] entry ${JSON.stringify(entry)} missing ${key}`);
}
if (typeof value !== "string") {
throw Error(`Invalid BLOB_SCHEDULE[${i}].${key} value ${value} expected string`);
}
if (value === MAX_UINT64_JSON) {
out[key] = Infinity;
} else {
const parsed = parseInt(value, 10);
if (Number.isNaN(parsed)) {
throw Error(`Invalid BLOB_SCHEDULE[${i}].${key} value ${value} expected number`);
}
out[key] = parsed;
}
}
return out;
});
validateBlobSchedule(blobSchedule);
return blobSchedule;
}