@lodestar/prover
Version:
A Typescript implementation of the Ethereum Consensus light client
118 lines (100 loc) • 3.73 kB
text/typescript
import {Logger} from "@lodestar/logger";
import {ZERO_ADDRESS} from "../constants.js";
import {ELRequestHandler} from "../interfaces.js";
import {
ELApi,
ELApiParams,
ELApiReturn,
JsonRpcBatchRequest,
JsonRpcBatchResponse,
JsonRpcRequest,
JsonRpcResponse,
JsonRpcResponseWithResultPayload,
} from "../types.js";
import {
isRequest,
isValidBatchResponse,
isValidResponse,
logRequest,
logResponse,
mergeBatchReqResp,
} from "./json_rpc.js";
import {isNullish} from "./validation.js";
export type Optional<T, K extends keyof T> = Omit<T, K> & {[P in keyof T]?: T[P] | undefined};
export class ELRpcProvider {
private handler: ELRequestHandler;
private logger: Logger;
private requestId = 0;
constructor(handler: ELRequestHandler, logger: Logger) {
this.handler = handler;
this.logger = logger;
}
/**
* Request the EL RPC Provider
*
* @template K
* @template E
* @param {K} method - RPC Method
* @param {ELApiParams[K]} params - RPC Params
* @param {{raiseError?: E}} [opts]
* @return {*} {Promise<E extends false ? JsonRpcResponse<ELApiReturn[K]> : JsonRpcResponseWithResultPayload<ELApiReturn[K]>>}
* @memberof ELRpc
*/
async request<K extends keyof ELApi, E extends boolean>(
method: K,
params: ELApiParams[K],
opts?: {raiseError?: E}
): Promise<E extends false ? JsonRpcResponse<ELApiReturn[K]> : JsonRpcResponseWithResultPayload<ELApiReturn[K]>> {
const {raiseError} = opts ?? {raiseError: true};
const payload: JsonRpcRequest = {jsonrpc: "2.0", method, params, id: this.getRequestId()};
logRequest(payload, this.logger);
const response = await this.handler(payload);
logResponse(response, this.logger);
if (raiseError && !isValidResponse(response)) {
throw new Error(`Invalid response from RPC. method=${method} params=${JSON.stringify(params)}`);
}
return response as JsonRpcResponseWithResultPayload<ELApiReturn[K]>;
}
async batchRequest<E extends boolean>(
input: JsonRpcBatchRequest,
opts: {raiseError: E}
): Promise<
E extends false
? {request: JsonRpcRequest; response: JsonRpcResponse}[]
: {request: JsonRpcRequest; response: JsonRpcResponseWithResultPayload<unknown>}[]
> {
const payloads: JsonRpcBatchRequest = [];
for (const req of input) {
if (isRequest(req) && isNullish(req.id)) {
payloads.push({jsonrpc: "2.0", method: req.method, params: req.params, id: this.getRequestId()});
} else {
payloads.push(req);
}
}
logRequest(payloads, this.logger);
const response = await this.handler(payloads);
logResponse(response, this.logger);
if (isNullish(response)) {
throw new Error("Invalid empty response from server.");
}
if (opts.raiseError && !isValidBatchResponse(payloads, response as JsonRpcBatchResponse)) {
throw new Error(
`Invalid response from RPC. payload=${JSON.stringify(payloads)} response=${JSON.stringify(response)}}`
);
}
return mergeBatchReqResp(payloads, response as JsonRpcBatchResponse) as E extends false
? {request: JsonRpcRequest; response: JsonRpcResponse}[]
: {request: JsonRpcRequest; response: JsonRpcResponseWithResultPayload<unknown>}[];
}
async verifyCompatibility(): Promise<void> {
try {
await this.request("eth_getProof", [ZERO_ADDRESS, [], "latest"], {raiseError: true});
} catch (err) {
this.logger.error("Execution compatibility failed.", undefined, err as Error);
throw new Error("RPC does not support 'eth_getProof', which is required for the prover to work properly.");
}
}
getRequestId(): string {
return (++this.requestId).toString();
}
}