From b45bdd8dc02fe5e016a9aaf7cbdda118d1713cdf Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:52:27 -0500 Subject: [PATCH 01/17] feat: add new getHistoricalSummaries endpoint to debug namespace --- packages/api/src/beacon/routes/debug.ts | 17 +++++++++++++++++ .../beacon-node/src/api/impl/debug/index.ts | 16 +++++++++++++++- packages/types/src/capella/sszTypes.ts | 6 +++++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/packages/api/src/beacon/routes/debug.ts b/packages/api/src/beacon/routes/debug.ts index 9afc96d5a637..947958868a70 100644 --- a/packages/api/src/beacon/routes/debug.ts +++ b/packages/api/src/beacon/routes/debug.ts @@ -135,6 +135,13 @@ export type Endpoints = { BeaconState, ExecutionOptimisticFinalizedAndVersionMeta >; + getHistoricalSummaries: Endpoint< + "GET", + EmptyArgs, + EmptyRequest, + ValueOf, + EmptyMeta + >; }; export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { @@ -196,5 +203,15 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions Date: Wed, 27 Nov 2024 05:30:48 -0500 Subject: [PATCH 02/17] Add JSON response --- packages/api/src/beacon/routes/debug.ts | 13 +++---------- packages/beacon-node/src/api/impl/debug/index.ts | 7 +++++-- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/api/src/beacon/routes/debug.ts b/packages/api/src/beacon/routes/debug.ts index 947958868a70..9d5888e2e9c9 100644 --- a/packages/api/src/beacon/routes/debug.ts +++ b/packages/api/src/beacon/routes/debug.ts @@ -85,7 +85,7 @@ const DebugChainHeadListType = ArrayOf(DebugChainHeadType); type ProtoNodeList = ValueOf; type DebugChainHeadList = ValueOf; type ForkChoiceResponse = ValueOf; - +type HistoricalSummariesResponse = ValueOf; export type Endpoints = { /** * Retrieves all possible chain heads (leaves of fork choice tree). @@ -135,13 +135,7 @@ export type Endpoints = { BeaconState, ExecutionOptimisticFinalizedAndVersionMeta >; - getHistoricalSummaries: Endpoint< - "GET", - EmptyArgs, - EmptyRequest, - ValueOf, - EmptyMeta - >; + getHistoricalSummaries: Endpoint<"GET", EmptyArgs, EmptyRequest, HistoricalSummariesResponse, EmptyMeta>; }; export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { @@ -204,13 +198,12 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions Date: Mon, 2 Dec 2024 12:06:44 -0500 Subject: [PATCH 03/17] Restructure to use stateId and add proof to response --- packages/api/src/beacon/routes/debug.ts | 28 +++++++++++++++---- .../beacon-node/src/api/impl/debug/index.ts | 25 +++++++++++------ 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/packages/api/src/beacon/routes/debug.ts b/packages/api/src/beacon/routes/debug.ts index 9d5888e2e9c9..1fa84c2d51dd 100644 --- a/packages/api/src/beacon/routes/debug.ts +++ b/packages/api/src/beacon/routes/debug.ts @@ -85,7 +85,13 @@ const DebugChainHeadListType = ArrayOf(DebugChainHeadType); type ProtoNodeList = ValueOf; type DebugChainHeadList = ValueOf; type ForkChoiceResponse = ValueOf; -type HistoricalSummariesResponse = ValueOf; +const HistoricalSummariesResponseType = new ContainerType( + { + HistoricalSummaries: ssz.capella.HistoricalSummaries, + proof: ArrayOf(ssz.Bytes8), + }, + {jsonCase: "eth2"} +); export type Endpoints = { /** * Retrieves all possible chain heads (leaves of fork choice tree). @@ -135,7 +141,13 @@ export type Endpoints = { BeaconState, ExecutionOptimisticFinalizedAndVersionMeta >; - getHistoricalSummaries: Endpoint<"GET", EmptyArgs, EmptyRequest, HistoricalSummariesResponse, EmptyMeta>; + getHistoricalSummaries: Endpoint< + "GET", + StateArgs, + {params: {state_id: string}}, + ValueOf, + EmptyMeta + >; }; export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { @@ -198,11 +210,17 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions ({params: {state_id: stateId.toString()}}), + parseReq: ({params}) => ({stateId: params.state_id}), + schema: { + params: {state_id: Schema.StringRequired}, + }, + }, resp: { - data: ssz.capella.HistoricalSummaries, + data: HistoricalSummariesResponseType, meta: EmptyMetaCodec, }, }, diff --git a/packages/beacon-node/src/api/impl/debug/index.ts b/packages/beacon-node/src/api/impl/debug/index.ts index 285ef22f55f4..ec90547f2354 100644 --- a/packages/beacon-node/src/api/impl/debug/index.ts +++ b/packages/beacon-node/src/api/impl/debug/index.ts @@ -1,13 +1,13 @@ import {routes} from "@lodestar/api"; import {ApplicationMethods} from "@lodestar/api/server"; import {ExecutionStatus} from "@lodestar/fork-choice"; -import {ForkSeq, ZERO_HASH_HEX} from "@lodestar/params"; -import {BeaconStateCapella} from "@lodestar/state-transition"; -import {BeaconState} from "@lodestar/types"; +import {ForkName, ForkSeq, ZERO_HASH_HEX} from "@lodestar/params"; +import {BeaconState, ssz} from "@lodestar/types"; import {isOptimisticBlock} from "../../../util/forkChoice.js"; import {getStateSlotFromBytes} from "../../../util/multifork.js"; import {getStateResponseWithRegen} from "../beacon/state/utils.js"; import {ApiModules} from "../types.js"; +import {Tree} from "@chainsafe/persistent-merkle-tree"; export function getDebugApi({ chain, @@ -86,8 +86,8 @@ export function getDebugApi({ }, }; }, - async getHistoricalSummaries(_, context) { - const {state} = await getStateResponseWithRegen(chain, "head"); + async getHistoricalSummaries({stateId}, _context) { + const {state} = await getStateResponseWithRegen(chain, stateId); let slot: number; if (state instanceof Uint8Array) { slot = getStateSlotFromBytes(state); @@ -97,10 +97,17 @@ export function getDebugApi({ if (config.getForkSeq(slot) < ForkSeq.capella) { throw new Error("Historical summaries are not supported before Capella"); } - if (context?.returnBytes) { - return {data: (state as BeaconStateCapella).historicalSummaries.serialize()}; - } - return {data: (state as BeaconStateCapella).historicalSummaries.toValue()}; + const fork = config.getForkName(slot) as Exclude; + const stateView = state instanceof Uint8Array ? ssz[fork].BeaconState.deserializeToViewDU(state) : state; + const gindex = ssz[fork].BeaconState.getPathInfo(["historicalSummaries"]); + const proof = new Tree(stateView.node).getSingleProof(gindex.gindex); + + return { + data: { + HistoricalSummaries: (stateView as any).historicalSummaries.toValue(), + proof: proof, + }, + }; }, }; } From 9eb5077438388e89bb3333a3bab6a4d703770b59 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:26:49 -0500 Subject: [PATCH 04/17] add test scaffolding --- .../unit/beacon/genericServerTest/debug.test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/api/test/unit/beacon/genericServerTest/debug.test.ts b/packages/api/test/unit/beacon/genericServerTest/debug.test.ts index d6140fa20512..e0480388a23d 100644 --- a/packages/api/test/unit/beacon/genericServerTest/debug.test.ts +++ b/packages/api/test/unit/beacon/genericServerTest/debug.test.ts @@ -58,5 +58,21 @@ describe("beacon / debug", () => { expect(res.wireFormat()).toBe(WireFormat.ssz); expect(toHexString(res.ssz())).toBe(toHexString(stateSerialized)); }); + it("getHistoricalSummaries", async () => { + const state = ssz.deneb.BeaconState.defaultValue(); + mockApi.getHistoricalSummaries.mockResolvedValue({ + data: {HistoricalSummaries: state.historicalSummaries, proof: []}, + meta: undefined, + }); + + const httpClient = new HttpClient({baseUrl}); + const client = getClient(config, httpClient); + + const res = await client.getHistoricalSummaries({stateId: "head"}, {responseWireFormat: WireFormat.json}); + + expect(res.ok).toBe(true); + expect(res.wireFormat()).toBe(WireFormat.json); + expect(res.json()).toStrictEqual({data: {Historical_summaries: [], proof: []}}); + }); }); }); From 67413ed315fea7c878932e42bf21ace830eb4fe0 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 3 Dec 2024 07:32:29 -0500 Subject: [PATCH 05/17] Address feedback --- packages/api/src/beacon/routes/debug.ts | 9 ++------- .../beacon/genericServerTest/debug.test.ts | 18 +----------------- .../api/test/unit/beacon/testData/debug.ts | 4 ++++ .../beacon-node/src/api/impl/debug/index.ts | 7 ++++++- 4 files changed, 13 insertions(+), 25 deletions(-) diff --git a/packages/api/src/beacon/routes/debug.ts b/packages/api/src/beacon/routes/debug.ts index 1fa84c2d51dd..5588b18230e8 100644 --- a/packages/api/src/beacon/routes/debug.ts +++ b/packages/api/src/beacon/routes/debug.ts @@ -85,6 +85,7 @@ const DebugChainHeadListType = ArrayOf(DebugChainHeadType); type ProtoNodeList = ValueOf; type DebugChainHeadList = ValueOf; type ForkChoiceResponse = ValueOf; +type HistoricalSummariesList = ValueOf; const HistoricalSummariesResponseType = new ContainerType( { HistoricalSummaries: ssz.capella.HistoricalSummaries, @@ -141,13 +142,7 @@ export type Endpoints = { BeaconState, ExecutionOptimisticFinalizedAndVersionMeta >; - getHistoricalSummaries: Endpoint< - "GET", - StateArgs, - {params: {state_id: string}}, - ValueOf, - EmptyMeta - >; + getHistoricalSummaries: Endpoint<"GET", StateArgs, {params: {state_id: string}}, HistoricalSummariesList, EmptyMeta>; }; export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { diff --git a/packages/api/test/unit/beacon/genericServerTest/debug.test.ts b/packages/api/test/unit/beacon/genericServerTest/debug.test.ts index e0480388a23d..bb40fed88204 100644 --- a/packages/api/test/unit/beacon/genericServerTest/debug.test.ts +++ b/packages/api/test/unit/beacon/genericServerTest/debug.test.ts @@ -23,7 +23,7 @@ describe("beacon / debug", () => { // Get state by SSZ - describe("get state in SSZ format", () => { + describe.skip("get state in SSZ format", () => { const mockApi = getMockApi(getDefinitions(config)); let baseUrl: string; let server: FastifyInstance; @@ -58,21 +58,5 @@ describe("beacon / debug", () => { expect(res.wireFormat()).toBe(WireFormat.ssz); expect(toHexString(res.ssz())).toBe(toHexString(stateSerialized)); }); - it("getHistoricalSummaries", async () => { - const state = ssz.deneb.BeaconState.defaultValue(); - mockApi.getHistoricalSummaries.mockResolvedValue({ - data: {HistoricalSummaries: state.historicalSummaries, proof: []}, - meta: undefined, - }); - - const httpClient = new HttpClient({baseUrl}); - const client = getClient(config, httpClient); - - const res = await client.getHistoricalSummaries({stateId: "head"}, {responseWireFormat: WireFormat.json}); - - expect(res.ok).toBe(true); - expect(res.wireFormat()).toBe(WireFormat.json); - expect(res.json()).toStrictEqual({data: {Historical_summaries: [], proof: []}}); - }); }); }); diff --git a/packages/api/test/unit/beacon/testData/debug.ts b/packages/api/test/unit/beacon/testData/debug.ts index cb2799939ae3..292685011264 100644 --- a/packages/api/test/unit/beacon/testData/debug.ts +++ b/packages/api/test/unit/beacon/testData/debug.ts @@ -76,4 +76,8 @@ export const testData: GenericServerTestCases = { meta: {executionOptimistic: true, finalized: false, version: ForkName.altair}, }, }, + getHistoricalSummaries: { + args: {stateId: "head"}, + res: {data: {HistoricalSummaries: [], proof: []}}, + }, }; diff --git a/packages/beacon-node/src/api/impl/debug/index.ts b/packages/beacon-node/src/api/impl/debug/index.ts index ec90547f2354..e829abc0396c 100644 --- a/packages/beacon-node/src/api/impl/debug/index.ts +++ b/packages/beacon-node/src/api/impl/debug/index.ts @@ -8,6 +8,7 @@ import {getStateSlotFromBytes} from "../../../util/multifork.js"; import {getStateResponseWithRegen} from "../beacon/state/utils.js"; import {ApiModules} from "../types.js"; import {Tree} from "@chainsafe/persistent-merkle-tree"; +import {loadState} from "@lodestar/state-transition"; export function getDebugApi({ chain, @@ -98,12 +99,16 @@ export function getDebugApi({ throw new Error("Historical summaries are not supported before Capella"); } const fork = config.getForkName(slot) as Exclude; - const stateView = state instanceof Uint8Array ? ssz[fork].BeaconState.deserializeToViewDU(state) : state; + + const stateView = + state instanceof Uint8Array ? loadState(config, chain.getHeadState(), state).state : state.clone(); + const gindex = ssz[fork].BeaconState.getPathInfo(["historicalSummaries"]); const proof = new Tree(stateView.node).getSingleProof(gindex.gindex); return { data: { + // biome-ignore lint/suspicious/noExplicitAny: state is definitely Capella or later based on above check HistoricalSummaries: (stateView as any).historicalSummaries.toValue(), proof: proof, }, From 3886d03f38acf2df386b378140d8fde00f48e869 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 3 Dec 2024 08:10:03 -0500 Subject: [PATCH 06/17] Move getHistoricalSummaries to lodestar namespace --- packages/api/src/beacon/routes/debug.ts | 25 +------------- packages/api/src/beacon/routes/lodestar.ts | 33 +++++++++++++++++- .../beacon/genericServerTest/debug.test.ts | 2 +- .../api/test/unit/beacon/testData/debug.ts | 4 --- .../beacon-node/src/api/impl/debug/index.ts | 31 +---------------- .../src/api/impl/lodestar/index.ts | 34 +++++++++++++++++-- 6 files changed, 67 insertions(+), 62 deletions(-) diff --git a/packages/api/src/beacon/routes/debug.ts b/packages/api/src/beacon/routes/debug.ts index 5588b18230e8..9afc96d5a637 100644 --- a/packages/api/src/beacon/routes/debug.ts +++ b/packages/api/src/beacon/routes/debug.ts @@ -85,14 +85,7 @@ const DebugChainHeadListType = ArrayOf(DebugChainHeadType); type ProtoNodeList = ValueOf; type DebugChainHeadList = ValueOf; type ForkChoiceResponse = ValueOf; -type HistoricalSummariesList = ValueOf; -const HistoricalSummariesResponseType = new ContainerType( - { - HistoricalSummaries: ssz.capella.HistoricalSummaries, - proof: ArrayOf(ssz.Bytes8), - }, - {jsonCase: "eth2"} -); + export type Endpoints = { /** * Retrieves all possible chain heads (leaves of fork choice tree). @@ -142,7 +135,6 @@ export type Endpoints = { BeaconState, ExecutionOptimisticFinalizedAndVersionMeta >; - getHistoricalSummaries: Endpoint<"GET", StateArgs, {params: {state_id: string}}, HistoricalSummariesList, EmptyMeta>; }; export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { @@ -204,20 +196,5 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions ({params: {state_id: stateId.toString()}}), - parseReq: ({params}) => ({stateId: params.state_id}), - schema: { - params: {state_id: Schema.StringRequired}, - }, - }, - resp: { - data: HistoricalSummariesResponseType, - meta: EmptyMetaCodec, - }, - }, }; } diff --git a/packages/api/src/beacon/routes/lodestar.ts b/packages/api/src/beacon/routes/lodestar.ts index 81191efd2696..bf524c764f92 100644 --- a/packages/api/src/beacon/routes/lodestar.ts +++ b/packages/api/src/beacon/routes/lodestar.ts @@ -1,8 +1,10 @@ import {ChainForkConfig} from "@lodestar/config"; -import {Epoch, RootHex, Slot} from "@lodestar/types"; +import {Epoch, RootHex, Slot, ssz} from "@lodestar/types"; import { + ArrayOf, EmptyArgs, EmptyMeta, + EmptyMetaCodec, EmptyRequest, EmptyRequestCodec, EmptyResponseCodec, @@ -11,6 +13,8 @@ import { } from "../../utils/codecs.js"; import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js"; import {FilterGetPeers, NodePeer, PeerDirection, PeerState} from "./node.js"; +import {ContainerType, ValueOf} from "@chainsafe/ssz"; +import {StateArgs} from "./beacon/state.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes @@ -75,6 +79,15 @@ export type LodestarNodePeer = NodePeer & { export type LodestarThreadType = "main" | "network" | "discv5"; +type HistoricalSummariesList = ValueOf; +const HistoricalSummariesResponseType = new ContainerType( + { + HistoricalSummaries: ssz.capella.HistoricalSummaries, + proof: ArrayOf(ssz.Bytes8), + }, + {jsonCase: "eth2"} +); + export type Endpoints = { /** Trigger to write a heapdump to disk at `dirpath`. May take > 1min */ writeHeapdump: Endpoint< @@ -247,6 +260,9 @@ export type Endpoints = { {root: RootHex; slot: Slot}[], EmptyMeta >; + + /** Returns historical summaries and proof for a given state ID */ + getHistoricalSummaries: Endpoint<"GET", StateArgs, {params: {state_id: string}}, HistoricalSummariesList, EmptyMeta>; }; export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { @@ -387,5 +403,20 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions ({params: {state_id: stateId.toString()}}), + parseReq: ({params}) => ({stateId: params.state_id}), + schema: { + params: {state_id: Schema.StringRequired}, + }, + }, + resp: { + data: HistoricalSummariesResponseType, + meta: EmptyMetaCodec, + }, + }, }; } diff --git a/packages/api/test/unit/beacon/genericServerTest/debug.test.ts b/packages/api/test/unit/beacon/genericServerTest/debug.test.ts index bb40fed88204..d6140fa20512 100644 --- a/packages/api/test/unit/beacon/genericServerTest/debug.test.ts +++ b/packages/api/test/unit/beacon/genericServerTest/debug.test.ts @@ -23,7 +23,7 @@ describe("beacon / debug", () => { // Get state by SSZ - describe.skip("get state in SSZ format", () => { + describe("get state in SSZ format", () => { const mockApi = getMockApi(getDefinitions(config)); let baseUrl: string; let server: FastifyInstance; diff --git a/packages/api/test/unit/beacon/testData/debug.ts b/packages/api/test/unit/beacon/testData/debug.ts index 292685011264..cb2799939ae3 100644 --- a/packages/api/test/unit/beacon/testData/debug.ts +++ b/packages/api/test/unit/beacon/testData/debug.ts @@ -76,8 +76,4 @@ export const testData: GenericServerTestCases = { meta: {executionOptimistic: true, finalized: false, version: ForkName.altair}, }, }, - getHistoricalSummaries: { - args: {stateId: "head"}, - res: {data: {HistoricalSummaries: [], proof: []}}, - }, }; diff --git a/packages/beacon-node/src/api/impl/debug/index.ts b/packages/beacon-node/src/api/impl/debug/index.ts index e829abc0396c..a004bd80e8f2 100644 --- a/packages/beacon-node/src/api/impl/debug/index.ts +++ b/packages/beacon-node/src/api/impl/debug/index.ts @@ -1,14 +1,12 @@ import {routes} from "@lodestar/api"; import {ApplicationMethods} from "@lodestar/api/server"; import {ExecutionStatus} from "@lodestar/fork-choice"; -import {ForkName, ForkSeq, ZERO_HASH_HEX} from "@lodestar/params"; +import {ZERO_HASH_HEX} from "@lodestar/params"; import {BeaconState, ssz} from "@lodestar/types"; import {isOptimisticBlock} from "../../../util/forkChoice.js"; import {getStateSlotFromBytes} from "../../../util/multifork.js"; import {getStateResponseWithRegen} from "../beacon/state/utils.js"; import {ApiModules} from "../types.js"; -import {Tree} from "@chainsafe/persistent-merkle-tree"; -import {loadState} from "@lodestar/state-transition"; export function getDebugApi({ chain, @@ -87,32 +85,5 @@ export function getDebugApi({ }, }; }, - async getHistoricalSummaries({stateId}, _context) { - const {state} = await getStateResponseWithRegen(chain, stateId); - let slot: number; - if (state instanceof Uint8Array) { - slot = getStateSlotFromBytes(state); - } else { - slot = state.slot; - } - if (config.getForkSeq(slot) < ForkSeq.capella) { - throw new Error("Historical summaries are not supported before Capella"); - } - const fork = config.getForkName(slot) as Exclude; - - const stateView = - state instanceof Uint8Array ? loadState(config, chain.getHeadState(), state).state : state.clone(); - - const gindex = ssz[fork].BeaconState.getPathInfo(["historicalSummaries"]); - const proof = new Tree(stateView.node).getSingleProof(gindex.gindex); - - return { - data: { - // biome-ignore lint/suspicious/noExplicitAny: state is definitely Capella or later based on above check - HistoricalSummaries: (stateView as any).historicalSummaries.toValue(), - proof: proof, - }, - }; - }, }; } diff --git a/packages/beacon-node/src/api/impl/lodestar/index.ts b/packages/beacon-node/src/api/impl/lodestar/index.ts index 10787194f5f5..80e30d0e7922 100644 --- a/packages/beacon-node/src/api/impl/lodestar/index.ts +++ b/packages/beacon-node/src/api/impl/lodestar/index.ts @@ -4,8 +4,8 @@ import {routes} from "@lodestar/api"; import {ApplicationMethods} from "@lodestar/api/server"; import {ChainForkConfig} from "@lodestar/config"; import {Repository} from "@lodestar/db"; -import {SLOTS_PER_EPOCH} from "@lodestar/params"; -import {getLatestWeakSubjectivityCheckpointEpoch} from "@lodestar/state-transition"; +import {ForkName, ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params"; +import {getLatestWeakSubjectivityCheckpointEpoch, loadState} from "@lodestar/state-transition"; import {ssz} from "@lodestar/types"; import {toHex, toRootHex} from "@lodestar/utils"; import {BeaconChain} from "../../../chain/index.js"; @@ -14,6 +14,9 @@ import {IBeaconDb} from "../../../db/interface.js"; import {GossipType} from "../../../network/index.js"; import {profileNodeJS, writeHeapSnapshot} from "../../../util/profile.js"; import {ApiModules} from "../types.js"; +import {Tree} from "@chainsafe/persistent-merkle-tree"; +import {getStateSlotFromBytes} from "../../../index.js"; +import {getStateResponseWithRegen} from "../beacon/state/utils.js"; export function getLodestarApi({ chain, @@ -187,6 +190,33 @@ export function getLodestarApi({ async dumpDbStateIndex() { return {data: await db.stateArchive.dumpRootIndexEntries()}; }, + async getHistoricalSummaries({stateId}, _context) { + const {state} = await getStateResponseWithRegen(chain, stateId); + let slot: number; + if (state instanceof Uint8Array) { + slot = getStateSlotFromBytes(state); + } else { + slot = state.slot; + } + if (config.getForkSeq(slot) < ForkSeq.capella) { + throw new Error("Historical summaries are not supported before Capella"); + } + const fork = config.getForkName(slot) as Exclude; + + const stateView = + state instanceof Uint8Array ? loadState(config, chain.getHeadState(), state).state : state.clone(); + + const gindex = ssz[fork].BeaconState.getPathInfo(["historicalSummaries"]); + const proof = new Tree(stateView.node).getSingleProof(gindex.gindex); + + return { + data: { + // biome-ignore lint/suspicious/noExplicitAny: state is definitely Capella or later based on above check + HistoricalSummaries: (stateView as any).historicalSummaries.toValue(), + proof: proof, + }, + }; + }, }; } From 0b6010b3b2bbf2a0abd0e9e0e618a61a5d1702f3 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 3 Dec 2024 08:19:42 -0500 Subject: [PATCH 07/17] add lodestar namespace unit test --- .../beacon/genericServerTest/lodestar.test.ts | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 packages/api/test/unit/beacon/genericServerTest/lodestar.test.ts diff --git a/packages/api/test/unit/beacon/genericServerTest/lodestar.test.ts b/packages/api/test/unit/beacon/genericServerTest/lodestar.test.ts new file mode 100644 index 000000000000..8bc20e12f569 --- /dev/null +++ b/packages/api/test/unit/beacon/genericServerTest/lodestar.test.ts @@ -0,0 +1,54 @@ +import {config} from "@lodestar/config/default"; +import {FastifyInstance} from "fastify"; +import {afterAll, beforeAll, describe, expect, it, vi} from "vitest"; +import {getClient} from "../../../../src/beacon/client/lodestar.js"; +import {Endpoints, getDefinitions} from "../../../../src/beacon/routes/lodestar.js"; +import {getRoutes} from "../../../../src/beacon/server/lodestar.js"; +import {HttpClient} from "../../../../src/utils/client/httpClient.js"; +import {AnyEndpoint} from "../../../../src/utils/codecs.js"; +import {FastifyRoute} from "../../../../src/utils/server/index.js"; +import {WireFormat} from "../../../../src/utils/wireFormat.js"; +import {getMockApi, getTestServer} from "../../../utils/utils.js"; + +describe("lodestar", () => { + describe("get HistoricalSummaries as json", () => { + const mockApi = getMockApi(getDefinitions(config)); + let baseUrl: string; + let server: FastifyInstance; + + beforeAll(async () => { + const res = getTestServer(); + server = res.server; + for (const route of Object.values(getRoutes(config, mockApi))) { + server.route(route as FastifyRoute); + } + baseUrl = await res.start(); + }); + + afterAll(async () => { + if (server !== undefined) await server.close(); + }); + + it("getHistoricalSummaries", async () => { + mockApi.getHistoricalSummaries.mockResolvedValue({ + data: { + HistoricalSummaries: [], + proof: [], + }, + }); + + const httpClient = new HttpClient({baseUrl}); + const client = getClient(config, httpClient); + + const res = await client.getHistoricalSummaries({stateId: "head"}, {responseWireFormat: WireFormat.json}); + + expect(res.ok).toBe(true); + expect(res.wireFormat()).toBe(WireFormat.json); + expect(res.json().data).toStrictEqual({ + // biome-ignore lint/style/useNamingConvention: + Historical_summaries: [], + proof: [], + }); + }); + }); +}); From 8991efe86bb4261b9a63deb57deb9efcefa2d082 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 3 Dec 2024 08:24:51 -0500 Subject: [PATCH 08/17] update route name to lodestar namespace --- packages/api/src/beacon/routes/lodestar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/src/beacon/routes/lodestar.ts b/packages/api/src/beacon/routes/lodestar.ts index bf524c764f92..98a2d6284590 100644 --- a/packages/api/src/beacon/routes/lodestar.ts +++ b/packages/api/src/beacon/routes/lodestar.ts @@ -404,7 +404,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions ({params: {state_id: stateId.toString()}}), From bd790f97ce824274fce4b35f6ac0b7f2fda6c1ae Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:47:45 -0500 Subject: [PATCH 09/17] cast state object as Capella state --- packages/beacon-node/src/api/impl/lodestar/index.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/beacon-node/src/api/impl/lodestar/index.ts b/packages/beacon-node/src/api/impl/lodestar/index.ts index 80e30d0e7922..c80c9b862b78 100644 --- a/packages/beacon-node/src/api/impl/lodestar/index.ts +++ b/packages/beacon-node/src/api/impl/lodestar/index.ts @@ -5,7 +5,7 @@ import {ApplicationMethods} from "@lodestar/api/server"; import {ChainForkConfig} from "@lodestar/config"; import {Repository} from "@lodestar/db"; import {ForkName, ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params"; -import {getLatestWeakSubjectivityCheckpointEpoch, loadState} from "@lodestar/state-transition"; +import {BeaconStateCapella, getLatestWeakSubjectivityCheckpointEpoch, loadState} from "@lodestar/state-transition"; import {ssz} from "@lodestar/types"; import {toHex, toRootHex} from "@lodestar/utils"; import {BeaconChain} from "../../../chain/index.js"; @@ -203,16 +203,16 @@ export function getLodestarApi({ } const fork = config.getForkName(slot) as Exclude; - const stateView = - state instanceof Uint8Array ? loadState(config, chain.getHeadState(), state).state : state.clone(); + const stateView = ( + state instanceof Uint8Array ? loadState(config, chain.getHeadState(), state).state : state.clone() + ) as BeaconStateCapella; const gindex = ssz[fork].BeaconState.getPathInfo(["historicalSummaries"]); const proof = new Tree(stateView.node).getSingleProof(gindex.gindex); return { data: { - // biome-ignore lint/suspicious/noExplicitAny: state is definitely Capella or later based on above check - HistoricalSummaries: (stateView as any).historicalSummaries.toValue(), + HistoricalSummaries: stateView.historicalSummaries.toValue(), proof: proof, }, }; From 0f581ec508f90ad7af64681335365663d3453d3b Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 3 Dec 2024 21:33:35 +0100 Subject: [PATCH 10/17] Lint --- packages/api/src/beacon/routes/lodestar.ts | 4 ++-- packages/beacon-node/src/api/impl/lodestar/index.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/api/src/beacon/routes/lodestar.ts b/packages/api/src/beacon/routes/lodestar.ts index 98a2d6284590..8b016d047535 100644 --- a/packages/api/src/beacon/routes/lodestar.ts +++ b/packages/api/src/beacon/routes/lodestar.ts @@ -1,3 +1,4 @@ +import {ContainerType, ValueOf} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; import {Epoch, RootHex, Slot, ssz} from "@lodestar/types"; import { @@ -12,9 +13,8 @@ import { JsonOnlyResponseCodec, } from "../../utils/codecs.js"; import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js"; -import {FilterGetPeers, NodePeer, PeerDirection, PeerState} from "./node.js"; -import {ContainerType, ValueOf} from "@chainsafe/ssz"; import {StateArgs} from "./beacon/state.js"; +import {FilterGetPeers, NodePeer, PeerDirection, PeerState} from "./node.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes diff --git a/packages/beacon-node/src/api/impl/lodestar/index.ts b/packages/beacon-node/src/api/impl/lodestar/index.ts index c80c9b862b78..aec5ff1ebf4f 100644 --- a/packages/beacon-node/src/api/impl/lodestar/index.ts +++ b/packages/beacon-node/src/api/impl/lodestar/index.ts @@ -1,5 +1,6 @@ import fs from "node:fs"; import path from "node:path"; +import {Tree} from "@chainsafe/persistent-merkle-tree"; import {routes} from "@lodestar/api"; import {ApplicationMethods} from "@lodestar/api/server"; import {ChainForkConfig} from "@lodestar/config"; @@ -11,12 +12,11 @@ import {toHex, toRootHex} from "@lodestar/utils"; import {BeaconChain} from "../../../chain/index.js"; import {QueuedStateRegenerator, RegenRequest} from "../../../chain/regen/index.js"; import {IBeaconDb} from "../../../db/interface.js"; +import {getStateSlotFromBytes} from "../../../index.js"; import {GossipType} from "../../../network/index.js"; import {profileNodeJS, writeHeapSnapshot} from "../../../util/profile.js"; -import {ApiModules} from "../types.js"; -import {Tree} from "@chainsafe/persistent-merkle-tree"; -import {getStateSlotFromBytes} from "../../../index.js"; import {getStateResponseWithRegen} from "../beacon/state/utils.js"; +import {ApiModules} from "../types.js"; export function getLodestarApi({ chain, From 7c8c3e4359824f77bb2b99d71679be9d4eb8e2b4 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 3 Dec 2024 21:33:56 +0100 Subject: [PATCH 11/17] json properties need to be lower case --- packages/api/src/beacon/routes/lodestar.ts | 14 +++++++++++--- .../unit/beacon/genericServerTest/lodestar.test.ts | 5 ++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/api/src/beacon/routes/lodestar.ts b/packages/api/src/beacon/routes/lodestar.ts index 8b016d047535..de6b9b3ba595 100644 --- a/packages/api/src/beacon/routes/lodestar.ts +++ b/packages/api/src/beacon/routes/lodestar.ts @@ -79,15 +79,16 @@ export type LodestarNodePeer = NodePeer & { export type LodestarThreadType = "main" | "network" | "discv5"; -type HistoricalSummariesList = ValueOf; const HistoricalSummariesResponseType = new ContainerType( { - HistoricalSummaries: ssz.capella.HistoricalSummaries, + historicalSummaries: ssz.capella.HistoricalSummaries, proof: ArrayOf(ssz.Bytes8), }, {jsonCase: "eth2"} ); +export type HistoricalSummariesList = ValueOf; + export type Endpoints = { /** Trigger to write a heapdump to disk at `dirpath`. May take > 1min */ writeHeapdump: Endpoint< @@ -262,7 +263,14 @@ export type Endpoints = { >; /** Returns historical summaries and proof for a given state ID */ - getHistoricalSummaries: Endpoint<"GET", StateArgs, {params: {state_id: string}}, HistoricalSummariesList, EmptyMeta>; + getHistoricalSummaries: Endpoint< + // ⏎ + "GET", + StateArgs, + {params: {state_id: string}}, + HistoricalSummariesList, + EmptyMeta + >; }; export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { diff --git a/packages/api/test/unit/beacon/genericServerTest/lodestar.test.ts b/packages/api/test/unit/beacon/genericServerTest/lodestar.test.ts index 8bc20e12f569..2b1ee8909c77 100644 --- a/packages/api/test/unit/beacon/genericServerTest/lodestar.test.ts +++ b/packages/api/test/unit/beacon/genericServerTest/lodestar.test.ts @@ -32,7 +32,7 @@ describe("lodestar", () => { it("getHistoricalSummaries", async () => { mockApi.getHistoricalSummaries.mockResolvedValue({ data: { - HistoricalSummaries: [], + historicalSummaries: [], proof: [], }, }); @@ -45,8 +45,7 @@ describe("lodestar", () => { expect(res.ok).toBe(true); expect(res.wireFormat()).toBe(WireFormat.json); expect(res.json().data).toStrictEqual({ - // biome-ignore lint/style/useNamingConvention: - Historical_summaries: [], + historical_summaries: [], proof: [], }); }); From 0381799685a193e69765bf621d4e7ad3a2f55ad6 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 3 Dec 2024 21:41:26 +0100 Subject: [PATCH 12/17] Make it v1 since it's now part of lodestar namespace --- packages/api/src/beacon/routes/lodestar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/src/beacon/routes/lodestar.ts b/packages/api/src/beacon/routes/lodestar.ts index de6b9b3ba595..5e5c1fdd953e 100644 --- a/packages/api/src/beacon/routes/lodestar.ts +++ b/packages/api/src/beacon/routes/lodestar.ts @@ -412,7 +412,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions ({params: {state_id: stateId.toString()}}), From 510342424b112a6cbe43359cd79bb3c12a94ccb2 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 3 Dec 2024 21:42:41 +0100 Subject: [PATCH 13/17] Group with other /lodestar endpoints --- packages/api/src/beacon/routes/lodestar.ts | 50 +++++++++++----------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/api/src/beacon/routes/lodestar.ts b/packages/api/src/beacon/routes/lodestar.ts index 5e5c1fdd953e..2355b3d19118 100644 --- a/packages/api/src/beacon/routes/lodestar.ts +++ b/packages/api/src/beacon/routes/lodestar.ts @@ -228,6 +228,16 @@ export type Endpoints = { {count: number} >; + /** Returns historical summaries and proof for a given state ID */ + getHistoricalSummaries: Endpoint< + // ⏎ + "GET", + StateArgs, + {params: {state_id: string}}, + HistoricalSummariesList, + EmptyMeta + >; + /** Dump Discv5 Kad values */ discv5GetKadValues: Endpoint< // ⏎ @@ -261,16 +271,6 @@ export type Endpoints = { {root: RootHex; slot: Slot}[], EmptyMeta >; - - /** Returns historical summaries and proof for a given state ID */ - getHistoricalSummaries: Endpoint< - // ⏎ - "GET", - StateArgs, - {params: {state_id: string}}, - HistoricalSummariesList, - EmptyMeta - >; }; export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { @@ -389,6 +389,21 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions ({params: {state_id: stateId.toString()}}), + parseReq: ({params}) => ({stateId: params.state_id}), + schema: { + params: {state_id: Schema.StringRequired}, + }, + }, + resp: { + data: HistoricalSummariesResponseType, + meta: EmptyMetaCodec, + }, + }, discv5GetKadValues: { url: "/eth/v1/debug/discv5_kad_values", method: "GET", @@ -411,20 +426,5 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions ({params: {state_id: stateId.toString()}}), - parseReq: ({params}) => ({stateId: params.state_id}), - schema: { - params: {state_id: Schema.StringRequired}, - }, - }, - resp: { - data: HistoricalSummariesResponseType, - meta: EmptyMetaCodec, - }, - }, }; } From 5d23e9dd8eaa41608512624171edcf52f40c24f3 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 3 Dec 2024 21:57:04 +0100 Subject: [PATCH 14/17] Simplify beacon node impl --- .../src/api/impl/lodestar/index.ts | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/beacon-node/src/api/impl/lodestar/index.ts b/packages/beacon-node/src/api/impl/lodestar/index.ts index aec5ff1ebf4f..763952dfe327 100644 --- a/packages/beacon-node/src/api/impl/lodestar/index.ts +++ b/packages/beacon-node/src/api/impl/lodestar/index.ts @@ -5,14 +5,13 @@ import {routes} from "@lodestar/api"; import {ApplicationMethods} from "@lodestar/api/server"; import {ChainForkConfig} from "@lodestar/config"; import {Repository} from "@lodestar/db"; -import {ForkName, ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params"; +import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params"; import {BeaconStateCapella, getLatestWeakSubjectivityCheckpointEpoch, loadState} from "@lodestar/state-transition"; import {ssz} from "@lodestar/types"; import {toHex, toRootHex} from "@lodestar/utils"; import {BeaconChain} from "../../../chain/index.js"; import {QueuedStateRegenerator, RegenRequest} from "../../../chain/regen/index.js"; import {IBeaconDb} from "../../../db/interface.js"; -import {getStateSlotFromBytes} from "../../../index.js"; import {GossipType} from "../../../network/index.js"; import {profileNodeJS, writeHeapSnapshot} from "../../../util/profile.js"; import {getStateResponseWithRegen} from "../beacon/state/utils.js"; @@ -190,29 +189,25 @@ export function getLodestarApi({ async dumpDbStateIndex() { return {data: await db.stateArchive.dumpRootIndexEntries()}; }, - async getHistoricalSummaries({stateId}, _context) { + + async getHistoricalSummaries({stateId}) { const {state} = await getStateResponseWithRegen(chain, stateId); - let slot: number; - if (state instanceof Uint8Array) { - slot = getStateSlotFromBytes(state); - } else { - slot = state.slot; - } - if (config.getForkSeq(slot) < ForkSeq.capella) { - throw new Error("Historical summaries are not supported before Capella"); - } - const fork = config.getForkName(slot) as Exclude; const stateView = ( state instanceof Uint8Array ? loadState(config, chain.getHeadState(), state).state : state.clone() ) as BeaconStateCapella; + const fork = config.getForkName(stateView.slot); + if (ForkSeq[fork] < ForkSeq.capella) { + throw new Error("Historical summaries are not supported before Capella"); + } + const gindex = ssz[fork].BeaconState.getPathInfo(["historicalSummaries"]); const proof = new Tree(stateView.node).getSingleProof(gindex.gindex); return { data: { - HistoricalSummaries: stateView.historicalSummaries.toValue(), + historicalSummaries: stateView.historicalSummaries.toValue(), proof: proof, }, }; From 3822f9f348f30bc54995bf542b5eab53ae7df322 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 3 Dec 2024 21:59:23 +0100 Subject: [PATCH 15/17] Rename return type --- packages/api/src/beacon/routes/lodestar.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/api/src/beacon/routes/lodestar.ts b/packages/api/src/beacon/routes/lodestar.ts index 2355b3d19118..de05b7f2bb44 100644 --- a/packages/api/src/beacon/routes/lodestar.ts +++ b/packages/api/src/beacon/routes/lodestar.ts @@ -87,7 +87,7 @@ const HistoricalSummariesResponseType = new ContainerType( {jsonCase: "eth2"} ); -export type HistoricalSummariesList = ValueOf; +export type HistoricalSummariesResponse = ValueOf; export type Endpoints = { /** Trigger to write a heapdump to disk at `dirpath`. May take > 1min */ @@ -234,7 +234,7 @@ export type Endpoints = { "GET", StateArgs, {params: {state_id: string}}, - HistoricalSummariesList, + HistoricalSummariesResponse, EmptyMeta >; From eda591da0fed25fffd39798ccab4ec52a571aa88 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 3 Dec 2024 22:01:27 +0100 Subject: [PATCH 16/17] Update test description --- .../api/test/unit/beacon/genericServerTest/lodestar.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/test/unit/beacon/genericServerTest/lodestar.test.ts b/packages/api/test/unit/beacon/genericServerTest/lodestar.test.ts index 2b1ee8909c77..b0f78f4bd62c 100644 --- a/packages/api/test/unit/beacon/genericServerTest/lodestar.test.ts +++ b/packages/api/test/unit/beacon/genericServerTest/lodestar.test.ts @@ -10,7 +10,7 @@ import {FastifyRoute} from "../../../../src/utils/server/index.js"; import {WireFormat} from "../../../../src/utils/wireFormat.js"; import {getMockApi, getTestServer} from "../../../utils/utils.js"; -describe("lodestar", () => { +describe("beacon / lodestar", () => { describe("get HistoricalSummaries as json", () => { const mockApi = getMockApi(getDefinitions(config)); let baseUrl: string; From a2e9cd7095496770869589e98c899a4bcfc5cdf2 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 3 Dec 2024 22:03:19 +0100 Subject: [PATCH 17/17] Fix variable name --- packages/beacon-node/src/api/impl/lodestar/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/beacon-node/src/api/impl/lodestar/index.ts b/packages/beacon-node/src/api/impl/lodestar/index.ts index 763952dfe327..aeef2e11a83e 100644 --- a/packages/beacon-node/src/api/impl/lodestar/index.ts +++ b/packages/beacon-node/src/api/impl/lodestar/index.ts @@ -202,8 +202,8 @@ export function getLodestarApi({ throw new Error("Historical summaries are not supported before Capella"); } - const gindex = ssz[fork].BeaconState.getPathInfo(["historicalSummaries"]); - const proof = new Tree(stateView.node).getSingleProof(gindex.gindex); + const {gindex} = ssz[fork].BeaconState.getPathInfo(["historicalSummaries"]); + const proof = new Tree(stateView.node).getSingleProof(gindex); return { data: {