2023-03-19 21:17:05 +00:00
|
|
|
import type { Plugin, PluginAPI } from "@lumeweb/interface-relay";
|
|
|
|
import fetch, { Request, RequestInit } from "node-fetch";
|
2023-03-29 04:02:16 +00:00
|
|
|
import NodeCache from "node-cache";
|
|
|
|
import { Client, Prover } from "./client/index.js";
|
|
|
|
import { MemoryStore } from "./client/memory-store.js";
|
2023-03-29 08:58:17 +00:00
|
|
|
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";
|
2023-03-27 12:04:12 +00:00
|
|
|
|
2023-03-19 21:17:05 +00:00
|
|
|
const EXECUTION_RPC_URL =
|
2023-03-29 16:09:46 +00:00
|
|
|
"https://solemn-small-frost.discover.quiknode.pro/dbbe3dc75a8b828611df3f12722de5cc88214947/";
|
2023-03-19 21:17:05 +00:00
|
|
|
|
|
|
|
const CONSENSUS_RPC_URL = "https://www.lightclientdata.org";
|
|
|
|
|
2023-03-27 12:04:12 +00:00
|
|
|
interface ExecutionRequest {
|
|
|
|
method: string;
|
2023-03-29 04:02:16 +00:00
|
|
|
params: any[];
|
2023-03-27 12:04:12 +00:00
|
|
|
}
|
|
|
|
|
2023-03-29 04:02:16 +00:00
|
|
|
interface ConsensusCommitteeHashesRequest {
|
|
|
|
start: number;
|
|
|
|
count: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ConsensusCommitteePeriodRequest {
|
|
|
|
period: number | "latest";
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ConsensusBlockRequest {
|
|
|
|
block: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
let client: Client;
|
|
|
|
|
2023-03-29 09:31:54 +00:00
|
|
|
const RPC_NO_CACHE = [
|
|
|
|
"eth_call",
|
|
|
|
"eth_estimateGas",
|
|
|
|
"eth_sendRawTransaction",
|
|
|
|
"eth_getTransactionReceipt",
|
|
|
|
"eth_getTransactionCount",
|
|
|
|
];
|
|
|
|
|
2023-03-19 21:17:05 +00:00
|
|
|
const plugin: Plugin = {
|
|
|
|
name: "eth",
|
|
|
|
async plugin(api: PluginAPI): Promise<void> {
|
2023-03-29 04:02:16 +00:00
|
|
|
const prover = new Prover(CONSENSUS_RPC_URL);
|
|
|
|
const store = new MemoryStore();
|
2023-03-29 08:58:17 +00:00
|
|
|
client = new Client(prover, store, CONSENSUS_RPC_URL, EXECUTION_RPC_URL);
|
2023-03-29 04:02:16 +00:00
|
|
|
await client.sync();
|
2023-03-29 08:58:17 +00:00
|
|
|
client.provider.rpc.pluginApi = api;
|
|
|
|
const provider = client.provider;
|
2023-03-29 04:02:16 +00:00
|
|
|
|
|
|
|
api.registerMethod("consensus_committee_hashes", {
|
2023-03-19 21:17:05 +00:00
|
|
|
cacheable: false,
|
2023-03-29 04:02:16 +00:00
|
|
|
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"');
|
|
|
|
}
|
2023-03-27 12:04:12 +00:00
|
|
|
|
2023-03-29 04:02:16 +00:00
|
|
|
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;
|
2023-03-27 12:04:12 +00:00
|
|
|
|
2023-03-29 04:02:16 +00:00
|
|
|
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;
|
2023-03-19 21:17:05 +00:00
|
|
|
},
|
|
|
|
});
|
2023-03-29 04:02:16 +00:00
|
|
|
|
2023-03-27 12:04:12 +00:00
|
|
|
api.registerMethod("execution_request", {
|
2023-03-19 21:17:05 +00:00
|
|
|
cacheable: false,
|
2023-03-27 12:04:12 +00:00
|
|
|
async handler(request: ExecutionRequest): Promise<object> {
|
2023-03-29 08:58:17 +00:00
|
|
|
const cache = provider.rpc.getCachedRequest(request);
|
2023-03-29 04:02:16 +00:00
|
|
|
|
2023-03-29 08:58:17 +00:00
|
|
|
if (cache) {
|
|
|
|
return cache;
|
|
|
|
}
|
2023-03-29 04:02:16 +00:00
|
|
|
|
2023-03-29 08:58:17 +00:00
|
|
|
if (provider.rpcMethodSupported(request.method)) {
|
|
|
|
await provider.rpcMethod(request.method, request.params);
|
|
|
|
} else {
|
|
|
|
await provider.rpc.request(request);
|
2023-03-29 04:02:16 +00:00
|
|
|
}
|
2023-03-29 08:58:17 +00:00
|
|
|
let ret = provider.rpc.getCachedRequest(request);
|
|
|
|
|
2023-03-29 09:31:54 +00:00
|
|
|
if (RPC_NO_CACHE.includes(request.method)) {
|
|
|
|
provider.rpc.deleteCachedRequest(request);
|
|
|
|
}
|
2023-03-29 08:58:17 +00:00
|
|
|
// @ts-ignore
|
|
|
|
return { ...ret, id: request.id ?? ret.id };
|
2023-03-29 04:02:16 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
api.registerMethod("consensus_optimistic_update", {
|
|
|
|
cacheable: false,
|
|
|
|
async handler(): Promise<object> {
|
2023-03-29 15:31:07 +00:00
|
|
|
return await handleGETRequest(
|
|
|
|
`${CONSENSUS_RPC_URL}/eth/v1/beacon/light_client/optimistic_update`
|
2023-03-29 04:02:16 +00:00
|
|
|
);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
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;
|
|
|
|
|
2023-03-29 08:58:17 +00:00
|
|
|
if (
|
|
|
|
BigInt(block) > BigInt(client.latestPeriod) ||
|
|
|
|
!client.blockHashCache.has(request.block)
|
|
|
|
) {
|
2023-03-29 04:02:16 +00:00
|
|
|
await client.sync();
|
|
|
|
}
|
|
|
|
|
2023-03-29 08:58:17 +00:00
|
|
|
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 {}
|
2023-03-29 04:02:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (client.blockCache.has(request.block)) {
|
|
|
|
client.blockCache.ttl(request.block);
|
|
|
|
|
|
|
|
return client.blockCache.get(request.block);
|
|
|
|
}
|
|
|
|
|
2023-03-29 08:58:17 +00:00
|
|
|
const ret = await handleGETRequest(
|
|
|
|
`${CONSENSUS_RPC_URL}/eth/v2/beacon/blocks/${request.block}`
|
2023-03-29 04:02:16 +00:00
|
|
|
);
|
2023-03-29 08:58:17 +00:00
|
|
|
client.blockCache.set(request.block, ret);
|
2023-03-29 04:02:16 +00:00
|
|
|
|
2023-03-29 08:58:17 +00:00
|
|
|
return ret;
|
2023-03-19 21:17:05 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
export default plugin;
|