Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add standard endpoint to retrieve fork choice context #7127

Merged
merged 1 commit into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions packages/api/src/beacon/routes/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,34 @@ const DebugChainHeadType = new ContainerType(
{jsonCase: "eth2"}
);

const ForkChoiceNodeType = new ContainerType(
{
slot: ssz.Slot,
blockRoot: stringType,
parentRoot: stringType,
justifiedEpoch: ssz.Epoch,
finalizedEpoch: ssz.Epoch,
weight: ssz.UintNum64,
validity: new StringType<"valid" | "invalid" | "optimistic">(),
executionBlockHash: stringType,
},
{jsonCase: "eth2"}
);
const ForkChoiceResponseType = new ContainerType(
{
justifiedCheckpoint: ssz.phase0.Checkpoint,
finalizedCheckpoint: ssz.phase0.Checkpoint,
forkChoiceNodes: ArrayOf(ForkChoiceNodeType),
},
{jsonCase: "eth2"}
);

const ProtoNodeListType = ArrayOf(ProtoNodeType);
const DebugChainHeadListType = ArrayOf(DebugChainHeadType);

type ProtoNodeList = ValueOf<typeof ProtoNodeListType>;
type DebugChainHeadList = ValueOf<typeof DebugChainHeadListType>;
type ForkChoiceResponse = ValueOf<typeof ForkChoiceResponseType>;

export type Endpoints = {
/**
Expand All @@ -77,6 +100,18 @@ export type Endpoints = {
EmptyMeta
>;

/**
* Retrieves all current fork choice context
*/
getDebugForkChoice: Endpoint<
// ⏎
"GET",
EmptyArgs,
EmptyRequest,
ForkChoiceResponse,
EmptyMeta
>;

/**
* Dump all ProtoArray's nodes to debug
*/
Expand Down Expand Up @@ -115,6 +150,24 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions<Endpo
onlySupport: WireFormat.json,
},
},
getDebugForkChoice: {
url: "/eth/v1/debug/fork_choice",
method: "GET",
req: EmptyRequestCodec,
resp: {
data: ForkChoiceResponseType,
meta: EmptyMetaCodec,
onlySupport: WireFormat.json,
transform: {
Copy link
Member Author

@nflaig nflaig Oct 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as per spec it's not wrapped in data container... it's too late to change it now though 😢

toResponse: (data) => ({
...(data as ForkChoiceResponse),
}),
fromResponse: (resp) => ({
data: resp as ForkChoiceResponse,
}),
},
},
},
getProtoArrayNodes: {
url: "/eth/v0/debug/forkchoice",
method: "GET",
Expand Down
1 change: 0 additions & 1 deletion packages/api/test/unit/beacon/oapiSpec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,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
"getDebugForkChoice", // https://github.com/ChainSafe/lodestar/issues/5700
/* Must support ssz response body */
"getLightClientUpdatesByRange", // https://github.com/ChainSafe/lodestar/issues/6841
];
Expand Down
30 changes: 29 additions & 1 deletion packages/api/test/unit/beacon/testData/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,41 @@ import {ssz} from "@lodestar/types";
import {Endpoints} from "../../../../src/beacon/routes/debug.js";
import {GenericServerTestCases} from "../../../utils/genericServerTest.js";

const rootHex = toHexString(Buffer.alloc(32, 1));
const root = new Uint8Array(32).fill(1);
const rootHex = toHexString(root);

export const testData: GenericServerTestCases<Endpoints> = {
getDebugChainHeadsV2: {
args: undefined,
res: {data: [{slot: 1, root: rootHex, executionOptimistic: true}]},
},
getDebugForkChoice: {
args: undefined,
res: {
data: {
justifiedCheckpoint: {
epoch: 2,
root,
},
finalizedCheckpoint: {
epoch: 1,
root,
},
forkChoiceNodes: [
{
slot: 1,
blockRoot: rootHex,
parentRoot: rootHex,
justifiedEpoch: 1,
finalizedEpoch: 1,
weight: 1,
validity: "valid",
executionBlockHash: rootHex,
},
],
},
},
},
getProtoArrayNodes: {
args: undefined,
res: {
Expand Down
31 changes: 31 additions & 0 deletions packages/beacon-node/src/api/impl/debug/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {routes} from "@lodestar/api";
import {ApplicationMethods} from "@lodestar/api/server";
import {ExecutionStatus} from "@lodestar/fork-choice";
import {BeaconState} from "@lodestar/types";
import {ZERO_HASH_HEX} from "@lodestar/params";
import {getStateResponseWithRegen} from "../beacon/state/utils.js";
import {ApiModules} from "../types.js";
import {isOptimisticBlock} from "../../../util/forkChoice.js";
Expand All @@ -22,6 +24,35 @@ export function getDebugApi({
};
},

async getDebugForkChoice() {
return {
data: {
justifiedCheckpoint: chain.forkChoice.getJustifiedCheckpoint(),
finalizedCheckpoint: chain.forkChoice.getFinalizedCheckpoint(),
forkChoiceNodes: chain.forkChoice.getAllNodes().map((node) => ({
slot: node.slot,
blockRoot: node.blockRoot,
parentRoot: node.parentRoot,
justifiedEpoch: node.justifiedEpoch,
finalizedEpoch: node.finalizedEpoch,
weight: node.weight,
validity: (() => {
switch (node.executionStatus) {
case ExecutionStatus.Valid:
return "valid";
case ExecutionStatus.Invalid:
return "invalid";
case ExecutionStatus.Syncing:
case ExecutionStatus.PreMerge:
return "optimistic";
}
})(),
executionBlockHash: node.executionPayloadBlockHash ?? ZERO_HASH_HEX,
})),
},
};
},

async getProtoArrayNodes() {
const nodes = chain.forkChoice.getAllNodes().map((node) => ({
// if node has executionPayloadNumber, it will overwrite the below default
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/api/rest/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export class RestApiServer {
if (err.validation) {
const {instancePath, message} = err.validation[0];
const payload: ErrorResponse = {
code: err.statusCode ?? 400,
nflaig marked this conversation as resolved.
Show resolved Hide resolved
code: 400,
message: `${instancePath.substring(instancePath.lastIndexOf("/") + 1)} ${message}`,
stacktraces,
};
Expand Down
Loading