From 06f003954a1fc4ba8c3fc986a54aed704e58e507 Mon Sep 17 00:00:00 2001 From: Hiroyuki Naito Date: Wed, 13 Mar 2024 19:04:07 +0900 Subject: [PATCH 1/4] Added a expected withdrawals API interface --- packages/api/src/beacon/routes/builder.ts | 65 +++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 packages/api/src/beacon/routes/builder.ts diff --git a/packages/api/src/beacon/routes/builder.ts b/packages/api/src/beacon/routes/builder.ts new file mode 100644 index 000000000000..d00f582c1f91 --- /dev/null +++ b/packages/api/src/beacon/routes/builder.ts @@ -0,0 +1,65 @@ +import {Slot, ValidatorIndex, WithdrawalIndex} from "@lodestar/types"; +import {ReturnTypes, RoutesData, Schema, sameType, ReqSerializers} 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 = { + 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 { + 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 { + return { + // Just sent the proof JSON as-is + getExpectedWithdrawals: sameType(), + }; +} From 8cfb479d59826d0f052ff9a332e38e2fd4a91013 Mon Sep 17 00:00:00 2001 From: Hiroyuki Naito Date: Thu, 14 Mar 2024 18:57:50 +0900 Subject: [PATCH 2/4] Added router definition --- packages/api/src/beacon/client/builder.ts | 13 +++++++++++ packages/api/src/beacon/client/index.ts | 2 ++ packages/api/src/beacon/index.ts | 1 + packages/api/src/beacon/routes/index.ts | 3 +++ packages/api/src/beacon/server/builder.ts | 9 ++++++++ packages/api/src/beacon/server/index.ts | 2 ++ packages/beacon-node/src/api/impl/api.ts | 2 ++ .../beacon-node/src/api/impl/builder/index.ts | 23 +++++++++++++++++++ 8 files changed, 55 insertions(+) create mode 100644 packages/api/src/beacon/client/builder.ts create mode 100644 packages/api/src/beacon/server/builder.ts create mode 100644 packages/beacon-node/src/api/impl/builder/index.ts diff --git a/packages/api/src/beacon/client/builder.ts b/packages/api/src/beacon/client/builder.ts new file mode 100644 index 000000000000..a2cce018e96f --- /dev/null +++ b/packages/api/src/beacon/client/builder.ts @@ -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(routesData, reqSerializers, returnTypes, httpClient); +} diff --git a/packages/api/src/beacon/client/index.ts b/packages/api/src/beacon/client/index.ts index 9fbe17bf337a..8e1af0c89c70 100644 --- a/packages/api/src/beacon/client/index.ts +++ b/packages/api/src/beacon/client/index.ts @@ -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; @@ -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), }; } diff --git a/packages/api/src/beacon/index.ts b/packages/api/src/beacon/index.ts index d07f6f4bed53..eaf42fff5157 100644 --- a/packages/api/src/beacon/index.ts +++ b/packages/api/src/beacon/index.ts @@ -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[]; diff --git a/packages/api/src/beacon/routes/index.ts b/packages/api/src/beacon/routes/index.ts index 81eb0cd3276c..c2dd2682d1de 100644 --- a/packages/api/src/beacon/routes/index.ts +++ b/packages/api/src/beacon/routes/index.ts @@ -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"; @@ -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; @@ -28,6 +30,7 @@ export type Api = { node: NodeApi; proof: ProofApi; validator: ValidatorApi; + builder: BuilderApi; }; // Reasoning of the API definitions diff --git a/packages/api/src/beacon/server/builder.ts b/packages/api/src/beacon/server/builder.ts new file mode 100644 index 000000000000..c076bf88d532 --- /dev/null +++ b/packages/api/src/beacon/server/builder.ts @@ -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): ServerRoutes { + // All routes return JSON, use a server auto-generator + return getGenericJsonServer, ReqTypes>({routesData, getReturnTypes, getReqSerializers}, config, api); +} diff --git a/packages/api/src/beacon/server/index.ts b/packages/api/src/beacon/server/index.ts index da77dfad32af..68c5037d748d 100644 --- a/packages/api/src/beacon/server/index.ts +++ b/packages/api/src/beacon/server/index.ts @@ -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}; @@ -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) { diff --git a/packages/beacon-node/src/api/impl/api.ts b/packages/beacon-node/src/api/impl/api.ts index d962d310b5ba..e250fefa208d 100644 --- a/packages/beacon-node/src/api/impl/api.ts +++ b/packages/beacon-node/src/api/impl/api.ts @@ -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} { return { @@ -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), }; } diff --git a/packages/beacon-node/src/api/impl/builder/index.ts b/packages/beacon-node/src/api/impl/builder/index.ts new file mode 100644 index 000000000000..a4d3f69e23d6 --- /dev/null +++ b/packages/beacon-node/src/api/impl/builder/index.ts @@ -0,0 +1,23 @@ +import {routes, ServerApi} from "@lodestar/api"; +import {Slot} from "@lodestar/types"; +import {ApiModules} from "../types.js"; + +export function getBuilderApi({chain, config}: Pick): ServerApi { + return { + async getExpectedWithdrawals(stateId: routes.beacon.StateId, proposalSlot?: Slot | undefined) { + // eslint-disable-next-line no-console + console.log(chain, config, stateId, proposalSlot); + return { + executionOptimistic: false, + data: [ + { + index: 1, + validatorIndex: 1, + address: "0xAbcF8e0d4e9587369b2301D0790347320302cc09", + amount: 1, + }, + ], + }; + }, + }; +} From cfb37047373363c31049cacb4f7585749980591b Mon Sep 17 00:00:00 2001 From: Hiroyuki Naito Date: Thu, 14 Mar 2024 19:09:20 +0900 Subject: [PATCH 3/4] Changed the reponse as snake case --- packages/api/src/beacon/routes/builder.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/api/src/beacon/routes/builder.ts b/packages/api/src/beacon/routes/builder.ts index d00f582c1f91..439f04ba7bb9 100644 --- a/packages/api/src/beacon/routes/builder.ts +++ b/packages/api/src/beacon/routes/builder.ts @@ -1,5 +1,5 @@ import {Slot, ValidatorIndex, WithdrawalIndex} from "@lodestar/types"; -import {ReturnTypes, RoutesData, Schema, sameType, ReqSerializers} from "../../utils/index.js"; +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"; @@ -59,7 +59,7 @@ export function getReqSerializers(): ReqSerializers { export function getReturnTypes(): ReturnTypes { return { - // Just sent the proof JSON as-is - getExpectedWithdrawals: sameType(), + // Just sent the JSON as snake case + getExpectedWithdrawals: jsonType("snake"), }; } From 7359cadee3d105b4714e46593ce2ecd0406a8d0f Mon Sep 17 00:00:00 2001 From: Hiroyuki Naito Date: Fri, 15 Mar 2024 21:00:27 +0900 Subject: [PATCH 4/4] Added logic --- .../beacon-node/src/api/impl/builder/index.ts | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/packages/beacon-node/src/api/impl/builder/index.ts b/packages/beacon-node/src/api/impl/builder/index.ts index a4d3f69e23d6..877b7a948a1c 100644 --- a/packages/beacon-node/src/api/impl/builder/index.ts +++ b/packages/beacon-node/src/api/impl/builder/index.ts @@ -1,23 +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, config}: Pick): ServerApi { +export function getBuilderApi({chain}: Pick): ServerApi { 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(chain, config, stateId, proposalSlot); + console.log("Prolosal Slot", proposalSlot, "State data", state, "expectedWithdrawlsData", expectedWithdrawals); return { - executionOptimistic: false, - data: [ - { - index: 1, - validatorIndex: 1, - address: "0xAbcF8e0d4e9587369b2301D0790347320302cc09", - amount: 1, - }, - ], + 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, +// }, +// ],