Skip to content
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
86cdd4d
feat: add BPO
ensi321 Apr 19, 2025
01695a8
Fix edge case inspired by @dguenther
ensi321 Apr 20, 2025
1188a17
Add unit test
ensi321 Apr 20, 2025
f4cf9ff
getMaxBlobsPerBlock takes epoch
ensi321 May 9, 2025
ea75921
lint
ensi321 May 9, 2025
eac3a69
Update config
ensi321 May 13, 2025
5d72a7a
Update config
ensi321 May 13, 2025
b5218ea
Fix test
ensi321 May 13, 2025
fbefbf5
fix spec test
ensi321 May 13, 2025
5028e2c
lint
ensi321 May 13, 2025
4f7c457
fix spec test
ensi321 May 14, 2025
3d748ba
Fix gnosis config
nflaig May 16, 2025
12dad4c
Update getMaxBlobsPerBlock to only check epoch
nflaig May 16, 2025
dea4739
Fix blob schedule parsing and config/spec output
nflaig May 16, 2025
d48746f
Merge branch 'unstable' into nc/BPO
nflaig May 20, 2025
921693e
Merge branch 'unstable' into nc/BPO
nflaig May 20, 2025
7c36eb1
Return smallest MAX_BLOBS_PER_BLOCK value if epoch is out of range
nflaig May 20, 2025
11fdc1d
Add comment to clarify that default min value is only for testing
nflaig May 20, 2025
f8ff2c9
Revert change to gossip block validation test
nflaig May 20, 2025
7381c14
Deduplicate spec json type definition
nflaig May 20, 2025
adb43df
Update config files to more closely align with CL spec files
nflaig May 20, 2025
288fc33
Fix more inconsistencies
nflaig May 20, 2025
f3a0367
Only assert BLOB_SCHEDULE value if Fulu is scheduled
nflaig May 20, 2025
1c36c2a
Add unit tests for getMaxBlobsPerBlock
nflaig May 20, 2025
cf194df
Improve type safety and validation
nflaig May 20, 2025
43ade43
Remove import
nflaig May 20, 2025
d507ca8
More type safety
nflaig May 20, 2025
f0cfc8d
More validation
nflaig May 20, 2025
a703a0a
Special handling for deneb and electra
ensi321 May 21, 2025
e918c2c
format
ensi321 May 21, 2025
58df57c
Make unit test run on fulu fork
ensi321 May 21, 2025
87f6c7d
Lint
nflaig May 21, 2025
0b9fbe7
Update comment
nflaig May 21, 2025
71f57dc
Move blob schedule length check down
nflaig May 21, 2025
3da64d0
Update comment
nflaig May 21, 2025
01246be
Simplify a bit
nflaig May 21, 2025
5371e1e
Keep previous change
nflaig May 21, 2025
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
4 changes: 2 additions & 2 deletions packages/api/src/beacon/routes/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {ContainerType, ValueOf} from "@chainsafe/ssz";
import {ChainForkConfig} from "@lodestar/config";
import {ChainForkConfig, SpecJson} from "@lodestar/config";
import {ssz} from "@lodestar/types";
import {
ArrayOf,
Expand All @@ -24,7 +24,7 @@ export const ForkListType = ArrayOf(ssz.phase0.Fork);

export type DepositContract = ValueOf<typeof DepositContractType>;
export type ForkList = ValueOf<typeof ForkListType>;
export type Spec = Record<string, string>;
export type Spec = SpecJson;

export type Endpoints = {
/**
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/api/impl/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {specConstants} from "./constants.js";
* [altair](https://github.com/ethereum/consensus.0-specs/blob/v1.1.10/presets/mainnet/altair.yaml) values
* - Configuration for the beacon node, for example the [mainnet](https://github.com/ethereum/consensus-specs/blob/v1.1.10/configs/mainnet.yaml) values
*/
export function renderJsonSpec(config: ChainConfig): Record<string, string> {
export function renderJsonSpec(config: ChainConfig): routes.config.Spec {
const configJson = chainConfigToJson(config);
const presetJson = presetToJson(activePreset);
const constantsJson = specValuesToJson(specConstants);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {ModuleThread} from "@chainsafe/threads";
import {BeaconConfig} from "@lodestar/config";
import {BeaconConfig, SpecJson} from "@lodestar/config";
import {LoggerNode, LoggerNodeOpts} from "@lodestar/logger/node";
import {BeaconStateTransitionMetrics} from "@lodestar/state-transition";
import {Gauge, Histogram} from "@lodestar/utils";
Expand All @@ -20,7 +20,7 @@ export type HistoricalStateRegenModules = HistoricalStateRegenInitModules & {
};

export type HistoricalStateWorkerData = {
chainConfigJson: Record<string, string>;
chainConfigJson: SpecJson;
genesisValidatorsRoot: Uint8Array;
genesisTime: number;
maxConcurrency: number;
Expand Down
8 changes: 6 additions & 2 deletions packages/beacon-node/src/chain/validation/blobSidecar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import {
KZG_COMMITMENT_SUBTREE_INDEX0,
isForkPostElectra,
} from "@lodestar/params";
import {computeStartSlotAtEpoch, getBlockHeaderProposerSignatureSet} from "@lodestar/state-transition";
import {
computeEpochAtSlot,
computeStartSlotAtEpoch,
getBlockHeaderProposerSignatureSet,
} from "@lodestar/state-transition";
import {BlobIndex, Root, Slot, SubnetID, deneb, ssz} from "@lodestar/types";
import {toRootHex, verifyMerkleBranch} from "@lodestar/utils";

Expand All @@ -25,7 +29,7 @@ export async function validateGossipBlobSidecar(
const blobSlot = blobSidecar.signedBlockHeader.message.slot;

// [REJECT] The sidecar's index is consistent with `MAX_BLOBS_PER_BLOCK` -- i.e. `blob_sidecar.index < MAX_BLOBS_PER_BLOCK`.
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(fork);
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(blobSlot));
if (blobSidecar.index >= maxBlobsPerBlock) {
throw new BlobSidecarGossipError(GossipAction.REJECT, {
code: BlobSidecarErrorCode.INDEX_TOO_LARGE,
Expand Down
3 changes: 2 additions & 1 deletion packages/beacon-node/src/chain/validation/block.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {ChainForkConfig} from "@lodestar/config";
import {ForkName, isForkPostDeneb} from "@lodestar/params";
import {
computeEpochAtSlot,
computeStartSlotAtEpoch,
computeTimeAtSlot,
getBlockProposerSignatureSet,
Expand Down Expand Up @@ -113,7 +114,7 @@ export async function validateGossipBlock(
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer -- i.e. validate that len(body.signed_beacon_block.message.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK
if (isForkPostDeneb(fork)) {
const blobKzgCommitmentsLen = (block as deneb.BeaconBlock).body.blobKzgCommitments.length;
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(fork);
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(blockSlot));
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
throw new BlockGossipError(GossipAction.REJECT, {
code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS,
Expand Down
3 changes: 2 additions & 1 deletion packages/beacon-node/src/network/core/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {PeerScoreStatsDump} from "@chainsafe/libp2p-gossipsub/score";
import {PublishOpts} from "@chainsafe/libp2p-gossipsub/types";
import {routes} from "@lodestar/api";
import {SpecJson} from "@lodestar/config";
import {LoggerNodeOpts} from "@lodestar/logger/node";
import {ResponseIncoming} from "@lodestar/reqresp";
import {phase0} from "@lodestar/types";
Expand Down Expand Up @@ -73,7 +74,7 @@ export interface INetworkCore extends INetworkCorePublic {
export type NetworkWorkerData = {
// TODO: Review if NetworkOptions is safe for passing
opts: NetworkOptions;
chainConfigJson: Record<string, string>;
chainConfigJson: SpecJson;
genesisValidatorsRoot: Uint8Array;
genesisTime: number;
activeValidatorCount: number;
Expand Down
6 changes: 3 additions & 3 deletions packages/beacon-node/src/network/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {BeaconConfig} from "@lodestar/config";
import {LoggerNode} from "@lodestar/logger/node";
import {ForkSeq} from "@lodestar/params";
import {ResponseIncoming} from "@lodestar/reqresp";
import {computeStartSlotAtEpoch, computeTimeAtSlot} from "@lodestar/state-transition";
import {computeEpochAtSlot, computeStartSlotAtEpoch, computeTimeAtSlot} from "@lodestar/state-transition";
import {
AttesterSlashing,
LightClientBootstrap,
Expand Down Expand Up @@ -505,11 +505,11 @@ export class Network implements INetwork {
peerId: PeerIdStr,
request: deneb.BlobSidecarsByRangeRequest
): Promise<deneb.BlobSidecar[]> {
const fork = this.config.getForkName(request.startSlot);
const epoch = computeEpochAtSlot(request.startSlot);
return collectMaxResponseTyped(
this.sendReqRespRequest(peerId, ReqRespMethod.BlobSidecarsByRange, [Version.V1], request),
// request's count represent the slots, so the actual max count received could be slots * blobs per slot
request.count * this.config.getMaxBlobsPerBlock(fork),
request.count * this.config.getMaxBlobsPerBlock(epoch),
responseSszTypeByMethod[ReqRespMethod.BlobSidecarsByRange]
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ describe("gossip block validation", () => {

beforeEach(() => {
// Fill up with kzg commitments
block.body.blobKzgCommitments = Array.from({length: config.MAX_BLOBS_PER_BLOCK}, () => new Uint8Array([0]));
block.body.blobKzgCommitments = Array.from(
{length: config.BLOB_SCHEDULE[0].MAX_BLOBS_PER_BLOCK},
() => new Uint8Array([0])
);

chain = getMockedBeaconChain();
vi.spyOn(chain.clock, "currentSlotWithGossipDisparity", "get").mockReturnValue(clockSlot);
Expand Down
9 changes: 9 additions & 0 deletions packages/beacon-node/test/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export function getConfig(fork: ForkName, forkEpoch = 0): ChainForkConfig {
BELLATRIX_FORK_EPOCH: 0,
CAPELLA_FORK_EPOCH: 0,
DENEB_FORK_EPOCH: forkEpoch,
BLOB_SCHEDULE: [{EPOCH: forkEpoch, MAX_BLOBS_PER_BLOCK: 6}],
});
case ForkName.electra:
return createChainForkConfig({
Expand All @@ -37,6 +38,10 @@ export function getConfig(fork: ForkName, forkEpoch = 0): ChainForkConfig {
CAPELLA_FORK_EPOCH: 0,
DENEB_FORK_EPOCH: 0,
ELECTRA_FORK_EPOCH: forkEpoch,
BLOB_SCHEDULE: [
{EPOCH: 0, MAX_BLOBS_PER_BLOCK: 6},
{EPOCH: forkEpoch, MAX_BLOBS_PER_BLOCK: 9},
],
});
case ForkName.fulu:
return createChainForkConfig({
Expand All @@ -46,6 +51,10 @@ export function getConfig(fork: ForkName, forkEpoch = 0): ChainForkConfig {
DENEB_FORK_EPOCH: 0,
ELECTRA_FORK_EPOCH: 0,
FULU_FORK_EPOCH: forkEpoch,
BLOB_SCHEDULE: [
{EPOCH: 0, MAX_BLOBS_PER_BLOCK: 6},
{EPOCH: 0, MAX_BLOBS_PER_BLOCK: 9},
],
});
}
}
14 changes: 11 additions & 3 deletions packages/config/src/chainConfig/configs/mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export const chainConfig: ChainConfig = {
MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096,
BLOB_SIDECAR_SUBNET_COUNT: 6,
MAX_BLOBS_PER_BLOCK: 6,
// MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK
// MAX_REQUEST_BLOCKS_DENEB * BLOB_SCHEDULE[0].MAX_BLOBS_PER_BLOCK
MAX_REQUEST_BLOB_SIDECARS: 768,

// Electra
Expand All @@ -119,7 +119,7 @@ export const chainConfig: ChainConfig = {
MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000,
BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9,
MAX_BLOBS_PER_BLOCK_ELECTRA: 9,
// MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA
// MAX_REQUEST_BLOCKS_DENEB * BLOB_SCHEDULE[1].MAX_BLOBS_PER_BLOCK
MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 1152,

// Fulu
Expand All @@ -130,5 +130,13 @@ export const chainConfig: ChainConfig = {
NODE_CUSTODY_REQUIREMENT: 1,
VALIDATOR_CUSTODY_REQUIREMENT: 8,
BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: 32000000000,
MAX_BLOBS_PER_BLOCK_FULU: 12,

// Blob Scheduling
// ---------------------------------------------------------------
BLOB_SCHEDULE: [
// Deneb
{EPOCH: 269568, MAX_BLOBS_PER_BLOCK: 6},
// Electra
{EPOCH: 364032, MAX_BLOBS_PER_BLOCK: 9},
],
};
14 changes: 11 additions & 3 deletions packages/config/src/chainConfig/configs/minimal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export const chainConfig: ChainConfig = {
MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096,
BLOB_SIDECAR_SUBNET_COUNT: 6,
MAX_BLOBS_PER_BLOCK: 6,
// MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK
// MAX_REQUEST_BLOCKS_DENEB * BLOB_SCHEDULE[0].MAX_BLOBS_PER_BLOCK
MAX_REQUEST_BLOB_SIDECARS: 768,

// Electra
Expand All @@ -115,7 +115,7 @@ export const chainConfig: ChainConfig = {
MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000,
BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9,
MAX_BLOBS_PER_BLOCK_ELECTRA: 9,
// MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA
// MAX_REQUEST_BLOCKS_DENEB * BLOB_SCHEDULE[1].MAX_BLOBS_PER_BLOCK
MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 1152,

// Fulu
Expand All @@ -126,5 +126,13 @@ export const chainConfig: ChainConfig = {
NODE_CUSTODY_REQUIREMENT: 1,
VALIDATOR_CUSTODY_REQUIREMENT: 8,
BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: 32000000000,
MAX_BLOBS_PER_BLOCK_FULU: 12,

// Blob Scheduling
// ---------------------------------------------------------------
BLOB_SCHEDULE: [
// Deneb
{EPOCH: Infinity, MAX_BLOBS_PER_BLOCK: 6},
// Electra
{EPOCH: Infinity, MAX_BLOBS_PER_BLOCK: 9},
],
};
78 changes: 71 additions & 7 deletions packages/config/src/chainConfig/json.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import {fromHex, toHex} from "@lodestar/utils";
import {ChainConfig, SpecValue, SpecValueTypeName, chainConfigTypes} from "./types.js";
import {
BlobScheduleEntry,
ChainConfig,
SpecJson,
SpecValue,
SpecValueTypeName,
chainConfigTypes,
isBlobSchedule,
} from "./types.js";

const MAX_UINT64_JSON = "18446744073709551615";

export function chainConfigToJson(config: ChainConfig): Record<string, string> {
const json: Record<string, string> = {};
export function chainConfigToJson(config: ChainConfig): SpecJson {
const json: SpecJson = {};

for (const key of Object.keys(chainConfigTypes) as (keyof ChainConfig)[]) {
const value = config[key];
Expand All @@ -29,8 +37,8 @@ export function chainConfigFromJson(json: Record<string, unknown>): ChainConfig
return config;
}

export function specValuesToJson(spec: Record<string, SpecValue>): Record<string, string> {
const json: Record<string, string> = {};
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]));
Expand All @@ -45,10 +53,14 @@ export function toSpecValueTypeName(value: SpecValue): SpecValueTypeName {
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 {
export function serializeSpecValue(
value: SpecValue,
typeName: SpecValueTypeName
): string | Record<keyof BlobScheduleEntry, string>[] {
switch (typeName) {
case "number":
if (typeof value !== "number") {
Expand Down Expand Up @@ -76,12 +88,64 @@ export function serializeSpecValue(value: SpecValue, typeName: SpecValueTypeName
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") {
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;
}

if (typeof valueStr !== "string") {
throw Error(`Invalid ${keyName} value ${valueStr as string} expected string`);
throw Error(`Invalid ${keyName} value ${valueStr} expected string`);
}

switch (typeName) {
Expand Down
10 changes: 9 additions & 1 deletion packages/config/src/chainConfig/networks/gnosis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ export const gnosisChainConfig: ChainConfig = {
MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 64000000000,
BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 2,
MAX_BLOBS_PER_BLOCK_ELECTRA: 2,
// MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA
// MAX_REQUEST_BLOCKS_DENEB * BLOB_SCHEDULE[1].MAX_BLOBS_PER_BLOCK
MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 256,

// Blob Scheduling
BLOB_SCHEDULE: [
// Deneb
{EPOCH: 889856, MAX_BLOBS_PER_BLOCK: 2},
// Electra
{EPOCH: 1337856, MAX_BLOBS_PER_BLOCK: 2},
],
};
Loading
Loading