diff --git a/packages/api/src/beacon/client/events.ts b/packages/api/src/beacon/client/events.ts index 996899f98a1f..208cd44d7514 100644 --- a/packages/api/src/beacon/client/events.ts +++ b/packages/api/src/beacon/client/events.ts @@ -7,8 +7,8 @@ import {HttpStatusCode} from "../../utils/client/httpStatusCode.js"; /** * REST HTTP client for events routes */ -export function getClient(_config: IChainForkConfig, baseUrl: string): Api { - const eventSerdes = getEventSerdes(); +export function getClient(config: IChainForkConfig, baseUrl: string): Api { + const eventSerdes = getEventSerdes(config); return { eventstream: async (topics, signal, onEvent) => { diff --git a/packages/api/src/beacon/routes/events.ts b/packages/api/src/beacon/routes/events.ts index 1b139cc39cf3..f553a3202551 100644 --- a/packages/api/src/beacon/routes/events.ts +++ b/packages/api/src/beacon/routes/events.ts @@ -1,6 +1,7 @@ -import {Epoch, phase0, capella, Slot, ssz, StringType, RootHex, altair, UintNum64} from "@lodestar/types"; -import {ContainerType, Type, VectorCompositeType} from "@chainsafe/ssz"; -import {FINALIZED_ROOT_DEPTH} from "@lodestar/params"; +import {Epoch, phase0, capella, Slot, ssz, StringType, RootHex, altair, UintNum64, allForks} from "@lodestar/types"; +import {ContainerType} from "@chainsafe/ssz"; +import {IChainForkConfig} from "@lodestar/config"; + import {RouteDef, TypeJson} from "../../utils/index.js"; import {HttpStatusCode} from "../../utils/client/httpStatusCode.js"; import {ApiClientResponse} from "../../interfaces.js"; @@ -86,9 +87,9 @@ export type EventData = { executionOptimistic: boolean; }; [EventType.contributionAndProof]: altair.SignedContributionAndProof; - [EventType.lightClientOptimisticUpdate]: altair.LightClientOptimisticUpdate; - [EventType.lightClientFinalityUpdate]: altair.LightClientFinalityUpdate; - [EventType.lightClientUpdate]: altair.LightClientUpdate; + [EventType.lightClientOptimisticUpdate]: allForks.LightClientOptimisticUpdate; + [EventType.lightClientFinalityUpdate]: allForks.LightClientFinalityUpdate; + [EventType.lightClientUpdate]: allForks.LightClientUpdate; }; export type BeaconEvent = {[K in EventType]: {type: K; message: EventData[K]}}[EventType]; @@ -123,8 +124,12 @@ export type ReqTypes = { // It doesn't make sense to define a getReqSerializers() here given the exotic argument of eventstream() // The request is very simple: (topics) => {query: {topics}}, and the test will ensure compatibility server - client -export function getTypeByEvent(): {[K in EventType]: Type} { +export function getTypeByEvent(config: IChainForkConfig): {[K in EventType]: TypeJson} { const stringType = new StringType(); + const getLightClientTypeFromHeader = (data: allForks.LightClientHeader): allForks.AllForksLightClientSSZTypes => { + return config.getLightClientForkTypes(data.beacon.slot); + }; + return { [EventType.head]: new ContainerType( { @@ -178,31 +183,45 @@ export function getTypeByEvent(): {[K in EventType]: Type} { [EventType.contributionAndProof]: ssz.altair.SignedContributionAndProof, - [EventType.lightClientOptimisticUpdate]: new ContainerType( - { - syncAggregate: ssz.altair.SyncAggregate, - attestedHeader: ssz.altair.LightClientHeader, - signatureSlot: ssz.Slot, - }, - {jsonCase: "eth2"} - ), - [EventType.lightClientFinalityUpdate]: new ContainerType( - { - attestedHeader: ssz.altair.LightClientHeader, - finalizedHeader: ssz.altair.LightClientHeader, - finalityBranch: new VectorCompositeType(ssz.Bytes32, FINALIZED_ROOT_DEPTH), - syncAggregate: ssz.altair.SyncAggregate, - signatureSlot: ssz.Slot, - }, - {jsonCase: "eth2"} - ), - [EventType.lightClientUpdate]: ssz.altair.LightClientUpdate, + [EventType.lightClientOptimisticUpdate]: { + toJson: (data) => + getLightClientTypeFromHeader(((data as unknown) as allForks.LightClientOptimisticUpdate).attestedHeader)[ + "LightClientOptimisticUpdate" + ].toJson(data), + fromJson: (data) => + getLightClientTypeFromHeader( + // eslint-disable-next-line @typescript-eslint/naming-convention + ((data as unknown) as {attested_header: allForks.LightClientHeader}).attested_header + )["LightClientOptimisticUpdate"].fromJson(data), + }, + [EventType.lightClientFinalityUpdate]: { + toJson: (data) => + getLightClientTypeFromHeader(((data as unknown) as allForks.LightClientFinalityUpdate).attestedHeader)[ + "LightClientFinalityUpdate" + ].toJson(data), + fromJson: (data) => + getLightClientTypeFromHeader( + // eslint-disable-next-line @typescript-eslint/naming-convention + ((data as unknown) as {attested_header: allForks.LightClientHeader}).attested_header + )["LightClientFinalityUpdate"].fromJson(data), + }, + [EventType.lightClientUpdate]: { + toJson: (data) => + getLightClientTypeFromHeader(((data as unknown) as allForks.LightClientUpdate).attestedHeader)[ + "LightClientUpdate" + ].toJson(data), + fromJson: (data) => + getLightClientTypeFromHeader( + // eslint-disable-next-line @typescript-eslint/naming-convention + ((data as unknown) as {attested_header: allForks.LightClientHeader}).attested_header + )["LightClientUpdate"].fromJson(data), + }, }; } // eslint-disable-next-line @typescript-eslint/explicit-function-return-type -export function getEventSerdes() { - const typeByEvent = getTypeByEvent(); +export function getEventSerdes(config: IChainForkConfig) { + const typeByEvent = getTypeByEvent(config); return { toJson: (event: BeaconEvent): unknown => { diff --git a/packages/api/src/beacon/routes/lightclient.ts b/packages/api/src/beacon/routes/lightclient.ts index df32efd08252..e3defa3dec1a 100644 --- a/packages/api/src/beacon/routes/lightclient.ts +++ b/packages/api/src/beacon/routes/lightclient.ts @@ -1,6 +1,5 @@ -import {altair, ssz, StringType, SyncPeriod} from "@lodestar/types"; -import {ForkName} from "@lodestar/params"; -import {ContainerType} from "@chainsafe/ssz"; +import {ssz, SyncPeriod, allForks} from "@lodestar/types"; +import {ForkName, isForkLightClient} from "@lodestar/params"; import { ArrayOf, ReturnTypes, @@ -17,13 +16,6 @@ import {ApiClientResponse} from "../../interfaces.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes -export type LightClientBootstrap = { - header: altair.LightClientHeader; - currentSyncCommittee: altair.SyncCommittee; - /** Single branch proof from state root to currentSyncCommittee */ - currentSyncCommitteeBranch: Uint8Array[]; -}; - export type Api = { /** * Returns an array of best updates given a `startPeriod` and `count` number of sync committee period to return. @@ -39,7 +31,7 @@ export type Api = { ApiClientResponse<{ [HttpStatusCode.OK]: { version: ForkName; - data: altair.LightClientUpdate; + data: allForks.LightClientUpdate; }[]; }> >; @@ -51,7 +43,7 @@ export type Api = { ApiClientResponse<{ [HttpStatusCode.OK]: { version: ForkName; - data: altair.LightClientOptimisticUpdate; + data: allForks.LightClientOptimisticUpdate; }; }> >; @@ -59,7 +51,7 @@ export type Api = { ApiClientResponse<{ [HttpStatusCode.OK]: { version: ForkName; - data: altair.LightClientFinalityUpdate; + data: allForks.LightClientFinalityUpdate; }; }> >; @@ -74,7 +66,7 @@ export type Api = { ApiClientResponse<{ [HttpStatusCode.OK]: { version: ForkName; - data: altair.LightClientBootstrap; + data: allForks.LightClientBootstrap; }; }> >; @@ -138,16 +130,31 @@ export function getReqSerializers(): ReqSerializers { } export function getReturnTypes(): ReturnTypes { + // Form a TypeJson convertor for getUpdates + const VersionedUpdate = WithVersion((fork: ForkName) => + isForkLightClient(fork) ? ssz.allForksLightClient[fork].LightClientUpdate : ssz.altair.LightClientUpdate + ); + const getUpdates = { + toJson: (updates: {version: ForkName; data: allForks.LightClientUpdate}[]) => + updates.map((data) => VersionedUpdate.toJson(data)), + fromJson: (updates: unknown[]) => updates.map((data) => VersionedUpdate.fromJson(data)), + }; + return { - getUpdates: ArrayOf( - new ContainerType({ - version: new StringType(), - data: ssz.altair.LightClientUpdate, - }) + getUpdates, + getOptimisticUpdate: WithVersion((fork: ForkName) => + isForkLightClient(fork) + ? ssz.allForksLightClient[fork].LightClientOptimisticUpdate + : ssz.altair.LightClientOptimisticUpdate + ), + getFinalityUpdate: WithVersion((fork: ForkName) => + isForkLightClient(fork) + ? ssz.allForksLightClient[fork].LightClientFinalityUpdate + : ssz.altair.LightClientFinalityUpdate + ), + getBootstrap: WithVersion((fork: ForkName) => + isForkLightClient(fork) ? ssz.allForksLightClient[fork].LightClientBootstrap : ssz.altair.LightClientBootstrap ), - getOptimisticUpdate: WithVersion(() => ssz.altair.LightClientOptimisticUpdate), - getFinalityUpdate: WithVersion(() => ssz.altair.LightClientFinalityUpdate), - getBootstrap: WithVersion(() => ssz.altair.LightClientBootstrap), getCommitteeRoot: ContainerData(ArrayOf(ssz.Root)), }; } diff --git a/packages/api/src/beacon/server/events.ts b/packages/api/src/beacon/server/events.ts index 4ee4b36b4133..1235ca980bdf 100644 --- a/packages/api/src/beacon/server/events.ts +++ b/packages/api/src/beacon/server/events.ts @@ -4,7 +4,7 @@ import {ServerRoutes} from "../../utils/server/index.js"; import {ServerApi} from "../../interfaces.js"; export function getRoutes(config: IChainForkConfig, api: ServerApi): ServerRoutes { - const eventSerdes = getEventSerdes(); + const eventSerdes = getEventSerdes(config); return { // Non-JSON route. Server Sent Events (SSE) diff --git a/packages/api/test/unit/beacon/oapiSpec.test.ts b/packages/api/test/unit/beacon/oapiSpec.test.ts index c95de6a33196..381e852a5add 100644 --- a/packages/api/test/unit/beacon/oapiSpec.test.ts +++ b/packages/api/test/unit/beacon/oapiSpec.test.ts @@ -110,7 +110,7 @@ describe("eventstream event data", () => { } }); - const eventSerdes = routes.events.getEventSerdes(); + const eventSerdes = routes.events.getEventSerdes(config); const knownTopics = new Set(Object.values(routes.events.eventTypes)); for (const [topic, {value}] of Object.entries(eventstreamExamples ?? {})) { diff --git a/packages/beacon-node/src/chain/blocks/importBlock.ts b/packages/beacon-node/src/chain/blocks/importBlock.ts index 305245863675..b24667041cd3 100644 --- a/packages/beacon-node/src/chain/blocks/importBlock.ts +++ b/packages/beacon-node/src/chain/blocks/importBlock.ts @@ -1,4 +1,4 @@ -import {altair, capella, ssz} from "@lodestar/types"; +import {capella, ssz, allForks} from "@lodestar/types"; import {MAX_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params"; import {toHexString} from "@chainsafe/ssz"; import { @@ -275,7 +275,7 @@ export async function importBlock( if (blockEpoch >= this.config.ALTAIR_FORK_EPOCH) { try { this.lightClientServer.onImportBlockHead( - block.message as altair.BeaconBlock, + block.message as allForks.AllForksLightClient["BeaconBlock"], postState as CachedBeaconStateAltair, parentBlockSlot ); diff --git a/packages/beacon-node/src/chain/lightClient/index.ts b/packages/beacon-node/src/chain/lightClient/index.ts index 87c539f9e04c..67ac63f8ca3e 100644 --- a/packages/beacon-node/src/chain/lightClient/index.ts +++ b/packages/beacon-node/src/chain/lightClient/index.ts @@ -1,16 +1,23 @@ -import {altair, phase0, Root, RootHex, Slot, ssz, SyncPeriod} from "@lodestar/types"; +import {altair, phase0, Root, RootHex, Slot, ssz, SyncPeriod, allForks} from "@lodestar/types"; import {IChainForkConfig} from "@lodestar/config"; import { CachedBeaconStateAltair, computeStartSlotAtEpoch, computeSyncPeriodAtEpoch, computeSyncPeriodAtSlot, + executionPayloadToPayloadHeader, } from "@lodestar/state-transition"; -import {isBetterUpdate, toLightClientUpdateSummary, LightClientUpdateSummary} from "@lodestar/light-client/spec"; +import { + isBetterUpdate, + toLightClientUpdateSummary, + LightClientUpdateSummary, + upgradeLightClientHeader, +} from "@lodestar/light-client/spec"; import {ILogger, MapDef, pruneSetToMax} from "@lodestar/utils"; import {routes} from "@lodestar/api"; import {BitArray, CompositeViewDU, toHexString} from "@chainsafe/ssz"; -import {MIN_SYNC_COMMITTEE_PARTICIPANTS, SYNC_COMMITTEE_SIZE} from "@lodestar/params"; +import {MIN_SYNC_COMMITTEE_PARTICIPANTS, SYNC_COMMITTEE_SIZE, ForkName, ForkSeq, ForkExecution} from "@lodestar/params"; + import {IBeaconDb} from "../../db/index.js"; import {IMetrics} from "../../metrics/index.js"; import {ChainEventEmitter} from "../emitter.js"; @@ -22,6 +29,7 @@ import { getSyncCommitteesWitness, getFinalizedRootProof, getCurrentSyncCommitteeBranch, + getBlockBodyExecutionHeaderProof, } from "./proofs.js"; export type LightClientServerOpts = { @@ -32,7 +40,7 @@ type DependantRootHex = RootHex; type BlockRooHex = RootHex; export type SyncAttestedData = { - attestedHeader: phase0.BeaconBlockHeader; + attestedHeader: allForks.LightClientHeader; /** Precomputed root to prevent re-hashing */ blockRoot: Uint8Array; } & ( @@ -170,11 +178,11 @@ export class LightClientServer { * Keep in memory since this data is very transient, not useful after a few slots */ private readonly prevHeadData = new Map(); - private checkpointHeaders = new Map(); - private latestHeadUpdate: altair.LightClientOptimisticUpdate | null = null; + private checkpointHeaders = new Map(); + private latestHeadUpdate: allForks.LightClientOptimisticUpdate | null = null; private readonly zero: Pick; - private finalized: altair.LightClientFinalityUpdate | null = null; + private finalized: allForks.LightClientFinalityUpdate | null = null; constructor(private readonly opts: LightClientServerOpts, modules: LightClientServerModules) { const {config, db, metrics, emitter, logger} = modules; @@ -212,7 +220,11 @@ export class LightClientServer { * - Persist state witness * - Use block's syncAggregate */ - onImportBlockHead(block: altair.BeaconBlock, postState: CachedBeaconStateAltair, parentBlockSlot: Slot): void { + onImportBlockHead( + block: allForks.AllForksLightClient["BeaconBlock"], + postState: CachedBeaconStateAltair, + parentBlockSlot: Slot + ): void { // TEMP: To disable this functionality for fork_choice spec tests. // Since the tests have deep-reorgs attested data is not available often printing lots of error logs. // While this function is only called for head blocks, best to disable. @@ -244,7 +256,7 @@ export class LightClientServer { /** * API ROUTE to get `currentSyncCommittee` and `nextSyncCommittee` from a trusted state root */ - async getBootstrap(blockRoot: Uint8Array): Promise { + async getBootstrap(blockRoot: Uint8Array): Promise { const syncCommitteeWitness = await this.db.syncCommitteeWitness.get(blockRoot); if (!syncCommitteeWitness) { throw new LightClientServerError( @@ -276,7 +288,7 @@ export class LightClientServer { } return { - header: {beacon: header}, + header, currentSyncCommittee, currentSyncCommitteeBranch: getCurrentSyncCommitteeBranch(syncCommitteeWitness), }; @@ -289,7 +301,7 @@ export class LightClientServer { * - Has the most bits * - Signed header at the oldest slot */ - async getUpdate(period: number): Promise { + async getUpdate(period: number): Promise { // Signature data const update = await this.db.bestLightClientUpdate.get(period); if (!update) { @@ -320,11 +332,11 @@ export class LightClientServer { * API ROUTE to poll LightclientHeaderUpdate. * Clients should use the SSE type `light_client_optimistic_update` if available */ - getOptimisticUpdate(): altair.LightClientOptimisticUpdate | null { + getOptimisticUpdate(): allForks.LightClientOptimisticUpdate | null { return this.latestHeadUpdate; } - getFinalityUpdate(): altair.LightClientFinalityUpdate | null { + getFinalityUpdate(): allForks.LightClientFinalityUpdate | null { return this.finalized; } @@ -340,21 +352,14 @@ export class LightClientServer { } private async persistPostBlockImportData( - block: altair.BeaconBlock, + block: allForks.AllForksLightClient["BeaconBlock"], postState: CachedBeaconStateAltair, parentBlockSlot: Slot ): Promise { const blockSlot = block.slot; + const header = blockToLightClientHeader(this.config.getForkName(blockSlot), block); - const header: phase0.BeaconBlockHeader = { - slot: blockSlot, - proposerIndex: block.proposerIndex, - parentRoot: block.parentRoot, - stateRoot: block.stateRoot, - bodyRoot: this.config.getForkTypes(blockSlot).BeaconBlockBody.hashTreeRoot(block.body), - }; - - const blockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(header); + const blockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(header.beacon); const blockRootHex = toHexString(blockRoot); const syncCommitteeWitness = getSyncCommitteesWitness(postState); @@ -459,15 +464,16 @@ export class LightClientServer { return; } - const attestedPeriod = computeSyncPeriodAtSlot(attestedData.attestedHeader.slot); + const {attestedHeader, isFinalized} = attestedData; + const attestedPeriod = computeSyncPeriodAtSlot(attestedHeader.beacon.slot); if (syncPeriod !== attestedPeriod) { this.logger.debug("attested data period different than signature period", {syncPeriod, attestedPeriod}); this.metrics?.lightclientServer.onSyncAggregate.inc({event: "ignore_attested_period_diff"}); return; } - const headerUpdate: altair.LightClientOptimisticUpdate = { - attestedHeader: {beacon: attestedData.attestedHeader}, + const headerUpdate: allForks.LightClientOptimisticUpdate = { + attestedHeader, syncAggregate, signatureSlot, }; @@ -489,24 +495,29 @@ export class LightClientServer { // Persist latest best update for getLatestHeadUpdate() // TODO: Once SyncAggregate are constructed from P2P too, count bits to decide "best" - if (!this.latestHeadUpdate || attestedData.attestedHeader.slot > this.latestHeadUpdate.attestedHeader.beacon.slot) { + if (!this.latestHeadUpdate || attestedHeader.beacon.slot > this.latestHeadUpdate.attestedHeader.beacon.slot) { this.latestHeadUpdate = headerUpdate; this.metrics?.lightclientServer.onSyncAggregate.inc({event: "update_latest_head_update"}); } - if (attestedData.isFinalized) { + if (isFinalized) { const finalizedCheckpointRoot = attestedData.finalizedCheckpoint.root as Uint8Array; - const finalizedHeader = await this.getFinalizedHeader(finalizedCheckpointRoot); + let finalizedHeader = await this.getFinalizedHeader(finalizedCheckpointRoot); if ( finalizedHeader && (!this.finalized || - finalizedHeader.slot > this.finalized.finalizedHeader.beacon.slot || + finalizedHeader.beacon.slot > this.finalized.finalizedHeader.beacon.slot || syncAggregateParticipation > sumBits(this.finalized.syncAggregate.syncCommitteeBits)) ) { + // Fork of LightClientFinalityUpdate is based off on attested header's fork + const attestedFork = this.config.getForkName(attestedHeader.beacon.slot); + if (this.config.getForkName(finalizedHeader.beacon.slot) !== attestedFork) { + finalizedHeader = upgradeLightClientHeader(this.config, attestedFork, finalizedHeader); + } this.finalized = { - attestedHeader: {beacon: attestedData.attestedHeader}, - finalizedHeader: {beacon: finalizedHeader}, + attestedHeader, + finalizedHeader, syncAggregate, finalityBranch: attestedData.finalityBranch, signatureSlot, @@ -524,7 +535,7 @@ export class LightClientServer { } catch (e) { this.logger.error( "Error updating best LightClientUpdate", - {syncPeriod, slot: attestedData.attestedHeader.slot, blockRoot: toHexString(attestedData.blockRoot)}, + {syncPeriod, slot: attestedHeader.beacon.slot, blockRoot: toHexString(attestedData.blockRoot)}, e as Error ); } @@ -541,13 +552,14 @@ export class LightClientServer { attestedData: SyncAttestedData ): Promise { const prevBestUpdate = await this.db.bestLightClientUpdate.get(syncPeriod); + const {attestedHeader} = attestedData; if (prevBestUpdate) { const prevBestUpdateSummary = toLightClientUpdateSummary(prevBestUpdate); const nextBestUpdate: LightClientUpdateSummary = { activeParticipants: sumBits(syncAggregate.syncCommitteeBits), - attestedHeaderSlot: attestedData.attestedHeader.slot, + attestedHeaderSlot: attestedHeader.beacon.slot, signatureSlot, // The actual finalizedHeader is fetched below. To prevent a DB read we approximate the actual slot. // If update is not finalized finalizedHeaderSlot does not matter (see is_better_update), so setting @@ -575,36 +587,42 @@ export class LightClientServer { throw Error("nextSyncCommittee not available"); } const nextSyncCommitteeBranch = getNextSyncCommitteeBranch(syncCommitteeWitness); - const finalizedHeader = attestedData.isFinalized + const finalizedHeaderAttested = attestedData.isFinalized ? await this.getFinalizedHeader(attestedData.finalizedCheckpoint.root as Uint8Array) : null; - let newUpdate: altair.LightClientUpdate; - let isFinalized; - if (attestedData.isFinalized && finalizedHeader && computeSyncPeriodAtSlot(finalizedHeader.slot) == syncPeriod) { + let isFinalized, finalityBranch, finalizedHeader; + + if ( + attestedData.isFinalized && + finalizedHeaderAttested && + computeSyncPeriodAtSlot(finalizedHeaderAttested.beacon.slot) == syncPeriod + ) { isFinalized = true; - newUpdate = { - attestedHeader: {beacon: attestedData.attestedHeader}, - nextSyncCommittee: nextSyncCommittee, - nextSyncCommitteeBranch, - finalizedHeader: {beacon: finalizedHeader}, - finalityBranch: attestedData.finalityBranch, - syncAggregate, - signatureSlot, - }; + finalityBranch = attestedData.finalityBranch; + finalizedHeader = finalizedHeaderAttested; } else { isFinalized = false; - newUpdate = { - attestedHeader: {beacon: attestedData.attestedHeader}, - nextSyncCommittee: nextSyncCommittee, - nextSyncCommitteeBranch, - finalizedHeader: this.zero.finalizedHeader, - finalityBranch: this.zero.finalityBranch, - syncAggregate, - signatureSlot, - }; + finalityBranch = this.zero.finalityBranch; + finalizedHeader = this.zero.finalizedHeader; + } + + // Fork of LightClientUpdate is based off on attested header's fork + const attestedFork = this.config.getForkName(attestedHeader.beacon.slot); + if (this.config.getForkName(finalizedHeader.beacon.slot) !== attestedFork) { + finalizedHeader = upgradeLightClientHeader(this.config, attestedFork, finalizedHeader); } + const newUpdate = { + attestedHeader, + nextSyncCommittee: nextSyncCommittee, + nextSyncCommitteeBranch, + finalizedHeader, + finalityBranch, + syncAggregate, + signatureSlot, + } as allForks.LightClientUpdate; + // attestedData and the block of syncAggregate may not be in same sync period // should not use attested data slot as sync period // see https://github.com/ChainSafe/lodestar/issues/3933 @@ -639,7 +657,7 @@ export class LightClientServer { /** * Get finalized header from db. Keeps a small in-memory cache to speed up most of the lookups */ - private async getFinalizedHeader(finalizedBlockRoot: Uint8Array): Promise { + private async getFinalizedHeader(finalizedBlockRoot: Uint8Array): Promise { const finalizedBlockRootHex = toHexString(finalizedBlockRoot); const cachedFinalizedHeader = this.checkpointHeaders.get(finalizedBlockRootHex); if (cachedFinalizedHeader) { @@ -666,3 +684,30 @@ export class LightClientServer { export function sumBits(bits: BitArray): number { return bits.getTrueBitIndexes().length; } + +export function blockToLightClientHeader( + fork: ForkName, + block: allForks.AllForksLightClient["BeaconBlock"] +): allForks.LightClientHeader { + const blockSlot = block.slot; + const beacon: phase0.BeaconBlockHeader = { + slot: blockSlot, + proposerIndex: block.proposerIndex, + parentRoot: block.parentRoot, + stateRoot: block.stateRoot, + bodyRoot: (ssz[fork].BeaconBlockBody as allForks.AllForksLightClientSSZTypes["BeaconBlockBody"]).hashTreeRoot( + block.body + ), + }; + if (ForkSeq[fork] >= ForkSeq.capella) { + const blockBody = block.body as allForks.AllForksExecution["BeaconBlockBody"]; + const execution = executionPayloadToPayloadHeader(ForkSeq[fork], blockBody.executionPayload); + return { + beacon, + execution, + executionBranch: getBlockBodyExecutionHeaderProof(fork as ForkExecution, blockBody), + } as allForks.LightClientHeader; + } else { + return {beacon}; + } +} diff --git a/packages/beacon-node/src/chain/lightClient/proofs.ts b/packages/beacon-node/src/chain/lightClient/proofs.ts index 633dcba9c308..6f68e0eae7c4 100644 --- a/packages/beacon-node/src/chain/lightClient/proofs.ts +++ b/packages/beacon-node/src/chain/lightClient/proofs.ts @@ -1,6 +1,8 @@ import {BeaconStateAllForks} from "@lodestar/state-transition"; -import {FINALIZED_ROOT_GINDEX} from "@lodestar/params"; +import {FINALIZED_ROOT_GINDEX, BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX, ForkExecution} from "@lodestar/params"; +import {allForks, ssz} from "@lodestar/types"; import {Tree} from "@chainsafe/persistent-merkle-tree"; + import {SyncCommitteeWitness} from "./types.js"; export function getSyncCommitteesWitness(state: BeaconStateAllForks): SyncCommitteeWitness { @@ -42,3 +44,11 @@ export function getFinalizedRootProof(state: BeaconStateAllForks): Uint8Array[] state.commit(); return new Tree(state.node).getSingleProof(BigInt(FINALIZED_ROOT_GINDEX)); } + +export function getBlockBodyExecutionHeaderProof( + fork: ForkExecution, + body: allForks.AllForksExecution["BeaconBlockBody"] +): Uint8Array[] { + const bodyView = (ssz[fork].BeaconBlockBody as allForks.AllForksExecutionSSZTypes["BeaconBlockBody"]).toView(body); + return new Tree(bodyView.node).getSingleProof(BigInt(BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX)); +} diff --git a/packages/beacon-node/src/chain/validation/lightClientFinalityUpdate.ts b/packages/beacon-node/src/chain/validation/lightClientFinalityUpdate.ts index edeef0a07149..afa274ee46d9 100644 --- a/packages/beacon-node/src/chain/validation/lightClientFinalityUpdate.ts +++ b/packages/beacon-node/src/chain/validation/lightClientFinalityUpdate.ts @@ -1,5 +1,5 @@ import {IChainForkConfig} from "@lodestar/config"; -import {altair, ssz} from "@lodestar/types"; +import {allForks} from "@lodestar/types"; import {IBeaconChain} from "../interface.js"; import {LightClientError, LightClientErrorCode} from "../errors/lightClientError.js"; import {GossipAction} from "../errors/index.js"; @@ -9,7 +9,7 @@ import {updateReceivedTooEarly} from "./lightClientOptimisticUpdate.js"; export function validateLightClientFinalityUpdate( config: IChainForkConfig, chain: IBeaconChain, - gossipedFinalityUpdate: altair.LightClientFinalityUpdate + gossipedFinalityUpdate: allForks.LightClientFinalityUpdate ): void { // [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; @@ -31,10 +31,10 @@ export function validateLightClientFinalityUpdate( } // [IGNORE] The received finality_update matches the locally computed one exactly - if ( - localFinalityUpdate === null || - !ssz.altair.LightClientFinalityUpdate.equals(gossipedFinalityUpdate, localFinalityUpdate) - ) { + const sszType = config.getLightClientForkTypes(gossipedFinalityUpdate.attestedHeader.beacon.slot)[ + "LightClientFinalityUpdate" + ]; + if (localFinalityUpdate === null || !sszType.equals(gossipedFinalityUpdate, localFinalityUpdate)) { throw new LightClientError(GossipAction.IGNORE, { code: LightClientErrorCode.FINALITY_UPDATE_NOT_MATCHING_LOCAL, }); diff --git a/packages/beacon-node/src/chain/validation/lightClientOptimisticUpdate.ts b/packages/beacon-node/src/chain/validation/lightClientOptimisticUpdate.ts index 0d07d80fee47..123a1630f589 100644 --- a/packages/beacon-node/src/chain/validation/lightClientOptimisticUpdate.ts +++ b/packages/beacon-node/src/chain/validation/lightClientOptimisticUpdate.ts @@ -1,4 +1,4 @@ -import {altair, ssz} from "@lodestar/types"; +import {allForks} from "@lodestar/types"; import {IChainForkConfig} from "@lodestar/config"; import {computeTimeAtSlot} from "@lodestar/state-transition"; import {IBeaconChain} from "../interface.js"; @@ -10,7 +10,7 @@ import {MAXIMUM_GOSSIP_CLOCK_DISPARITY} from "../../constants/index.js"; export function validateLightClientOptimisticUpdate( config: IChainForkConfig, chain: IBeaconChain, - gossipedOptimisticUpdate: altair.LightClientOptimisticUpdate + gossipedOptimisticUpdate: allForks.LightClientOptimisticUpdate ): void { // [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; @@ -32,10 +32,10 @@ export function validateLightClientOptimisticUpdate( } // [IGNORE] The received optimistic_update matches the locally computed one exactly - if ( - localOptimisticUpdate === null || - !ssz.altair.LightClientOptimisticUpdate.equals(gossipedOptimisticUpdate, localOptimisticUpdate) - ) { + const sszType = config.getLightClientForkTypes(gossipedOptimisticUpdate.attestedHeader.beacon.slot)[ + "LightClientOptimisticUpdate" + ]; + if (localOptimisticUpdate === null || !sszType.equals(gossipedOptimisticUpdate, localOptimisticUpdate)) { throw new LightClientError(GossipAction.IGNORE, { code: LightClientErrorCode.OPTIMISTIC_UPDATE_NOT_MATCHING_LOCAL, }); @@ -56,7 +56,7 @@ export function validateLightClientOptimisticUpdate( export function updateReceivedTooEarly( config: IChainForkConfig, genesisTime: number, - update: Pick + update: Pick ): boolean { const signatureSlot13TimestampMs = computeTimeAtSlot(config, update.signatureSlot + 1 / 3, genesisTime) * 1000; const earliestAllowedTimestampMs = signatureSlot13TimestampMs - MAXIMUM_GOSSIP_CLOCK_DISPARITY; diff --git a/packages/beacon-node/src/db/repositories/lightclientBestUpdate.ts b/packages/beacon-node/src/db/repositories/lightclientBestUpdate.ts index b90e77dc1b44..2595cfa7f89e 100644 --- a/packages/beacon-node/src/db/repositories/lightclientBestUpdate.ts +++ b/packages/beacon-node/src/db/repositories/lightclientBestUpdate.ts @@ -1,14 +1,39 @@ import {IChainForkConfig} from "@lodestar/config"; import {Bucket, IDatabaseController, Repository} from "@lodestar/db"; -import {altair, ssz, SyncPeriod} from "@lodestar/types"; +import {ssz, SyncPeriod, allForks} from "@lodestar/types"; + +const SLOT_BYTE_COUNT = 8; /** * Best PartialLightClientUpdate in each SyncPeriod * * Used to prepare light client updates */ -export class BestLightClientUpdateRepository extends Repository { +export class BestLightClientUpdateRepository extends Repository { constructor(config: IChainForkConfig, db: IDatabaseController) { + // Pick some type but won't be used super(config, db, Bucket.lightClient_bestLightClientUpdate, ssz.altair.LightClientUpdate); } + + // Overrides for multi-fork + encodeValue(value: allForks.LightClientUpdate): Uint8Array { + // Not easy to have a fixed slot position for all forks in attested header, so lets + // prefix by attestedHeader's slot bytes + const slotBytes = ssz.Slot.serialize(value.attestedHeader.beacon.slot) as Uint8Array; + const valueBytes = this.config + .getLightClientForkTypes(value.attestedHeader.beacon.slot) + .LightClientUpdate.serialize(value) as Uint8Array; + + const prefixedData = new Uint8Array(SLOT_BYTE_COUNT + valueBytes.length); + prefixedData.set(slotBytes, 0); + prefixedData.set(valueBytes, SLOT_BYTE_COUNT); + + return prefixedData; + } + + decodeValue(data: Uint8Array): allForks.LightClientUpdate { + // First slot is written + const slot = ssz.Slot.deserialize(data.subarray(0, SLOT_BYTE_COUNT)); + return this.config.getLightClientForkTypes(slot).LightClientUpdate.deserialize(data.subarray(SLOT_BYTE_COUNT)); + } } diff --git a/packages/beacon-node/src/db/repositories/lightclientCheckpointHeader.ts b/packages/beacon-node/src/db/repositories/lightclientCheckpointHeader.ts index 907e964e67c3..e80ec9d1fe1a 100644 --- a/packages/beacon-node/src/db/repositories/lightclientCheckpointHeader.ts +++ b/packages/beacon-node/src/db/repositories/lightclientCheckpointHeader.ts @@ -1,6 +1,8 @@ import {IChainForkConfig} from "@lodestar/config"; import {Bucket, IDatabaseController, Repository} from "@lodestar/db"; -import {phase0, ssz} from "@lodestar/types"; +import {ssz, allForks} from "@lodestar/types"; + +import {getLightClientHeaderTypeFromBytes} from "../../util/multifork.js"; /** * Block headers by block root. Until finality includes all headers seen by this node. After finality, @@ -8,8 +10,18 @@ import {phase0, ssz} from "@lodestar/types"; * * Used to prepare light client updates */ -export class CheckpointHeaderRepository extends Repository { +export class CheckpointHeaderRepository extends Repository { constructor(config: IChainForkConfig, db: IDatabaseController) { - super(config, db, Bucket.lightClient_checkpointHeader, ssz.phase0.BeaconBlockHeader); + // Pick some type but won't be used + super(config, db, Bucket.lightClient_checkpointHeader, ssz.altair.LightClientHeader); + } + + // Overrides for multi-fork + encodeValue(value: allForks.LightClientHeader): Uint8Array { + return this.config.getLightClientForkTypes(value.beacon.slot).LightClientHeader.serialize(value) as Uint8Array; + } + + decodeValue(data: Uint8Array): allForks.LightClientHeader { + return getLightClientHeaderTypeFromBytes(this.config, data).deserialize(data); } } diff --git a/packages/beacon-node/src/network/gossip/gossipsub.ts b/packages/beacon-node/src/network/gossip/gossipsub.ts index 8b44d4499823..0cde08a57f81 100644 --- a/packages/beacon-node/src/network/gossip/gossipsub.ts +++ b/packages/beacon-node/src/network/gossip/gossipsub.ts @@ -265,7 +265,7 @@ export class Eth2Gossipsub extends GossipSub { ); } - async publishLightClientFinalityUpdate(lightClientFinalityUpdate: altair.LightClientFinalityUpdate): Promise { + async publishLightClientFinalityUpdate(lightClientFinalityUpdate: allForks.LightClientFinalityUpdate): Promise { const fork = this.config.getForkName(lightClientFinalityUpdate.signatureSlot); await this.publishObject( {type: GossipType.light_client_finality_update, fork}, @@ -274,7 +274,7 @@ export class Eth2Gossipsub extends GossipSub { } async publishLightClientOptimisticUpdate( - lightClientOptimisitcUpdate: altair.LightClientOptimisticUpdate + lightClientOptimisitcUpdate: allForks.LightClientOptimisticUpdate ): Promise { const fork = this.config.getForkName(lightClientOptimisitcUpdate.signatureSlot); await this.publishObject( diff --git a/packages/beacon-node/src/network/gossip/interface.ts b/packages/beacon-node/src/network/gossip/interface.ts index 61ae8e93073c..25972ac06146 100644 --- a/packages/beacon-node/src/network/gossip/interface.ts +++ b/packages/beacon-node/src/network/gossip/interface.ts @@ -75,8 +75,8 @@ export type GossipTypeMap = { [GossipType.attester_slashing]: phase0.AttesterSlashing; [GossipType.sync_committee_contribution_and_proof]: altair.SignedContributionAndProof; [GossipType.sync_committee]: altair.SyncCommitteeMessage; - [GossipType.light_client_finality_update]: altair.LightClientFinalityUpdate; - [GossipType.light_client_optimistic_update]: altair.LightClientOptimisticUpdate; + [GossipType.light_client_finality_update]: allForks.LightClientFinalityUpdate; + [GossipType.light_client_optimistic_update]: allForks.LightClientOptimisticUpdate; [GossipType.bls_to_execution_change]: capella.SignedBLSToExecutionChange; }; @@ -95,10 +95,10 @@ export type GossipFnByType = { ) => Promise | void; [GossipType.sync_committee]: (syncCommittee: altair.SyncCommitteeMessage) => Promise | void; [GossipType.light_client_finality_update]: ( - lightClientFinalityUpdate: altair.LightClientFinalityUpdate + lightClientFinalityUpdate: allForks.LightClientFinalityUpdate ) => Promise | void; [GossipType.light_client_optimistic_update]: ( - lightClientOptimisticUpdate: altair.LightClientOptimisticUpdate + lightClientOptimisticUpdate: allForks.LightClientOptimisticUpdate ) => Promise | void; [GossipType.bls_to_execution_change]: ( blsToExecutionChange: capella.SignedBLSToExecutionChange diff --git a/packages/beacon-node/src/network/gossip/topic.ts b/packages/beacon-node/src/network/gossip/topic.ts index 650aa2b49d80..9c3ab74ac784 100644 --- a/packages/beacon-node/src/network/gossip/topic.ts +++ b/packages/beacon-node/src/network/gossip/topic.ts @@ -1,6 +1,12 @@ import {ssz} from "@lodestar/types"; import {IForkDigestContext} from "@lodestar/config"; -import {ATTESTATION_SUBNET_COUNT, ForkName, ForkSeq, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params"; +import { + ATTESTATION_SUBNET_COUNT, + ForkName, + ForkSeq, + SYNC_COMMITTEE_SUBNET_COUNT, + isForkLightClient, +} from "@lodestar/params"; import {GossipEncoding, GossipTopic, GossipType, GossipTopicTypeMap} from "./interface.js"; import {DEFAULT_ENCODING} from "./constants.js"; @@ -92,9 +98,13 @@ export function getGossipSSZType(topic: GossipTopic) { case GossipType.sync_committee: return ssz.altair.SyncCommitteeMessage; case GossipType.light_client_optimistic_update: - return ssz.altair.LightClientOptimisticUpdate; + return isForkLightClient(topic.fork) + ? ssz.allForksLightClient[topic.fork].LightClientOptimisticUpdate + : ssz.altair.LightClientOptimisticUpdate; case GossipType.light_client_finality_update: - return ssz.altair.LightClientFinalityUpdate; + return isForkLightClient(topic.fork) + ? ssz.allForksLightClient[topic.fork].LightClientFinalityUpdate + : ssz.altair.LightClientFinalityUpdate; case GossipType.bls_to_execution_change: return ssz.capella.SignedBLSToExecutionChange; } diff --git a/packages/beacon-node/src/network/network.ts b/packages/beacon-node/src/network/network.ts index 60e115ec1907..daea6a9b1af5 100644 --- a/packages/beacon-node/src/network/network.ts +++ b/packages/beacon-node/src/network/network.ts @@ -6,7 +6,7 @@ import {ILogger, sleep} from "@lodestar/utils"; import {ATTESTATION_SUBNET_COUNT, ForkName, ForkSeq, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params"; import {SignableENR} from "@chainsafe/discv5"; import {computeEpochAtSlot, computeTimeAtSlot} from "@lodestar/state-transition"; -import {altair, deneb, Epoch, phase0} from "@lodestar/types"; +import {deneb, Epoch, phase0, allForks} from "@lodestar/types"; import {routes} from "@lodestar/api"; import {IMetrics} from "../metrics/index.js"; import {ChainEvent, IBeaconChain, IBeaconClock} from "../chain/index.js"; @@ -557,7 +557,7 @@ export class Network implements INetwork { } } - private onLightClientFinalityUpdate = async (finalityUpdate: altair.LightClientFinalityUpdate): Promise => { + private onLightClientFinalityUpdate = async (finalityUpdate: allForks.LightClientFinalityUpdate): Promise => { if (this.hasAttachedSyncCommitteeMember()) { try { // messages SHOULD be broadcast after one-third of slot has transpired @@ -575,7 +575,7 @@ export class Network implements INetwork { }; private onLightClientOptimisticUpdate = async ( - optimisticUpdate: altair.LightClientOptimisticUpdate + optimisticUpdate: allForks.LightClientOptimisticUpdate ): Promise => { if (this.hasAttachedSyncCommitteeMember()) { try { diff --git a/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts b/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts index 1929ed22e253..8334cddf2add 100644 --- a/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts +++ b/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts @@ -204,9 +204,9 @@ export class ReqRespBeaconNode extends ReqResp implements IReqRespBeaconNode { ); } - async lightClientBootstrap(peerId: PeerId, request: Root): Promise { + async lightClientBootstrap(peerId: PeerId, request: Root): Promise { return collectExactOne( - this.sendRequest( + this.sendRequest( peerId, ReqRespMethod.LightClientBootstrap, [Version.V1], @@ -215,9 +215,9 @@ export class ReqRespBeaconNode extends ReqResp implements IReqRespBeaconNode { ); } - async lightClientOptimisticUpdate(peerId: PeerId): Promise { + async lightClientOptimisticUpdate(peerId: PeerId): Promise { return collectExactOne( - this.sendRequest( + this.sendRequest( peerId, ReqRespMethod.LightClientOptimisticUpdate, [Version.V1], @@ -226,9 +226,9 @@ export class ReqRespBeaconNode extends ReqResp implements IReqRespBeaconNode { ); } - async lightClientFinalityUpdate(peerId: PeerId): Promise { + async lightClientFinalityUpdate(peerId: PeerId): Promise { return collectExactOne( - this.sendRequest( + this.sendRequest( peerId, ReqRespMethod.LightClientFinalityUpdate, [Version.V1], @@ -240,9 +240,9 @@ export class ReqRespBeaconNode extends ReqResp implements IReqRespBeaconNode { async lightClientUpdatesByRange( peerId: PeerId, request: altair.LightClientUpdatesByRange - ): Promise { + ): Promise { return collectMaxResponse( - this.sendRequest( + this.sendRequest( peerId, ReqRespMethod.LightClientUpdatesByRange, [Version.V1], diff --git a/packages/beacon-node/src/network/reqresp/handlers/lightClientBootstrap.ts b/packages/beacon-node/src/network/reqresp/handlers/lightClientBootstrap.ts index 5ad51debea92..e49024b5a873 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/lightClientBootstrap.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/lightClientBootstrap.ts @@ -6,13 +6,13 @@ import { LightClientServerError, LightClientServerErrorCode, } from "@lodestar/reqresp"; -import {altair, Root} from "@lodestar/types"; +import {Root, allForks} from "@lodestar/types"; import {IBeaconChain} from "../../../chain/index.js"; export async function* onLightClientBootstrap( requestBody: Root, chain: IBeaconChain -): AsyncIterable> { +): AsyncIterable> { try { yield { type: EncodedPayloadType.ssz, diff --git a/packages/beacon-node/src/network/reqresp/handlers/lightClientFinalityUpdate.ts b/packages/beacon-node/src/network/reqresp/handlers/lightClientFinalityUpdate.ts index 77bfab2cd16c..085f00c56fd6 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/lightClientFinalityUpdate.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/lightClientFinalityUpdate.ts @@ -1,10 +1,10 @@ import {EncodedPayload, ResponseError, RespStatus, EncodedPayloadType} from "@lodestar/reqresp"; -import {altair} from "@lodestar/types"; +import {allForks} from "@lodestar/types"; import {IBeaconChain} from "../../../chain/index.js"; export async function* onLightClientFinalityUpdate( chain: IBeaconChain -): AsyncIterable> { +): AsyncIterable> { const finalityUpdate = chain.lightClientServer.getFinalityUpdate(); if (finalityUpdate === null) { throw new ResponseError(RespStatus.RESOURCE_UNAVAILABLE, "No latest finality update available"); diff --git a/packages/beacon-node/src/network/reqresp/handlers/lightClientOptimisticUpdate.ts b/packages/beacon-node/src/network/reqresp/handlers/lightClientOptimisticUpdate.ts index 8bfae7d0f61f..b23be775810e 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/lightClientOptimisticUpdate.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/lightClientOptimisticUpdate.ts @@ -1,10 +1,10 @@ import {EncodedPayload, EncodedPayloadType, ResponseError, RespStatus} from "@lodestar/reqresp"; -import {altair} from "@lodestar/types"; +import {allForks} from "@lodestar/types"; import {IBeaconChain} from "../../../chain/index.js"; export async function* onLightClientOptimisticUpdate( chain: IBeaconChain -): AsyncIterable> { +): AsyncIterable> { const optimisticUpdate = chain.lightClientServer.getOptimisticUpdate(); if (optimisticUpdate === null) { throw new ResponseError(RespStatus.RESOURCE_UNAVAILABLE, "No latest optimistic update available"); diff --git a/packages/beacon-node/src/network/reqresp/handlers/lightClientUpdatesByRange.ts b/packages/beacon-node/src/network/reqresp/handlers/lightClientUpdatesByRange.ts index d5ab43984a19..3e7150399874 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/lightClientUpdatesByRange.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/lightClientUpdatesByRange.ts @@ -1,4 +1,4 @@ -import {altair} from "@lodestar/types"; +import {altair, allForks} from "@lodestar/types"; import {MAX_REQUEST_LIGHT_CLIENT_UPDATES} from "@lodestar/params"; import { EncodedPayload, @@ -13,7 +13,7 @@ import {IBeaconChain} from "../../../chain/index.js"; export async function* onLightClientUpdatesByRange( requestBody: altair.LightClientUpdatesByRange, chain: IBeaconChain -): AsyncIterable> { +): AsyncIterable> { const count = Math.min(MAX_REQUEST_LIGHT_CLIENT_UPDATES, requestBody.count); for (let period = requestBody.startPeriod; period < requestBody.startPeriod + count; period++) { try { diff --git a/packages/beacon-node/src/network/reqresp/interface.ts b/packages/beacon-node/src/network/reqresp/interface.ts index 2cb9529b5674..efcbc2dac94a 100644 --- a/packages/beacon-node/src/network/reqresp/interface.ts +++ b/packages/beacon-node/src/network/reqresp/interface.ts @@ -18,13 +18,13 @@ export interface IReqRespBeaconNode { peerId: PeerId, request: deneb.BeaconBlockAndBlobsSidecarByRootRequest ): Promise; - lightClientBootstrap(peerId: PeerId, request: Uint8Array): Promise; - lightClientOptimisticUpdate(peerId: PeerId): Promise; - lightClientFinalityUpdate(peerId: PeerId): Promise; + lightClientBootstrap(peerId: PeerId, request: Uint8Array): Promise; + lightClientOptimisticUpdate(peerId: PeerId): Promise; + lightClientFinalityUpdate(peerId: PeerId): Promise; lightClientUpdatesByRange( peerId: PeerId, request: altair.LightClientUpdatesByRange - ): Promise; + ): Promise; } /** diff --git a/packages/beacon-node/src/util/multifork.ts b/packages/beacon-node/src/util/multifork.ts index b131660382be..938c52607678 100644 --- a/packages/beacon-node/src/util/multifork.ts +++ b/packages/beacon-node/src/util/multifork.ts @@ -53,3 +53,26 @@ export function getStateTypeFromBytes( const slot = bytesToInt(bytes.subarray(SLOT_BYTES_POSITION_IN_STATE, SLOT_BYTES_POSITION_IN_STATE + SLOT_BYTE_COUNT)); return config.getForkTypes(slot).BeaconState; } + +/** + * First field in update is beacon, first field in beacon is slot + * + * header = { + * beacon: { + * slot + * ... + * } + * ... + * } + * ... + */ +const SLOT_BYTES_POSITION_IN_LIGHTCLIENTHEADER = 0; +export function getLightClientHeaderTypeFromBytes( + config: IChainForkConfig, + bytes: Buffer | Uint8Array +): allForks.AllForksLightClientSSZTypes["LightClientHeader"] { + const slot = bytesToInt( + bytes.subarray(SLOT_BYTES_POSITION_IN_LIGHTCLIENTHEADER, SLOT_BYTES_POSITION_IN_LIGHTCLIENTHEADER + SLOT_BYTE_COUNT) + ); + return config.getLightClientForkTypes(slot).LightClientHeader; +} diff --git a/packages/beacon-node/test/e2e/network/gossipsub.test.ts b/packages/beacon-node/test/e2e/network/gossipsub.test.ts index e7f1ec2450d6..f9f21a1d1513 100644 --- a/packages/beacon-node/test/e2e/network/gossipsub.test.ts +++ b/packages/beacon-node/test/e2e/network/gossipsub.test.ts @@ -1,7 +1,7 @@ import sinon from "sinon"; import {expect} from "chai"; import {createIBeaconConfig, createIChainForkConfig, defaultChainConfig} from "@lodestar/config"; -import {capella, altair, phase0, ssz} from "@lodestar/types"; +import {capella, phase0, ssz, allForks} from "@lodestar/types"; import {sleep} from "@lodestar/utils"; import {computeStartSlotAtEpoch} from "@lodestar/state-transition"; @@ -225,8 +225,8 @@ describe("gossipsub", function () { }); it("Publish and receive a LightClientOptimisticUpdate", async function () { - let onLightClientOptimisticUpdate: (ou: altair.LightClientOptimisticUpdate) => void; - const onLightClientOptimisticUpdatePromise = new Promise( + let onLightClientOptimisticUpdate: (ou: allForks.LightClientOptimisticUpdate) => void; + const onLightClientOptimisticUpdatePromise = new Promise( (resolve) => (onLightClientOptimisticUpdate = resolve) ); @@ -252,7 +252,7 @@ describe("gossipsub", function () { } } - const lightClientOptimisticUpdate = ssz.altair.LightClientOptimisticUpdate.defaultValue(); + const lightClientOptimisticUpdate = ssz.capella.LightClientOptimisticUpdate.defaultValue(); lightClientOptimisticUpdate.signatureSlot = START_SLOT; await netA.gossip.publishLightClientOptimisticUpdate(lightClientOptimisticUpdate); @@ -261,8 +261,8 @@ describe("gossipsub", function () { }); it("Publish and receive a LightClientFinalityUpdate", async function () { - let onLightClientFinalityUpdate: (fu: altair.LightClientFinalityUpdate) => void; - const onLightClientFinalityUpdatePromise = new Promise( + let onLightClientFinalityUpdate: (fu: allForks.LightClientFinalityUpdate) => void; + const onLightClientFinalityUpdatePromise = new Promise( (resolve) => (onLightClientFinalityUpdate = resolve) ); @@ -288,7 +288,7 @@ describe("gossipsub", function () { } } - const lightClientFinalityUpdate = ssz.altair.LightClientFinalityUpdate.defaultValue(); + const lightClientFinalityUpdate = ssz.capella.LightClientFinalityUpdate.defaultValue(); lightClientFinalityUpdate.signatureSlot = START_SLOT; await netA.gossip.publishLightClientFinalityUpdate(lightClientFinalityUpdate); diff --git a/packages/beacon-node/test/spec/presets/index.test.ts b/packages/beacon-node/test/spec/presets/index.test.ts index 052118f5681e..e48091692347 100644 --- a/packages/beacon-node/test/spec/presets/index.test.ts +++ b/packages/beacon-node/test/spec/presets/index.test.ts @@ -1,5 +1,8 @@ import path from "node:path"; import {ACTIVE_PRESET} from "@lodestar/params"; +import {Type} from "@chainsafe/ssz"; +import {ssz} from "@lodestar/types"; + import {RunnerType} from "../utils/types.js"; import {SkipOpts, specTestIterator} from "../utils/specTestIterator.js"; import {ethereumConsensusSpecsTests} from "../specTestVersioning.js"; @@ -32,6 +35,33 @@ const skipOpts: SkipOpts = {}; /* eslint-disable @typescript-eslint/naming-convention */ +// TODO: capella +// Map all lightclient types to altair for addressing the specs +// which are are to be updated in separate PR +const overrideSSZTypes: Record>> = { + deneb: { + LightClientHeader: ssz.altair.LightClientHeader, + LightClientUpdate: ssz.altair.LightClientUpdate, + LightClientOptimisticUpdate: ssz.altair.LightClientOptimisticUpdate, + LightClientFinalityUpdate: ssz.altair.LightClientFinalityUpdate, + LightClientBootstrap: ssz.altair.LightClientBootstrap, + }, + capella: { + LightClientHeader: ssz.altair.LightClientHeader, + LightClientUpdate: ssz.altair.LightClientUpdate, + LightClientOptimisticUpdate: ssz.altair.LightClientOptimisticUpdate, + LightClientFinalityUpdate: ssz.altair.LightClientFinalityUpdate, + LightClientBootstrap: ssz.altair.LightClientBootstrap, + }, + bellatrix: { + LightClientHeader: ssz.altair.LightClientHeader, + LightClientUpdate: ssz.altair.LightClientUpdate, + LightClientOptimisticUpdate: ssz.altair.LightClientOptimisticUpdate, + LightClientFinalityUpdate: ssz.altair.LightClientFinalityUpdate, + LightClientBootstrap: ssz.altair.LightClientBootstrap, + }, +}; + specTestIterator( path.join(ethereumConsensusSpecsTests.outputDir, "tests", ACTIVE_PRESET), { @@ -57,7 +87,7 @@ specTestIterator( shuffling: {type: RunnerType.default, fn: shuffling}, ssz_static: { type: RunnerType.custom, - fn: sszStatic(), + fn: sszStatic(undefined, overrideSSZTypes), }, sync: {type: RunnerType.default, fn: forkChoiceTest({onlyPredefinedResponses: true})}, transition: { diff --git a/packages/beacon-node/test/spec/presets/ssz_static.ts b/packages/beacon-node/test/spec/presets/ssz_static.ts index 17e9d342cf9c..599dd1587020 100644 --- a/packages/beacon-node/test/spec/presets/ssz_static.ts +++ b/packages/beacon-node/test/spec/presets/ssz_static.ts @@ -26,7 +26,7 @@ type Types = Record>; // tests / mainnet / altair / ssz_static / Validator / ssz_random / case_0/roots.yaml // -export const sszStatic = (skippedTypes?: string[]) => ( +export const sszStatic = (skippedTypes?: string[], overrideSSZTypes?: Record) => ( fork: ForkName, typeName: string, testSuite: string, @@ -39,6 +39,7 @@ export const sszStatic = (skippedTypes?: string[]) => ( /* eslint-disable @typescript-eslint/strict-boolean-expressions */ const sszType = + (((overrideSSZTypes ?? {})[fork] ?? {}) as Types)[typeName] || (ssz[fork] as Types)[typeName] || (ssz.capella as Types)[typeName] || (ssz.bellatrix as Types)[typeName] || diff --git a/packages/beacon-node/test/unit/chain/validation/lightClientFinalityUpdate.test.ts b/packages/beacon-node/test/unit/chain/validation/lightClientFinalityUpdate.test.ts index 26e6eb004039..5aea7bad9e33 100644 --- a/packages/beacon-node/test/unit/chain/validation/lightClientFinalityUpdate.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/lightClientFinalityUpdate.test.ts @@ -1,7 +1,6 @@ import {expect} from "chai"; import sinon from "sinon"; -import {createIBeaconConfig} from "@lodestar/config"; -import {config} from "@lodestar/config/default"; +import {createIBeaconConfig, createIChainForkConfig, defaultChainConfig} from "@lodestar/config"; import {altair, ssz} from "@lodestar/types"; import {computeTimeAtSlot} from "@lodestar/state-transition"; @@ -14,6 +13,14 @@ import {IBeaconChain} from "../../../../src/chain/index.js"; describe("Light Client Finality Update validation", function () { let fakeClock: sinon.SinonFakeTimers; const afterEachCallbacks: (() => Promise | void)[] = []; + const config = createIChainForkConfig({ + ...defaultChainConfig, + /* eslint-disable @typescript-eslint/naming-convention */ + ALTAIR_FORK_EPOCH: 1, + BELLATRIX_FORK_EPOCH: 3, + CAPELLA_FORK_EPOCH: Infinity, + }); + beforeEach(() => { fakeClock = sinon.useFakeTimers(); }); @@ -93,15 +100,15 @@ describe("Light Client Finality Update validation", function () { it("should return invalid - finality update not matching local", async () => { const lightClientFinalityUpdate: altair.LightClientFinalityUpdate = ssz.altair.LightClientFinalityUpdate.defaultValue(); - lightClientFinalityUpdate.finalizedHeader.beacon.slot = 2; - lightClientFinalityUpdate.signatureSlot = lightClientFinalityUpdate.finalizedHeader.beacon.slot + 1; + lightClientFinalityUpdate.finalizedHeader.beacon.slot = 42; + lightClientFinalityUpdate.attestedHeader.beacon.slot = lightClientFinalityUpdate.finalizedHeader.beacon.slot + 1; const chain = mockChain(); // make lightclientserver return another update with different value from gossiped chain.lightClientServer.getFinalityUpdate = () => { const defaultValue = ssz.altair.LightClientFinalityUpdate.defaultValue(); - defaultValue.finalizedHeader.beacon.slot = 1; + defaultValue.finalizedHeader.beacon.slot = 41; return defaultValue; }; @@ -120,8 +127,8 @@ describe("Light Client Finality Update validation", function () { it("should return invalid - not matching local when no local finality update yet", async () => { const lightClientFinalityUpdate: altair.LightClientFinalityUpdate = ssz.altair.LightClientFinalityUpdate.defaultValue(); - lightClientFinalityUpdate.finalizedHeader.beacon.slot = 2; - lightClientFinalityUpdate.signatureSlot = lightClientFinalityUpdate.finalizedHeader.beacon.slot + 1; + lightClientFinalityUpdate.finalizedHeader.beacon.slot = 42; + lightClientFinalityUpdate.attestedHeader.beacon.slot = lightClientFinalityUpdate.finalizedHeader.beacon.slot + 1; const chain = mockChain(); @@ -149,6 +156,7 @@ describe("Light Client Finality Update validation", function () { // satisfy: // No other finality_update with a lower or equal finalized_header.beacon.slot was already forwarded on the network lightClientFinalityUpdate.finalizedHeader.beacon.slot = 2; + lightClientFinalityUpdate.signatureSlot = lightClientFinalityUpdate.finalizedHeader.beacon.slot + 1; chain.lightClientServer.getFinalityUpdate = () => { const defaultValue = ssz.altair.LightClientFinalityUpdate.defaultValue(); diff --git a/packages/beacon-node/test/unit/chain/validation/lightClientOptimisticUpdate.test.ts b/packages/beacon-node/test/unit/chain/validation/lightClientOptimisticUpdate.test.ts index a5d2bfb1d7cc..71c941af1013 100644 --- a/packages/beacon-node/test/unit/chain/validation/lightClientOptimisticUpdate.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/lightClientOptimisticUpdate.test.ts @@ -1,7 +1,6 @@ import {expect} from "chai"; import sinon from "sinon"; -import {createIBeaconConfig} from "@lodestar/config"; -import {config} from "@lodestar/config/default"; +import {createIBeaconConfig, createIChainForkConfig, defaultChainConfig} from "@lodestar/config"; import {altair, ssz} from "@lodestar/types"; import {computeTimeAtSlot} from "@lodestar/state-transition"; @@ -14,6 +13,15 @@ import {IBeaconChain} from "../../../../src/chain/index.js"; describe("Light Client Optimistic Update validation", function () { let fakeClock: sinon.SinonFakeTimers; const afterEachCallbacks: (() => Promise | void)[] = []; + // eslint-disable-next-line @typescript-eslint/naming-convention + const config = createIChainForkConfig({ + ...defaultChainConfig, + /* eslint-disable @typescript-eslint/naming-convention */ + ALTAIR_FORK_EPOCH: 1, + BELLATRIX_FORK_EPOCH: 3, + CAPELLA_FORK_EPOCH: Infinity, + }); + beforeEach(() => { fakeClock = sinon.useFakeTimers(); }); @@ -93,7 +101,7 @@ describe("Light Client Optimistic Update validation", function () { it("should return invalid - optimistic update not matching local", async () => { const lightclientOptimisticUpdate: altair.LightClientOptimisticUpdate = ssz.altair.LightClientOptimisticUpdate.defaultValue(); - lightclientOptimisticUpdate.attestedHeader.beacon.slot = 2; + lightclientOptimisticUpdate.attestedHeader.beacon.slot = 42; const chain = mockChain(); @@ -118,7 +126,7 @@ describe("Light Client Optimistic Update validation", function () { it("should return invalid - not matching local when no local optimistic update yet", async () => { const lightclientOptimisticUpdate: altair.LightClientOptimisticUpdate = ssz.altair.LightClientOptimisticUpdate.defaultValue(); - lightclientOptimisticUpdate.attestedHeader.beacon.slot = 2; + lightclientOptimisticUpdate.attestedHeader.beacon.slot = 42; const chain = mockChain(); @@ -145,12 +153,6 @@ describe("Light Client Optimistic Update validation", function () { // No other optimistic_update with a lower or equal attested_header.beacon.slot was already forwarded on the network lightclientOptimisticUpdate.attestedHeader.beacon.slot = 2; - chain.lightClientServer.getOptimisticUpdate = () => { - const defaultValue = ssz.altair.LightClientOptimisticUpdate.defaultValue(); - defaultValue.attestedHeader.beacon.slot = 1; - return defaultValue; - }; - // satisfy: // [IGNORE] The optimistic_update is received after the block at signature_slot was given enough time to propagate // through the network -- i.e. validate that one-third of optimistic_update.signature_slot has transpired diff --git a/packages/config/src/forkConfig/index.ts b/packages/config/src/forkConfig/index.ts index cc82aab595c8..38d72d1c1d02 100644 --- a/packages/config/src/forkConfig/index.ts +++ b/packages/config/src/forkConfig/index.ts @@ -1,4 +1,12 @@ -import {GENESIS_EPOCH, ForkName, SLOTS_PER_EPOCH, ForkSeq, isForkExecution, isForkBlobs} from "@lodestar/params"; +import { + GENESIS_EPOCH, + ForkName, + SLOTS_PER_EPOCH, + ForkSeq, + isForkLightClient, + isForkExecution, + isForkBlobs, +} from "@lodestar/params"; import {Slot, allForks, Version, ssz} from "@lodestar/types"; import {IChainConfig} from "../chainConfig/index.js"; import {IForkConfig, IForkInfo} from "./types.js"; @@ -96,6 +104,13 @@ export function createIForkConfig(config: IChainConfig): IForkConfig { } return ssz.allForksBlinded[forkName] as allForks.AllForksBlindedSSZTypes; }, + getLightClientForkTypes(slot: Slot): allForks.AllForksLightClientSSZTypes { + const forkName = this.getForkName(slot); + if (!isForkLightClient(forkName)) { + throw Error(`Invalid slot=${slot} fork=${forkName} for lightclient fork types`); + } + return ssz.allForksLightClient[forkName] as allForks.AllForksLightClientSSZTypes; + }, getBlobsForkTypes(slot: Slot): allForks.AllForksBlobsSSZTypes { const forkName = this.getForkName(slot); if (!isForkBlobs(forkName)) { diff --git a/packages/config/src/forkConfig/types.ts b/packages/config/src/forkConfig/types.ts index 3404b07ca975..5619ee0b3cc3 100644 --- a/packages/config/src/forkConfig/types.ts +++ b/packages/config/src/forkConfig/types.ts @@ -30,10 +30,12 @@ export interface IForkConfig { getForkVersion(slot: Slot): Version; /** Get SSZ types by hard-fork */ getForkTypes(slot: Slot): allForks.AllForksSSZTypes; - /** Get execution SSZ tyoes by hard-fork*/ + /** Get lightclient SSZ types by hard-fork*/ + getLightClientForkTypes(slot: Slot): allForks.AllForksLightClientSSZTypes; + /** Get execution SSZ types by hard-fork*/ getExecutionForkTypes(slot: Slot): allForks.AllForksExecutionSSZTypes; /** Get blinded SSZ types by hard-fork */ getBlindedForkTypes(slot: Slot): allForks.AllForksBlindedSSZTypes; - /** Get blobs SSZ tyoes by hard-fork*/ + /** Get blobs SSZ types by hard-fork*/ getBlobsForkTypes(slot: Slot): allForks.AllForksBlobsSSZTypes; } diff --git a/packages/light-client/src/events.ts b/packages/light-client/src/events.ts index 42d701a3e3ea..3bc500f461d1 100644 --- a/packages/light-client/src/events.ts +++ b/packages/light-client/src/events.ts @@ -1,4 +1,4 @@ -import {altair} from "@lodestar/types"; +import {allForks} from "@lodestar/types"; export enum LightclientEvent { lightClientOptimisticUpdate = "light_client_optimistic_update", @@ -6,8 +6,8 @@ export enum LightclientEvent { } export type LightclientEmitterEvents = { - [LightclientEvent.lightClientOptimisticUpdate]: (newHeader: altair.LightClientHeader) => void; - [LightclientEvent.lightClientFinalityUpdate]: (newHeader: altair.LightClientHeader) => void; + [LightclientEvent.lightClientOptimisticUpdate]: (newHeader: allForks.LightClientHeader) => void; + [LightclientEvent.lightClientFinalityUpdate]: (newHeader: allForks.LightClientHeader) => void; }; export type LightclientEmitter = MittEmitter; diff --git a/packages/light-client/src/index.ts b/packages/light-client/src/index.ts index e64ab517242d..295864c5bab2 100644 --- a/packages/light-client/src/index.ts +++ b/packages/light-client/src/index.ts @@ -1,7 +1,7 @@ import mitt from "mitt"; import {init as initBls} from "@chainsafe/bls/switchable"; import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD} from "@lodestar/params"; -import {altair, phase0, RootHex, Slot, SyncPeriod} from "@lodestar/types"; +import {phase0, RootHex, Slot, SyncPeriod, allForks} from "@lodestar/types"; import {createIBeaconConfig, IBeaconConfig, IChainForkConfig} from "@lodestar/config"; import {isErrorAborted, sleep} from "@lodestar/utils"; import {fromHexString, toHexString} from "@chainsafe/ssz"; @@ -33,7 +33,7 @@ export type LightclientInitArgs = { opts?: LightclientOpts; genesisData: GenesisData; transport: LightClientTransport; - bootstrap: altair.LightClientBootstrap; + bootstrap: allForks.LightClientBootstrap; }; /** Provides some protection against a server client sending header updates too far away in the future */ @@ -172,7 +172,7 @@ export class Lightclient { this.status = {code: RunStatusCode.stopped}; } - getHead(): altair.LightClientHeader { + getHead(): allForks.LightClientHeader { return this.lightclientSpec.store.optimisticHeader; } @@ -286,7 +286,7 @@ export class Lightclient { * Processes new optimistic header updates in only known synced sync periods. * This headerUpdate may update the head if there's enough participation. */ - private processOptimisticUpdate(optimisticUpdate: altair.LightClientOptimisticUpdate): void { + private processOptimisticUpdate(optimisticUpdate: allForks.LightClientOptimisticUpdate): void { this.lightclientSpec.onOptimisticUpdate(this.currentSlotWithTolerance(), optimisticUpdate); } @@ -294,11 +294,11 @@ export class Lightclient { * Processes new header updates in only known synced sync periods. * This headerUpdate may update the head if there's enough participation. */ - private processFinalizedUpdate(finalizedUpdate: altair.LightClientFinalityUpdate): void { + private processFinalizedUpdate(finalizedUpdate: allForks.LightClientFinalityUpdate): void { this.lightclientSpec.onFinalityUpdate(this.currentSlotWithTolerance(), finalizedUpdate); } - private processSyncCommitteeUpdate(update: altair.LightClientUpdate): void { + private processSyncCommitteeUpdate(update: allForks.LightClientUpdate): void { this.lightclientSpec.onUpdate(this.currentSlotWithTolerance(), update); } diff --git a/packages/light-client/src/spec/index.ts b/packages/light-client/src/spec/index.ts index ea933665256f..9123101f5bfc 100644 --- a/packages/light-client/src/spec/index.ts +++ b/packages/light-client/src/spec/index.ts @@ -1,12 +1,13 @@ import {IBeaconConfig} from "@lodestar/config"; import {UPDATE_TIMEOUT} from "@lodestar/params"; -import {altair, Slot} from "@lodestar/types"; +import {Slot, allForks} from "@lodestar/types"; import {computeSyncPeriodAtSlot} from "../utils/index.js"; import {getSyncCommitteeAtPeriod, processLightClientUpdate, ProcessUpdateOpts} from "./processLightClientUpdate.js"; import {ILightClientStore, LightClientStore, LightClientStoreEvents} from "./store.js"; import {ZERO_FINALITY_BRANCH, ZERO_HEADER, ZERO_NEXT_SYNC_COMMITTEE_BRANCH, ZERO_SYNC_COMMITTEE} from "./utils.js"; export {isBetterUpdate, toLightClientUpdateSummary, LightClientUpdateSummary} from "./isBetterUpdate.js"; +export {upgradeLightClientHeader} from "./utils.js"; export class LightclientSpec { readonly store: ILightClientStore; @@ -14,16 +15,16 @@ export class LightclientSpec { constructor( config: IBeaconConfig, private readonly opts: ProcessUpdateOpts & LightClientStoreEvents, - bootstrap: altair.LightClientBootstrap + bootstrap: allForks.LightClientBootstrap ) { this.store = new LightClientStore(config, bootstrap, opts); } - onUpdate(currentSlot: Slot, update: altair.LightClientUpdate): void { + onUpdate(currentSlot: Slot, update: allForks.LightClientUpdate): void { processLightClientUpdate(this.store, currentSlot, this.opts, update); } - onFinalityUpdate(currentSlot: Slot, finalityUpdate: altair.LightClientFinalityUpdate): void { + onFinalityUpdate(currentSlot: Slot, finalityUpdate: allForks.LightClientFinalityUpdate): void { this.onUpdate(currentSlot, { attestedHeader: finalityUpdate.attestedHeader, nextSyncCommittee: ZERO_SYNC_COMMITTEE, @@ -35,7 +36,7 @@ export class LightclientSpec { }); } - onOptimisticUpdate(currentSlot: Slot, optimisticUpdate: altair.LightClientOptimisticUpdate): void { + onOptimisticUpdate(currentSlot: Slot, optimisticUpdate: allForks.LightClientOptimisticUpdate): void { this.onUpdate(currentSlot, { attestedHeader: optimisticUpdate.attestedHeader, nextSyncCommittee: ZERO_SYNC_COMMITTEE, diff --git a/packages/light-client/src/spec/isBetterUpdate.ts b/packages/light-client/src/spec/isBetterUpdate.ts index a0f537327328..e149386cf97f 100644 --- a/packages/light-client/src/spec/isBetterUpdate.ts +++ b/packages/light-client/src/spec/isBetterUpdate.ts @@ -1,5 +1,5 @@ import {SYNC_COMMITTEE_SIZE} from "@lodestar/params"; -import {altair, Slot} from "@lodestar/types"; +import {Slot, allForks} from "@lodestar/types"; import {computeSyncPeriodAtSlot} from "../utils/index.js"; import {isFinalityUpdate, isSyncCommitteeUpdate, sumBits} from "./utils.js"; @@ -82,7 +82,7 @@ export function isSafeLightClientUpdate(update: LightClientUpdateSummary): boole ); } -export function toLightClientUpdateSummary(update: altair.LightClientUpdate): LightClientUpdateSummary { +export function toLightClientUpdateSummary(update: allForks.LightClientUpdate): LightClientUpdateSummary { return { activeParticipants: sumBits(update.syncAggregate.syncCommitteeBits), attestedHeaderSlot: update.attestedHeader.beacon.slot, diff --git a/packages/light-client/src/spec/processLightClientUpdate.ts b/packages/light-client/src/spec/processLightClientUpdate.ts index 5ca212385a06..9ac0beb023ec 100644 --- a/packages/light-client/src/spec/processLightClientUpdate.ts +++ b/packages/light-client/src/spec/processLightClientUpdate.ts @@ -1,5 +1,5 @@ import {SYNC_COMMITTEE_SIZE} from "@lodestar/params"; -import {altair, Slot, SyncPeriod} from "@lodestar/types"; +import {Slot, SyncPeriod, allForks} from "@lodestar/types"; import {pruneSetToMax} from "@lodestar/utils"; import {computeSyncPeriodAtSlot, deserializeSyncCommittee, sumBits} from "../utils/index.js"; import {isBetterUpdate, LightClientUpdateSummary, toLightClientUpdateSummary} from "./isBetterUpdate.js"; @@ -16,7 +16,7 @@ export function processLightClientUpdate( store: ILightClientStore, currentSlot: Slot, opts: ProcessUpdateOpts, - update: altair.LightClientUpdate + update: allForks.LightClientUpdate ): void { if (update.signatureSlot > currentSlot) { throw Error(`update slot ${update.signatureSlot} must not be in the future, current slot ${currentSlot}`); diff --git a/packages/light-client/src/spec/store.ts b/packages/light-client/src/spec/store.ts index 36a35debd9fd..6cd88a485d87 100644 --- a/packages/light-client/src/spec/store.ts +++ b/packages/light-client/src/spec/store.ts @@ -1,6 +1,6 @@ import type {PublicKey} from "@chainsafe/bls/types"; import {IBeaconConfig} from "@lodestar/config"; -import {altair, SyncPeriod} from "@lodestar/types"; +import {SyncPeriod, allForks} from "@lodestar/types"; import {computeSyncPeriodAtSlot, deserializeSyncCommittee} from "../utils/index.js"; import {LightClientUpdateSummary} from "./isBetterUpdate.js"; @@ -18,29 +18,29 @@ export interface ILightClientStore { setActiveParticipants(period: SyncPeriod, activeParticipants: number): void; // Header that is finalized - finalizedHeader: altair.LightClientHeader; + finalizedHeader: allForks.LightClientHeader; // Most recent available reasonably-safe header - optimisticHeader: altair.LightClientHeader; + optimisticHeader: allForks.LightClientHeader; } export interface LightClientStoreEvents { - onSetFinalizedHeader?: (header: altair.LightClientHeader) => void; - onSetOptimisticHeader?: (header: altair.LightClientHeader) => void; + onSetFinalizedHeader?: (header: allForks.LightClientHeader) => void; + onSetOptimisticHeader?: (header: allForks.LightClientHeader) => void; } export class LightClientStore implements ILightClientStore { readonly syncCommittees = new Map(); readonly bestValidUpdates = new Map(); - private _finalizedHeader: altair.LightClientHeader; - private _optimisticHeader: altair.LightClientHeader; + private _finalizedHeader: allForks.LightClientHeader; + private _optimisticHeader: allForks.LightClientHeader; private readonly maxActiveParticipants = new Map(); constructor( readonly config: IBeaconConfig, - bootstrap: altair.LightClientBootstrap, + bootstrap: allForks.LightClientBootstrap, private readonly events: LightClientStoreEvents ) { const bootstrapPeriod = computeSyncPeriodAtSlot(bootstrap.header.beacon.slot); @@ -49,20 +49,20 @@ export class LightClientStore implements ILightClientStore { this._optimisticHeader = bootstrap.header; } - get finalizedHeader(): altair.LightClientHeader { + get finalizedHeader(): allForks.LightClientHeader { return this._finalizedHeader; } - set finalizedHeader(value: altair.LightClientHeader) { + set finalizedHeader(value: allForks.LightClientHeader) { this._finalizedHeader = value; this.events.onSetFinalizedHeader?.(value); } - get optimisticHeader(): altair.LightClientHeader { + get optimisticHeader(): allForks.LightClientHeader { return this._optimisticHeader; } - set optimisticHeader(value: altair.LightClientHeader) { + set optimisticHeader(value: allForks.LightClientHeader) { this._optimisticHeader = value; this.events.onSetOptimisticHeader?.(value); } @@ -95,7 +95,7 @@ export type SyncCommitteeFast = { }; export type LightClientUpdateWithSummary = { - update: altair.LightClientUpdate; + update: allForks.LightClientUpdate; summary: LightClientUpdateSummary; }; diff --git a/packages/light-client/src/spec/utils.ts b/packages/light-client/src/spec/utils.ts index b9ec4a7ee7cf..04c17b7edc0a 100644 --- a/packages/light-client/src/spec/utils.ts +++ b/packages/light-client/src/spec/utils.ts @@ -1,6 +1,7 @@ import {BitArray, byteArrayEquals} from "@chainsafe/ssz"; -import {FINALIZED_ROOT_DEPTH, NEXT_SYNC_COMMITTEE_DEPTH} from "@lodestar/params"; -import {altair, phase0, ssz} from "@lodestar/types"; +import {FINALIZED_ROOT_DEPTH, NEXT_SYNC_COMMITTEE_DEPTH, ForkSeq, ForkName} from "@lodestar/params"; +import {altair, phase0, ssz, allForks, capella, deneb} from "@lodestar/types"; +import {IChainForkConfig} from "@lodestar/config"; export const GENESIS_SLOT = 0; export const ZERO_HASH = new Uint8Array(32); @@ -20,7 +21,7 @@ export function getSafetyThreshold(maxActiveParticipants: number): number { return Math.floor(maxActiveParticipants / SAFETY_THRESHOLD_FACTOR); } -export function isSyncCommitteeUpdate(update: altair.LightClientUpdate): boolean { +export function isSyncCommitteeUpdate(update: allForks.LightClientUpdate): boolean { return ( // Fast return for when constructing full LightClientUpdate from partial updates update.nextSyncCommitteeBranch !== ZERO_NEXT_SYNC_COMMITTEE_BRANCH && @@ -28,7 +29,7 @@ export function isSyncCommitteeUpdate(update: altair.LightClientUpdate): boolean ); } -export function isFinalityUpdate(update: altair.LightClientUpdate): boolean { +export function isFinalityUpdate(update: allForks.LightClientUpdate): boolean { return ( // Fast return for when constructing full LightClientUpdate from partial updates update.finalityBranch !== ZERO_FINALITY_BRANCH && @@ -45,3 +46,39 @@ export function isZeroedSyncCommittee(syncCommittee: altair.SyncCommittee): bool // Fast return for when constructing full LightClientUpdate from partial updates return syncCommittee === ZERO_SYNC_COMMITTEE || byteArrayEquals(syncCommittee.pubkeys[0], ZERO_PUBKEY); } + +export function upgradeLightClientHeader( + config: IChainForkConfig, + targetFork: ForkName, + header: altair.LightClientHeader +): allForks.LightClientHeader { + const upgradedHeader = header; + + const headerFork = config.getForkName(header.beacon.slot); + switch (headerFork) { + case ForkName.phase0: + throw Error(`Invalid target fork=${headerFork} for LightClientHeader`); + + case ForkName.altair: + case ForkName.bellatrix: + // Break if no further upgradation is required else fall through + if (ForkSeq[targetFork] <= ForkSeq.bellatrix) break; + // eslint-disable-next-line no-fallthrough + + case ForkName.capella: + (upgradedHeader as capella.LightClientHeader).execution = ssz.capella.LightClientHeader.fields.execution.defaultValue(); + (upgradedHeader as capella.LightClientHeader).executionBranch = ssz.capella.LightClientHeader.fields.executionBranch.defaultValue(); + + // Break if no further upgradation is required else fall through + if (ForkSeq[targetFork] <= ForkSeq.capella) break; + // eslint-disable-next-line no-fallthrough + + case ForkName.deneb: + (upgradedHeader as deneb.LightClientHeader).execution.excessDataGas = ssz.deneb.LightClientHeader.fields.execution.fields.excessDataGas.defaultValue(); + + // Break if no further upgradation is required else fall through + if (ForkSeq[targetFork] <= ForkSeq.deneb) break; + // eslint-disable-next-line no-fallthrough + } + return upgradedHeader; +} diff --git a/packages/light-client/src/spec/validateLightClientBootstrap.ts b/packages/light-client/src/spec/validateLightClientBootstrap.ts index af25b0414596..e1334d82800c 100644 --- a/packages/light-client/src/spec/validateLightClientBootstrap.ts +++ b/packages/light-client/src/spec/validateLightClientBootstrap.ts @@ -1,12 +1,12 @@ import {byteArrayEquals} from "@chainsafe/ssz"; -import {altair, Root, ssz} from "@lodestar/types"; +import {Root, ssz, allForks} from "@lodestar/types"; import {toHex} from "@lodestar/utils"; import {isValidMerkleBranch} from "../utils/verifyMerkleBranch.js"; const CURRENT_SYNC_COMMITTEE_INDEX = 22; const CURRENT_SYNC_COMMITTEE_DEPTH = 5; -export function validateLightClientBootstrap(trustedBlockRoot: Root, bootstrap: altair.LightClientBootstrap): void { +export function validateLightClientBootstrap(trustedBlockRoot: Root, bootstrap: allForks.LightClientBootstrap): void { const headerRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(bootstrap.header.beacon); if (!byteArrayEquals(headerRoot, trustedBlockRoot)) { throw Error(`bootstrap header root ${toHex(headerRoot)} != trusted root ${toHex(trustedBlockRoot)}`); diff --git a/packages/light-client/src/spec/validateLightClientUpdate.ts b/packages/light-client/src/spec/validateLightClientUpdate.ts index cb45a9ca5550..1599d988fb38 100644 --- a/packages/light-client/src/spec/validateLightClientUpdate.ts +++ b/packages/light-client/src/spec/validateLightClientUpdate.ts @@ -1,4 +1,4 @@ -import {altair, Root, ssz} from "@lodestar/types"; +import {Root, ssz, allForks} from "@lodestar/types"; import bls from "@chainsafe/bls/switchable"; import type {PublicKey, Signature} from "@chainsafe/bls/types"; import { @@ -18,7 +18,7 @@ import {ILightClientStore} from "./store.js"; export function validateLightClientUpdate( store: ILightClientStore, - update: altair.LightClientUpdate, + update: allForks.LightClientUpdate, syncCommittee: SyncCommitteeFast ): void { // Verify sync committee has sufficient participants diff --git a/packages/light-client/src/transport/interface.ts b/packages/light-client/src/transport/interface.ts index cc3038f57e3f..dcd3210df70c 100644 --- a/packages/light-client/src/transport/interface.ts +++ b/packages/light-client/src/transport/interface.ts @@ -1,4 +1,4 @@ -import {altair, SyncPeriod} from "@lodestar/types"; +import {allForks, SyncPeriod} from "@lodestar/types"; import {ForkName} from "@lodestar/params"; export interface LightClientTransport { @@ -8,24 +8,24 @@ export interface LightClientTransport { ): Promise< { version: ForkName; - data: altair.LightClientUpdate; + data: allForks.LightClientUpdate; }[] >; /** * Returns the latest optimistic head update available. Clients should use the SSE type `light_client_optimistic_update` * unless to get the very first head update after syncing, or if SSE are not supported by the server. */ - getOptimisticUpdate(): Promise<{version: ForkName; data: altair.LightClientOptimisticUpdate}>; - getFinalityUpdate(): Promise<{version: ForkName; data: altair.LightClientFinalityUpdate}>; + getOptimisticUpdate(): Promise<{version: ForkName; data: allForks.LightClientOptimisticUpdate}>; + getFinalityUpdate(): Promise<{version: ForkName; data: allForks.LightClientFinalityUpdate}>; /** * Fetch a bootstrapping state with a proof to a trusted block root. * The trusted block root should be fetched with similar means to a weak subjectivity checkpoint. * Only block roots for checkpoints are guaranteed to be available. */ - getBootstrap(blockRoot: string): Promise<{version: ForkName; data: altair.LightClientBootstrap}>; + getBootstrap(blockRoot: string): Promise<{version: ForkName; data: allForks.LightClientBootstrap}>; // registers handler for LightClientOptimisticUpdate. This can come either via sse or p2p - onOptimisticUpdate(handler: (optimisticUpdate: altair.LightClientOptimisticUpdate) => void): void; + onOptimisticUpdate(handler: (optimisticUpdate: allForks.LightClientOptimisticUpdate) => void): void; // registers handler for LightClientFinalityUpdate. This can come either via sse or p2p - onFinalityUpdate(handler: (finalityUpdate: altair.LightClientFinalityUpdate) => void): void; + onFinalityUpdate(handler: (finalityUpdate: allForks.LightClientFinalityUpdate) => void): void; } diff --git a/packages/light-client/src/transport/rest.ts b/packages/light-client/src/transport/rest.ts index da65def2766c..2ac3cbc79ac0 100644 --- a/packages/light-client/src/transport/rest.ts +++ b/packages/light-client/src/transport/rest.ts @@ -1,13 +1,13 @@ import EventEmitter from "events"; import StrictEventEmitter from "strict-event-emitter-types"; -import {allForks, altair, SyncPeriod} from "@lodestar/types"; +import {allForks, SyncPeriod} from "@lodestar/types"; import {Api, ApiError, routes} from "@lodestar/api"; import {ForkName} from "@lodestar/params"; import {LightClientTransport} from "./interface.js"; export type LightClientRestEvents = { - [routes.events.EventType.lightClientFinalityUpdate]: altair.LightClientFinalityUpdate; - [routes.events.EventType.lightClientOptimisticUpdate]: altair.LightClientOptimisticUpdate; + [routes.events.EventType.lightClientFinalityUpdate]: allForks.LightClientFinalityUpdate; + [routes.events.EventType.lightClientOptimisticUpdate]: allForks.LightClientOptimisticUpdate; }; type RestEvents = StrictEventEmitter; @@ -27,7 +27,7 @@ export class LightClientRestTransport extends (EventEmitter as {new (): RestEven ): Promise< { version: ForkName; - data: altair.LightClientUpdate; + data: allForks.LightClientUpdate; }[] > { const res = await this.api.lightclient.getUpdates(startPeriod, count); @@ -35,19 +35,19 @@ export class LightClientRestTransport extends (EventEmitter as {new (): RestEven return res.response; } - async getOptimisticUpdate(): Promise<{version: ForkName; data: altair.LightClientOptimisticUpdate}> { + async getOptimisticUpdate(): Promise<{version: ForkName; data: allForks.LightClientOptimisticUpdate}> { const res = await this.api.lightclient.getOptimisticUpdate(); ApiError.assert(res); return res.response; } - async getFinalityUpdate(): Promise<{version: ForkName; data: altair.LightClientFinalityUpdate}> { + async getFinalityUpdate(): Promise<{version: ForkName; data: allForks.LightClientFinalityUpdate}> { const res = await this.api.lightclient.getFinalityUpdate(); ApiError.assert(res); return res.response; } - async getBootstrap(blockRoot: string): Promise<{version: ForkName; data: altair.LightClientBootstrap}> { + async getBootstrap(blockRoot: string): Promise<{version: ForkName; data: allForks.LightClientBootstrap}> { const res = await this.api.lightclient.getBootstrap(blockRoot); ApiError.assert(res); return res.response; @@ -59,12 +59,12 @@ export class LightClientRestTransport extends (EventEmitter as {new (): RestEven return res.response; } - onOptimisticUpdate(handler: (optimisticUpdate: altair.LightClientOptimisticUpdate) => void): void { + onOptimisticUpdate(handler: (optimisticUpdate: allForks.LightClientOptimisticUpdate) => void): void { this.subscribeEventstream(); this.eventEmitter.on(routes.events.EventType.lightClientOptimisticUpdate, handler); } - onFinalityUpdate(handler: (finalityUpdate: altair.LightClientFinalityUpdate) => void): void { + onFinalityUpdate(handler: (finalityUpdate: allForks.LightClientFinalityUpdate) => void): void { this.subscribeEventstream(); this.eventEmitter.on(routes.events.EventType.lightClientFinalityUpdate, handler); } diff --git a/packages/light-client/src/types.ts b/packages/light-client/src/types.ts index 21a4a5a926bb..6e6126f8a481 100644 --- a/packages/light-client/src/types.ts +++ b/packages/light-client/src/types.ts @@ -1,14 +1,14 @@ import type {PublicKey} from "@chainsafe/bls/types"; -import {altair, SyncPeriod} from "@lodestar/types"; +import {SyncPeriod, allForks} from "@lodestar/types"; export type LightClientStoreFast = { snapshot: LightClientSnapshotFast; - bestUpdates: Map; + bestUpdates: Map; }; export type LightClientSnapshotFast = { /** Beacon block header */ - header: altair.LightClientHeader; + header: allForks.LightClientHeader; /** Sync committees corresponding to the header */ currentSyncCommittee: SyncCommitteeFast; nextSyncCommittee: SyncCommitteeFast; diff --git a/packages/light-client/src/validation.ts b/packages/light-client/src/validation.ts index d42ab5e6c8ae..7b4c67392f5a 100644 --- a/packages/light-client/src/validation.ts +++ b/packages/light-client/src/validation.ts @@ -1,4 +1,4 @@ -import {altair, Root, Slot, ssz} from "@lodestar/types"; +import {altair, Root, Slot, ssz, allForks} from "@lodestar/types"; import bls from "@chainsafe/bls/switchable"; import type {PublicKey, Signature} from "@chainsafe/bls/types"; import { @@ -24,7 +24,7 @@ import {computeSyncPeriodAtSlot} from "./utils/clock.js"; export function assertValidLightClientUpdate( config: IBeaconConfig, syncCommittee: SyncCommitteeFast, - update: altair.LightClientUpdate + update: allForks.LightClientUpdate ): void { // DIFF FROM SPEC: An update with the same header.slot can be valid and valuable to the lightclient // It may have more consensus and result in a better snapshot whilst not advancing the state @@ -64,7 +64,7 @@ export function assertValidLightClientUpdate( * * Where `hashTreeRoot(state) == update.finalityHeader.stateRoot` */ -export function assertValidFinalityProof(update: altair.LightClientFinalityUpdate): void { +export function assertValidFinalityProof(update: allForks.LightClientFinalityUpdate): void { if ( !isValidMerkleBranch( ssz.phase0.BeaconBlockHeader.hashTreeRoot(update.finalizedHeader.beacon), @@ -94,7 +94,7 @@ export function assertValidFinalityProof(update: altair.LightClientFinalityUpdat * * Where `hashTreeRoot(state) == update.header.stateRoot` */ -export function assertValidSyncCommitteeProof(update: altair.LightClientUpdate): void { +export function assertValidSyncCommitteeProof(update: allForks.LightClientUpdate): void { if ( !isValidMerkleBranch( ssz.altair.SyncCommittee.hashTreeRoot(update.nextSyncCommittee), diff --git a/packages/light-client/test/utils/utils.ts b/packages/light-client/test/utils/utils.ts index ad44cd85c8ec..8638e6f86180 100644 --- a/packages/light-client/test/utils/utils.ts +++ b/packages/light-client/test/utils/utils.ts @@ -2,7 +2,6 @@ import bls from "@chainsafe/bls/switchable"; import {PointFormat, PublicKey, SecretKey} from "@chainsafe/bls/types"; import {hash, Tree} from "@chainsafe/persistent-merkle-tree"; import {BitArray, fromHexString} from "@chainsafe/ssz"; -import {routes} from "@lodestar/api"; import {IBeaconConfig} from "@lodestar/config"; import { DOMAIN_SYNC_COMMITTEE, @@ -12,7 +11,7 @@ import { SLOTS_PER_EPOCH, SYNC_COMMITTEE_SIZE, } from "@lodestar/params"; -import {altair, phase0, Slot, ssz, SyncPeriod} from "@lodestar/types"; +import {altair, phase0, Slot, ssz, SyncPeriod, allForks} from "@lodestar/types"; import {SyncCommitteeFast} from "../../src/types.js"; import {computeSigningRoot} from "../../src/utils/domain.js"; import {getLcLoggerConsole} from "../../src/utils/logger.js"; @@ -159,7 +158,7 @@ export function computeLightclientUpdate(config: IBeaconConfig, period: SyncPeri export function computeLightClientSnapshot( period: SyncPeriod ): { - snapshot: routes.lightclient.LightClientBootstrap; + snapshot: allForks.LightClientBootstrap; checkpointRoot: Uint8Array; } { const currentSyncCommittee = getInteropSyncCommittee(period).syncCommittee; diff --git a/packages/params/src/forkName.ts b/packages/params/src/forkName.ts index ad19f63d5dc6..23fe84ea6473 100644 --- a/packages/params/src/forkName.ts +++ b/packages/params/src/forkName.ts @@ -20,12 +20,22 @@ export enum ForkSeq { deneb = 4, } -export type ForkExecution = Exclude; +export type ForkLightClient = Exclude; +export function isForkLightClient(fork: ForkName): fork is ForkLightClient { + return fork !== ForkName.phase0; +} + +export type ForkExecution = Exclude; export function isForkExecution(fork: ForkName): fork is ForkExecution { - return fork !== ForkName.phase0 && fork !== ForkName.altair; + return isForkLightClient(fork) && fork !== ForkName.altair; +} + +export type ForkWithdrawals = Exclude; +export function isForkWithdrawals(fork: ForkName): fork is ForkWithdrawals { + return isForkExecution(fork) && fork !== ForkName.capella; } export type ForkBlobs = Exclude; export function isForkBlobs(fork: ForkName): fork is ForkBlobs { - return isForkExecution(fork) && fork !== ForkName.bellatrix && fork !== ForkName.capella; + return isForkWithdrawals(fork) && fork !== ForkName.capella; } diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index 4862d73ce280..8809a6b80ada 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -6,7 +6,15 @@ import {presetStatus} from "./presetStatus.js"; import {userSelectedPreset, userOverrides} from "./setPreset.js"; export {BeaconPreset} from "./interface.js"; -export {ForkName, ForkSeq, ForkExecution, ForkBlobs, isForkExecution, isForkBlobs} from "./forkName.js"; +export { + ForkName, + ForkSeq, + ForkExecution, + ForkBlobs, + isForkExecution, + isForkBlobs, + isForkLightClient, +} from "./forkName.js"; export {presetToJson} from "./json.js"; export {PresetName}; @@ -186,6 +194,21 @@ export const FINALIZED_ROOT_GINDEX = 105; */ export const FINALIZED_ROOT_DEPTH = 6; export const FINALIZED_ROOT_INDEX = 41; + +/** + * ```ts + * types.ssz.capella.BeaconBlockBody.getPathInfo(['executionPayload']).gindex + * ``` + */ +export const BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX = 25; +/** + * ```ts + * Math.floor(Math.log2(EXECUTION_PAYLOAD_GINDEX)) + * ``` + */ +export const BLOCK_BODY_EXECUTION_PAYLOAD_DEPTH = 4; +export const BLOCK_BODY_EXECUTION_PAYLOAD_INDEX = 9; + /** * ```ts * config.types.altair.BeaconState.getPathGindex(["nextSyncCommittee"]) diff --git a/packages/reqresp/src/protocols/LightClientBootstrap.ts b/packages/reqresp/src/protocols/LightClientBootstrap.ts index 07d60db63d80..b4ae6fac4096 100644 --- a/packages/reqresp/src/protocols/LightClientBootstrap.ts +++ b/packages/reqresp/src/protocols/LightClientBootstrap.ts @@ -1,10 +1,11 @@ -import {altair, Root, ssz} from "@lodestar/types"; +import {Root, ssz, allForks} from "@lodestar/types"; +import {isForkLightClient} from "@lodestar/params"; import {toHex} from "@lodestar/utils"; import {Encoding, ProtocolDefinitionGenerator} from "../types.js"; import {getContextBytesLightclient} from "./utils.js"; // eslint-disable-next-line @typescript-eslint/naming-convention -export const LightClientBootstrap: ProtocolDefinitionGenerator = ( +export const LightClientBootstrap: ProtocolDefinitionGenerator = ( modules, handler ) => { @@ -14,7 +15,10 @@ export const LightClientBootstrap: ProtocolDefinitionGenerator ssz.Root, - responseType: () => ssz.altair.LightClientBootstrap, + responseType: (forkName) => + isForkLightClient(forkName) + ? ssz.allForksLightClient[forkName].LightClientBootstrap + : ssz.altair.LightClientBootstrap, renderRequestBody: (req) => toHex(req), contextBytes: getContextBytesLightclient( (bootstrap) => modules.config.getForkName(bootstrap.header.beacon.slot), diff --git a/packages/reqresp/src/protocols/LightClientFinalityUpdate.ts b/packages/reqresp/src/protocols/LightClientFinalityUpdate.ts index e96c6c0d0fef..c3dcb5ce26c2 100644 --- a/packages/reqresp/src/protocols/LightClientFinalityUpdate.ts +++ b/packages/reqresp/src/protocols/LightClientFinalityUpdate.ts @@ -1,9 +1,10 @@ -import {altair, ssz} from "@lodestar/types"; +import {ssz, allForks} from "@lodestar/types"; +import {isForkLightClient} from "@lodestar/params"; import {Encoding, ProtocolDefinitionGenerator} from "../types.js"; import {getContextBytesLightclient} from "./utils.js"; // eslint-disable-next-line @typescript-eslint/naming-convention -export const LightClientFinalityUpdate: ProtocolDefinitionGenerator = ( +export const LightClientFinalityUpdate: ProtocolDefinitionGenerator = ( modules, handler ) => { @@ -13,7 +14,10 @@ export const LightClientFinalityUpdate: ProtocolDefinitionGenerator null, - responseType: () => ssz.altair.LightClientFinalityUpdate, + responseType: (forkName) => + isForkLightClient(forkName) + ? ssz.allForksLightClient[forkName].LightClientFinalityUpdate + : ssz.altair.LightClientFinalityUpdate, contextBytes: getContextBytesLightclient((update) => modules.config.getForkName(update.signatureSlot), modules), inboundRateLimits: { // Finality updates should not be requested more than once per epoch. diff --git a/packages/reqresp/src/protocols/LightClientOptimisticUpdate.ts b/packages/reqresp/src/protocols/LightClientOptimisticUpdate.ts index 19912a7708f4..ba97a92ece3e 100644 --- a/packages/reqresp/src/protocols/LightClientOptimisticUpdate.ts +++ b/packages/reqresp/src/protocols/LightClientOptimisticUpdate.ts @@ -1,9 +1,10 @@ -import {altair, ssz} from "@lodestar/types"; +import {ssz, allForks} from "@lodestar/types"; +import {isForkLightClient} from "@lodestar/params"; import {Encoding, ProtocolDefinitionGenerator} from "../types.js"; import {getContextBytesLightclient} from "./utils.js"; // eslint-disable-next-line @typescript-eslint/naming-convention -export const LightClientOptimisticUpdate: ProtocolDefinitionGenerator = ( +export const LightClientOptimisticUpdate: ProtocolDefinitionGenerator = ( modules, handler ) => { @@ -13,7 +14,10 @@ export const LightClientOptimisticUpdate: ProtocolDefinitionGenerator null, - responseType: () => ssz.altair.LightClientOptimisticUpdate, + responseType: (forkName) => + isForkLightClient(forkName) + ? ssz.allForksLightClient[forkName].LightClientOptimisticUpdate + : ssz.altair.LightClientOptimisticUpdate, contextBytes: getContextBytesLightclient((update) => modules.config.getForkName(update.signatureSlot), modules), inboundRateLimits: { // Optimistic updates should not be requested more than once per slot. diff --git a/packages/reqresp/src/protocols/LightClientUpdatesByRange.ts b/packages/reqresp/src/protocols/LightClientUpdatesByRange.ts index 74350e70a2cc..1f6594a6a7c3 100644 --- a/packages/reqresp/src/protocols/LightClientUpdatesByRange.ts +++ b/packages/reqresp/src/protocols/LightClientUpdatesByRange.ts @@ -1,12 +1,12 @@ -import {MAX_REQUEST_LIGHT_CLIENT_UPDATES} from "@lodestar/params"; -import {altair, ssz} from "@lodestar/types"; +import {MAX_REQUEST_LIGHT_CLIENT_UPDATES, isForkLightClient} from "@lodestar/params"; +import {altair, ssz, allForks} from "@lodestar/types"; import {Encoding, ProtocolDefinitionGenerator} from "../types.js"; import {getContextBytesLightclient} from "./utils.js"; // eslint-disable-next-line @typescript-eslint/naming-convention export const LightClientUpdatesByRange: ProtocolDefinitionGenerator< altair.LightClientUpdatesByRange, - altair.LightClientUpdate + allForks.LightClientUpdate > = (modules, handler) => { return { method: "light_client_updates_by_range", @@ -14,7 +14,8 @@ export const LightClientUpdatesByRange: ProtocolDefinitionGenerator< encoding: Encoding.SSZ_SNAPPY, handler, requestType: () => ssz.altair.LightClientUpdatesByRange, - responseType: () => ssz.altair.LightClientUpdate, + responseType: (forkName) => + isForkLightClient(forkName) ? ssz.allForksLightClient[forkName].LightClientUpdate : ssz.altair.LightClientUpdate, renderRequestBody: (req) => `${req.startPeriod},${req.count}`, contextBytes: getContextBytesLightclient((update) => modules.config.getForkName(update.signatureSlot), modules), inboundRateLimits: { diff --git a/packages/types/src/allForks/sszTypes.ts b/packages/types/src/allForks/sszTypes.ts index 37f755ff9eb0..d55e45269e0e 100644 --- a/packages/types/src/allForks/sszTypes.ts +++ b/packages/types/src/allForks/sszTypes.ts @@ -106,6 +106,49 @@ export const allForksBlinded = { }, }; +export const allForksLightClient = { + altair: { + BeaconBlock: altair.BeaconBlock, + BeaconBlockBody: altair.BeaconBlockBody, + LightClientHeader: altair.LightClientHeader, + LightClientBootstrap: altair.LightClientBootstrap, + LightClientUpdate: altair.LightClientUpdate, + LightClientFinalityUpdate: altair.LightClientFinalityUpdate, + LightClientOptimisticUpdate: altair.LightClientOptimisticUpdate, + LightClientStore: altair.LightClientStore, + }, + bellatrix: { + BeaconBlock: bellatrix.BeaconBlock, + BeaconBlockBody: bellatrix.BeaconBlockBody, + LightClientHeader: altair.LightClientHeader, + LightClientBootstrap: altair.LightClientBootstrap, + LightClientUpdate: altair.LightClientUpdate, + LightClientFinalityUpdate: altair.LightClientFinalityUpdate, + LightClientOptimisticUpdate: altair.LightClientOptimisticUpdate, + LightClientStore: altair.LightClientStore, + }, + capella: { + BeaconBlock: capella.BeaconBlock, + BeaconBlockBody: capella.BeaconBlockBody, + LightClientHeader: capella.LightClientHeader, + LightClientBootstrap: capella.LightClientBootstrap, + LightClientUpdate: capella.LightClientUpdate, + LightClientFinalityUpdate: capella.LightClientFinalityUpdate, + LightClientOptimisticUpdate: capella.LightClientOptimisticUpdate, + LightClientStore: capella.LightClientStore, + }, + deneb: { + BeaconBlock: deneb.BeaconBlock, + BeaconBlockBody: deneb.BeaconBlockBody, + LightClientHeader: deneb.LightClientHeader, + LightClientBootstrap: deneb.LightClientBootstrap, + LightClientUpdate: deneb.LightClientUpdate, + LightClientFinalityUpdate: deneb.LightClientFinalityUpdate, + LightClientOptimisticUpdate: deneb.LightClientOptimisticUpdate, + LightClientStore: deneb.LightClientStore, + }, +}; + export const allForksBlobs = { deneb: { SignedBeaconBlockAndBlobsSidecar: deneb.SignedBeaconBlockAndBlobsSidecar, diff --git a/packages/types/src/allForks/types.ts b/packages/types/src/allForks/types.ts index 0171a7254fd7..d03bb1e20d3d 100644 --- a/packages/types/src/allForks/types.ts +++ b/packages/types/src/allForks/types.ts @@ -71,6 +71,22 @@ export type FullOrBlindedSignedBeaconBlock = SignedBeaconBlock | SignedBlindedBe export type BuilderBid = bellatrix.BuilderBid | capella.BuilderBid | deneb.BuilderBid; export type SignedBuilderBid = bellatrix.SignedBuilderBid | capella.SignedBuilderBid | deneb.SignedBuilderBid; +export type LightClientHeader = altair.LightClientHeader | capella.LightClientHeader | deneb.LightClientHeader; +export type LightClientBootstrap = + | altair.LightClientBootstrap + | capella.LightClientBootstrap + | deneb.LightClientBootstrap; +export type LightClientUpdate = altair.LightClientUpdate | capella.LightClientUpdate | deneb.LightClientUpdate; +export type LightClientFinalityUpdate = + | altair.LightClientFinalityUpdate + | capella.LightClientFinalityUpdate + | deneb.LightClientFinalityUpdate; +export type LightClientOptimisticUpdate = + | altair.LightClientOptimisticUpdate + | capella.LightClientOptimisticUpdate + | deneb.LightClientOptimisticUpdate; +export type LightClientStore = altair.LightClientStore | capella.LightClientStore | deneb.LightClientStore; + export type SignedBeaconBlockAndBlobsSidecar = deneb.SignedBeaconBlockAndBlobsSidecar; /** * Types known to change between forks @@ -83,6 +99,7 @@ export type AllForksTypes = { Metadata: Metadata; ExecutionPayload: ExecutionPayload; ExecutionPayloadHeader: ExecutionPayloadHeader; + LightClientHeader: LightClientHeader; BuilderBid: BuilderBid; SignedBuilderBid: SignedBuilderBid; SignedBeaconBlockAndBlobsSidecar: SignedBeaconBlockAndBlobsSidecar; @@ -94,6 +111,21 @@ export type AllForksBlindedTypes = { SignedBeaconBlock: SignedBlindedBeaconBlock; }; +export type AllForksLightClient = { + BeaconBlock: altair.BeaconBlock | bellatrix.BeaconBlock | capella.BeaconBlock | deneb.BeaconBlock; + LightClientHeader: LightClientHeader; + LightClientBootstrap: LightClientBootstrap; + LightClientUpdate: LightClientUpdate; + LightClientFinalityUpdate: LightClientFinalityUpdate; + LightClientOptimisticUpdate: LightClientOptimisticUpdate; + LightClientStore: LightClientStore; +}; + +export type AllForksExecution = { + BeaconBlock: bellatrix.BeaconBlock | capella.BeaconBlock | deneb.BeaconBlock; + BeaconBlockBody: bellatrix.BeaconBlockBody | capella.BeaconBlockBody | deneb.BeaconBlockBody; +}; + /** * An AllForks type must accept as any parameter the UNION of all fork types. * The generic argument of `AllForksTypeOf` must be the union of the fork types: @@ -200,6 +232,45 @@ export type AllForksBlindedSSZTypes = { >; }; +export type AllForksLightClientSSZTypes = { + BeaconBlock: AllForksTypeOf< + | typeof altairSsz.BeaconBlock + | typeof bellatrixSsz.BeaconBlock + | typeof capellaSsz.BeaconBlock + | typeof denebSsz.BeaconBlock + >; + BeaconBlockBody: AllForksTypeOf< + | typeof altairSsz.BeaconBlockBody + | typeof bellatrixSsz.BeaconBlockBody + | typeof capellaSsz.BeaconBlockBody + | typeof denebSsz.BeaconBlockBody + >; + LightClientHeader: AllForksTypeOf< + typeof altairSsz.LightClientHeader | typeof capellaSsz.LightClientHeader | typeof denebSsz.LightClientHeader + >; + LightClientBootstrap: AllForksTypeOf< + | typeof altairSsz.LightClientBootstrap + | typeof capellaSsz.LightClientBootstrap + | typeof denebSsz.LightClientBootstrap + >; + LightClientUpdate: AllForksTypeOf< + typeof altairSsz.LightClientUpdate | typeof capellaSsz.LightClientUpdate | typeof denebSsz.LightClientUpdate + >; + LightClientFinalityUpdate: AllForksTypeOf< + | typeof altairSsz.LightClientFinalityUpdate + | typeof capellaSsz.LightClientFinalityUpdate + | typeof denebSsz.LightClientFinalityUpdate + >; + LightClientOptimisticUpdate: AllForksTypeOf< + | typeof altairSsz.LightClientOptimisticUpdate + | typeof capellaSsz.LightClientOptimisticUpdate + | typeof denebSsz.LightClientOptimisticUpdate + >; + LightClientStore: AllForksTypeOf< + typeof altairSsz.LightClientStore | typeof capellaSsz.LightClientStore | typeof denebSsz.LightClientStore + >; +}; + export type AllForksBlobsSSZTypes = { SignedBeaconBlockAndBlobsSidecar: AllForksTypeOf; }; diff --git a/packages/types/src/altair/types.ts b/packages/types/src/altair/types.ts index a805144e1def..f891bcca376f 100644 --- a/packages/types/src/altair/types.ts +++ b/packages/types/src/altair/types.ts @@ -14,9 +14,11 @@ export type BeaconBlockBody = ValueOf; export type BeaconBlock = ValueOf; export type SignedBeaconBlock = ValueOf; export type BeaconState = ValueOf; + export type LightClientHeader = ValueOf; export type LightClientBootstrap = ValueOf; export type LightClientUpdate = ValueOf; export type LightClientFinalityUpdate = ValueOf; export type LightClientOptimisticUpdate = ValueOf; +export type LightClientStore = ValueOf; export type LightClientUpdatesByRange = ValueOf; diff --git a/packages/types/src/capella/sszTypes.ts b/packages/types/src/capella/sszTypes.ts index 3ad637455531..858de7d635bd 100644 --- a/packages/types/src/capella/sszTypes.ts +++ b/packages/types/src/capella/sszTypes.ts @@ -1,5 +1,12 @@ -import {ContainerType, ListCompositeType} from "@chainsafe/ssz"; -import {HISTORICAL_ROOTS_LIMIT, MAX_WITHDRAWALS_PER_PAYLOAD, MAX_BLS_TO_EXECUTION_CHANGES} from "@lodestar/params"; +import {ContainerType, ListCompositeType, VectorCompositeType} from "@chainsafe/ssz"; +import { + HISTORICAL_ROOTS_LIMIT, + MAX_WITHDRAWALS_PER_PAYLOAD, + MAX_BLS_TO_EXECUTION_CHANGES, + BLOCK_BODY_EXECUTION_PAYLOAD_DEPTH as EXECUTION_PAYLOAD_DEPTH, + EPOCHS_PER_SYNC_COMMITTEE_PERIOD, + SLOTS_PER_EPOCH, +} from "@lodestar/params"; import {ssz as primitiveSsz} from "../primitive/index.js"; import {ssz as phase0Ssz} from "../phase0/index.js"; import {ssz as altairSsz} from "../altair/index.js"; @@ -16,6 +23,7 @@ const { ExecutionAddress, Gwei, UintBn256, + Bytes32, } = primitiveSsz; export const Withdrawal = new ContainerType( @@ -193,3 +201,62 @@ export const SignedBlindedBeaconBlock = new ContainerType( }, {typeName: "SignedBlindedBeaconBlock", jsonCase: "eth2"} ); + +export const LightClientHeader = new ContainerType( + { + beacon: phase0Ssz.BeaconBlockHeader, + execution: ExecutionPayloadHeader, + executionBranch: new VectorCompositeType(Bytes32, EXECUTION_PAYLOAD_DEPTH), + }, + {typeName: "LightClientHeader", jsonCase: "eth2"} +); + +export const LightClientBootstrap = new ContainerType( + { + header: LightClientHeader, + currentSyncCommittee: altairSsz.SyncCommittee, + currentSyncCommitteeBranch: altairSsz.LightClientBootstrap.fields.currentSyncCommitteeBranch, + }, + {typeName: "LightClientBootstrap", jsonCase: "eth2"} +); + +export const LightClientUpdate = new ContainerType( + { + attestedHeader: LightClientHeader, + nextSyncCommittee: altairSsz.SyncCommittee, + nextSyncCommitteeBranch: altairSsz.LightClientUpdate.fields.nextSyncCommitteeBranch, + finalizedHeader: LightClientHeader, + finalityBranch: altairSsz.LightClientUpdate.fields.finalityBranch, + syncAggregate: altairSsz.SyncAggregate, + signatureSlot: Slot, + }, + {typeName: "LightClientUpdate", jsonCase: "eth2"} +); + +export const LightClientFinalityUpdate = new ContainerType( + { + attestedHeader: LightClientHeader, + finalizedHeader: LightClientHeader, + finalityBranch: altairSsz.LightClientFinalityUpdate.fields.finalityBranch, + syncAggregate: altairSsz.SyncAggregate, + signatureSlot: Slot, + }, + {typeName: "LightClientFinalityUpdate", jsonCase: "eth2"} +); + +export const LightClientOptimisticUpdate = new ContainerType( + { + attestedHeader: LightClientHeader, + syncAggregate: altairSsz.SyncAggregate, + signatureSlot: Slot, + }, + {typeName: "LightClientOptimisticUpdate", jsonCase: "eth2"} +); + +export const LightClientStore = new ContainerType( + { + snapshot: LightClientBootstrap, + validUpdates: new ListCompositeType(LightClientUpdate, EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH), + }, + {typeName: "LightClientStore", jsonCase: "eth2"} +); diff --git a/packages/types/src/capella/types.ts b/packages/types/src/capella/types.ts index 2239a6d3537b..1dfb375fb358 100644 --- a/packages/types/src/capella/types.ts +++ b/packages/types/src/capella/types.ts @@ -22,3 +22,10 @@ export type FullOrBlindedExecutionPayload = ExecutionPayload | ExecutionPayloadH export type BuilderBid = ValueOf; export type SignedBuilderBid = ValueOf; + +export type LightClientHeader = ValueOf; +export type LightClientBootstrap = ValueOf; +export type LightClientUpdate = ValueOf; +export type LightClientFinalityUpdate = ValueOf; +export type LightClientOptimisticUpdate = ValueOf; +export type LightClientStore = ValueOf; diff --git a/packages/types/src/deneb/sszTypes.ts b/packages/types/src/deneb/sszTypes.ts index 9f79e2b23d74..221fd0e5dc7d 100644 --- a/packages/types/src/deneb/sszTypes.ts +++ b/packages/types/src/deneb/sszTypes.ts @@ -1,10 +1,13 @@ -import {ContainerType, ListCompositeType, ByteVectorType} from "@chainsafe/ssz"; +import {ContainerType, ListCompositeType, ByteVectorType, VectorCompositeType} from "@chainsafe/ssz"; import { HISTORICAL_ROOTS_LIMIT, FIELD_ELEMENTS_PER_BLOB, MAX_BLOBS_PER_BLOCK, MAX_REQUEST_BLOCKS, BYTES_PER_FIELD_ELEMENT, + BLOCK_BODY_EXECUTION_PAYLOAD_DEPTH as EXECUTION_PAYLOAD_DEPTH, + EPOCHS_PER_SYNC_COMMITTEE_PERIOD, + SLOTS_PER_EPOCH, } from "@lodestar/params"; import {ssz as primitiveSsz} from "../primitive/index.js"; import {ssz as phase0Ssz} from "../phase0/index.js"; @@ -239,3 +242,62 @@ export const BeaconState = new ContainerType( }, {typeName: "BeaconState", jsonCase: "eth2"} ); + +export const LightClientHeader = new ContainerType( + { + beacon: phase0Ssz.BeaconBlockHeader, + execution: ExecutionPayloadHeader, + executionBranch: new VectorCompositeType(Bytes32, EXECUTION_PAYLOAD_DEPTH), + }, + {typeName: "LightClientHeader", jsonCase: "eth2"} +); + +export const LightClientBootstrap = new ContainerType( + { + header: LightClientHeader, + currentSyncCommittee: altairSsz.SyncCommittee, + currentSyncCommitteeBranch: altairSsz.LightClientBootstrap.fields.currentSyncCommitteeBranch, + }, + {typeName: "LightClientBootstrap", jsonCase: "eth2"} +); + +export const LightClientUpdate = new ContainerType( + { + attestedHeader: LightClientHeader, + nextSyncCommittee: altairSsz.SyncCommittee, + nextSyncCommitteeBranch: altairSsz.LightClientUpdate.fields.nextSyncCommitteeBranch, + finalizedHeader: LightClientHeader, + finalityBranch: altairSsz.LightClientUpdate.fields.finalityBranch, + syncAggregate: altairSsz.SyncAggregate, + signatureSlot: Slot, + }, + {typeName: "LightClientUpdate", jsonCase: "eth2"} +); + +export const LightClientFinalityUpdate = new ContainerType( + { + attestedHeader: LightClientHeader, + finalizedHeader: LightClientHeader, + finalityBranch: altairSsz.LightClientFinalityUpdate.fields.finalityBranch, + syncAggregate: altairSsz.SyncAggregate, + signatureSlot: Slot, + }, + {typeName: "LightClientFinalityUpdate", jsonCase: "eth2"} +); + +export const LightClientOptimisticUpdate = new ContainerType( + { + attestedHeader: LightClientHeader, + syncAggregate: altairSsz.SyncAggregate, + signatureSlot: Slot, + }, + {typeName: "LightClientOptimisticUpdate", jsonCase: "eth2"} +); + +export const LightClientStore = new ContainerType( + { + snapshot: LightClientBootstrap, + validUpdates: new ListCompositeType(LightClientUpdate, EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH), + }, + {typeName: "LightClientStore", jsonCase: "eth2"} +); diff --git a/packages/types/src/deneb/types.ts b/packages/types/src/deneb/types.ts index b185d1afad0c..bb419bd3d00a 100644 --- a/packages/types/src/deneb/types.ts +++ b/packages/types/src/deneb/types.ts @@ -32,3 +32,10 @@ export type FullOrBlindedExecutionPayload = ExecutionPayload | ExecutionPayloadH export type BuilderBid = ValueOf; export type SignedBuilderBid = ValueOf; + +export type LightClientHeader = ValueOf; +export type LightClientBootstrap = ValueOf; +export type LightClientUpdate = ValueOf; +export type LightClientFinalityUpdate = ValueOf; +export type LightClientOptimisticUpdate = ValueOf; +export type LightClientStore = ValueOf; diff --git a/packages/types/src/sszTypes.ts b/packages/types/src/sszTypes.ts index 6a751fabfb6e..2a7df948a447 100644 --- a/packages/types/src/sszTypes.ts +++ b/packages/types/src/sszTypes.ts @@ -10,3 +10,4 @@ export const allForks = allForksSsz.allForks; export const allForksBlinded = allForksSsz.allForksBlinded; export const allForksExecution = allForksSsz.allForksExecution; export const allForksBlobs = allForksSsz.allForksBlobs; +export const allForksLightClient = allForksSsz.allForksLightClient;