240 lines
5.8 KiB
TypeScript
240 lines
5.8 KiB
TypeScript
import type { Plugin, PluginAPI } from "@lumeweb/interface-relay";
|
|
import fetch, { Request, RequestInit } from "node-fetch";
|
|
import NodeCache from "node-cache";
|
|
import stringify from "json-stringify-deterministic";
|
|
import { Client, Prover } from "./client/index.js";
|
|
import { MemoryStore } from "./client/memory-store.js";
|
|
|
|
const EXECUTION_RPC_URL =
|
|
"https://g.w.lavanet.xyz:443/gateway/eth/rpc-http/f195d68175eb091ec1f71d00f8952b85";
|
|
|
|
const CONSENSUS_RPC_URL = "https://www.lightclientdata.org";
|
|
|
|
const RPC_CACHE = new NodeCache({ stdTTL: 60 * 60 * 12 });
|
|
|
|
async function doFetch(url: string, request: RequestInit) {
|
|
sanitizeRequestArgs(url, request);
|
|
|
|
let req = new Request(url, {
|
|
...request,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
});
|
|
|
|
const resp = await fetch(req);
|
|
|
|
return (await resp.json()) as any;
|
|
}
|
|
|
|
function sanitizeRequestArgs(url: string, request: RequestInit) {
|
|
if (!request || typeof request !== "object") {
|
|
throw Error("invalid request");
|
|
}
|
|
|
|
[
|
|
"agent",
|
|
"hostname",
|
|
"referrer",
|
|
"referrerPolicy",
|
|
"compress",
|
|
"port",
|
|
"protocol",
|
|
"hostname",
|
|
"insecureHTTPParser",
|
|
"highWaterMark",
|
|
"size",
|
|
].forEach((element) => {
|
|
if (element in request) {
|
|
delete request[element];
|
|
}
|
|
});
|
|
}
|
|
|
|
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 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);
|
|
await client.sync();
|
|
|
|
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 tempRequest = {
|
|
method: request.method,
|
|
...request.params,
|
|
};
|
|
const hash = api.util.crypto
|
|
.createHash(stringify(tempRequest))
|
|
.toString("hex");
|
|
|
|
if (RPC_CACHE.has(hash)) {
|
|
RPC_CACHE.ttl(hash);
|
|
return RPC_CACHE.get(hash);
|
|
}
|
|
|
|
try {
|
|
let resp = await doFetch(EXECUTION_RPC_URL, {
|
|
method: "POST",
|
|
body: JSON.stringify(request),
|
|
});
|
|
if (resp && resp.result) {
|
|
RPC_CACHE.set(hash, resp);
|
|
}
|
|
|
|
return resp;
|
|
} catch (e) {
|
|
return e;
|
|
}
|
|
},
|
|
});
|
|
|
|
api.registerMethod("consensus_optimistic_update", {
|
|
cacheable: false,
|
|
async handler(): Promise<object> {
|
|
return await doFetch(
|
|
`${CONSENSUS_RPC_URL}/eth/v1/beacon/light_client/optimistic_update`,
|
|
{
|
|
method: "GET",
|
|
}
|
|
);
|
|
},
|
|
});
|
|
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)) {
|
|
await client.sync();
|
|
}
|
|
|
|
if (!client.blockHashCache.has(request.block)) {
|
|
throw new Error("block not found");
|
|
}
|
|
|
|
if (client.blockCache.has(request.block)) {
|
|
client.blockCache.ttl(request.block);
|
|
|
|
return client.blockCache.get(request.block);
|
|
}
|
|
|
|
await client.getExecutionFromBlockRoot(
|
|
request.block as any,
|
|
client.blockHashCache.get(request.block)
|
|
);
|
|
|
|
return client.blockCache.get(request.block);
|
|
},
|
|
});
|
|
},
|
|
};
|
|
|
|
export default plugin;
|