Skip to content

Commit

Permalink
feat: add debug_getHistoricalSummaries endpoint (#7245)
Browse files Browse the repository at this point in the history
* feat: add new getHistoricalSummaries endpoint to debug namespace

* Add JSON response

* Restructure to use stateId and add proof to response

* add test scaffolding

* Address feedback

* Move getHistoricalSummaries to lodestar namespace

* add lodestar namespace unit test

* update route name to lodestar namespace

* cast state object as Capella state

* Lint

* json properties need to be lower case

* Make it v1 since it's now part of lodestar namespace

* Group with other /lodestar endpoints

* Simplify beacon node impl

* Rename return type

* Update test description

* Fix variable name

---------

Co-authored-by: Nico Flaig <[email protected]>
  • Loading branch information
acolytec3 and nflaig authored Dec 5, 2024
1 parent b5fb76c commit d55bb2d
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 5 deletions.
41 changes: 40 additions & 1 deletion packages/api/src/beacon/routes/lodestar.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import {ContainerType, ValueOf} from "@chainsafe/ssz";
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,
EmptyResponseData,
JsonOnlyResponseCodec,
} from "../../utils/codecs.js";
import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js";
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
Expand Down Expand Up @@ -75,6 +79,16 @@ export type LodestarNodePeer = NodePeer & {

export type LodestarThreadType = "main" | "network" | "discv5";

const HistoricalSummariesResponseType = new ContainerType(
{
historicalSummaries: ssz.capella.HistoricalSummaries,
proof: ArrayOf(ssz.Bytes8),
},
{jsonCase: "eth2"}
);

export type HistoricalSummariesResponse = ValueOf<typeof HistoricalSummariesResponseType>;

export type Endpoints = {
/** Trigger to write a heapdump to disk at `dirpath`. May take > 1min */
writeHeapdump: Endpoint<
Expand Down Expand Up @@ -214,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}},
HistoricalSummariesResponse,
EmptyMeta
>;

/** Dump Discv5 Kad values */
discv5GetKadValues: Endpoint<
// ⏎
Expand Down Expand Up @@ -365,6 +389,21 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions<Endpo
},
resp: JsonOnlyResponseCodec,
},
getHistoricalSummaries: {
url: "/eth/v1/lodestar/historical_summaries/{state_id}",
method: "GET",
req: {
writeReq: ({stateId}) => ({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",
Expand Down
53 changes: 53 additions & 0 deletions packages/api/test/unit/beacon/genericServerTest/lodestar.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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("beacon / lodestar", () => {
describe("get HistoricalSummaries as json", () => {
const mockApi = getMockApi<Endpoints>(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<AnyEndpoint>);
}
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({
historical_summaries: [],
proof: [],
});
});
});
});
2 changes: 1 addition & 1 deletion packages/beacon-node/src/api/impl/debug/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {routes} from "@lodestar/api";
import {ApplicationMethods} from "@lodestar/api/server";
import {ExecutionStatus} from "@lodestar/fork-choice";
import {ZERO_HASH_HEX} from "@lodestar/params";
import {BeaconState} from "@lodestar/types";
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";
Expand Down
29 changes: 27 additions & 2 deletions packages/beacon-node/src/api/impl/lodestar/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
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";
import {Repository} from "@lodestar/db";
import {SLOTS_PER_EPOCH} from "@lodestar/params";
import {getLatestWeakSubjectivityCheckpointEpoch} from "@lodestar/state-transition";
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 {GossipType} from "../../../network/index.js";
import {profileNodeJS, writeHeapSnapshot} from "../../../util/profile.js";
import {getStateResponseWithRegen} from "../beacon/state/utils.js";
import {ApiModules} from "../types.js";

export function getLodestarApi({
Expand Down Expand Up @@ -187,6 +189,29 @@ export function getLodestarApi({
async dumpDbStateIndex() {
return {data: await db.stateArchive.dumpRootIndexEntries()};
},

async getHistoricalSummaries({stateId}) {
const {state} = await getStateResponseWithRegen(chain, stateId);

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);

return {
data: {
historicalSummaries: stateView.historicalSummaries.toValue(),
proof: proof,
},
};
},
};
}

Expand Down
6 changes: 5 additions & 1 deletion packages/types/src/capella/sszTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ export const HistoricalSummary = new ContainerType(
{typeName: "HistoricalSummary", jsonCase: "eth2"}
);

export const HistoricalSummaries = new ListCompositeType(HistoricalSummary, HISTORICAL_ROOTS_LIMIT, {
typeName: "HistoricalSummaries",
});

// we don't reuse bellatrix.BeaconState fields since we need to replace some keys
// and we cannot keep order doing that
export const BeaconState = new ContainerType(
Expand Down Expand Up @@ -168,7 +172,7 @@ export const BeaconState = new ContainerType(
nextWithdrawalIndex: WithdrawalIndex, // [New in Capella]
nextWithdrawalValidatorIndex: ValidatorIndex, // [New in Capella]
// Deep history valid from Capella onwards
historicalSummaries: new ListCompositeType(HistoricalSummary, HISTORICAL_ROOTS_LIMIT), // [New in Capella]
historicalSummaries: HistoricalSummaries, // [New in Capella]
},
{typeName: "BeaconState", jsonCase: "eth2"}
);
Expand Down

0 comments on commit d55bb2d

Please sign in to comment.