Skip to content

Commit 105a388

Browse files
authored
feat: add ssz support to LC updates by range endpoint (#7119)
1 parent cbc7c90 commit 105a388

File tree

3 files changed

+54
-12
lines changed

3 files changed

+54
-12
lines changed

packages/api/src/beacon/routes/lightclient.ts

+53-9
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import {
77
ssz,
88
SyncPeriod,
99
} from "@lodestar/types";
10-
import {ForkName} from "@lodestar/params";
11-
import {ChainForkConfig} from "@lodestar/config";
10+
import {fromHex} from "@lodestar/utils";
11+
import {ForkName, ZERO_HASH} from "@lodestar/params";
12+
import {BeaconConfig, ChainForkConfig, createBeaconConfig} from "@lodestar/config";
13+
import {genesisData, NetworkName} from "@lodestar/config/networks";
1214
import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js";
13-
import {VersionCodec, VersionMeta} from "../../utils/metadata.js";
15+
import {MetaHeader, VersionCodec, VersionMeta} from "../../utils/metadata.js";
1416
import {getLightClientForkTypes, toForkName} from "../../utils/fork.js";
1517
import {
1618
EmptyArgs,
@@ -19,7 +21,6 @@ import {
1921
EmptyMetaCodec,
2022
EmptyRequest,
2123
WithVersion,
22-
JsonOnlyResp,
2324
} from "../../utils/codecs.js";
2425

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

93-
export function getDefinitions(_config: ChainForkConfig): RouteDefinitions<Endpoints> {
94+
export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoints> {
95+
// Cache config so fork digests don't need to be recomputed
96+
let beaconConfig: BeaconConfig | undefined;
97+
98+
const cachedBeaconConfig = (): BeaconConfig => {
99+
if (beaconConfig === undefined) {
100+
const genesisValidatorsRoot = genesisData[config.CONFIG_NAME as NetworkName]?.genesisValidatorsRoot;
101+
beaconConfig = createBeaconConfig(config, genesisValidatorsRoot ? fromHex(genesisValidatorsRoot) : ZERO_HASH);
102+
}
103+
return beaconConfig;
104+
};
105+
94106
return {
95107
getLightClientUpdatesByRange: {
96108
url: "/eth/v1/beacon/light_client/updates",
@@ -100,7 +112,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions<Endpo
100112
parseReq: ({query}) => ({startPeriod: query.start_period, count: query.count}),
101113
schema: {query: {start_period: Schema.UintRequired, count: Schema.UintRequired}},
102114
},
103-
resp: JsonOnlyResp({
115+
resp: {
104116
data: {
105117
toJson: (data, meta) => {
106118
const json: unknown[] = [];
@@ -118,12 +130,44 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions<Endpo
118130
}
119131
return value;
120132
},
133+
serialize: (data, meta) => {
134+
const chunks: Uint8Array[] = [];
135+
for (const [i, update] of data.entries()) {
136+
const version = meta.versions[i];
137+
const forkDigest = cachedBeaconConfig().forkName2ForkDigest(version);
138+
const serialized = getLightClientForkTypes(version).LightClientUpdate.serialize(update);
139+
const length = ssz.UintNum64.serialize(4 + serialized.length);
140+
chunks.push(length, forkDigest, serialized);
141+
}
142+
return Buffer.concat(chunks);
143+
},
144+
deserialize: (data) => {
145+
let offset = 0;
146+
const updates: LightClientUpdate[] = [];
147+
while (offset < data.length) {
148+
const length = ssz.UintNum64.deserialize(data.subarray(offset, offset + 8));
149+
const forkDigest = ssz.ForkDigest.deserialize(data.subarray(offset + 8, offset + 12));
150+
const version = cachedBeaconConfig().forkDigest2ForkName(forkDigest);
151+
updates.push(
152+
getLightClientForkTypes(version).LightClientUpdate.deserialize(
153+
data.subarray(offset + 12, offset + 8 + length)
154+
)
155+
);
156+
offset += 8 + length;
157+
}
158+
return updates;
159+
},
121160
},
122161
meta: {
123162
toJson: (meta) => meta,
124163
fromJson: (val) => val as {versions: ForkName[]},
125-
toHeadersObject: () => ({}),
126-
fromHeaders: () => ({versions: []}),
164+
toHeadersObject: (meta) => ({
165+
[MetaHeader.Version]: meta.versions.join(","),
166+
}),
167+
fromHeaders: (headers) => {
168+
const versions = headers.getOrDefault(MetaHeader.Version, "");
169+
return {versions: versions === "" ? [] : (versions.split(",") as ForkName[])};
170+
},
127171
},
128172
transform: {
129173
toResponse: (data, meta) => {
@@ -147,7 +191,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions<Endpo
147191
return {data: updates, meta};
148192
},
149193
},
150-
}),
194+
},
151195
},
152196
getLightClientOptimisticUpdate: {
153197
url: "/eth/v1/beacon/light_client/optimistic_update",

packages/api/test/unit/beacon/oapiSpec.test.ts

-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,6 @@ const ignoredOperations = [
5555
/* missing route */
5656
"getDepositSnapshot", // Won't fix for now, see https://github.com/ChainSafe/lodestar/issues/5697
5757
"getNextWithdrawals", // https://github.com/ChainSafe/lodestar/issues/5696
58-
/* Must support ssz response body */
59-
"getLightClientUpdatesByRange", // https://github.com/ChainSafe/lodestar/issues/6841
6058
];
6159

6260
const ignoredProperties: Record<string, IgnoredProperty> = {

packages/api/test/unit/beacon/testData/lightclient.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const signatureSlot = ssz.Slot.defaultValue();
1414
export const testData: GenericServerTestCases<Endpoints> = {
1515
getLightClientUpdatesByRange: {
1616
args: {startPeriod: 1, count: 2},
17-
res: {data: [lightClientUpdate], meta: {versions: [ForkName.bellatrix]}},
17+
res: {data: [lightClientUpdate, lightClientUpdate], meta: {versions: [ForkName.altair, ForkName.altair]}},
1818
},
1919
getLightClientOptimisticUpdate: {
2020
args: undefined,

0 commit comments

Comments
 (0)