Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/config/src/chainConfig/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {ACTIVE_PRESET} from "@lodestar/params";
import {defaultChainConfig} from "./default.js";
import {ChainConfig} from "./types.js";

export {chainConfigToJson, chainConfigFromJson, specValuesToJson} from "./json.js";
export {chainConfigToJson, chainConfigFromJson, specValuesToJson, deserializeBlobSchedule} from "./json.js";
export * from "./types.js";
export * from "./default.js";

Expand Down
83 changes: 44 additions & 39 deletions packages/config/src/chainConfig/json.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {fromHex, toHex} from "@lodestar/utils";
import {
BlobSchedule,
BlobScheduleEntry,
ChainConfig,
SpecJson,
Expand Down Expand Up @@ -103,45 +104,7 @@ export function serializeSpecValue(

export function deserializeSpecValue(valueStr: unknown, typeName: SpecValueTypeName, keyName: string): SpecValue {
if (typeName === "blob_schedule") {
if (!Array.isArray(valueStr)) {
throw Error(`Invalid BLOB_SCHEDULE value ${valueStr} expected array`);
}

const blobSchedule = valueStr.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;
});

return blobSchedule;
return deserializeBlobSchedule(valueStr);
}

if (typeof valueStr !== "string") {
Expand All @@ -165,3 +128,45 @@ export function deserializeSpecValue(valueStr: unknown, typeName: SpecValueTypeN
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;
});

return blobSchedule;
}
31 changes: 30 additions & 1 deletion packages/validator/src/util/params.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ChainConfig, SpecJson, chainConfigToJson} from "@lodestar/config";
import {BlobScheduleEntry, ChainConfig, SpecJson, chainConfigToJson, deserializeBlobSchedule} from "@lodestar/config";
import {BeaconPreset, activePreset, presetToJson} from "@lodestar/params";

export class NotEqualParamsError extends Error {}
Expand Down Expand Up @@ -52,6 +52,35 @@ export function assertEqualParams(localConfig: ChainConfig, externalSpecJson: Sp
continue;
}

if (key === "BLOB_SCHEDULE") {
const localBlobSchedule = deserializeBlobSchedule(localSpecJson[key]).sort((a, b) => a.EPOCH - b.EPOCH);
const remoteBlobSchedule = deserializeBlobSchedule(externalSpecJson[key]).sort((a, b) => a.EPOCH - b.EPOCH);

if (localBlobSchedule.length !== remoteBlobSchedule.length) {
errors.push(`BLOB_SCHEDULE different length: ${localBlobSchedule.length} != ${remoteBlobSchedule.length}`);

// Skip per entry comparison
continue;
}

for (let i = 0; i < localBlobSchedule.length; i++) {
const localEntry = localBlobSchedule[i];
const remoteEntry = remoteBlobSchedule[i];

for (const entryKey of ["EPOCH", "MAX_BLOBS_PER_BLOCK"] as Array<keyof BlobScheduleEntry>) {
const localValue = String(localEntry[entryKey]);
const remoteValue = String(remoteEntry[entryKey]);

if (localValue !== remoteValue) {
errors.push(`BLOB_SCHEDULE[${i}].${entryKey} different value: ${localValue} != ${remoteValue}`);
}
}
}

// Skip generic string comparison
continue;
}

// Must compare JSON serialized specs, to ensure all strings are rendered in the same way
// Must compare as lowercase to ensure checksum addresses and names have same capilatization
const localValue = String(localSpecJson[key]).toLocaleLowerCase();
Expand Down
Loading