relay-plugin-eth/src/index.ts

222 lines
5.9 KiB
TypeScript

import type { Plugin, PluginAPI } from "@lumeweb/interface-relay";
import fetch, { Request, RequestInit } from "node-fetch";
import NodeCache from "node-cache";
import { Client, Prover } from "./client/index.js";
import { MemoryStore } from "./client/memory-store.js";
import { computeSyncPeriodAtSlot } from "@lodestar/light-client/utils";
import { toHexString } from "@chainsafe/ssz";
import { DEFAULT_BATCH_SIZE } from "./client/constants.js";
import { handleGETRequest } from "./client/utils.js";
const EXECUTION_RPC_URL =
"https://solemn-small-frost.discover.quiknode.pro/dbbe3dc75a8b828611df3f12722de5cc88214947/";
const CONSENSUS_RPC_URL = "https://www.lightclientdata.org";
interface ExecutionRequest {
method: string;
params: any[];
}
interface ConsensusCommitteeHashesRequest {
start: number;
count: number;
}
interface ConsensusCommitteePeriodRequest {
period: number | "latest";
}
interface ConsensusBlockRequest {
block: number;
}
let client: Client;
const RPC_NO_CACHE = [
"eth_call",
"eth_estimateGas",
"eth_sendRawTransaction",
"eth_getTransactionReceipt",
"eth_getTransactionCount",
"eth_getProof",
];
const plugin: Plugin = {
name: "eth",
async plugin(api: PluginAPI): Promise<void> {
const prover = new Prover(CONSENSUS_RPC_URL);
const store = new MemoryStore();
client = new Client(prover, store, CONSENSUS_RPC_URL, EXECUTION_RPC_URL);
await client.sync();
client.provider.rpc.pluginApi = api;
const provider = client.provider;
api.registerMethod("consensus_committee_hashes", {
cacheable: false,
async handler(
request: ConsensusCommitteeHashesRequest
): Promise<Uint8Array> {
if (!(request?.start && typeof request.start == "number")) {
throw new Error('start required and must be a number"');
}
if (!(request?.count && typeof request.count == "number")) {
throw new Error('count required and must be a number"');
}
if (!client.isSynced) {
await client.sync();
}
let hashes;
try {
hashes = store.getCommitteeHashes(request.start, request.count);
} catch {
await client.sync();
}
if (!hashes) {
try {
hashes = store.getCommitteeHashes(request.start, request.count);
} catch (e) {
return e;
}
}
return hashes;
},
});
api.registerMethod("consensus_committee_period", {
cacheable: false,
async handler(
request: ConsensusCommitteePeriodRequest
): Promise<Uint8Array> {
if (
!(
request?.period &&
(typeof request.period == "number" || request.period === "latest")
)
) {
throw new Error('period required and must be a number or "latest"');
}
if (!client.isSynced) {
await client.sync();
}
let committee;
try {
committee = store.getCommittee(
request.period === "latest" ? client.latestPeriod : request.period
);
} catch {
await client.sync();
}
if (!committee) {
try {
committee = store.getCommittee(
request.period === "latest" ? client.latestPeriod : request.period
);
} catch (e) {
return e;
}
}
return committee;
},
});
api.registerMethod("execution_request", {
cacheable: false,
async handler(request: ExecutionRequest): Promise<object> {
/* const cache = provider.rpc.getCachedRequest(request);
if (cache) {
return cache;
}
if (provider.rpcMethodSupported(request.method)) {
await provider.rpcMethod(request.method, request.params);
} else {
await provider.rpc.request(request);
}
let ret = provider.rpc.getCachedRequest(request);
if (RPC_NO_CACHE.includes(request.method)) {
provider.rpc.deleteCachedRequest(request);
}*/
let ret = await provider.rpc.request(request);
// @ts-ignore
return { ...ret, id: request.id ?? ret.id };
},
});
api.registerMethod("consensus_optimistic_update", {
cacheable: false,
async handler(): Promise<object> {
return await handleGETRequest(
`${CONSENSUS_RPC_URL}/eth/v1/beacon/light_client/optimistic_update`
);
},
});
api.registerMethod("consensus_block", {
cacheable: false,
async handler(request: ConsensusBlockRequest): Promise<object> {
try {
BigInt(request?.block);
} catch {
throw new Error("block is required and must be a number");
}
const block = request?.block;
if (
BigInt(block) > BigInt(client.latestPeriod) ||
!client.blockHashCache.has(request.block)
) {
await client.sync();
}
if (
!client.blockHashCache.has(request.block) &&
!client.blockCache.has(request.block)
) {
let state;
try {
const period = computeSyncPeriodAtSlot(request.block);
state = await prover.getSyncUpdate(
period,
period,
DEFAULT_BATCH_SIZE
);
await client.getExecutionFromBlockRoot(
request.block as any,
toHexString(state.attestedHeader.beacon.bodyRoot)
);
} catch {}
}
if (client.blockCache.has(request.block)) {
client.blockCache.ttl(request.block);
return client.blockCache.get(request.block);
}
const ret = await handleGETRequest(
`${CONSENSUS_RPC_URL}/eth/v2/beacon/blocks/${request.block}`
);
client.blockCache.set(request.block, ret);
return ret;
},
});
},
};
export default plugin;