From 105a38808f91db1d1a1ecb206496df1c81c49869 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 11 Oct 2024 17:19:13 +0100 Subject: [PATCH] feat: add ssz support to LC updates by range endpoint (#7119) --- packages/api/src/beacon/routes/lightclient.ts | 62 ++++++++++++++++--- .../api/test/unit/beacon/oapiSpec.test.ts | 2 - .../test/unit/beacon/testData/lightclient.ts | 2 +- 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/packages/api/src/beacon/routes/lightclient.ts b/packages/api/src/beacon/routes/lightclient.ts index decf8982b654..bac73b43ddb2 100644 --- a/packages/api/src/beacon/routes/lightclient.ts +++ b/packages/api/src/beacon/routes/lightclient.ts @@ -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, @@ -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 @@ -90,7 +91,18 @@ export type Endpoints = { >; }; -export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { +export function getDefinitions(config: ChainForkConfig): RouteDefinitions { + // 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", @@ -100,7 +112,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions ({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[] = []; @@ -118,12 +130,44 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { + 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) => { @@ -147,7 +191,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions = { diff --git a/packages/api/test/unit/beacon/testData/lightclient.ts b/packages/api/test/unit/beacon/testData/lightclient.ts index b86e1f338329..f405d514fc5d 100644 --- a/packages/api/test/unit/beacon/testData/lightclient.ts +++ b/packages/api/test/unit/beacon/testData/lightclient.ts @@ -14,7 +14,7 @@ const signatureSlot = ssz.Slot.defaultValue(); export const testData: GenericServerTestCases = { 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,