Skip to content

Commit

Permalink
feat: add ssz support to LC updates by range endpoint (#7119)
Browse files Browse the repository at this point in the history
  • Loading branch information
nflaig authored Oct 11, 2024
1 parent cbc7c90 commit 105a388
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 12 deletions.
62 changes: 53 additions & 9 deletions packages/api/src/beacon/routes/lightclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import {
ssz,
SyncPeriod,
} from "@lodestar/types";
import {ForkName} from "@lodestar/params";
import {ChainForkConfig} from "@lodestar/config";
import {fromHex} from "@lodestar/utils";
import {ForkName, ZERO_HASH} from "@lodestar/params";
import {BeaconConfig, ChainForkConfig, createBeaconConfig} from "@lodestar/config";
import {genesisData, NetworkName} from "@lodestar/config/networks";
import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js";
import {VersionCodec, VersionMeta} from "../../utils/metadata.js";
import {MetaHeader, VersionCodec, VersionMeta} from "../../utils/metadata.js";
import {getLightClientForkTypes, toForkName} from "../../utils/fork.js";
import {
EmptyArgs,
Expand All @@ -19,7 +21,6 @@ import {
EmptyMetaCodec,
EmptyRequest,
WithVersion,
JsonOnlyResp,
} from "../../utils/codecs.js";

// See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes
Expand Down Expand Up @@ -90,7 +91,18 @@ export type Endpoints = {
>;
};

export function getDefinitions(_config: ChainForkConfig): RouteDefinitions<Endpoints> {
export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoints> {
// Cache config so fork digests don't need to be recomputed
let beaconConfig: BeaconConfig | undefined;

const cachedBeaconConfig = (): BeaconConfig => {
if (beaconConfig === undefined) {
const genesisValidatorsRoot = genesisData[config.CONFIG_NAME as NetworkName]?.genesisValidatorsRoot;
beaconConfig = createBeaconConfig(config, genesisValidatorsRoot ? fromHex(genesisValidatorsRoot) : ZERO_HASH);
}
return beaconConfig;
};

return {
getLightClientUpdatesByRange: {
url: "/eth/v1/beacon/light_client/updates",
Expand All @@ -100,7 +112,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions<Endpo
parseReq: ({query}) => ({startPeriod: query.start_period, count: query.count}),
schema: {query: {start_period: Schema.UintRequired, count: Schema.UintRequired}},
},
resp: JsonOnlyResp({
resp: {
data: {
toJson: (data, meta) => {
const json: unknown[] = [];
Expand All @@ -118,12 +130,44 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions<Endpo
}
return value;
},
serialize: (data, meta) => {
const chunks: Uint8Array[] = [];
for (const [i, update] of data.entries()) {
const version = meta.versions[i];
const forkDigest = cachedBeaconConfig().forkName2ForkDigest(version);
const serialized = getLightClientForkTypes(version).LightClientUpdate.serialize(update);
const length = ssz.UintNum64.serialize(4 + serialized.length);
chunks.push(length, forkDigest, serialized);
}
return Buffer.concat(chunks);
},
deserialize: (data) => {
let offset = 0;
const updates: LightClientUpdate[] = [];
while (offset < data.length) {
const length = ssz.UintNum64.deserialize(data.subarray(offset, offset + 8));
const forkDigest = ssz.ForkDigest.deserialize(data.subarray(offset + 8, offset + 12));
const version = cachedBeaconConfig().forkDigest2ForkName(forkDigest);
updates.push(
getLightClientForkTypes(version).LightClientUpdate.deserialize(
data.subarray(offset + 12, offset + 8 + length)
)
);
offset += 8 + length;
}
return updates;
},
},
meta: {
toJson: (meta) => meta,
fromJson: (val) => val as {versions: ForkName[]},
toHeadersObject: () => ({}),
fromHeaders: () => ({versions: []}),
toHeadersObject: (meta) => ({
[MetaHeader.Version]: meta.versions.join(","),
}),
fromHeaders: (headers) => {
const versions = headers.getOrDefault(MetaHeader.Version, "");
return {versions: versions === "" ? [] : (versions.split(",") as ForkName[])};
},
},
transform: {
toResponse: (data, meta) => {
Expand All @@ -147,7 +191,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions<Endpo
return {data: updates, meta};
},
},
}),
},
},
getLightClientOptimisticUpdate: {
url: "/eth/v1/beacon/light_client/optimistic_update",
Expand Down
2 changes: 0 additions & 2 deletions packages/api/test/unit/beacon/oapiSpec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ const ignoredOperations = [
/* missing route */
"getDepositSnapshot", // Won't fix for now, see https://github.com/ChainSafe/lodestar/issues/5697
"getNextWithdrawals", // https://github.com/ChainSafe/lodestar/issues/5696
/* Must support ssz response body */
"getLightClientUpdatesByRange", // https://github.com/ChainSafe/lodestar/issues/6841
];

const ignoredProperties: Record<string, IgnoredProperty> = {
Expand Down
2 changes: 1 addition & 1 deletion packages/api/test/unit/beacon/testData/lightclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const signatureSlot = ssz.Slot.defaultValue();
export const testData: GenericServerTestCases<Endpoints> = {
getLightClientUpdatesByRange: {
args: {startPeriod: 1, count: 2},
res: {data: [lightClientUpdate], meta: {versions: [ForkName.bellatrix]}},
res: {data: [lightClientUpdate, lightClientUpdate], meta: {versions: [ForkName.altair, ForkName.altair]}},
},
getLightClientOptimisticUpdate: {
args: undefined,
Expand Down

0 comments on commit 105a388

Please sign in to comment.