UNPKG

@lodestar/api

Version:

A Typescript REST client for the Ethereum Consensus API

366 lines (340 loc) 10.7 kB
import {ContainerType, OptionalType, ValueOf} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; import {ArrayOf, StringType, fulu, ssz, stringType} from "@lodestar/types"; import { EmptyArgs, EmptyMeta, EmptyMetaCodec, EmptyRequest, EmptyRequestCodec, EmptyResponseCodec, EmptyResponseData, JsonOnlyResponseCodec, } from "../../utils/codecs.js"; import {HttpStatusCode} from "../../utils/httpStatusCode.js"; import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js"; import {WireFormat} from "../../utils/wireFormat.js"; // TODO: Workaround for tsgo import-elision bug: ensure this is treated as a runtime value. // https://github.com/microsoft/typescript-go/issues/2212 void HttpStatusCode; export const NetworkIdentityType = new ContainerType( { /** Cryptographic hash of a peer’s public key. [Read more](https://docs.libp2p.io/concepts/peer-id/) */ peerId: stringType, /** Ethereum node record. [Read more](https://eips.ethereum.org/EIPS/eip-778) */ enr: stringType, p2pAddresses: ArrayOf(stringType), discoveryAddresses: ArrayOf(stringType), // TODO Fulu: replace with `ssz.fulu.Metadata` once `custody_group_count` is more widely supported /** Based on Ethereum Consensus [Metadata object](https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md#metadata) */ metadata: ssz.altair.Metadata, }, {jsonCase: "eth2"} ); export const PeerCountType = new ContainerType( { disconnected: ssz.UintNum64, connecting: ssz.UintNum64, connected: ssz.UintNum64, disconnecting: ssz.UintNum64, }, {jsonCase: "eth2"} ); export const SyncingStatusType = new ContainerType( { /** Head slot node is trying to reach */ headSlot: ssz.Slot, /** How many slots node needs to process to reach head. 0 if synced. */ syncDistance: ssz.Slot, /** Set to true if the node is syncing, false if the node is synced. */ isSyncing: ssz.Boolean, /** Set to true if the node is optimistically tracking head. */ isOptimistic: ssz.Boolean, /** Set to true if the connected el client is offline */ elOffline: ssz.Boolean, }, {jsonCase: "eth2"} ); export type NetworkIdentity = ValueOf<typeof NetworkIdentityType> & { metadata: Partial<fulu.Metadata>; }; export type PeerState = "disconnected" | "connecting" | "connected" | "disconnecting"; export type PeerDirection = "inbound" | "outbound"; export const NodePeerType = new ContainerType( { peerId: stringType, enr: new OptionalType(stringType), lastSeenP2pAddress: stringType, state: new StringType<PeerState>(), // the spec does not specify direction for a disconnected peer, lodestar uses null in that case direction: new OptionalType(new StringType<PeerDirection>()), }, {jsonCase: "eth2"} ); export const NodePeersType = ArrayOf(NodePeerType); export type NodePeer = ValueOf<typeof NodePeerType>; export type NodePeers = ValueOf<typeof NodePeersType>; export type PeersMeta = {count: number}; export type PeerCount = ValueOf<typeof PeerCountType>; export type FilterGetPeers = { state?: PeerState[]; direction?: PeerDirection[]; }; export type SyncingStatus = ValueOf<typeof SyncingStatusType>; export enum NodeHealth { READY = HttpStatusCode.OK, SYNCING = HttpStatusCode.PARTIAL_CONTENT, NOT_INITIALIZED_OR_ISSUES = HttpStatusCode.SERVICE_UNAVAILABLE, } /** * Client code as defined in https://github.com/ethereum/execution-apis/blob/dc4dbca37ef8697d782f431af19120beaf5517f5/src/engine/identification.md#clientcode * ClientCode.XX is dedicated to other clients which do not have their own code */ export enum ClientCode { BU = "BU", // besu EJ = "EJ", // ethereumJS EG = "EG", // erigon GE = "GE", // go-ethereum GR = "GR", // grandine LH = "LH", // lighthouse LS = "LS", // lodestar NM = "NM", // nethermind NB = "NB", // nimbus TE = "TE", // trin-execution TK = "TK", // teku PM = "PM", // prysm RH = "RH", // reth XX = "XX", // unknown } /** * A structure which uniquely identifies a client implementation and its version. * Mirrors the client version specification in the Engine API. * https://github.com/ethereum/execution-apis/blob/dc4dbca37ef8697d782f431af19120beaf5517f5/src/engine/identification.md */ export type ClientVersion = { code: ClientCode; name: string; version: string; commit: string; }; export type NodeVersionV2 = { beaconNode: ClientVersion; executionClient?: ClientVersion; }; /** * Read information about the beacon node. */ export type Endpoints = { /** * Get node network identity * Retrieves data about the node's network presence */ getNetworkIdentity: Endpoint< // ⏎ "GET", EmptyArgs, EmptyRequest, NetworkIdentity, EmptyMeta >; /** * Get node network peers * Retrieves data about the node's network peers. By default this returns all peers. Multiple query params are combined using AND conditions */ getPeers: Endpoint< "GET", FilterGetPeers, {query: {state?: PeerState[]; direction?: PeerDirection[]}}, NodePeers, PeersMeta >; /** * Get peer * Retrieves data about the given peer */ getPeer: Endpoint< // ⏎ "GET", {peerId: string}, {params: {peer_id: string}}, NodePeer, EmptyMeta >; /** * Get peer count * Retrieves number of known peers. */ getPeerCount: Endpoint< // ⏎ "GET", EmptyArgs, EmptyRequest, PeerCount, EmptyMeta >; /** * Get version string of the running beacon node. * Requests that the beacon node identify information about its implementation in a format similar to a [HTTP User-Agent](https://tools.ietf.org/html/rfc7231#section-5.5.3) field. */ getNodeVersion: Endpoint< // ⏎ "GET", EmptyArgs, EmptyRequest, {version: string}, EmptyMeta >; /** * Get version information for the beacon node and execution client. * Retrieves structured information about the version of the beacon node and its attached execution client * in the same format as used on the * [Engine API](https://github.com/ethereum/execution-apis/blob/dc4dbca37ef8697d782f431af19120beaf5517f5/src/engine/identification.md). * * Version information about the execution client may not be available at all times and is therefore optional. * * If the beacon node receives multiple values from `engine_getClientVersionV1`, * the first value should be returned on this endpoint. */ getNodeVersionV2: Endpoint< // ⏎ "GET", EmptyArgs, EmptyRequest, NodeVersionV2, EmptyMeta >; /** * Get node syncing status * Requests the beacon node to describe if it's currently syncing or not, and if it is, what block it is up to. */ getSyncingStatus: Endpoint< // ⏎ "GET", EmptyArgs, EmptyRequest, SyncingStatus, EmptyMeta >; /** * Get health check * Returns node health status in http status codes. Useful for load balancers. */ getHealth: Endpoint< // ⏎ "GET", {syncingStatus?: number}, {query: {syncing_status?: number}}, EmptyResponseData, EmptyMeta >; }; export function getDefinitions(_config: ChainForkConfig): RouteDefinitions<Endpoints> { return { getNetworkIdentity: { url: "/eth/v1/node/identity", method: "GET", req: EmptyRequestCodec, resp: { onlySupport: WireFormat.json, // TODO Fulu: clean this up data: { ...JsonOnlyResponseCodec.data, toJson: (data) => { const json = NetworkIdentityType.toJson(data); const {custodyGroupCount} = data.metadata; (json as {metadata: {custody_group_count: string | undefined}}).metadata.custody_group_count = custodyGroupCount !== undefined ? String(custodyGroupCount) : undefined; return json; }, fromJson: (json) => { const data = NetworkIdentityType.fromJson(json); const { metadata: {custody_group_count}, } = json as {metadata: {custody_group_count: string | undefined}}; (data.metadata as Partial<fulu.Metadata>).custodyGroupCount = custody_group_count !== undefined ? parseInt(custody_group_count) : undefined; return data; }, }, meta: EmptyMetaCodec, }, }, getPeers: { url: "/eth/v1/node/peers", method: "GET", req: { writeReq: ({state, direction}) => ({query: {state, direction}}), parseReq: ({query}) => ({state: query.state, direction: query.direction}), schema: {query: {state: Schema.StringArray, direction: Schema.StringArray}}, }, resp: { data: NodePeersType, meta: { toJson: (d) => d, fromJson: (d) => ({count: (d as PeersMeta).count}), toHeadersObject: () => ({}), fromHeaders: () => ({}) as PeersMeta, }, transform: { toResponse: (data, meta) => ({data, meta}), fromResponse: (resp) => resp as {data: NodePeer[]; meta: PeersMeta}, }, onlySupport: WireFormat.json, }, }, getPeer: { url: "/eth/v1/node/peers/{peer_id}", method: "GET", req: { writeReq: ({peerId}) => ({params: {peer_id: peerId}}), parseReq: ({params}) => ({peerId: params.peer_id}), schema: {params: {peer_id: Schema.StringRequired}}, }, resp: { data: NodePeerType, meta: EmptyMetaCodec, onlySupport: WireFormat.json, }, }, getPeerCount: { url: "/eth/v1/node/peer_count", method: "GET", req: EmptyRequestCodec, resp: { data: PeerCountType, meta: EmptyMetaCodec, }, }, getNodeVersion: { url: "/eth/v1/node/version", method: "GET", req: EmptyRequestCodec, resp: JsonOnlyResponseCodec, }, getNodeVersionV2: { url: "/eth/v2/node/version", method: "GET", req: EmptyRequestCodec, resp: JsonOnlyResponseCodec, }, getSyncingStatus: { url: "/eth/v1/node/syncing", method: "GET", req: EmptyRequestCodec, resp: { data: SyncingStatusType, meta: EmptyMetaCodec, }, }, getHealth: { url: "/eth/v1/node/health", method: "GET", req: { writeReq: ({syncingStatus}) => ({query: {syncing_status: syncingStatus}}), parseReq: ({query}) => ({syncingStatus: query.syncing_status}), schema: {query: {syncing_status: Schema.Uint}}, }, resp: EmptyResponseCodec, }, }; }