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: implement expected withdrawals API #6514

Closed
Closed
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
13 changes: 13 additions & 0 deletions packages/api/src/beacon/client/builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {ChainForkConfig} from "@lodestar/config";
import {generateGenericJsonClient, IHttpClient} from "../../utils/client/index.js";
import {Api, getReqSerializers, getReturnTypes, ReqTypes, routesData} from "../routes/builder.js";

/**
* REST HTTP client for config routes
*/
export function getClient(config: ChainForkConfig, httpClient: IHttpClient): Api {
const reqSerializers = getReqSerializers();
const returnTypes = getReturnTypes();
// All routes return JSON, use a client auto-generator
return generateGenericJsonClient<Api, ReqTypes>(routesData, reqSerializers, returnTypes, httpClient);
}
2 changes: 2 additions & 0 deletions packages/api/src/beacon/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as lodestar from "./lodestar.js";
import * as node from "./node.js";
import * as proof from "./proof.js";
import * as validator from "./validator.js";
import * as builder from "./builder.js";

type ClientModules = HttpClientModules & {
config: ChainForkConfig;
Expand All @@ -34,5 +35,6 @@ export function getClient(opts: HttpClientOptions, modules: ClientModules): Api
node: node.getClient(config, httpClient),
proof: proof.getClient(config, httpClient),
validator: validator.getClient(config, httpClient),
builder: builder.getClient(config, httpClient),
};
}
1 change: 1 addition & 0 deletions packages/api/src/beacon/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ const allNamespacesObj: {[K in keyof Api]: true} = {
node: true,
proof: true,
validator: true,
builder: true,
};
export const allNamespaces = Object.keys(allNamespacesObj) as ApiNamespace[];
65 changes: 65 additions & 0 deletions packages/api/src/beacon/routes/builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {Slot, ValidatorIndex, WithdrawalIndex} from "@lodestar/types";
import {ReturnTypes, RoutesData, Schema, ReqSerializers, jsonType} from "../../utils/index.js";
import {HttpStatusCode} from "../../utils/client/httpStatusCode.js";
import {ApiClientResponse} from "../../interfaces.js";
import {StateId, ExecutionOptimistic} from "./beacon/state.js";

export type ExpectedWithdrawals = {
index: WithdrawalIndex;
validatorIndex: ValidatorIndex;
address: string;
amount: number;
};

// See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes

export type Api = {
getExpectedWithdrawals(
stateId: StateId,
proposalSlot?: Slot | undefined
): Promise<
ApiClientResponse<
{
[HttpStatusCode.OK]: {
executionOptimistic: ExecutionOptimistic;
data: ExpectedWithdrawals[];
};
},
HttpStatusCode.NOT_FOUND | HttpStatusCode.BAD_REQUEST
>
>;
};
/**
* Define javascript values for each route
*/
export const routesData: RoutesData<Api> = {
getExpectedWithdrawals: {url: "/eth/v1/builder/states/{state_id}/expected_withdrawals", method: "GET"},
};

/* eslint-disable @typescript-eslint/naming-convention */
export type ReqTypes = {
getExpectedWithdrawals: {params: {state_id: StateId}; query: {proposal_slot?: Slot | undefined}};
};

export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
return {
getExpectedWithdrawals: {
writeReq: (state_id, proposal_slot) => ({
params: {state_id},
query: {proposal_slot},
}),
parseReq: ({params, query}) => [params.state_id, query.proposal_slot],
schema: {
params: {state_id: Schema.StringRequired},
query: {proposal_slot: Schema.Uint},
},
},
};
}

export function getReturnTypes(): ReturnTypes<Api> {
return {
// Just sent the JSON as snake case
getExpectedWithdrawals: jsonType("snake"),
};
}
3 changes: 3 additions & 0 deletions packages/api/src/beacon/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {Api as LodestarApi} from "./lodestar.js";
import {Api as NodeApi} from "./node.js";
import {Api as ProofApi} from "./proof.js";
import {Api as ValidatorApi} from "./validator.js";
import {Api as BuilderApi} from "./builder.js";

export * as beacon from "./beacon/index.js";
export * as config from "./config.js";
Expand All @@ -17,6 +18,7 @@ export * as lodestar from "./lodestar.js";
export * as node from "./node.js";
export * as proof from "./proof.js";
export * as validator from "./validator.js";
export * as builder from "./builder.js";

export type Api = {
beacon: BeaconApi;
Expand All @@ -28,6 +30,7 @@ export type Api = {
node: NodeApi;
proof: ProofApi;
validator: ValidatorApi;
builder: BuilderApi;
};

// Reasoning of the API definitions
Expand Down
9 changes: 9 additions & 0 deletions packages/api/src/beacon/server/builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {ChainForkConfig} from "@lodestar/config";
import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../routes/builder.js";
import {ServerRoutes, getGenericJsonServer} from "../../utils/server/index.js";
import {ServerApi} from "../../interfaces.js";

export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<Api, ReqTypes> {
// All routes return JSON, use a server auto-generator
return getGenericJsonServer<ServerApi<Api>, ReqTypes>({routesData, getReturnTypes, getReqSerializers}, config, api);
}
2 changes: 2 additions & 0 deletions packages/api/src/beacon/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import * as lodestar from "./lodestar.js";
import * as node from "./node.js";
import * as proof from "./proof.js";
import * as validator from "./validator.js";
import * as builder from "./builder.js";

// Re-export for usage in beacon-node
export {ApiError};
Expand Down Expand Up @@ -43,6 +44,7 @@ export function registerRoutes(
node: () => node.getRoutes(config, api.node),
proof: () => proof.getRoutes(config, api.proof),
validator: () => validator.getRoutes(config, api.validator),
builder: () => builder.getRoutes(config, api.builder),
};

for (const namespace of enabledNamespaces) {
Expand Down
2 changes: 2 additions & 0 deletions packages/beacon-node/src/api/impl/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {getLodestarApi} from "./lodestar/index.js";
import {getNodeApi} from "./node/index.js";
import {getProofApi} from "./proof/index.js";
import {getValidatorApi} from "./validator/index.js";
import {getBuilderApi} from "./builder/index.js";

export function getApi(opts: ApiOptions, modules: ApiModules): {[K in keyof Api]: ServerApi<Api[K]>} {
return {
Expand All @@ -22,5 +23,6 @@ export function getApi(opts: ApiOptions, modules: ApiModules): {[K in keyof Api]
node: getNodeApi(opts, modules),
proof: getProofApi(opts, modules),
validator: getValidatorApi(modules),
builder: getBuilderApi(modules),
};
}
34 changes: 34 additions & 0 deletions packages/beacon-node/src/api/impl/builder/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {routes, ServerApi} from "@lodestar/api";
import {Slot} from "@lodestar/types";
import {getExpectedWithdrawals} from "@lodestar/state-transition";
import {CachedBeaconStateCapella} from "@lodestar/state-transition/src/types.js";
import {ExpectedWithdrawals} from "@lodestar/api/src/beacon/routes/builder.js";
import {ApiModules} from "../types.js";
import {resolveStateId} from "../beacon/state/utils.js";

export function getBuilderApi({chain}: Pick<ApiModules, "chain" | "config">): ServerApi<routes.builder.Api> {
return {
async getExpectedWithdrawals(stateId: routes.beacon.StateId, proposalSlot?: Slot | undefined) {
const {state, executionOptimistic} = await resolveStateId(chain, stateId, {allowRegen: true});
const expectedWithdrawals = getExpectedWithdrawals(state as CachedBeaconStateCapella).withdrawals;
// eslint-disable-next-line no-console
console.log("Prolosal Slot", proposalSlot, "State data", state, "expectedWithdrawlsData", expectedWithdrawals);
return {
executionOptimistic: executionOptimistic,
data: expectedWithdrawals.map((item: {address: Uint8Array}) => ({
...item,
address: Buffer.from(item.address).toString("hex"), // Convert Uint8Array to hexadecimal string
})) as ExpectedWithdrawals[],
};
},
};
}

// data: [
// {
// index: 1,
// validatorIndex: 1,
// address: "0xAbcF8e0d4e9587369b2301D0790347320302cc09",
// amount: 1,
// },
// ],
Loading