Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add cli flag to disable light client server #6913

Merged
merged 12 commits into from
Jul 18, 2024
20 changes: 15 additions & 5 deletions packages/beacon-node/src/api/impl/lightclient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {routes} from "@lodestar/api";
import {ApplicationMethods} from "@lodestar/api/server";
import {MAX_REQUEST_LIGHT_CLIENT_UPDATES, MAX_REQUEST_LIGHT_CLIENT_COMMITTEE_HASHES} from "@lodestar/params";
import {ApiModules} from "../types.js";

import {assertLightClientServer} from "../../../node/utils/lightclient.js";
// TODO: Import from lightclient/server package

export function getLightclientApi({
Expand All @@ -12,16 +12,21 @@ export function getLightclientApi({
}: Pick<ApiModules, "chain" | "config">): ApplicationMethods<routes.lightclient.Endpoints> {
return {
async getLightClientUpdatesByRange({startPeriod, count}) {
const lightClientServer = chain.lightClientServer;
assertLightClientServer(lightClientServer);

const maxAllowedCount = Math.min(MAX_REQUEST_LIGHT_CLIENT_UPDATES, count);
const periods = Array.from({length: maxAllowedCount}, (_ignored, i) => i + startPeriod);
const updates = await Promise.all(periods.map((period) => chain.lightClientServer.getUpdate(period)));
const updates = await Promise.all(periods.map((period) => lightClientServer.getUpdate(period)));
return {
data: updates,
meta: {versions: updates.map((update) => config.getForkName(update.attestedHeader.beacon.slot))},
};
},

async getLightClientOptimisticUpdate() {
assertLightClientServer(chain.lightClientServer);

const update = chain.lightClientServer.getOptimisticUpdate();
if (update === null) {
throw Error("No optimistic update available");
Expand All @@ -30,6 +35,8 @@ export function getLightclientApi({
},

async getLightClientFinalityUpdate() {
assertLightClientServer(chain.lightClientServer);

const update = chain.lightClientServer.getFinalityUpdate();
if (update === null) {
throw Error("No finality update available");
Expand All @@ -38,16 +45,19 @@ export function getLightclientApi({
},

async getLightClientBootstrap({blockRoot}) {
assertLightClientServer(chain.lightClientServer);

const bootstrapProof = await chain.lightClientServer.getBootstrap(fromHex(blockRoot));
return {data: bootstrapProof, meta: {version: config.getForkName(bootstrapProof.header.beacon.slot)}};
},

async getLightClientCommitteeRoot({startPeriod, count}) {
const lightClientServer = chain.lightClientServer;
assertLightClientServer(lightClientServer);

const maxAllowedCount = Math.min(MAX_REQUEST_LIGHT_CLIENT_COMMITTEE_HASHES, count);
const periods = Array.from({length: maxAllowedCount}, (_ignored, i) => i + startPeriod);
const committeeHashes = await Promise.all(
periods.map((period) => chain.lightClientServer.getCommitteeRoot(period))
);
const committeeHashes = await Promise.all(periods.map((period) => lightClientServer.getCommitteeRoot(period)));
return {data: committeeHashes};
},
};
Expand Down
6 changes: 4 additions & 2 deletions packages/beacon-node/src/chain/archiver/archiveBlocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export async function archiveBlocks(
config: ChainForkConfig,
db: IBeaconDb,
forkChoice: IForkChoice,
lightclientServer: LightClientServer,
lightclientServer: LightClientServer | undefined,
logger: Logger,
finalizedCheckpoint: CheckpointHex,
currentEpoch: Epoch,
Expand Down Expand Up @@ -111,7 +111,9 @@ export async function archiveBlocks(
nonCheckpointBlockRoots.push(block.root);
}

await lightclientServer.pruneNonCheckpointData(nonCheckpointBlockRoots);
if (lightclientServer) {
await lightclientServer.pruneNonCheckpointData(nonCheckpointBlockRoots);
}

logger.verbose("Archiving of finalized blocks complete", {
totalArchived: finalizedCanonicalBlocks.length,
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/chain/blocks/importBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ export async function importBlock(
// we want to import block asap so do this in the next event loop
callInNextEventLoop(() => {
try {
this.lightClientServer.onImportBlockHead(
this.lightClientServer?.onImportBlockHead(
block.message as BeaconBlock<ForkLightClient>,
postState as CachedBeaconStateAltair,
parentBlockSlot
Expand Down
7 changes: 4 additions & 3 deletions packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export class BeaconChain implements IBeaconChain {
readonly clock: IClock;
readonly emitter: ChainEventEmitter;
readonly regen: QueuedStateRegenerator;
readonly lightClientServer: LightClientServer;
readonly lightClientServer?: LightClientServer;
readonly reprocessController: ReprocessController;

// Ops pool
Expand Down Expand Up @@ -305,7 +305,9 @@ export class BeaconChain implements IBeaconChain {
signal,
});

const lightClientServer = new LightClientServer(opts, {config, db, metrics, emitter, logger});
if (!opts.disableLightClientServer) {
this.lightClientServer = new LightClientServer(opts, {config, db, metrics, emitter, logger});
}

this.reprocessController = new ReprocessController(this.metrics);

Expand All @@ -316,7 +318,6 @@ export class BeaconChain implements IBeaconChain {
this.regen = regen;
this.bls = bls;
this.emitter = emitter;
this.lightClientServer = lightClientServer;

this.archiver = new Archiver(db, this, logger, signal, opts);
// always run PrepareNextSlotScheduler except for fork_choice spec tests
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/chain/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export interface IBeaconChain {
readonly clock: IClock;
readonly emitter: ChainEventEmitter;
readonly regen: IStateRegenerator;
readonly lightClientServer: LightClientServer;
readonly lightClientServer?: LightClientServer;
readonly reprocessController: ReprocessController;
readonly pubkey2index: PubkeyIndexMap;
readonly index2pubkey: Index2PubkeyCache;
Expand Down
1 change: 1 addition & 0 deletions packages/beacon-node/src/chain/lightClient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {

export type LightClientServerOpts = {
disableLightClientServerOnImportBlockHead?: boolean;
disableLightClientServer?: boolean;
};

type DependentRootHex = RootHex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {LightClientFinalityUpdate} from "@lodestar/types";
import {IBeaconChain} from "../interface.js";
import {LightClientError, LightClientErrorCode} from "../errors/lightClientError.js";
import {GossipAction} from "../errors/index.js";
import {assertLightClientServer} from "../../node/utils/lightclient.js";
import {updateReceivedTooEarly} from "./lightClientOptimisticUpdate.js";

// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/p2p-interface.md#light_client_finality_update
Expand All @@ -11,6 +12,8 @@ export function validateLightClientFinalityUpdate(
chain: IBeaconChain,
gossipedFinalityUpdate: LightClientFinalityUpdate
): void {
assertLightClientServer(chain.lightClientServer);

// [IGNORE] No other finality_update with a lower or equal finalized_header.slot was already forwarded on the network
const gossipedFinalitySlot = gossipedFinalityUpdate.finalizedHeader.beacon.slot;
const localFinalityUpdate = chain.lightClientServer.getFinalityUpdate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import {IBeaconChain} from "../interface.js";
import {LightClientError, LightClientErrorCode} from "../errors/lightClientError.js";
import {GossipAction} from "../errors/index.js";
import {MAXIMUM_GOSSIP_CLOCK_DISPARITY} from "../../constants/index.js";
import {assertLightClientServer} from "../../node/utils/lightclient.js";

// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/p2p-interface.md#light_client_optimistic_update
export function validateLightClientOptimisticUpdate(
config: ChainForkConfig,
chain: IBeaconChain,
gossipedOptimisticUpdate: LightClientOptimisticUpdate
): void {
assertLightClientServer(chain.lightClientServer);

// [IGNORE] No other optimistic_update with a lower or equal attested_header.slot was already forwarded on the network
const gossipedAttestedSlot = gossipedOptimisticUpdate.attestedHeader.beacon.slot;
const localOptimisticUpdate = chain.lightClientServer.getOptimisticUpdate();
Expand Down
14 changes: 10 additions & 4 deletions packages/beacon-node/src/network/core/networkCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -499,19 +499,25 @@ export class NetworkCore implements INetworkCore {
private subscribeCoreTopicsAtFork(fork: ForkName): void {
if (this.subscribedForks.has(fork)) return;
this.subscribedForks.add(fork);
const {subscribeAllSubnets} = this.opts;
const {subscribeAllSubnets, disableLightClientServer} = this.opts;

for (const topic of getCoreTopicsAtFork(fork, {subscribeAllSubnets})) {
for (const topic of getCoreTopicsAtFork(fork, {
subscribeAllSubnets,
disableLightClientServer,
})) {
this.gossip.subscribeTopic({...topic, fork});
}
}

private unsubscribeCoreTopicsAtFork(fork: ForkName): void {
if (!this.subscribedForks.has(fork)) return;
this.subscribedForks.delete(fork);
const {subscribeAllSubnets} = this.opts;
const {subscribeAllSubnets, disableLightClientServer} = this.opts;

for (const topic of getCoreTopicsAtFork(fork, {subscribeAllSubnets})) {
for (const topic of getCoreTopicsAtFork(fork, {
subscribeAllSubnets,
disableLightClientServer,
})) {
this.gossip.unsubscribeTopic({...topic, fork});
}
}
Expand Down
12 changes: 9 additions & 3 deletions packages/beacon-node/src/network/gossip/gossipsub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export type Eth2GossipsubOpts = {
gossipsubAwaitHandler?: boolean;
disableFloodPublish?: boolean;
skipParamsLog?: boolean;
disableLightClientServer?: boolean;
};

/**
Expand Down Expand Up @@ -124,7 +125,9 @@ export class Eth2Gossipsub extends GossipSub {
isFinite(config.BELLATRIX_FORK_EPOCH) ? GOSSIP_MAX_SIZE_BELLATRIX : GOSSIP_MAX_SIZE
),
metricsRegister: metricsRegister as MetricsRegister | null,
metricsTopicStrToLabel: metricsRegister ? getMetricsTopicStrToLabel(config) : undefined,
metricsTopicStrToLabel: metricsRegister
? getMetricsTopicStrToLabel(config, {disableLightClientServer: opts.disableLightClientServer ?? false})
: undefined,
asyncValidation: true,

maxOutboundBufferSize: MAX_OUTBOUND_BUFFER_SIZE,
Expand Down Expand Up @@ -321,11 +324,14 @@ function attSubnetLabel(subnet: number): string {
else return `0${subnet}`;
}

function getMetricsTopicStrToLabel(config: BeaconConfig): TopicStrToLabel {
function getMetricsTopicStrToLabel(config: BeaconConfig, opts: {disableLightClientServer: boolean}): TopicStrToLabel {
const metricsTopicStrToLabel = new Map<TopicStr, TopicLabel>();

for (const {name: fork} of config.forksAscendingEpochOrder) {
const topics = getCoreTopicsAtFork(fork, {subscribeAllSubnets: true});
const topics = getCoreTopicsAtFork(fork, {
subscribeAllSubnets: true,
disableLightClientServer: opts.disableLightClientServer,
});
for (const topic of topics) {
metricsTopicStrToLabel.set(stringifyGossipTopic(config, {...topic, fork}), topic.type);
}
Expand Down
8 changes: 5 additions & 3 deletions packages/beacon-node/src/network/gossip/topic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ export function parseGossipTopic(forkDigestContext: ForkDigestContext, topicStr:
*/
export function getCoreTopicsAtFork(
fork: ForkName,
opts: {subscribeAllSubnets?: boolean}
opts: {subscribeAllSubnets?: boolean; disableLightClientServer?: boolean}
): GossipTopicTypeMap[keyof GossipTopicTypeMap][] {
// Common topics for all forks
const topics: GossipTopicTypeMap[keyof GossipTopicTypeMap][] = [
Expand All @@ -227,8 +227,10 @@ export function getCoreTopicsAtFork(
// Any fork after altair included
if (ForkSeq[fork] >= ForkSeq.altair) {
topics.push({type: GossipType.sync_committee_contribution_and_proof});
topics.push({type: GossipType.light_client_optimistic_update});
topics.push({type: GossipType.light_client_finality_update});
if (!opts.disableLightClientServer) {
topics.push({type: GossipType.light_client_optimistic_update});
topics.push({type: GossipType.light_client_finality_update});
}
}

if (opts.subscribeAllSubnets) {
Expand Down
7 changes: 5 additions & 2 deletions packages/beacon-node/src/network/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import {SubnetsServiceOpts} from "./subnets/interface.js";
export interface NetworkOptions
extends PeerManagerOpts,
// remove all Functions
Omit<ReqRespBeaconNodeOpts, "getPeerLogMetadata" | "onRateLimit">,
Omit<ReqRespBeaconNodeOpts, "getPeerLogMetadata" | "onRateLimit" | "disableLightClientServer">,
NetworkProcessorOpts,
PeerRpcScoreOpts,
SubnetsServiceOpts,
Eth2GossipsubOpts {
Omit<Eth2GossipsubOpts, "disableLightClientServer"> {
localMultiaddrs: string[];
bootMultiaddrs?: string[];
subscribeAllSubnets?: boolean;
Expand All @@ -22,6 +22,7 @@ export interface NetworkOptions
private?: boolean;
useWorker?: boolean;
maxYoungGenerationSizeMb?: number;
disableLightClientServer?: boolean;
}

export const defaultNetworkOptions: NetworkOptions = {
Expand All @@ -41,4 +42,6 @@ export const defaultNetworkOptions: NetworkOptions = {
slotsToSubscribeBeforeAggregatorDuty: 2,
// this should only be set to true if useWorker is true
beaconAttestationBatchValidation: true,
// This will enable the light client server by default
disableLightClientServer: false,
};
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export interface ReqRespBeaconNodeModules {
getHandler: GetReqRespHandlerFn;
}

export type ReqRespBeaconNodeOpts = ReqRespOpts;
export type ReqRespBeaconNodeOpts = ReqRespOpts & {disableLightClientServer?: boolean};

/**
* Implementation of Ethereum Consensus p2p Req/Resp domain.
Expand All @@ -72,6 +72,7 @@ export class ReqRespBeaconNode extends ReqResp {

private readonly config: BeaconConfig;
protected readonly logger: Logger;
protected readonly disableLightClientServer: boolean;

constructor(modules: ReqRespBeaconNodeModules, options: ReqRespBeaconNodeOpts = {}) {
const {events, peersData, peerRpcScores, metadata, metrics, logger} = modules;
Expand All @@ -95,6 +96,7 @@ export class ReqRespBeaconNode extends ReqResp {
}
);

this.disableLightClientServer = options.disableLightClientServer ?? false;
this.peerRpcScores = peerRpcScores;
this.peersData = peersData;
this.config = modules.config;
Expand Down Expand Up @@ -233,7 +235,7 @@ export class ReqRespBeaconNode extends ReqResp {
);
}

if (ForkSeq[fork] >= ForkSeq.altair) {
if (ForkSeq[fork] >= ForkSeq.altair && !this.disableLightClientServer) {
// Should be okay to enable before altair, but for consistency only enable afterwards
protocolsAtFork.push(
[protocols.LightClientBootstrap(this.config), this.getHandler(ReqRespMethod.LightClientBootstrap)],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import {
import {Root} from "@lodestar/types";
import {IBeaconChain} from "../../../chain/index.js";
import {ReqRespMethod, responseSszTypeByMethod} from "../types.js";
import {assertLightClientServer} from "../../../node/utils/lightclient.js";

export async function* onLightClientBootstrap(requestBody: Root, chain: IBeaconChain): AsyncIterable<ResponseOutgoing> {
assertLightClientServer(chain.lightClientServer);

try {
const bootstrap = await chain.lightClientServer.getBootstrap(requestBody);
const fork = chain.config.getForkName(bootstrap.header.beacon.slot);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {ResponseOutgoing, RespStatus, ResponseError} from "@lodestar/reqresp";
import {IBeaconChain} from "../../../chain/index.js";
import {ReqRespMethod, responseSszTypeByMethod} from "../types.js";
import {assertLightClientServer} from "../../../node/utils/lightclient.js";

export async function* onLightClientFinalityUpdate(chain: IBeaconChain): AsyncIterable<ResponseOutgoing> {
assertLightClientServer(chain.lightClientServer);

const update = chain.lightClientServer.getFinalityUpdate();
if (update === null) {
throw new ResponseError(RespStatus.RESOURCE_UNAVAILABLE, "No latest finality update available");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {ResponseOutgoing, ResponseError, RespStatus} from "@lodestar/reqresp";
import {IBeaconChain} from "../../../chain/index.js";
import {ReqRespMethod, responseSszTypeByMethod} from "../types.js";
import {assertLightClientServer} from "../../../node/utils/lightclient.js";

export async function* onLightClientOptimisticUpdate(chain: IBeaconChain): AsyncIterable<ResponseOutgoing> {
assertLightClientServer(chain.lightClientServer);

const update = chain.lightClientServer.getOptimisticUpdate();
if (update === null) {
throw new ResponseError(RespStatus.RESOURCE_UNAVAILABLE, "No latest optimistic update available");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ import {
} from "@lodestar/reqresp";
import {IBeaconChain} from "../../../chain/index.js";
import {ReqRespMethod, responseSszTypeByMethod} from "../types.js";
import {assertLightClientServer} from "../../../node/utils/lightclient.js";

export async function* onLightClientUpdatesByRange(
requestBody: altair.LightClientUpdatesByRange,
chain: IBeaconChain
): AsyncIterable<ResponseOutgoing> {
assertLightClientServer(chain.lightClientServer);

const count = Math.min(MAX_REQUEST_LIGHT_CLIENT_UPDATES, requestBody.count);
for (let period = requestBody.startPeriod; period < requestBody.startPeriod + count; period++) {
try {
Expand Down
7 changes: 7 additions & 0 deletions packages/beacon-node/src/node/utils/lightclient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {LightClientServer} from "../../chain/lightClient/index.js";

export function assertLightClientServer(server: LightClientServer | undefined): asserts server is LightClientServer {
if (!server) {
throw Error("Light client server is disabled");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {describe, it, expect, beforeEach, afterEach, vi} from "vitest";
import {createChainForkConfig, defaultChainConfig} from "@lodestar/config";
import {altair, ssz} from "@lodestar/types";
import {computeTimeAtSlot} from "@lodestar/state-transition";
import {RequiredSelective} from "@lodestar/utils";
import {validateLightClientFinalityUpdate} from "../../../../src/chain/validation/lightClientFinalityUpdate.js";
import {LightClientErrorCode} from "../../../../src/chain/errors/lightClientError.js";
import {IBeaconChain} from "../../../../src/chain/index.js";
Expand Down Expand Up @@ -30,7 +31,7 @@ describe("Light Client Finality Update validation", function () {
}
});

function mockChain(): IBeaconChain {
function mockChain(): RequiredSelective<IBeaconChain, "lightClientServer"> {
const chain = getMockedBeaconChain();
vi.spyOn(chain, "genesisTime", "get").mockReturnValue(0);
return chain;
Expand Down
Loading
Loading