relay-plugin-eth/src/client/rpc/rpc.ts

157 lines
3.6 KiB
TypeScript

import NodeCache from "node-cache";
import { PluginAPI } from "@lumeweb/interface-relay";
import stringify from "json-stringify-deterministic";
export type RPCRequest = {
method: string;
params: any[];
};
export type RPCRequestRaw = RPCRequest & {
jsonrpc: string;
id: string;
};
export type RPCResponse = {
success: boolean;
result: any;
};
export type ProviderConfig = {
URL: string;
unsupportedMethods?: string[];
supportBatchRequests?: boolean;
batchSize?: number;
};
export class RPC {
get cache(): NodeCache {
return this._cache;
}
private provider: ProviderConfig;
private _cache = new NodeCache({ stdTTL: 60 * 60 * 12 });
constructor(provider: ProviderConfig) {
this.provider = provider;
}
private _pluginApi?: PluginAPI;
set pluginApi(value: PluginAPI) {
this._pluginApi = value;
}
async request(request: RPCRequest) {
if (this.provider.unsupportedMethods?.includes(request.method)) {
throw new Error("method not supported by the provider");
}
return await this._retryRequest(request);
}
async requestBatch(requests: RPCRequest[]) {
if (
this.provider?.unsupportedMethods &&
requests.some((r) => this.provider.unsupportedMethods!.includes(r.method))
) {
throw new Error("method not supported by the provider");
}
const res = [];
for (const request of requests) {
const r = await this._retryRequest(request);
res.push(r);
}
return res;
}
protected async _request(request: RPCRequestRaw): Promise<RPCResponse> {
try {
const response = await (
await fetch(this.provider.URL, {
method: "POST",
body: JSON.stringify(request),
})
).json();
if (response.result) {
const tempRequest = {
method: request.method,
params: request.params,
};
const hash = this._pluginApi.util.crypto
.createHash(stringify(tempRequest))
.toString("hex");
// this._cache.set(hash, response);
}
return {
success: !response.error,
result: response.error || response.result,
};
} catch (e) {
return {
success: false,
result: { message: `request failed: ${e}` },
};
}
}
private async _retryRequest(
_request: RPCRequest,
retry = 5
): Promise<RPCResponse> {
const request = {
..._request,
jsonrpc: "2.0",
id: this.generateId(),
};
for (let i = retry; i > 0; i--) {
const res = await this._request(request);
if (res.success) {
return res;
} else if (i == 1) {
console.error(
`RPC batch request failed after maximum retries: ${JSON.stringify(
request,
null,
2
)} ${JSON.stringify(res, null, 2)}`
);
}
}
throw new Error("RPC request failed");
}
private generateId(): string {
return Math.floor(Math.random() * 2 ** 64).toFixed();
}
public getCachedRequest(request: RPCRequest): RPCResponse | null {
const hash = this.hashRequest(request);
if (this.cache.has(hash)) {
this.cache.ttl(hash);
return this.cache.get(hash);
}
return null;
}
public deleteCachedRequest(request: RPCRequest): void {
const hash = this.hashRequest(request);
this.cache.del(hash);
}
private hashRequest(request: RPCRequest): string {
const tempRequest = {
method: request.method,
params: request.params,
};
return this._pluginApi.util.crypto
.createHash(stringify(tempRequest))
.toString("hex");
}
}