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 { 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 { 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 { 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 { 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); } // @ts-ignore return { ...ret, id: request.id ?? ret.id }; }, }); api.registerMethod("consensus_optimistic_update", { cacheable: false, async handler(): Promise { return await handleGETRequest( `${CONSENSUS_RPC_URL}/eth/v1/beacon/light_client/optimistic_update` ); }, }); api.registerMethod("consensus_block", { cacheable: false, async handler(request: ConsensusBlockRequest): Promise { 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;