Skip to content

Commit

Permalink
Merge 6ea5c53 into 8c42768
Browse files Browse the repository at this point in the history
  • Loading branch information
dadepo authored Oct 28, 2022
2 parents 8c42768 + 6ea5c53 commit 14fe5af
Show file tree
Hide file tree
Showing 50 changed files with 1,240 additions and 182 deletions.
32 changes: 13 additions & 19 deletions packages/api/src/beacon/routes/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,6 @@ import {RouteDef, TypeJson} from "../../utils/index.js";

// See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes

export type LightclientOptimisticHeaderUpdate = {
syncAggregate: altair.SyncAggregate;
attestedHeader: phase0.BeaconBlockHeader;
};

export type LightclientFinalizedUpdate = {
attestedHeader: phase0.BeaconBlockHeader;
finalizedHeader: phase0.BeaconBlockHeader;
finalityBranch: Uint8Array[];
syncAggregate: altair.SyncAggregate;
};

export enum EventType {
/**
* The node has finished processing, resulting in a new head. previous_duty_dependent_root is
Expand All @@ -38,9 +26,11 @@ export enum EventType {
/** The node has received a valid sync committee SignedContributionAndProof (from P2P or API) */
contributionAndProof = "contribution_and_proof",
/** New or better optimistic header update available */
lightclientOptimisticUpdate = "light_client_optimistic_update",
/** New or better finalized update available */
lightclientFinalizedUpdate = "light_client_finalized_update",
lightClientOptimisticUpdate = "light_client_optimistic_update",
/** New or better finality update available */
lightClientFinalityUpdate = "light_client_finality_update",
/** New or better light client update available */
lightClientUpdate = "light_client_update",
}

export type EventData = {
Expand Down Expand Up @@ -77,8 +67,9 @@ export type EventData = {
executionOptimistic: boolean;
};
[EventType.contributionAndProof]: altair.SignedContributionAndProof;
[EventType.lightclientOptimisticUpdate]: LightclientOptimisticHeaderUpdate;
[EventType.lightclientFinalizedUpdate]: LightclientFinalizedUpdate;
[EventType.lightClientOptimisticUpdate]: altair.LightClientOptimisticUpdate;
[EventType.lightClientFinalityUpdate]: altair.LightClientFinalityUpdate;
[EventType.lightClientUpdate]: altair.LightClientUpdate;
};

export type BeaconEvent = {[K in EventType]: {type: K; message: EventData[K]}}[EventType];
Expand Down Expand Up @@ -163,22 +154,25 @@ export function getTypeByEvent(): {[K in EventType]: Type<EventData[K]>} {

[EventType.contributionAndProof]: ssz.altair.SignedContributionAndProof,

[EventType.lightclientOptimisticUpdate]: new ContainerType(
[EventType.lightClientOptimisticUpdate]: new ContainerType(
{
syncAggregate: ssz.altair.SyncAggregate,
attestedHeader: ssz.phase0.BeaconBlockHeader,
signatureSlot: ssz.Slot,
},
{jsonCase: "eth2"}
),
[EventType.lightclientFinalizedUpdate]: new ContainerType(
[EventType.lightClientFinalityUpdate]: new ContainerType(
{
attestedHeader: ssz.phase0.BeaconBlockHeader,
finalizedHeader: ssz.phase0.BeaconBlockHeader,
finalityBranch: new VectorCompositeType(ssz.Bytes32, FINALIZED_ROOT_DEPTH),
syncAggregate: ssz.altair.SyncAggregate,
signatureSlot: ssz.Slot,
},
{jsonCase: "eth2"}
),
[EventType.lightClientUpdate]: ssz.altair.LightClientUpdate,
};
}

Expand Down
52 changes: 10 additions & 42 deletions packages/api/src/beacon/routes/lightclient.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {ContainerType, JsonPath, VectorCompositeType} from "@chainsafe/ssz";
import {JsonPath} from "@chainsafe/ssz";
import {Proof} from "@chainsafe/persistent-merkle-tree";
import {FINALIZED_ROOT_DEPTH} from "@lodestar/params";
import {altair, phase0, ssz, SyncPeriod} from "@lodestar/types";
import {
ArrayOf,
Expand All @@ -14,14 +13,10 @@ import {
ReqEmpty,
} from "../../utils/index.js";
import {queryParseProofPathsArr, querySerializeProofPathsArr} from "../../utils/serdes.js";
import {LightclientOptimisticHeaderUpdate, LightclientFinalizedUpdate} from "./events.js";

// Re-export for convenience when importing routes.lightclient.LightclientOptimisticHeaderUpdate
export {LightclientOptimisticHeaderUpdate, LightclientFinalizedUpdate};

// See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes

export type LightclientSnapshotWithProof = {
export type LightClientBootstrap = {
header: phase0.BeaconBlockHeader;
currentSyncCommittee: altair.SyncCommittee;
/** Single branch proof from state root to currentSyncCommittee */
Expand All @@ -46,14 +41,14 @@ export type Api = {
* 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<{data: LightclientOptimisticHeaderUpdate}>;
getFinalityUpdate(): Promise<{data: LightclientFinalizedUpdate}>;
getOptimisticUpdate(): Promise<{data: altair.LightClientOptimisticUpdate}>;
getFinalityUpdate(): Promise<{data: altair.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<{data: LightclientSnapshotWithProof}>;
getBootstrap(blockRoot: string): Promise<{data: LightClientBootstrap}>;
};

/**
Expand All @@ -62,8 +57,8 @@ export type Api = {
export const routesData: RoutesData<Api> = {
getStateProof: {url: "/eth/v1/beacon/light_client/proof/{state_id}", method: "GET"},
getUpdates: {url: "/eth/v1/beacon/light_client/updates", method: "GET"},
getOptimisticUpdate: {url: "/eth/v1/beacon/light_client/optimistic_update/", method: "GET"},
getFinalityUpdate: {url: "/eth/v1/beacon/light_client/finality_update/", method: "GET"},
getOptimisticUpdate: {url: "/eth/v1/beacon/light_client/optimistic_update", method: "GET"},
getFinalityUpdate: {url: "/eth/v1/beacon/light_client/finality_update", method: "GET"},
getBootstrap: {url: "/eth/v1/beacon/light_client/bootstrap/{block_root}", method: "GET"},
};

Expand Down Expand Up @@ -102,39 +97,12 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
}

export function getReturnTypes(): ReturnTypes<Api> {
const lightclientSnapshotWithProofType = new ContainerType(
{
header: ssz.phase0.BeaconBlockHeader,
currentSyncCommittee: ssz.altair.SyncCommittee,
currentSyncCommitteeBranch: new VectorCompositeType(ssz.Root, 5),
},
{jsonCase: "eth2"}
);

const lightclientHeaderUpdate = new ContainerType(
{
syncAggregate: ssz.altair.SyncAggregate,
attestedHeader: ssz.phase0.BeaconBlockHeader,
},
{jsonCase: "eth2"}
);

const lightclientFinalizedUpdate = new ContainerType(
{
attestedHeader: ssz.phase0.BeaconBlockHeader,
finalizedHeader: ssz.phase0.BeaconBlockHeader,
finalityBranch: new VectorCompositeType(ssz.Bytes32, FINALIZED_ROOT_DEPTH),
syncAggregate: ssz.altair.SyncAggregate,
},
{jsonCase: "eth2"}
);

return {
// Just sent the proof JSON as-is
getStateProof: sameType(),
getUpdates: ContainerData(ArrayOf(ssz.altair.LightClientUpdate)),
getOptimisticUpdate: ContainerData(lightclientHeaderUpdate),
getFinalityUpdate: ContainerData(lightclientFinalizedUpdate),
getBootstrap: ContainerData(lightclientSnapshotWithProofType),
getOptimisticUpdate: ContainerData(ssz.altair.LightClientOptimisticUpdate),
getFinalityUpdate: ContainerData(ssz.altair.LightClientFinalityUpdate),
getBootstrap: ContainerData(ssz.altair.LightClientBootstrap),
};
}
7 changes: 5 additions & 2 deletions packages/api/test/unit/beacon/testData/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,17 @@ export const eventTestData: EventData = {
signature:
"0xac118511474a94f857300b315c50585c32a713e4452e26a6bb98cdb619936370f126ed3b6bb64469259ee92e69791d9e12d324ce6fd90081680ce72f39d85d50b0ff977260a8667465e613362c6d6e6e745e1f9323ec1d6f16041c4e358839ac",
}),
[EventType.lightclientOptimisticUpdate]: {
[EventType.lightClientOptimisticUpdate]: {
syncAggregate: ssz.altair.SyncAggregate.defaultValue(),
attestedHeader: ssz.phase0.BeaconBlockHeader.defaultValue(),
signatureSlot: ssz.Slot.defaultValue(),
},
[EventType.lightclientFinalizedUpdate]: {
[EventType.lightClientFinalityUpdate]: {
attestedHeader: ssz.phase0.BeaconBlockHeader.defaultValue(),
finalizedHeader: ssz.phase0.BeaconBlockHeader.defaultValue(),
finalityBranch: [root],
syncAggregate: ssz.altair.SyncAggregate.defaultValue(),
signatureSlot: ssz.Slot.defaultValue(),
},
[EventType.lightClientUpdate]: ssz.altair.LightClientUpdate.defaultValue(),
};
4 changes: 3 additions & 1 deletion packages/api/test/unit/beacon/testData/lightclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const root = Uint8Array.from(Buffer.alloc(32, 1));
const lightClientUpdate = ssz.altair.LightClientUpdate.defaultValue();
const syncAggregate = ssz.altair.SyncAggregate.defaultValue();
const header = ssz.phase0.BeaconBlockHeader.defaultValue();
const signatureSlot = ssz.Slot.defaultValue();

export const testData: GenericServerTestCases<Api> = {
getStateProof: {
Expand Down Expand Up @@ -41,7 +42,7 @@ export const testData: GenericServerTestCases<Api> = {
},
getOptimisticUpdate: {
args: [],
res: {data: {syncAggregate, attestedHeader: header}},
res: {data: {syncAggregate, attestedHeader: header, signatureSlot}},
},
getFinalityUpdate: {
args: [],
Expand All @@ -51,6 +52,7 @@ export const testData: GenericServerTestCases<Api> = {
attestedHeader: header,
finalizedHeader: lightClientUpdate.finalizedHeader,
finalityBranch: lightClientUpdate.finalityBranch,
signatureSlot: lightClientUpdate.attestedHeader.slot + 1,
},
},
},
Expand Down
10 changes: 6 additions & 4 deletions packages/beacon-node/src/api/impl/events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ const chainEventMap = {
[routes.events.EventType.finalizedCheckpoint]: ChainEvent.finalized as const,
[routes.events.EventType.chainReorg]: ChainEvent.forkChoiceReorg as const,
[routes.events.EventType.contributionAndProof]: ChainEvent.contributionAndProof as const,
[routes.events.EventType.lightclientOptimisticUpdate]: ChainEvent.lightclientOptimisticUpdate as const,
[routes.events.EventType.lightclientFinalizedUpdate]: ChainEvent.lightclientFinalizedUpdate as const,
[routes.events.EventType.lightClientOptimisticUpdate]: ChainEvent.lightClientOptimisticUpdate as const,
[routes.events.EventType.lightClientFinalityUpdate]: ChainEvent.lightClientFinalityUpdate as const,
[routes.events.EventType.lightClientUpdate]: ChainEvent.lightClientUpdate as const,
};

export function getEventsApi({chain, config}: Pick<ApiModules, "chain" | "config">): routes.events.Api {
Expand Down Expand Up @@ -60,8 +61,9 @@ export function getEventsApi({chain, config}: Pick<ApiModules, "chain" | "config
},
],
[routes.events.EventType.contributionAndProof]: (contributionAndProof) => [contributionAndProof],
[routes.events.EventType.lightclientOptimisticUpdate]: (headerUpdate) => [headerUpdate],
[routes.events.EventType.lightclientFinalizedUpdate]: (headerUpdate) => [headerUpdate],
[routes.events.EventType.lightClientOptimisticUpdate]: (headerUpdate) => [headerUpdate],
[routes.events.EventType.lightClientFinalityUpdate]: (headerUpdate) => [headerUpdate],
[routes.events.EventType.lightClientUpdate]: (headerUpdate) => [headerUpdate],
};

return {
Expand Down
22 changes: 16 additions & 6 deletions packages/beacon-node/src/api/impl/lightclient/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {routes} from "@lodestar/api";
import {fromHexString} from "@chainsafe/ssz";
import {ProofType, Tree} from "@chainsafe/persistent-merkle-tree";
import {SyncPeriod} from "@lodestar/types";
import {MAX_REQUEST_LIGHT_CLIENT_UPDATES} from "@lodestar/params";
import {ApiModules} from "../types.js";
import {resolveStateId} from "../beacon/state/utils.js";
import {IApiOptions} from "../../options.js";
Expand Down Expand Up @@ -41,19 +43,27 @@ export function getLightclientApi(
};
},

// eslint-disable-next-line @typescript-eslint/naming-convention
async getUpdates(start_period, count) {
const periods = Array.from({length: count}, (_ignored, i) => i + start_period);
const updates = await Promise.all(periods.map((period) => chain.lightClientServer.getUpdates(period)));
async getUpdates(startPeriod: SyncPeriod, count: number) {
const maxAllowedCount = Math.min(MAX_REQUEST_LIGHT_CLIENT_UPDATES, count);
const periods = Array.from({length: maxAllowedCount}, (_ignored, i) => i + startPeriod);
const updates = await Promise.all(periods.map((period) => chain.lightClientServer.getUpdate(period)));
return {data: updates};
},

async getOptimisticUpdate() {
return {data: await chain.lightClientServer.getOptimisticUpdate()};
const data = chain.lightClientServer.getOptimisticUpdate();
if (data === null) {
throw Error("No optimistic update available");
}
return {data};
},

async getFinalityUpdate() {
return {data: await chain.lightClientServer.getFinalityUpdate()};
const data = chain.lightClientServer.getFinalityUpdate();
if (data === null) {
throw Error("No finality update available");
}
return {data};
},

async getBootstrap(blockRoot) {
Expand Down
13 changes: 9 additions & 4 deletions packages/beacon-node/src/chain/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,15 @@ export enum ChainEvent {
/**
* A new lightclient optimistic header update is available to be broadcasted to connected light-clients
*/
lightclientOptimisticUpdate = "lightclient:header_update",
lightClientOptimisticUpdate = "lightclient:header_update",
/**
* A new lightclient finalized header update is available to be broadcasted to connected light-clients
*/
lightclientFinalizedUpdate = "lightclient:finalized_update",
lightClientFinalityUpdate = "lightclient:finality_update",
/**
* A new lightclient update is available to be broadcasted to connected light-clients
*/
lightClientUpdate = "lightclient:update",
}

export type HeadEventData = routes.events.EventData[routes.events.EventType.head];
Expand All @@ -114,8 +118,9 @@ export interface IChainEvents {
[ChainEvent.forkChoiceJustified]: (checkpoint: CheckpointWithHex) => void;
[ChainEvent.forkChoiceFinalized]: (checkpoint: CheckpointWithHex) => void;

[ChainEvent.lightclientOptimisticUpdate]: (optimisticUpdate: routes.events.LightclientOptimisticHeaderUpdate) => void;
[ChainEvent.lightclientFinalizedUpdate]: (finalizedUpdate: routes.events.LightclientFinalizedUpdate) => void;
[ChainEvent.lightClientOptimisticUpdate]: (optimisticUpdate: altair.LightClientOptimisticUpdate) => void;
[ChainEvent.lightClientFinalityUpdate]: (finalizedUpdate: altair.LightClientFinalityUpdate) => void;
[ChainEvent.lightClientUpdate]: (update: altair.LightClientUpdate) => void;
}

/**
Expand Down
30 changes: 30 additions & 0 deletions packages/beacon-node/src/chain/errors/lightClientError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {LodestarError} from "@lodestar/utils";
import {GossipActionError} from "./gossipValidation.js";

export enum LightClientErrorCode {
FINALITY_UPDATE_ALREADY_FORWARDED = "FINALITY_UPDATE_ALREADY_FORWARDED",
OPTIMISTIC_UPDATE_ALREADY_FORWARDED = "OPTIMISTIC_UPDATE_ALREADY_FORWARDED",
FINALITY_UPDATE_RECEIVED_TOO_EARLY = "FINALITY_UPDATE_RECEIVED_TOO_EARLY",
OPTIMISTIC_UPDATE_RECEIVED_TOO_EARLY = "OPTIMISTIC_UPDATE_RECEIVED_TOO_EARLY",
FINALITY_UPDATE_NOT_MATCHING_LOCAL = "FINALITY_UPDATE_NOT_MATCHING_LOCAL",
OPTIMISTIC_UPDATE_NOT_MATCHING_LOCAL = "OPTIMISTIC_UPDATE_NOT_MATCHING_LOCAL",
}
export type LightClientErrorType =
| {code: LightClientErrorCode.FINALITY_UPDATE_ALREADY_FORWARDED}
| {code: LightClientErrorCode.OPTIMISTIC_UPDATE_ALREADY_FORWARDED}
| {code: LightClientErrorCode.FINALITY_UPDATE_RECEIVED_TOO_EARLY}
| {code: LightClientErrorCode.OPTIMISTIC_UPDATE_RECEIVED_TOO_EARLY}
| {code: LightClientErrorCode.FINALITY_UPDATE_NOT_MATCHING_LOCAL}
| {code: LightClientErrorCode.OPTIMISTIC_UPDATE_NOT_MATCHING_LOCAL};

export class LightClientError extends GossipActionError<LightClientErrorType> {}

// Errors for the light client server

export enum LightClientServerErrorCode {
RESOURCE_UNAVAILABLE = "RESOURCE_UNAVALIABLE",
}

export type LightClientServerErrorType = {code: LightClientServerErrorCode.RESOURCE_UNAVAILABLE};

export class LightClientServerError extends LodestarError<LightClientServerErrorType> {}
Loading

0 comments on commit 14fe5af

Please sign in to comment.