Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
ef7a51a
Store fork digests by id
ensi321 Jun 11, 2025
86a2163
Pre-compute fork digest with blob schedule
ensi321 Jun 11, 2025
1f8017b
interface
ensi321 Jun 11, 2025
72040f4
Mix blob schedule into fork digest calculation
ensi321 Jun 11, 2025
2b8e35e
Refactor getMaxBlobsPerBlock
ensi321 Jun 11, 2025
b5dd877
Update ENR fork digest verification
ensi321 Jun 11, 2025
dd940e2
Subscribe/unsubscribe at blob schedule boundary
ensi321 Jun 11, 2025
e5949a5
lint
ensi321 Jun 11, 2025
2f64088
Add boundary
ensi321 Jun 12, 2025
59320b8
Add fork to boundary
ensi321 Jun 12, 2025
1e93a2e
Add forkDigest2ForkNameWithEpoch
ensi321 Jun 12, 2025
3c0a453
Fix some type
ensi321 Jun 12, 2025
ab7c588
lint
ensi321 Jun 12, 2025
78bb936
Add boundary to ContextBytesFactory. Use clock to determine context byte
ensi321 Jun 12, 2025
3f72c14
registerProtocolsAtBoundary
ensi321 Jun 12, 2025
521de85
fix types
ensi321 Jun 12, 2025
c299786
lint
ensi321 Jun 12, 2025
7819e12
Skip test case
ensi321 Jun 12, 2025
593c21d
Simplify code
ensi321 Jun 13, 2025
e06b6cf
Replace ForkDigestId with Epoch
ensi321 Jun 13, 2025
b2c3109
cache sorted blob schedule
ensi321 Jun 13, 2025
2833c55
Use chunk
ensi321 Jun 13, 2025
f2791eb
Remove fork and blob schedule from ContextBytesFactory
ensi321 Jun 13, 2025
bbf8f44
lint
ensi321 Jun 13, 2025
29fe7e9
lint
ensi321 Jun 13, 2025
ba35fb3
add todos
ensi321 Jun 13, 2025
b9935df
Fix comparison bug
ensi321 Jun 13, 2025
f489072
Merge branch 'unstable' into nc/bpo-fork-digest
ensi321 Jun 19, 2025
178bb49
Merge branch 'unstable' into nc/bpo-fork-digest
ensi321 Jun 20, 2025
812bbce
Init
ensi321 Jun 21, 2025
bcb4eec
Address @nflaig's comment
ensi321 Jun 22, 2025
2ece6e7
Rewrite getActiveSubscribeBoundaries
ensi321 Jun 22, 2025
32ed2c2
Add unit test for getActiveSubscribeBoundaries
ensi321 Jun 22, 2025
a458f96
fix unit test
ensi321 Jun 23, 2025
5bdb096
Update packages/beacon-node/test/unit/network/subscribeBoundary.test.ts
ensi321 Jun 23, 2025
aa5edad
Update packages/config/src/forkConfig/types.ts
ensi321 Jun 23, 2025
e602c41
Address @nflaig's comment
ensi321 Jun 23, 2025
4d5c264
Fix spec test
ensi321 Jun 23, 2025
65a64ae
fix unit test
ensi321 Jun 23, 2025
5ab8e2d
Merge branch 'nc/bpo-fork-digest-4' into nc/bpo-fork-digest
ensi321 Jun 23, 2025
03f1f65
Harmonize merge
ensi321 Jun 23, 2025
9011a1b
Clean up
ensi321 Jun 23, 2025
4d2fb01
Update reqresp
ensi321 Jun 23, 2025
594debd
Merge branch 'nc/bpo-fork-digest-4' into nc/bpo-fork-digest
ensi321 Jun 23, 2025
5417f0c
improve readability
ensi321 Jun 23, 2025
e0f9ea6
Move SubscribeBoundary type to config package
ensi321 Jun 23, 2025
51701c0
Move getSubscribeBoundary to ForkConfig
ensi321 Jun 23, 2025
3904f13
Merge branch 'nc/bpo-fork-digest-4' into nc/bpo-fork-digest
ensi321 Jun 23, 2025
4ee7686
Add boundary2ForkDigest
ensi321 Jun 23, 2025
f79162e
Add fork digest unit test
ensi321 Jun 23, 2025
6e08e20
fix check-build
ensi321 Jun 23, 2025
f02a8d2
Fix unit test
ensi321 Jun 23, 2025
6c7789d
Merge branch 'nc/bpo-fork-digest-4' into nc/bpo-fork-digest
ensi321 Jun 23, 2025
b512ef9
reduce diff
ensi321 Jun 24, 2025
04db358
Split export
ensi321 Jun 24, 2025
81f81ae
Fix check-types
ensi321 Jun 24, 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
6 changes: 4 additions & 2 deletions packages/api/src/beacon/routes/lightclient.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {ListCompositeType, ValueOf} from "@chainsafe/ssz";
import {BeaconConfig, ChainForkConfig, createBeaconConfig} from "@lodestar/config";
import {NetworkName, genesisData} from "@lodestar/config/networks";
import {ForkName, ZERO_HASH} from "@lodestar/params";
import {ForkName, SLOTS_PER_EPOCH, ZERO_HASH} from "@lodestar/params";
import {
LightClientBootstrap,
LightClientFinalityUpdate,
Expand Down Expand Up @@ -132,7 +132,9 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
const chunks: Uint8Array[] = [];
for (const [i, update] of data.entries()) {
const version = meta.versions[i];
const forkDigest = cachedBeaconConfig().forkName2ForkDigest(version);
const epoch = Math.floor(update.attestedHeader.beacon.slot / SLOTS_PER_EPOCH);
const boundary = cachedBeaconConfig().getSubscribeBoundary(epoch);
const forkDigest = cachedBeaconConfig().boundary2ForkDigest(boundary);
const serialized = getPostAltairForkTypes(version).LightClientUpdate.serialize(update);
const length = ssz.UintNum64.serialize(4 + serialized.length);
chunks.push(length, forkDigest, serialized);
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -812,7 +812,7 @@ export class BeaconChain implements IBeaconChain {
// fork_digest: The node's ForkDigest (compute_fork_digest(current_fork_version, genesis_validators_root)) where
// - current_fork_version is the fork version at the node's current epoch defined by the wall-clock time (not necessarily the epoch to which the node is sync)
// - genesis_validators_root is the static Root found in state.genesis_validators_root
forkDigest: this.config.forkName2ForkDigest(this.config.getForkName(this.clock.currentSlot)),
forkDigest: this.config.boundary2ForkDigest(this.config.getSubscribeBoundary(this.clock.currentEpoch)),
// finalized_root: state.finalized_checkpoint.root for the state corresponding to the head block (Note this defaults to Root(b'\x00' * 32) for the genesis finalized checkpoint).
finalizedRoot: finalizedCheckpoint.epoch === GENESIS_EPOCH ? ZERO_HASH : finalizedCheckpoint.root,
finalizedEpoch: finalizedCheckpoint.epoch,
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/chain/validation/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,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(computeEpochAtSlot(blockSlot));
const maxBlobsPerBlock = config.getMaxBlobsPerBlock(computeEpochAtSlot(blockSlot));
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
throw new BlockGossipError(GossipAction.REJECT, {
code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS,
Expand Down
16 changes: 9 additions & 7 deletions packages/beacon-node/src/network/core/networkCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {PeerScoreStatsDump} from "@chainsafe/libp2p-gossipsub/dist/src/score/pee
import {PublishOpts} from "@chainsafe/libp2p-gossipsub/types";
import {Connection, PrivateKey} from "@libp2p/interface";
import {routes} from "@lodestar/api";
import {BeaconConfig} from "@lodestar/config";
import {BeaconConfig, SubscribeBoundary, isSubscribeBoundaryPostFulu} from "@lodestar/config";
import type {LoggerNode} from "@lodestar/logger/node";
import {ResponseIncoming} from "@lodestar/reqresp";
import {Epoch, phase0, ssz, sszTypesFor} from "@lodestar/types";
Expand Down Expand Up @@ -32,7 +32,7 @@ import {CommitteeSubscription, IAttnetsService} from "../subnets/interface.js";
import {SyncnetsService} from "../subnets/syncnetsService.js";
import {getConnectionsMap} from "../util.js";
import {NetworkCoreMetrics, createNetworkCoreMetrics} from "./metrics.js";
import {INetworkCore, MultiaddrStr, SubscribeBoundary} from "./types.js";
import {INetworkCore, MultiaddrStr} from "./types.js";

type Mods = {
libp2p: Libp2p;
Expand Down Expand Up @@ -218,10 +218,9 @@ export class NetworkCore implements INetworkCore {
);

// Network spec decides version changes based on clock fork, not head fork
const forkCurrentSlot = config.getForkName(clock.currentSlot);

// Register only ReqResp protocols relevant to clock's fork
reqResp.registerProtocolsAtBoundary({fork: forkCurrentSlot});
const boundary = config.getSubscribeBoundary(clock.currentEpoch);
// Register only ReqResp protocols relevant to clock's epoch
reqResp.registerProtocolsAtBoundary(boundary);

// Bind discv5's ENR to local metadata
// biome-ignore lint/complexity/useLiteralKeys: `discovery` is a private attribute
Expand Down Expand Up @@ -464,7 +463,10 @@ export class NetworkCore implements INetworkCore {
if (activeBoundaries[i + 1] !== undefined) {
const prevBoundary = activeBoundaries[i];
const nextBoundary = activeBoundaries[i + 1];
const nextBoundaryEpoch = this.config.forks[nextBoundary.fork].epoch;
// If EPOCH does not exist, that means next boundary is still pre-fulu
const nextBoundaryEpoch = isSubscribeBoundaryPostFulu(nextBoundary)
? nextBoundary.EPOCH
: this.config.forks[nextBoundary.fork].epoch;

// Before subscribe boundary transition
if (epoch === nextBoundaryEpoch - FORK_EPOCH_LOOKAHEAD) {
Expand Down
3 changes: 0 additions & 3 deletions packages/beacon-node/src/network/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {PublishOpts} from "@chainsafe/libp2p-gossipsub/types";
import {routes} from "@lodestar/api";
import {SpecJson} from "@lodestar/config";
import {LoggerNodeOpts} from "@lodestar/logger/node";
import {ForkName} from "@lodestar/params";
import {ResponseIncoming} from "@lodestar/reqresp";
import {phase0} from "@lodestar/types";
import {PeerIdStr} from "../../util/peerId.js";
Expand All @@ -13,8 +12,6 @@ import {OutgoingRequestArgs} from "../reqresp/types.js";
import {CommitteeSubscription} from "../subnets/interface.js";

export type MultiaddrStr = string;
/* Boundary of network subscription. We subscribe/unsubscribe during fork transition */
export type SubscribeBoundary = {fork: ForkName};

// Interface shared by main Network class, and all backends
export interface INetworkCorePublic {
Expand Down
32 changes: 28 additions & 4 deletions packages/beacon-node/src/network/forks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {ChainForkConfig, ForkInfo} from "@lodestar/config";
import {ChainForkConfig, ForkInfo, SubscribeBoundary, isBlobSchedule} from "@lodestar/config";
import {ForkName} from "@lodestar/params";
import {Epoch} from "@lodestar/types";
import {SubscribeBoundary} from "./core/types.js";

/**
* Subscribe topics to the new fork N epochs before the fork. Remove all subscriptions N epochs after the fork
Expand Down Expand Up @@ -56,9 +55,34 @@ export function getActiveForks(config: ChainForkConfig, epoch: Epoch): ForkName[
}

export function getActiveSubscribeBoundaries(config: ChainForkConfig, epoch: Epoch): SubscribeBoundary[] {
const activeForks = getActiveForks(config, epoch);
const activeBoundaries: SubscribeBoundary[] = [];
const forkOrBlobScheduleList = config.forkOrBlobScheduleAscendingEpochOrder;

return activeForks.map((fork) => ({fork}));
for (let i = 0; i < forkOrBlobScheduleList.length; i++) {
const currForkOrBlobSchedule = forkOrBlobScheduleList[i];
const nextForkOrBlobSchedule = forkOrBlobScheduleList[i + 1];

const currEpoch = isBlobSchedule(currForkOrBlobSchedule)
? currForkOrBlobSchedule.EPOCH
: currForkOrBlobSchedule.epoch;
const nextEpoch =
nextForkOrBlobSchedule === undefined
? Infinity
: isBlobSchedule(nextForkOrBlobSchedule)
? nextForkOrBlobSchedule.EPOCH
: nextForkOrBlobSchedule.epoch;

// Edge case: If multiple fork/blob schedule start at the same epoch, only consider the latest one
if (currEpoch === nextEpoch) {
continue;
}

if (epoch >= currEpoch - FORK_EPOCH_LOOKAHEAD && epoch <= nextEpoch + FORK_EPOCH_LOOKAHEAD) {
activeBoundaries.push(config.getSubscribeBoundary(currEpoch));
}
}

return activeBoundaries;
}

/**
Expand Down
35 changes: 33 additions & 2 deletions packages/beacon-node/src/network/gossip/gossipsub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import {MetricsRegister, TopicLabel, TopicStrToLabel} from "@chainsafe/libp2p-go
import {PeerScoreParams} from "@chainsafe/libp2p-gossipsub/score";
import {SignaturePolicy, TopicStr} from "@chainsafe/libp2p-gossipsub/types";
import {BeaconConfig} from "@lodestar/config";
import {ATTESTATION_SUBNET_COUNT, ForkName, SLOTS_PER_EPOCH, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params";
import {
ATTESTATION_SUBNET_COUNT,
ForkName,
SLOTS_PER_EPOCH,
SYNC_COMMITTEE_SUBNET_COUNT,
isForkPostFulu,
} from "@lodestar/params";
import {SubnetID} from "@lodestar/types";
import {Logger, Map2d, Map2dArr} from "@lodestar/utils";

Expand Down Expand Up @@ -335,14 +341,39 @@ function attSubnetLabel(subnet: SubnetID): string {
function getMetricsTopicStrToLabel(config: BeaconConfig, opts: {disableLightClientServer: boolean}): TopicStrToLabel {
const metricsTopicStrToLabel = new Map<TopicStr, TopicLabel>();

// Hard forks
for (const {name: fork} of config.forksAscendingEpochOrder) {
const topics = getCoreTopicsAtFork(config, fork, {
subscribeAllSubnets: true,
disableLightClientServer: opts.disableLightClientServer,
});
if (!isForkPostFulu(fork)) {
for (const topic of topics) {
metricsTopicStrToLabel.set(stringifyGossipTopic(config, {...topic, boundary: {fork}}), topic.type);
}
} else {
// Post fulu fork activations require blob schedule to calculate gossip topic
const blobSchedule = config.getBlobParameters(config.forks[fork].epoch);
for (const topic of topics) {
metricsTopicStrToLabel.set(
stringifyGossipTopic(config, {...topic, boundary: {...blobSchedule, fork}}),
topic.type
);
}
}
}

// BPO forks
for (const entry of config.BLOB_SCHEDULE) {
const fork = config.getForkInfoAtEpoch(entry.EPOCH).name;
const topics = getCoreTopicsAtFork(config, fork, {
subscribeAllSubnets: true,
disableLightClientServer: opts.disableLightClientServer,
});
for (const topic of topics) {
metricsTopicStrToLabel.set(stringifyGossipTopic(config, {...topic, boundary: {fork}}), topic.type);
metricsTopicStrToLabel.set(stringifyGossipTopic(config, {...topic, boundary: {...entry, fork}}), topic.type);
}
}

return metricsTopicStrToLabel;
}
3 changes: 1 addition & 2 deletions packages/beacon-node/src/network/gossip/interface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {PeerIdStr} from "@chainsafe/libp2p-gossipsub/types";
import {Message, TopicValidatorResult} from "@libp2p/interface";
import {BeaconConfig} from "@lodestar/config";
import {BeaconConfig, SubscribeBoundary} from "@lodestar/config";
import {
AttesterSlashing,
LightClientFinalityUpdate,
Expand All @@ -21,7 +21,6 @@ import {AttestationError, AttestationErrorType} from "../../chain/errors/attesta
import {GossipActionError} from "../../chain/errors/gossipValidation.js";
import {IBeaconChain} from "../../chain/index.js";
import {JobItemQueue} from "../../util/queue/index.js";
import {SubscribeBoundary} from "../core/types.js";

export enum GossipType {
beacon_block = "beacon_block",
Expand Down
20 changes: 12 additions & 8 deletions packages/beacon-node/src/network/gossip/topic.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ChainConfig, ForkDigestContext} from "@lodestar/config";
import {BeaconConfig, ChainConfig} from "@lodestar/config";
import {
ATTESTATION_SUBNET_COUNT,
ForkName,
Expand All @@ -20,7 +20,8 @@ export interface IGossipTopicCache {
export class GossipTopicCache implements IGossipTopicCache {
private topicsByTopicStr = new Map<string, Required<GossipTopic>>();

constructor(private readonly forkDigestContext: ForkDigestContext) {}
// TODO: Switch forkDigestContext back to forkDigestContext type
constructor(private readonly forkDigestContext: BeaconConfig) {}

/** Returns cached GossipTopic, otherwise attempts to parse it from the str */
getTopic(topicStr: string): GossipTopic {
Expand Down Expand Up @@ -48,8 +49,8 @@ export class GossipTopicCache implements IGossipTopicCache {
/**
* Stringify a GossipTopic into a spec-ed formated topic string
*/
export function stringifyGossipTopic(forkDigestContext: ForkDigestContext, topic: GossipTopic): string {
const forkDigestHexNoPrefix = forkDigestContext.forkName2ForkDigestHex(topic.boundary.fork);
export function stringifyGossipTopic(forkDigestContext: BeaconConfig, topic: GossipTopic): string {
const forkDigestHexNoPrefix = forkDigestContext.boundary2ForkDigestHex(topic.boundary);
const topicType = stringifyGossipTopicType(topic);
const encoding = topic.encoding ?? DEFAULT_ENCODING;
return `/eth2/${forkDigestHexNoPrefix}/${topicType}/${encoding}`;
Expand Down Expand Up @@ -163,7 +164,7 @@ const gossipTopicRegex = /^\/eth2\/(\w+)\/(\w+)\/(\w+)/;
* /eth2/$FORK_DIGEST/$GOSSIP_TYPE/$ENCODING
* ```
*/
export function parseGossipTopic(forkDigestContext: ForkDigestContext, topicStr: string): Required<GossipTopic> {
export function parseGossipTopic(forkDigestContext: BeaconConfig, topicStr: string): Required<GossipTopic> {
try {
const matches = topicStr.match(gossipTopicRegex);
if (matches === null) {
Expand All @@ -173,6 +174,9 @@ export function parseGossipTopic(forkDigestContext: ForkDigestContext, topicStr:
const [, forkDigestHexNoPrefix, gossipTypeStr, encodingStr] = matches;

const fork = forkDigestContext.forkDigest2ForkName(forkDigestHexNoPrefix);
// TODO: This epoch is just an approximate. Will update in the next PR
const epoch = forkDigestContext.forks[fork].epoch;
const boundary = forkDigestContext.getSubscribeBoundary(epoch);
const encoding = parseEncodingStr(encodingStr);

// Inline-d the parseGossipTopicType() function since spreading the resulting object x4 the time to parse a topicStr
Expand All @@ -186,23 +190,23 @@ export function parseGossipTopic(forkDigestContext: ForkDigestContext, topicStr:
case GossipType.light_client_finality_update:
case GossipType.light_client_optimistic_update:
case GossipType.bls_to_execution_change:
return {type: gossipTypeStr, boundary: {fork}, encoding};
return {type: gossipTypeStr, boundary, encoding};
}

for (const gossipType of [GossipType.beacon_attestation as const, GossipType.sync_committee as const]) {
if (gossipTypeStr.startsWith(gossipType)) {
const subnetStr = gossipTypeStr.slice(gossipType.length + 1); // +1 for '_' concatenating the topic name and the subnet
const subnet = parseInt(subnetStr, 10);
if (Number.isNaN(subnet)) throw Error(`Subnet ${subnetStr} is not a number`);
return {type: gossipType, subnet, boundary: {fork}, encoding};
return {type: gossipType, subnet, boundary, encoding};
}
}

if (gossipTypeStr.startsWith(GossipType.blob_sidecar)) {
const subnetStr = gossipTypeStr.slice(GossipType.blob_sidecar.length + 1); // +1 for '_' concatenating the topic name and the subnet
const subnet = parseInt(subnetStr, 10);
if (Number.isNaN(subnet)) throw Error(`subnet ${subnetStr} is not a number`);
return {type: GossipType.blob_sidecar, subnet, boundary: {fork}, encoding};
return {type: GossipType.blob_sidecar, subnet, boundary, encoding};
}

throw Error(`Unknown gossip type ${gossipTypeStr}`);
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/network/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export function getENRForkID(config: BeaconConfig, clockEpoch: Epoch): phase0.EN

return {
// Current fork digest
forkDigest: config.forkName2ForkDigest(currentFork.name),
forkDigest: config.boundary2ForkDigest(config.getSubscribeBoundary(clockEpoch)),
// next planned fork versin
nextForkVersion: nextFork ? nextFork.version : currentFork.version,
// next fork epoch
Expand Down
Loading
Loading