2022-07-24 03:16:34 +00:00
|
|
|
//const require = createRequire(import.meta.url);
|
|
|
|
//import { createRequire } from "module";
|
|
|
|
|
2022-06-27 17:53:00 +00:00
|
|
|
import crypto from "crypto";
|
|
|
|
import jayson from "jayson/promise/index.js";
|
2022-07-19 22:31:15 +00:00
|
|
|
import { pack, unpack } from "msgpackr";
|
|
|
|
import { Mutex } from "async-mutex";
|
2022-06-27 17:53:00 +00:00
|
|
|
import NodeCache from "node-cache";
|
2022-07-19 22:31:15 +00:00
|
|
|
import { get as getDHT } from "./dht.js";
|
|
|
|
import { rpcMethods } from "./rpc/index.js";
|
2022-07-23 06:48:02 +00:00
|
|
|
import { start as startDns } from "./dns.js";
|
2022-07-04 23:17:58 +00:00
|
|
|
import {
|
2022-07-23 00:57:12 +00:00
|
|
|
JSONRPCError,
|
2022-07-19 22:31:15 +00:00
|
|
|
JSONRPCRequest,
|
|
|
|
JSONRPCResponseWithError,
|
|
|
|
JSONRPCResponseWithResult,
|
2022-07-04 23:17:58 +00:00
|
|
|
} from "jayson";
|
2022-08-24 17:05:38 +00:00
|
|
|
import config from "./config.js";
|
2022-07-23 00:57:12 +00:00
|
|
|
import { ERR_NOT_READY, errorExit } from "./error.js";
|
2022-07-25 06:45:16 +00:00
|
|
|
import log from "loglevel";
|
2022-08-03 06:02:40 +00:00
|
|
|
// @ts-ignore
|
|
|
|
import stringify from "json-stable-stringify";
|
2022-08-05 03:43:02 +00:00
|
|
|
import type { StreamFileResponse } from "./streams.js";
|
2022-08-24 17:05:38 +00:00
|
|
|
import { getStream } from "./streams.js";
|
2022-07-04 23:17:58 +00:00
|
|
|
|
2022-06-27 17:53:00 +00:00
|
|
|
const pendingRequests = new NodeCache();
|
|
|
|
const processedRequests = new NodeCache({
|
2022-07-19 22:31:15 +00:00
|
|
|
stdTTL: 60 * 60 * 12,
|
2022-06-27 17:53:00 +00:00
|
|
|
});
|
|
|
|
|
2022-07-04 23:17:58 +00:00
|
|
|
let jsonServer: jayson.Server;
|
|
|
|
|
2022-06-27 17:53:00 +00:00
|
|
|
interface RPCRequest {
|
2022-08-18 23:29:06 +00:00
|
|
|
bypassCache: boolean;
|
2022-07-19 22:31:15 +00:00
|
|
|
chain: string;
|
|
|
|
query: string;
|
|
|
|
data: string;
|
2022-06-27 17:53:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
interface RPCResponse {
|
2022-07-19 22:31:15 +00:00
|
|
|
updated: number;
|
2022-08-22 15:45:51 +00:00
|
|
|
data: any;
|
|
|
|
error?: string;
|
2022-06-27 17:53:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function hash(data: string): string {
|
2022-07-19 22:31:15 +00:00
|
|
|
return crypto.createHash("sha256").update(data).digest("hex");
|
2022-06-27 17:53:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function getRequestId(request: RPCRequest) {
|
2022-07-19 22:31:15 +00:00
|
|
|
const clonedRequest = Object.assign({}, request);
|
2022-06-27 17:53:00 +00:00
|
|
|
|
2022-07-19 22:31:15 +00:00
|
|
|
// @ts-ignore
|
2022-08-18 23:29:06 +00:00
|
|
|
delete clonedRequest.bypassCache;
|
2022-06-27 17:53:00 +00:00
|
|
|
|
2022-07-19 22:31:15 +00:00
|
|
|
return hash(stringify(clonedRequest));
|
2022-06-27 17:53:00 +00:00
|
|
|
}
|
|
|
|
|
2022-07-04 21:22:48 +00:00
|
|
|
function maybeProcessRequest(request: RPCRequest) {
|
2022-07-19 22:31:15 +00:00
|
|
|
if (!request.chain) {
|
|
|
|
throw new Error("RPC chain missing");
|
|
|
|
}
|
2022-06-27 17:53:00 +00:00
|
|
|
|
2022-07-19 22:31:15 +00:00
|
|
|
if (!request.data) {
|
|
|
|
throw new Error("RPC data missing");
|
|
|
|
}
|
2022-06-27 17:53:00 +00:00
|
|
|
|
2022-07-19 22:31:15 +00:00
|
|
|
return processRequest(request);
|
2022-06-27 17:53:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function processRequest(request: RPCRequest): Promise<RPCResponse> {
|
2022-07-19 22:31:15 +00:00
|
|
|
const reqId = getRequestId(request);
|
|
|
|
|
|
|
|
let lock: Mutex = pendingRequests.get(reqId) as Mutex;
|
|
|
|
const lockExists = !!lock;
|
|
|
|
|
|
|
|
if (!lockExists) {
|
|
|
|
lock = new Mutex();
|
|
|
|
pendingRequests.set(reqId, lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lock.isLocked()) {
|
|
|
|
await lock.waitForUnlock();
|
|
|
|
return processedRequests.get(reqId) as RPCResponse;
|
|
|
|
}
|
|
|
|
await lock.acquire();
|
|
|
|
|
2022-08-18 23:29:06 +00:00
|
|
|
if (!request.bypassCache && processedRequests.get(reqId)) {
|
2022-07-19 22:31:15 +00:00
|
|
|
return processedRequests.get(reqId) as RPCResponse;
|
|
|
|
}
|
|
|
|
|
|
|
|
let rpcResp;
|
|
|
|
|
|
|
|
let error;
|
|
|
|
try {
|
|
|
|
rpcResp = await processRpcRequest(
|
|
|
|
{
|
|
|
|
method: request.query,
|
|
|
|
jsonrpc: "2.0",
|
|
|
|
params: request.data,
|
|
|
|
id: 1,
|
|
|
|
} as unknown as JSONRPCRequest,
|
|
|
|
request.chain
|
|
|
|
);
|
|
|
|
} catch (e) {
|
|
|
|
error = (e as Error).message;
|
|
|
|
}
|
|
|
|
|
|
|
|
let dbData: RPCResponse = {
|
|
|
|
updated: Date.now(),
|
|
|
|
data: "",
|
|
|
|
};
|
|
|
|
|
|
|
|
if (rpcResp) {
|
|
|
|
rpcResp = rpcResp as JSONRPCResponseWithResult;
|
|
|
|
if (false === rpcResp.result) {
|
|
|
|
error = true;
|
2022-06-27 17:53:00 +00:00
|
|
|
}
|
2022-07-04 23:17:58 +00:00
|
|
|
|
2022-07-19 22:31:15 +00:00
|
|
|
rpcResp = rpcResp as unknown as JSONRPCResponseWithError;
|
2022-07-19 22:24:53 +00:00
|
|
|
|
2022-07-23 00:57:53 +00:00
|
|
|
if (rpcResp.error && typeof rpcResp.error === "object") {
|
|
|
|
error = (rpcResp.error as JSONRPCError).message;
|
2022-06-27 17:53:00 +00:00
|
|
|
}
|
2022-07-19 22:31:15 +00:00
|
|
|
}
|
2022-06-27 17:53:00 +00:00
|
|
|
|
2022-08-22 15:45:51 +00:00
|
|
|
if (error) {
|
|
|
|
dbData.error = error as string;
|
|
|
|
} else {
|
|
|
|
dbData.data = (rpcResp as unknown as JSONRPCResponseWithResult).result;
|
|
|
|
}
|
2022-06-27 17:53:00 +00:00
|
|
|
|
2022-07-23 06:49:02 +00:00
|
|
|
if (
|
2022-08-18 23:29:06 +00:00
|
|
|
(!processedRequests.get(reqId) || request.bypassCache) &&
|
2022-07-23 06:49:02 +00:00
|
|
|
dbData.data?.error !== ERR_NOT_READY
|
|
|
|
) {
|
2022-07-19 22:31:15 +00:00
|
|
|
processedRequests.set(reqId, dbData);
|
|
|
|
}
|
2022-06-27 17:53:00 +00:00
|
|
|
|
2022-07-19 22:31:15 +00:00
|
|
|
await lock.release();
|
2022-06-27 17:53:00 +00:00
|
|
|
|
2022-07-19 22:31:15 +00:00
|
|
|
return dbData;
|
2022-06-27 17:53:00 +00:00
|
|
|
}
|
|
|
|
|
2022-07-04 23:17:58 +00:00
|
|
|
export async function processRpcRequest(
|
2022-07-19 22:31:15 +00:00
|
|
|
request: JSONRPCRequest,
|
|
|
|
chain: string
|
2022-07-04 23:17:58 +00:00
|
|
|
): Promise<JSONRPCResponseWithResult | JSONRPCResponseWithError | undefined> {
|
2022-07-19 22:31:15 +00:00
|
|
|
return new Promise((resolve) => {
|
|
|
|
jsonServer.call(
|
|
|
|
request,
|
|
|
|
{ chain },
|
|
|
|
(
|
|
|
|
err?: JSONRPCResponseWithError | null,
|
|
|
|
result?: JSONRPCResponseWithResult
|
|
|
|
): void => {
|
|
|
|
if (err) {
|
|
|
|
return resolve(err);
|
|
|
|
}
|
|
|
|
resolve(result);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
2022-07-04 23:17:58 +00:00
|
|
|
}
|
|
|
|
|
2022-06-27 17:53:00 +00:00
|
|
|
export async function start() {
|
2022-07-19 22:31:15 +00:00
|
|
|
if (!config.str("pocket-app-id") || !config.str("pocket-app-key")) {
|
2022-08-24 17:05:38 +00:00
|
|
|
errorExit("Please set pocket-app-id and pocket-app-key config options.");
|
2022-07-19 22:31:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
jsonServer = new jayson.Server(rpcMethods, { useContext: true });
|
|
|
|
|
2022-07-23 00:58:32 +00:00
|
|
|
(await getDHT("server")).on("connection", RPCConnection.handleRequest);
|
2022-07-23 06:48:02 +00:00
|
|
|
|
|
|
|
await startDns();
|
2022-07-23 00:58:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class RPCConnection {
|
|
|
|
private _socket: any;
|
|
|
|
constructor(socket: any) {
|
|
|
|
this._socket = socket;
|
2022-07-19 22:31:15 +00:00
|
|
|
socket.rawStream._ondestroy = () => false;
|
2022-07-23 06:49:54 +00:00
|
|
|
socket.once("data", this.checkRpc.bind(this));
|
|
|
|
}
|
2022-07-22 23:58:28 +00:00
|
|
|
|
2022-07-23 06:49:54 +00:00
|
|
|
private async checkRpc(data: Buffer) {
|
|
|
|
if (data.toString() === "rpc") {
|
|
|
|
this._socket.once("data", this.processRequest);
|
|
|
|
}
|
|
|
|
}
|
2022-07-19 22:31:15 +00:00
|
|
|
|
2022-07-23 06:49:54 +00:00
|
|
|
private async processRequest(data: Buffer) {
|
|
|
|
let request: RPCRequest;
|
|
|
|
try {
|
|
|
|
request = unpack(data) as RPCRequest;
|
|
|
|
} catch (e) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const that = this as any;
|
2022-08-05 03:43:02 +00:00
|
|
|
let response;
|
2022-07-23 06:49:54 +00:00
|
|
|
try {
|
2022-08-05 03:43:02 +00:00
|
|
|
response = await maybeProcessRequest(request);
|
2022-07-23 06:49:54 +00:00
|
|
|
} catch (error) {
|
2022-07-25 06:45:16 +00:00
|
|
|
log.trace(error);
|
2022-07-23 06:49:54 +00:00
|
|
|
that.write(pack({ error }));
|
2022-08-05 03:43:02 +00:00
|
|
|
that.end();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (response.data?.streamId) {
|
|
|
|
const stream = getStream(
|
|
|
|
response.data?.streamId
|
|
|
|
) as AsyncIterable<Uint8Array>;
|
|
|
|
const emptyData = Uint8Array.from([]);
|
|
|
|
const streamResp = {
|
2022-08-05 13:55:24 +00:00
|
|
|
data: {
|
|
|
|
data: emptyData,
|
|
|
|
done: false,
|
|
|
|
} as StreamFileResponse,
|
2022-08-05 03:43:02 +00:00
|
|
|
};
|
|
|
|
for await (const chunk of stream) {
|
2022-08-05 13:55:24 +00:00
|
|
|
streamResp.data.data = chunk as unknown as Uint8Array;
|
2022-08-05 03:43:02 +00:00
|
|
|
that.write(pack(streamResp));
|
|
|
|
}
|
|
|
|
|
2022-08-05 13:55:24 +00:00
|
|
|
streamResp.data.data = emptyData;
|
|
|
|
streamResp.data.done = true;
|
2022-08-05 03:43:02 +00:00
|
|
|
response = streamResp;
|
2022-07-23 06:49:54 +00:00
|
|
|
}
|
2022-08-05 03:43:02 +00:00
|
|
|
|
|
|
|
that.write(pack(response));
|
2022-07-23 06:49:54 +00:00
|
|
|
that.end();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static handleRequest(socket: any) {
|
|
|
|
new RPCConnection(socket);
|
|
|
|
}
|
2022-06-27 17:53:00 +00:00
|
|
|
}
|