*Reformat
This commit is contained in:
parent
070b7825e2
commit
20e9511eae
|
@ -1,36 +1,35 @@
|
|||
// @ts-ignore
|
||||
import BConfig from "bcfg";
|
||||
import {errorExit} from "./util.js";
|
||||
import { errorExit } from "./util.js";
|
||||
|
||||
const config = new BConfig("lumeweb-relay");
|
||||
|
||||
config.inject({
|
||||
relayPort: 8080,
|
||||
relayPort: 8080,
|
||||
});
|
||||
|
||||
config.load({
|
||||
env: true,
|
||||
argv: true,
|
||||
env: true,
|
||||
argv: true,
|
||||
});
|
||||
try {
|
||||
config.open("config.conf");
|
||||
} catch (e) {
|
||||
}
|
||||
config.open("config.conf");
|
||||
} catch (e) {}
|
||||
|
||||
for (const setting of ["relay-domain", "afraid-username", "relay-seed"]) {
|
||||
if (!config.get(setting)) {
|
||||
errorExit(`Required config option ${setting} not set`);
|
||||
}
|
||||
if (!config.get(setting)) {
|
||||
errorExit(`Required config option ${setting} not set`);
|
||||
}
|
||||
}
|
||||
|
||||
let usingPocketGateway = true;
|
||||
|
||||
export function usePocketGateway() {
|
||||
return usingPocketGateway;
|
||||
return usingPocketGateway;
|
||||
}
|
||||
|
||||
export function updateUsePocketGateway(state: boolean): void {
|
||||
usingPocketGateway = state;
|
||||
usingPocketGateway = state;
|
||||
}
|
||||
|
||||
export default config;
|
||||
|
|
62
src/dht.ts
62
src/dht.ts
|
@ -1,57 +1,57 @@
|
|||
import {createRequire} from "module";
|
||||
import { createRequire } from "module";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const DHT = require("@hyperswarm/dht");
|
||||
import {errorExit} from "./util.js";
|
||||
import { errorExit } from "./util.js";
|
||||
import {
|
||||
deriveMyskyRootKeypair,
|
||||
ed25519Keypair,
|
||||
seedPhraseToSeed,
|
||||
validSeedPhrase,
|
||||
deriveMyskyRootKeypair,
|
||||
ed25519Keypair,
|
||||
seedPhraseToSeed,
|
||||
validSeedPhrase,
|
||||
} from "libskynet";
|
||||
import config from "./config.js";
|
||||
|
||||
let node: {
|
||||
ready: () => any;
|
||||
createServer: () => any;
|
||||
defaultKeyPair: any;
|
||||
on: any;
|
||||
ready: () => any;
|
||||
createServer: () => any;
|
||||
defaultKeyPair: any;
|
||||
on: any;
|
||||
};
|
||||
let server: {
|
||||
listen: (arg0: ed25519Keypair) => any;
|
||||
on: any;
|
||||
listen: (arg0: ed25519Keypair) => any;
|
||||
on: any;
|
||||
};
|
||||
|
||||
async function start() {
|
||||
const seed = config.str("relay-seed");
|
||||
const seed = config.str("relay-seed");
|
||||
|
||||
let [, err] = validSeedPhrase(seed);
|
||||
if (err !== null) {
|
||||
errorExit("RELAY_SEED is invalid. Aborting.");
|
||||
}
|
||||
let [, err] = validSeedPhrase(seed);
|
||||
if (err !== null) {
|
||||
errorExit("RELAY_SEED is invalid. Aborting.");
|
||||
}
|
||||
|
||||
const keyPair = deriveMyskyRootKeypair(seedPhraseToSeed(seed)[0]);
|
||||
const keyPair = deriveMyskyRootKeypair(seedPhraseToSeed(seed)[0]);
|
||||
|
||||
node = new DHT({keyPair});
|
||||
node = new DHT({ keyPair });
|
||||
|
||||
await node.ready();
|
||||
await node.ready();
|
||||
|
||||
server = node.createServer();
|
||||
await server.listen(keyPair);
|
||||
server = node.createServer();
|
||||
await server.listen(keyPair);
|
||||
|
||||
return node;
|
||||
return node;
|
||||
}
|
||||
|
||||
export async function get(
|
||||
ret: "server" | "dht" = "dht"
|
||||
ret: "server" | "dht" = "dht"
|
||||
): Promise<typeof server | typeof node> {
|
||||
if (!node) {
|
||||
await start();
|
||||
}
|
||||
if (!node) {
|
||||
await start();
|
||||
}
|
||||
|
||||
if (ret == "server") {
|
||||
return server;
|
||||
}
|
||||
if (ret == "server") {
|
||||
return server;
|
||||
}
|
||||
|
||||
return node;
|
||||
return node;
|
||||
}
|
||||
|
|
116
src/dns.ts
116
src/dns.ts
|
@ -1,96 +1,96 @@
|
|||
import cron from "node-cron";
|
||||
import fetch from "node-fetch";
|
||||
import {get as getDHT} from "./dht.js";
|
||||
import {overwriteRegistryEntry} from "libskynetnode";
|
||||
import {Buffer} from "buffer";
|
||||
import {Parser} from "xml2js";
|
||||
import {URL} from "url";
|
||||
import {errorExit, hashDataKey} from "./util.js";
|
||||
import {pack} from "msgpackr";
|
||||
import { get as getDHT } from "./dht.js";
|
||||
import { overwriteRegistryEntry } from "libskynetnode";
|
||||
import { Buffer } from "buffer";
|
||||
import { Parser } from "xml2js";
|
||||
import { URL } from "url";
|
||||
import { errorExit, hashDataKey } from "./util.js";
|
||||
import { pack } from "msgpackr";
|
||||
import config from "./config.js";
|
||||
|
||||
const {createHash} = await import("crypto");
|
||||
const { createHash } = await import("crypto");
|
||||
|
||||
let activeIp: string;
|
||||
|
||||
const REGISTRY_NODE_KEY = "lumeweb-dht-node";
|
||||
|
||||
async function ipUpdate() {
|
||||
let currentIp = await getCurrentIp();
|
||||
let currentIp = await getCurrentIp();
|
||||
|
||||
if (activeIp && currentIp === activeIp) {
|
||||
return;
|
||||
}
|
||||
if (activeIp && currentIp === activeIp) {
|
||||
return;
|
||||
}
|
||||
|
||||
let domain = await getDomainInfo();
|
||||
let domain = await getDomainInfo();
|
||||
|
||||
await fetch(domain.url[0].toString());
|
||||
await fetch(domain.url[0].toString());
|
||||
|
||||
activeIp = domain.address[0];
|
||||
activeIp = domain.address[0];
|
||||
}
|
||||
|
||||
export async function start() {
|
||||
const dht = (await getDHT()) as any;
|
||||
const dht = (await getDHT()) as any;
|
||||
|
||||
await ipUpdate();
|
||||
await ipUpdate();
|
||||
|
||||
await overwriteRegistryEntry(
|
||||
dht.defaultKeyPair,
|
||||
hashDataKey(REGISTRY_NODE_KEY),
|
||||
pack(`${config.str("relay-domain")}:${config.uint("relay-port")}`)
|
||||
);
|
||||
await overwriteRegistryEntry(
|
||||
dht.defaultKeyPair,
|
||||
hashDataKey(REGISTRY_NODE_KEY),
|
||||
pack(`${config.str("relay-domain")}:${config.uint("relay-port")}`)
|
||||
);
|
||||
|
||||
console.log(
|
||||
"node pubkey:",
|
||||
Buffer.from(dht.defaultKeyPair.publicKey).toString("hex")
|
||||
);
|
||||
console.log(
|
||||
"node pubkey:",
|
||||
Buffer.from(dht.defaultKeyPair.publicKey).toString("hex")
|
||||
);
|
||||
|
||||
cron.schedule("0 * * * *", ipUpdate);
|
||||
cron.schedule("0 * * * *", ipUpdate);
|
||||
}
|
||||
|
||||
async function getDomainInfo() {
|
||||
const relayDomain = config.str("relay-domain");
|
||||
const parser = new Parser();
|
||||
const relayDomain = config.str("relay-domain");
|
||||
const parser = new Parser();
|
||||
|
||||
const url = new URL("https://freedns.afraid.org/api/");
|
||||
const url = new URL("https://freedns.afraid.org/api/");
|
||||
|
||||
const params = url.searchParams;
|
||||
const params = url.searchParams;
|
||||
|
||||
params.append("action", "getdyndns");
|
||||
params.append("v", "2");
|
||||
params.append("style", "xml");
|
||||
params.append("action", "getdyndns");
|
||||
params.append("v", "2");
|
||||
params.append("style", "xml");
|
||||
|
||||
const hash = createHash("sha1");
|
||||
hash.update(
|
||||
`${config.str("afraid-username")}|${config.str("afraid-password")}`
|
||||
);
|
||||
const hash = createHash("sha1");
|
||||
hash.update(
|
||||
`${config.str("afraid-username")}|${config.str("afraid-password")}`
|
||||
);
|
||||
|
||||
params.append("sha", hash.digest().toString("hex"));
|
||||
params.append("sha", hash.digest().toString("hex"));
|
||||
|
||||
const response = await (await fetch(url.toString())).text();
|
||||
const response = await (await fetch(url.toString())).text();
|
||||
|
||||
if (/could not authenticate/i.test(response)) {
|
||||
errorExit("Failed to authenticate to afraid.org");
|
||||
if (/could not authenticate/i.test(response)) {
|
||||
errorExit("Failed to authenticate to afraid.org");
|
||||
}
|
||||
|
||||
const json = await parser.parseStringPromise(response);
|
||||
|
||||
let domain = null;
|
||||
|
||||
for (const item of json.xml.item) {
|
||||
if (item.host[0] === relayDomain) {
|
||||
domain = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const json = await parser.parseStringPromise(response);
|
||||
if (!domain) {
|
||||
errorExit(`Domain ${relayDomain} not found in afraid.org account`);
|
||||
}
|
||||
|
||||
let domain = null;
|
||||
|
||||
for (const item of json.xml.item) {
|
||||
if (item.host[0] === relayDomain) {
|
||||
domain = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!domain) {
|
||||
errorExit(`Domain ${relayDomain} not found in afraid.org account`);
|
||||
}
|
||||
|
||||
return domain;
|
||||
return domain;
|
||||
}
|
||||
|
||||
async function getCurrentIp(): Promise<string> {
|
||||
return await (await fetch("http://ip1.dynupdate.no-ip.com/")).text();
|
||||
return await (await fetch("http://ip1.dynupdate.no-ip.com/")).text();
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import {start as startDns} from "./dns.js";
|
||||
import {start as startRpc} from "./rpc.js";
|
||||
import {start as startRelay} from "./relay.js";
|
||||
import { start as startDns } from "./dns.js";
|
||||
import { start as startRpc } from "./rpc.js";
|
||||
import { start as startRelay } from "./relay.js";
|
||||
|
||||
await startDns();
|
||||
await startRpc();
|
||||
await startRelay();
|
||||
|
||||
process.on("uncaughtException", function (err) {
|
||||
console.log("Caught exception: " + err);
|
||||
console.log("Caught exception: " + err);
|
||||
});
|
||||
|
||||
export {};
|
||||
|
|
|
@ -1,45 +1,45 @@
|
|||
{
|
||||
"algorand-mainnet": "29",
|
||||
"algorand-mainnet-indexer": "dummy",
|
||||
"algorand-archival": "000D",
|
||||
"algorand-testnet": "45",
|
||||
"algorand-testnet-archival": "0A45",
|
||||
"arweave-mainnet": "30",
|
||||
"avax-mainnet": "3",
|
||||
"avax-archival": "00A3",
|
||||
"avax-fuji": "000E",
|
||||
"bsc-mainnet": "4",
|
||||
"bsc-archival": "10",
|
||||
"bsc-testnet": "11",
|
||||
"bsc-testnet-archival": "12",
|
||||
"btc-mainnet": "2",
|
||||
"eth-mainnet": "21",
|
||||
"eth-archival": "22",
|
||||
"eth-archival-trace": "28",
|
||||
"eth-goerli": "26",
|
||||
"poa-kovan": "24",
|
||||
"eth-rinkeby": "25",
|
||||
"eth-ropsten": "23",
|
||||
"evmos-mainnet": "46",
|
||||
"fuse-mainnet": "5",
|
||||
"fuse-archival": "000A",
|
||||
"gnosischain-mainnet": "27",
|
||||
"gnosischain-archival": "000C",
|
||||
"harmony-0": "40",
|
||||
"harmony-0-archival": "0A40",
|
||||
"harmony-1": "41",
|
||||
"harmony-1-archival": "0A41",
|
||||
"harmony-2": "42",
|
||||
"harmony-2-archival": "0A42",
|
||||
"harmony-3": "43",
|
||||
"harmony-3-archival": "0A43",
|
||||
"iotex-mainnet": "44",
|
||||
"oec-mainnet": "47",
|
||||
"mainnet": "1",
|
||||
"poly-mainnet": "9",
|
||||
"poly-archival": "000B",
|
||||
"poly-mumbai": "000F",
|
||||
"poly-mumbai-archival": "00AF",
|
||||
"sol-mainnet": "6",
|
||||
"sol-testnet": "31"
|
||||
"algorand-mainnet": "29",
|
||||
"algorand-mainnet-indexer": "dummy",
|
||||
"algorand-archival": "000D",
|
||||
"algorand-testnet": "45",
|
||||
"algorand-testnet-archival": "0A45",
|
||||
"arweave-mainnet": "30",
|
||||
"avax-mainnet": "3",
|
||||
"avax-archival": "00A3",
|
||||
"avax-fuji": "000E",
|
||||
"bsc-mainnet": "4",
|
||||
"bsc-archival": "10",
|
||||
"bsc-testnet": "11",
|
||||
"bsc-testnet-archival": "12",
|
||||
"btc-mainnet": "2",
|
||||
"eth-mainnet": "21",
|
||||
"eth-archival": "22",
|
||||
"eth-archival-trace": "28",
|
||||
"eth-goerli": "26",
|
||||
"poa-kovan": "24",
|
||||
"eth-rinkeby": "25",
|
||||
"eth-ropsten": "23",
|
||||
"evmos-mainnet": "46",
|
||||
"fuse-mainnet": "5",
|
||||
"fuse-archival": "000A",
|
||||
"gnosischain-mainnet": "27",
|
||||
"gnosischain-archival": "000C",
|
||||
"harmony-0": "40",
|
||||
"harmony-0-archival": "0A40",
|
||||
"harmony-1": "41",
|
||||
"harmony-1-archival": "0A41",
|
||||
"harmony-2": "42",
|
||||
"harmony-2-archival": "0A42",
|
||||
"harmony-3": "43",
|
||||
"harmony-3-archival": "0A43",
|
||||
"iotex-mainnet": "44",
|
||||
"oec-mainnet": "47",
|
||||
"mainnet": "1",
|
||||
"poly-mainnet": "9",
|
||||
"poly-archival": "000B",
|
||||
"poly-mumbai": "000F",
|
||||
"poly-mumbai-archival": "00AF",
|
||||
"sol-mainnet": "6",
|
||||
"sol-testnet": "31"
|
||||
}
|
||||
|
|
152
src/relay.ts
152
src/relay.ts
|
@ -1,118 +1,118 @@
|
|||
// @ts-ignore
|
||||
import DHT from "@hyperswarm/dht";
|
||||
// @ts-ignore
|
||||
import {relay} from "@hyperswarm/dht-relay";
|
||||
import { relay } from "@hyperswarm/dht-relay";
|
||||
// @ts-ignore
|
||||
import Stream from "@hyperswarm/dht-relay/ws";
|
||||
import express, {Express} from "express";
|
||||
import express, { Express } from "express";
|
||||
import path from "path";
|
||||
import {fileURLToPath} from "url";
|
||||
import { fileURLToPath } from "url";
|
||||
import config from "./config.js";
|
||||
import * as http from "http";
|
||||
import * as https from "https";
|
||||
import * as tls from "tls";
|
||||
import * as acme from "acme-client";
|
||||
import {Buffer} from "buffer";
|
||||
import {intervalToDuration} from "date-fns";
|
||||
import { Buffer } from "buffer";
|
||||
import { intervalToDuration } from "date-fns";
|
||||
import cron from "node-cron";
|
||||
import {get as getDHT} from "./dht.js";
|
||||
import { get as getDHT } from "./dht.js";
|
||||
import WS from "ws";
|
||||
// @ts-ignore
|
||||
import DHT from "@hyperswarm/dht";
|
||||
import {pack} from "msgpackr";
|
||||
import {overwriteRegistryEntry} from "libskynetnode";
|
||||
import {hashDataKey} from "./util.js";
|
||||
import { pack } from "msgpackr";
|
||||
import { overwriteRegistryEntry } from "libskynetnode";
|
||||
import { hashDataKey } from "./util.js";
|
||||
|
||||
let sslCtx: tls.SecureContext = tls.createSecureContext();
|
||||
const sslParams: tls.SecureContextOptions = {};
|
||||
|
||||
const sslPrivateKey = await acme.forge.createPrivateKey();
|
||||
const acmeClient = new acme.Client({
|
||||
accountKey: sslPrivateKey,
|
||||
directoryUrl: acme.directory.letsencrypt.production,
|
||||
accountKey: sslPrivateKey,
|
||||
directoryUrl: acme.directory.letsencrypt.production,
|
||||
});
|
||||
|
||||
let app: Express;
|
||||
let router = express.Router();
|
||||
|
||||
export async function start() {
|
||||
const relayPort = config.str("relay-port");
|
||||
app = express();
|
||||
app.use(function (req, res, next) {
|
||||
router(req, res, next);
|
||||
const relayPort = config.str("relay-port");
|
||||
app = express();
|
||||
app.use(function (req, res, next) {
|
||||
router(req, res, next);
|
||||
});
|
||||
|
||||
let httpsServer = https.createServer({
|
||||
SNICallback(servername, cb) {
|
||||
cb(null, sslCtx);
|
||||
},
|
||||
});
|
||||
|
||||
let httpServer = http.createServer(app);
|
||||
|
||||
cron.schedule("0 * * * *", createOrRenewSSl);
|
||||
|
||||
await new Promise((resolve) => {
|
||||
httpServer.listen(80, "0.0.0.0", function () {
|
||||
console.info("HTTP Listening on ", httpServer.address());
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
const dht = await getDHT();
|
||||
|
||||
let httpsServer = https.createServer({
|
||||
SNICallback(servername, cb) {
|
||||
cb(null, sslCtx);
|
||||
},
|
||||
let wsServer = new WS.Server({ server: httpsServer });
|
||||
|
||||
wsServer.on("connection", (socket: any) => {
|
||||
relay(dht, new Stream(false, socket));
|
||||
});
|
||||
|
||||
await new Promise((resolve) => {
|
||||
httpsServer.listen(relayPort, "0.0.0.0", function () {
|
||||
console.info("Relay started on ", httpsServer.address());
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
|
||||
let httpServer = http.createServer(app);
|
||||
|
||||
cron.schedule("0 * * * *", createOrRenewSSl);
|
||||
|
||||
await new Promise((resolve) => {
|
||||
httpServer.listen(80, "0.0.0.0", function () {
|
||||
console.info("HTTP Listening on ", httpServer.address());
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
const dht = await getDHT();
|
||||
|
||||
let wsServer = new WS.Server({server: httpsServer});
|
||||
|
||||
wsServer.on("connection", (socket: any) => {
|
||||
relay(dht, new Stream(false, socket));
|
||||
});
|
||||
|
||||
await new Promise((resolve) => {
|
||||
httpsServer.listen(relayPort, "0.0.0.0", function () {
|
||||
console.info("Relay started on ", httpsServer.address());
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
|
||||
await createOrRenewSSl();
|
||||
await createOrRenewSSl();
|
||||
}
|
||||
|
||||
async function createOrRenewSSl() {
|
||||
if (sslParams.cert) {
|
||||
const expires = (
|
||||
await acme.forge.readCertificateInfo(sslParams.cert as Buffer)
|
||||
).notAfter;
|
||||
if (sslParams.cert) {
|
||||
const expires = (
|
||||
await acme.forge.readCertificateInfo(sslParams.cert as Buffer)
|
||||
).notAfter;
|
||||
|
||||
let duration = intervalToDuration({start: new Date(), end: expires});
|
||||
let duration = intervalToDuration({ start: new Date(), end: expires });
|
||||
|
||||
let daysLeft = (duration.months as number) * 30 + (duration.days as number);
|
||||
let daysLeft = (duration.months as number) * 30 + (duration.days as number);
|
||||
|
||||
if (daysLeft > 30) {
|
||||
return;
|
||||
}
|
||||
if (daysLeft > 30) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const [certificateKey, certificateRequest] = await acme.forge.createCsr({
|
||||
commonName: config.str("relay-domain"),
|
||||
});
|
||||
const [certificateKey, certificateRequest] = await acme.forge.createCsr({
|
||||
commonName: config.str("relay-domain"),
|
||||
});
|
||||
|
||||
sslParams.cert = await acmeClient.auto({
|
||||
csr: certificateRequest,
|
||||
termsOfServiceAgreed: true,
|
||||
challengeCreateFn: async (authz, challenge, keyAuthorization) => {
|
||||
router.get(
|
||||
`/.well-known/acme-challenge/${challenge.token}`,
|
||||
(req, res) => {
|
||||
res.send(keyAuthorization);
|
||||
}
|
||||
);
|
||||
},
|
||||
challengeRemoveFn: async () => {
|
||||
router = express.Router();
|
||||
},
|
||||
challengePriority: ["http-01"],
|
||||
});
|
||||
sslParams.key = certificateKey;
|
||||
sslCtx = tls.createSecureContext(sslParams);
|
||||
sslParams.cert = await acmeClient.auto({
|
||||
csr: certificateRequest,
|
||||
termsOfServiceAgreed: true,
|
||||
challengeCreateFn: async (authz, challenge, keyAuthorization) => {
|
||||
router.get(
|
||||
`/.well-known/acme-challenge/${challenge.token}`,
|
||||
(req, res) => {
|
||||
res.send(keyAuthorization);
|
||||
}
|
||||
);
|
||||
},
|
||||
challengeRemoveFn: async () => {
|
||||
router = express.Router();
|
||||
},
|
||||
challengePriority: ["http-01"],
|
||||
});
|
||||
sslParams.key = certificateKey;
|
||||
sslCtx = tls.createSecureContext(sslParams);
|
||||
|
||||
console.log("SSL Certificate Updated");
|
||||
console.log("SSL Certificate Updated");
|
||||
}
|
||||
|
|
372
src/rpc.ts
372
src/rpc.ts
|
@ -1,28 +1,28 @@
|
|||
import crypto from "crypto";
|
||||
import jayson from "jayson/promise/index.js";
|
||||
import {pack, unpack} from "msgpackr";
|
||||
import {Mutex} from "async-mutex";
|
||||
import {createRequire} from "module";
|
||||
import { pack, unpack } from "msgpackr";
|
||||
import { Mutex } from "async-mutex";
|
||||
import { createRequire } from "module";
|
||||
import NodeCache from "node-cache";
|
||||
import {get as getDHT} from "./dht.js";
|
||||
import {rpcMethods} from "./rpc/index.js";
|
||||
import { get as getDHT } from "./dht.js";
|
||||
import { rpcMethods } from "./rpc/index.js";
|
||||
import PocketPKG from "@pokt-network/pocket-js";
|
||||
|
||||
const {Configuration, HttpRpcProvider, PocketAAT, Pocket} = PocketPKG;
|
||||
const { Configuration, HttpRpcProvider, PocketAAT, Pocket } = PocketPKG;
|
||||
import {
|
||||
JSONRPCRequest,
|
||||
JSONRPCResponseWithError,
|
||||
JSONRPCResponseWithResult,
|
||||
JSONRPCRequest,
|
||||
JSONRPCResponseWithError,
|
||||
JSONRPCResponseWithResult,
|
||||
} from "jayson";
|
||||
import config, {updateUsePocketGateway, usePocketGateway} from "./config.js";
|
||||
import {errorExit} from "./util.js";
|
||||
import config, { updateUsePocketGateway, usePocketGateway } from "./config.js";
|
||||
import { errorExit } from "./util.js";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
const stringify = require("json-stable-stringify");
|
||||
const pendingRequests = new NodeCache();
|
||||
const processedRequests = new NodeCache({
|
||||
stdTTL: 60 * 60 * 12,
|
||||
stdTTL: 60 * 60 * 12,
|
||||
});
|
||||
|
||||
type PocketAATObject = typeof PocketAAT;
|
||||
|
@ -32,235 +32,235 @@ let _aat: PocketAATObject;
|
|||
let jsonServer: jayson.Server;
|
||||
|
||||
interface RPCRequest {
|
||||
force: boolean;
|
||||
chain: string;
|
||||
query: string;
|
||||
data: string;
|
||||
force: boolean;
|
||||
chain: string;
|
||||
query: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
interface RPCResponse {
|
||||
updated: number;
|
||||
data:
|
||||
| any
|
||||
| {
|
||||
updated: number;
|
||||
data:
|
||||
| any
|
||||
| {
|
||||
error: string | boolean;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function hash(data: string): string {
|
||||
return crypto.createHash("sha256").update(data).digest("hex");
|
||||
return crypto.createHash("sha256").update(data).digest("hex");
|
||||
}
|
||||
|
||||
function getRequestId(request: RPCRequest) {
|
||||
const clonedRequest = Object.assign({}, request);
|
||||
const clonedRequest = Object.assign({}, request);
|
||||
|
||||
// @ts-ignore
|
||||
delete clonedRequest.force;
|
||||
// @ts-ignore
|
||||
delete clonedRequest.force;
|
||||
|
||||
return hash(stringify(clonedRequest));
|
||||
return hash(stringify(clonedRequest));
|
||||
}
|
||||
|
||||
function maybeProcessRequest(request: RPCRequest) {
|
||||
if (!request.chain) {
|
||||
throw new Error("RPC chain missing");
|
||||
}
|
||||
if (!request.chain) {
|
||||
throw new Error("RPC chain missing");
|
||||
}
|
||||
|
||||
if (!request.data) {
|
||||
throw new Error("RPC data missing");
|
||||
}
|
||||
if (!request.data) {
|
||||
throw new Error("RPC data missing");
|
||||
}
|
||||
|
||||
return processRequest(request);
|
||||
return processRequest(request);
|
||||
}
|
||||
|
||||
async function processRequest(request: RPCRequest): Promise<RPCResponse> {
|
||||
const reqId = getRequestId(request);
|
||||
const reqId = getRequestId(request);
|
||||
|
||||
let lock: Mutex = pendingRequests.get(reqId) as Mutex;
|
||||
const lockExists = !!lock;
|
||||
let lock: Mutex = pendingRequests.get(reqId) as Mutex;
|
||||
const lockExists = !!lock;
|
||||
|
||||
if (!lockExists) {
|
||||
lock = new Mutex();
|
||||
pendingRequests.set(reqId, 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();
|
||||
|
||||
if (!request.force && processedRequests.get(reqId)) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (lock.isLocked()) {
|
||||
await lock.waitForUnlock();
|
||||
return processedRequests.get(reqId) as RPCResponse;
|
||||
rpcResp = rpcResp as unknown as JSONRPCResponseWithError;
|
||||
|
||||
if (rpcResp.error) {
|
||||
// @ts-ignore
|
||||
error = rpcResp.error.message;
|
||||
}
|
||||
await lock.acquire();
|
||||
}
|
||||
|
||||
if (!request.force && processedRequests.get(reqId)) {
|
||||
return processedRequests.get(reqId) as RPCResponse;
|
||||
}
|
||||
dbData.data = error
|
||||
? { error }
|
||||
: (rpcResp as unknown as JSONRPCResponseWithResult).result;
|
||||
|
||||
let rpcResp;
|
||||
if (!processedRequests.get(reqId) || request.force) {
|
||||
processedRequests.set(reqId, dbData);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
await lock.release();
|
||||
|
||||
let dbData: RPCResponse = {
|
||||
updated: Date.now(),
|
||||
data: "",
|
||||
};
|
||||
|
||||
if (rpcResp) {
|
||||
rpcResp = rpcResp as JSONRPCResponseWithResult;
|
||||
if (false === rpcResp.result) {
|
||||
error = true;
|
||||
}
|
||||
|
||||
rpcResp = rpcResp as unknown as JSONRPCResponseWithError;
|
||||
|
||||
if (rpcResp.error) {
|
||||
// @ts-ignore
|
||||
error = rpcResp.error.message;
|
||||
}
|
||||
}
|
||||
|
||||
dbData.data = error
|
||||
? {error}
|
||||
: (rpcResp as unknown as JSONRPCResponseWithResult).result;
|
||||
|
||||
if (!processedRequests.get(reqId) || request.force) {
|
||||
processedRequests.set(reqId, dbData);
|
||||
}
|
||||
|
||||
await lock.release();
|
||||
|
||||
return dbData;
|
||||
return dbData;
|
||||
}
|
||||
|
||||
export function updateAat(aat: PocketAATObject): void {
|
||||
_aat = aat;
|
||||
_aat = aat;
|
||||
}
|
||||
|
||||
export function getAat(): PocketAATObject {
|
||||
return _aat;
|
||||
return _aat;
|
||||
}
|
||||
|
||||
export function getPocketServer(): typeof Pocket {
|
||||
return pocketServer;
|
||||
return pocketServer;
|
||||
}
|
||||
|
||||
export async function unlockAccount(
|
||||
accountPrivateKey: string,
|
||||
accountPublicKey: string,
|
||||
accountPassphrase: string
|
||||
accountPrivateKey: string,
|
||||
accountPublicKey: string,
|
||||
accountPassphrase: string
|
||||
): Promise<PocketAATObject> {
|
||||
try {
|
||||
// @ts-ignore
|
||||
const account = await pocketServer.keybase.importAccount(
|
||||
Buffer.from(accountPrivateKey, "hex"),
|
||||
accountPassphrase
|
||||
);
|
||||
try {
|
||||
// @ts-ignore
|
||||
const account = await pocketServer.keybase.importAccount(
|
||||
Buffer.from(accountPrivateKey, "hex"),
|
||||
accountPassphrase
|
||||
);
|
||||
|
||||
if (account instanceof Error) {
|
||||
// noinspection ExceptionCaughtLocallyJS
|
||||
throw account;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
await pocketServer.keybase.unlockAccount(
|
||||
account.addressHex,
|
||||
accountPassphrase,
|
||||
0
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
return await PocketAAT.from(
|
||||
"0.0.1",
|
||||
accountPublicKey,
|
||||
accountPublicKey,
|
||||
accountPrivateKey
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
if (account instanceof Error) {
|
||||
// noinspection ExceptionCaughtLocallyJS
|
||||
throw account;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
await pocketServer.keybase.unlockAccount(
|
||||
account.addressHex,
|
||||
accountPassphrase,
|
||||
0
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
return await PocketAAT.from(
|
||||
"0.0.1",
|
||||
accountPublicKey,
|
||||
accountPublicKey,
|
||||
accountPrivateKey
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
export async function processRpcRequest(
|
||||
request: JSONRPCRequest,
|
||||
chain: string
|
||||
request: JSONRPCRequest,
|
||||
chain: string
|
||||
): Promise<JSONRPCResponseWithResult | JSONRPCResponseWithError | undefined> {
|
||||
return new Promise((resolve) => {
|
||||
jsonServer.call(
|
||||
request,
|
||||
{chain},
|
||||
(
|
||||
err?: JSONRPCResponseWithError | null,
|
||||
result?: JSONRPCResponseWithResult
|
||||
): void => {
|
||||
if (err) {
|
||||
return resolve(err);
|
||||
}
|
||||
resolve(result);
|
||||
}
|
||||
);
|
||||
});
|
||||
return new Promise((resolve) => {
|
||||
jsonServer.call(
|
||||
request,
|
||||
{ chain },
|
||||
(
|
||||
err?: JSONRPCResponseWithError | null,
|
||||
result?: JSONRPCResponseWithResult
|
||||
): void => {
|
||||
if (err) {
|
||||
return resolve(err);
|
||||
}
|
||||
resolve(result);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export async function start() {
|
||||
if (!config.str("pocket-app-id") || !config.str("pocket-app-key")) {
|
||||
const pocketHost = config.str("pocket-host");
|
||||
const pocketPort = config.uint("pocket-port");
|
||||
if (!pocketHost || !pocketPort) {
|
||||
errorExit(
|
||||
"Please set pocket-host and pocket-port config options if you do not have an API key set"
|
||||
);
|
||||
}
|
||||
|
||||
const dispatchURL = new URL(
|
||||
`http://${config.str("pocket-host")}:${config.uint("pocket-port")}`
|
||||
);
|
||||
const rpcProvider = new HttpRpcProvider(dispatchURL);
|
||||
const configuration = new Configuration();
|
||||
// @ts-ignore
|
||||
pocketServer = new Pocket([dispatchURL], rpcProvider, configuration);
|
||||
updateUsePocketGateway(false);
|
||||
if (!config.str("pocket-app-id") || !config.str("pocket-app-key")) {
|
||||
const pocketHost = config.str("pocket-host");
|
||||
const pocketPort = config.uint("pocket-port");
|
||||
if (!pocketHost || !pocketPort) {
|
||||
errorExit(
|
||||
"Please set pocket-host and pocket-port config options if you do not have an API key set"
|
||||
);
|
||||
}
|
||||
|
||||
if (!usePocketGateway()) {
|
||||
updateAat(
|
||||
await unlockAccount(
|
||||
<string>config.str("pocket-account-private-key"),
|
||||
<string>config.str("pocket-account-public-key"),
|
||||
"0"
|
||||
)
|
||||
);
|
||||
}
|
||||
const dispatchURL = new URL(
|
||||
`http://${config.str("pocket-host")}:${config.uint("pocket-port")}`
|
||||
);
|
||||
const rpcProvider = new HttpRpcProvider(dispatchURL);
|
||||
const configuration = new Configuration();
|
||||
// @ts-ignore
|
||||
pocketServer = new Pocket([dispatchURL], rpcProvider, configuration);
|
||||
updateUsePocketGateway(false);
|
||||
}
|
||||
|
||||
jsonServer = new jayson.Server(rpcMethods, {useContext: true});
|
||||
if (!usePocketGateway()) {
|
||||
updateAat(
|
||||
await unlockAccount(
|
||||
<string>config.str("pocket-account-private-key"),
|
||||
<string>config.str("pocket-account-public-key"),
|
||||
"0"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
(await getDHT("server")).on("connection", (socket: any) => {
|
||||
socket.rawStream._ondestroy = () => false;
|
||||
socket.on("data", async (data: any) => {
|
||||
let request: RPCRequest;
|
||||
try {
|
||||
request = unpack(data) as RPCRequest;
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
jsonServer = new jayson.Server(rpcMethods, { useContext: true });
|
||||
|
||||
try {
|
||||
socket.write(pack(await maybeProcessRequest(request)));
|
||||
} catch (error) {
|
||||
console.trace(error);
|
||||
socket.write(pack({error}));
|
||||
}
|
||||
socket.end();
|
||||
});
|
||||
(await getDHT("server")).on("connection", (socket: any) => {
|
||||
socket.rawStream._ondestroy = () => false;
|
||||
socket.on("data", async (data: any) => {
|
||||
let request: RPCRequest;
|
||||
try {
|
||||
request = unpack(data) as RPCRequest;
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
socket.write(pack(await maybeProcessRequest(request)));
|
||||
} catch (error) {
|
||||
console.trace(error);
|
||||
socket.write(pack({ error }));
|
||||
}
|
||||
socket.end();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,112 +1,112 @@
|
|||
import {maybeMapChainId, reverseMapChainId} from "../util.js";
|
||||
import { maybeMapChainId, reverseMapChainId } from "../util.js";
|
||||
import minimatch from "minimatch";
|
||||
// @ts-ignore
|
||||
import HTTPClient from "algosdk/dist/cjs/src/client/client.js";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {RpcMethodList} from "./index.js";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { RpcMethodList } from "./index.js";
|
||||
import config from "../config.js";
|
||||
|
||||
const allowedEndpoints: { [endpoint: string]: ("GET" | "POST")[] } = {
|
||||
"/v2/teal/compile": ["POST"],
|
||||
"/v2/accounts/*": ["GET"],
|
||||
"/v2/teal/compile": ["POST"],
|
||||
"/v2/accounts/*": ["GET"],
|
||||
};
|
||||
|
||||
export function proxyRestMethod(
|
||||
apiServer: string,
|
||||
matchChainId: string
|
||||
apiServer: string,
|
||||
matchChainId: string
|
||||
): Function {
|
||||
return async function (args: any, context: object) {
|
||||
// @ts-ignore
|
||||
let chain = context.chain;
|
||||
let chainId = maybeMapChainId(chain);
|
||||
return async function (args: any, context: object) {
|
||||
// @ts-ignore
|
||||
let chain = context.chain;
|
||||
let chainId = maybeMapChainId(chain);
|
||||
|
||||
if (!chainId) {
|
||||
throw new Error("Invalid Chain");
|
||||
if (!chainId) {
|
||||
throw new Error("Invalid Chain");
|
||||
}
|
||||
|
||||
chainId = reverseMapChainId(chainId as string);
|
||||
if (!chainId || chainId !== matchChainId) {
|
||||
throw new Error("Invalid Chain");
|
||||
}
|
||||
|
||||
let method = args.method ?? false;
|
||||
let endpoint = args.endpoint ?? false;
|
||||
let data = args.data ?? false;
|
||||
let query = args.query ?? false;
|
||||
let fullHeaders = args.fullHeaders ?? {};
|
||||
|
||||
fullHeaders = { ...fullHeaders, Referer: "lumeweb_dns_relay" };
|
||||
|
||||
if (method) {
|
||||
method = method.toUpperCase();
|
||||
}
|
||||
|
||||
if (!endpoint) {
|
||||
throw new Error("Endpoint Missing");
|
||||
}
|
||||
|
||||
let found = false;
|
||||
|
||||
for (const theEndpoint in allowedEndpoints) {
|
||||
if (minimatch(endpoint, theEndpoint)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
throw new Error("Endpoint Invalid");
|
||||
}
|
||||
|
||||
let apiUrl;
|
||||
try {
|
||||
apiUrl = sprintf(apiServer, chainId, config.str("pocket-app-id"));
|
||||
} catch (e) {
|
||||
apiUrl = apiServer;
|
||||
}
|
||||
|
||||
const client = new HTTPClient({}, apiUrl);
|
||||
let resp;
|
||||
switch (method) {
|
||||
case "GET":
|
||||
resp = await client.get(endpoint, query, fullHeaders);
|
||||
break;
|
||||
case "POST":
|
||||
if (Array.isArray(data?.data)) {
|
||||
data = new Uint8Array(Buffer.from(data.data));
|
||||
}
|
||||
|
||||
chainId = reverseMapChainId(chainId as string);
|
||||
if (!chainId || chainId !== matchChainId) {
|
||||
throw new Error("Invalid Chain");
|
||||
resp = await client.post(endpoint, data, { ...fullHeaders });
|
||||
break;
|
||||
default:
|
||||
throw new Error("Method Invalid");
|
||||
}
|
||||
|
||||
const getCircularReplacer = () => {
|
||||
const seen = new WeakSet();
|
||||
return (key: string, value: any): any => {
|
||||
if (typeof value === "object" && value !== null) {
|
||||
if (seen.has(value)) {
|
||||
return;
|
||||
}
|
||||
seen.add(value);
|
||||
}
|
||||
|
||||
let method = args.method ?? false;
|
||||
let endpoint = args.endpoint ?? false;
|
||||
let data = args.data ?? false;
|
||||
let query = args.query ?? false;
|
||||
let fullHeaders = args.fullHeaders ?? {};
|
||||
|
||||
fullHeaders = {...fullHeaders, Referer: "lumeweb_dns_relay"};
|
||||
|
||||
if (method) {
|
||||
method = method.toUpperCase();
|
||||
}
|
||||
|
||||
if (!endpoint) {
|
||||
throw new Error("Endpoint Missing");
|
||||
}
|
||||
|
||||
let found = false;
|
||||
|
||||
for (const theEndpoint in allowedEndpoints) {
|
||||
if (minimatch(endpoint, theEndpoint)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
throw new Error("Endpoint Invalid");
|
||||
}
|
||||
|
||||
let apiUrl;
|
||||
try {
|
||||
apiUrl = sprintf(apiServer, chainId, config.str("pocket-app-id"));
|
||||
} catch (e) {
|
||||
apiUrl = apiServer;
|
||||
}
|
||||
|
||||
const client = new HTTPClient({}, apiUrl);
|
||||
let resp;
|
||||
switch (method) {
|
||||
case "GET":
|
||||
resp = await client.get(endpoint, query, fullHeaders);
|
||||
break;
|
||||
case "POST":
|
||||
if (Array.isArray(data?.data)) {
|
||||
data = new Uint8Array(Buffer.from(data.data));
|
||||
}
|
||||
|
||||
resp = await client.post(endpoint, data, {...fullHeaders});
|
||||
break;
|
||||
default:
|
||||
throw new Error("Method Invalid");
|
||||
}
|
||||
|
||||
const getCircularReplacer = () => {
|
||||
const seen = new WeakSet();
|
||||
return (key: string, value: any): any => {
|
||||
if (typeof value === "object" && value !== null) {
|
||||
if (seen.has(value)) {
|
||||
return;
|
||||
}
|
||||
seen.add(value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
};
|
||||
|
||||
return JSON.parse(JSON.stringify(resp, getCircularReplacer()));
|
||||
return value;
|
||||
};
|
||||
};
|
||||
|
||||
return JSON.parse(JSON.stringify(resp, getCircularReplacer()));
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
algorand_rest_request: proxyRestMethod(
|
||||
"http://mainnet-api.algonode.network",
|
||||
"algorand-mainnet"
|
||||
),
|
||||
//'algorand_rest_request': proxyRestMethod("https://%s.gateway.pokt.network/v1/lb/%s", "algorand-mainnet"),
|
||||
algorand_rest_indexer_request: proxyRestMethod(
|
||||
"http://mainnet-idx.algonode.network",
|
||||
"algorand-mainnet-indexer"
|
||||
),
|
||||
algorand_rest_request: proxyRestMethod(
|
||||
"http://mainnet-api.algonode.network",
|
||||
"algorand-mainnet"
|
||||
),
|
||||
//'algorand_rest_request': proxyRestMethod("https://%s.gateway.pokt.network/v1/lb/%s", "algorand-mainnet"),
|
||||
algorand_rest_indexer_request: proxyRestMethod(
|
||||
"http://mainnet-idx.algonode.network",
|
||||
"algorand-mainnet-indexer"
|
||||
),
|
||||
} as RpcMethodList;
|
||||
|
|
|
@ -1,106 +1,106 @@
|
|||
import {ethers} from "ethers";
|
||||
import {Pocket, PocketAAT} from "@pokt-network/pocket-js";
|
||||
import {maybeMapChainId, reverseMapChainId} from "../util.js";
|
||||
import {Connection} from "@solana/web3.js";
|
||||
import {getAat, getPocketServer} from "../rpc.js";
|
||||
import config, {usePocketGateway} from "../config.js";
|
||||
import { ethers } from "ethers";
|
||||
import { Pocket, PocketAAT } from "@pokt-network/pocket-js";
|
||||
import { maybeMapChainId, reverseMapChainId } from "../util.js";
|
||||
import { Connection } from "@solana/web3.js";
|
||||
import { getAat, getPocketServer } from "../rpc.js";
|
||||
import config, { usePocketGateway } from "../config.js";
|
||||
|
||||
type RpcProviderMethod = (method: string, params: Array<any>) => Promise<any>;
|
||||
|
||||
const gatewayProviders: { [name: string]: RpcProviderMethod } = {};
|
||||
|
||||
const gatewayMethods: {
|
||||
[name: string]: (chainId: string) => RpcProviderMethod;
|
||||
[name: string]: (chainId: string) => RpcProviderMethod;
|
||||
} = {
|
||||
default: (chainId: string): RpcProviderMethod => {
|
||||
const provider = new ethers.providers.JsonRpcProvider({
|
||||
url: `https://${chainId}.gateway.pokt.network/v1/lb/${config.str(
|
||||
"pocket-api-id"
|
||||
)}`,
|
||||
password: config.str("pocket-api-key"),
|
||||
});
|
||||
return provider.send.bind(provider);
|
||||
},
|
||||
"sol-mainnet": (chainId: string): RpcProviderMethod => {
|
||||
const provider = new Connection(
|
||||
`https://solana-mainnet.gateway.pokt.network/v1/lb/${config.str(
|
||||
"pocket-api-id"
|
||||
)}`
|
||||
);
|
||||
default: (chainId: string): RpcProviderMethod => {
|
||||
const provider = new ethers.providers.JsonRpcProvider({
|
||||
url: `https://${chainId}.gateway.pokt.network/v1/lb/${config.str(
|
||||
"pocket-api-id"
|
||||
)}`,
|
||||
password: config.str("pocket-api-key"),
|
||||
});
|
||||
return provider.send.bind(provider);
|
||||
},
|
||||
"sol-mainnet": (chainId: string): RpcProviderMethod => {
|
||||
const provider = new Connection(
|
||||
`https://solana-mainnet.gateway.pokt.network/v1/lb/${config.str(
|
||||
"pocket-api-id"
|
||||
)}`
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
return provider._rpcRequest.bind(provider);
|
||||
},
|
||||
// @ts-ignore
|
||||
return provider._rpcRequest.bind(provider);
|
||||
},
|
||||
};
|
||||
|
||||
export function proxyRpcMethod(
|
||||
method: string,
|
||||
chains: string[] = []
|
||||
method: string,
|
||||
chains: string[] = []
|
||||
): Function {
|
||||
return async function (args: any, context: object) {
|
||||
// @ts-ignore
|
||||
let chain = context.chain;
|
||||
let chainId = maybeMapChainId(chain);
|
||||
return async function (args: any, context: object) {
|
||||
// @ts-ignore
|
||||
let chain = context.chain;
|
||||
let chainId = maybeMapChainId(chain);
|
||||
|
||||
let chainMatch = true;
|
||||
let chainMatch = true;
|
||||
|
||||
if (
|
||||
chains.length > 0 &&
|
||||
!chains.includes(chain) &&
|
||||
!chains.includes(chainId.toString())
|
||||
) {
|
||||
chainMatch = false;
|
||||
}
|
||||
if (
|
||||
chains.length > 0 &&
|
||||
!chains.includes(chain) &&
|
||||
!chains.includes(chainId.toString())
|
||||
) {
|
||||
chainMatch = false;
|
||||
}
|
||||
|
||||
if (!chainId || !chainMatch) {
|
||||
throw new Error("Invalid Chain");
|
||||
}
|
||||
if (!chainId || !chainMatch) {
|
||||
throw new Error("Invalid Chain");
|
||||
}
|
||||
|
||||
if (usePocketGateway()) {
|
||||
chainId = reverseMapChainId(chainId as string);
|
||||
if (!chainId) {
|
||||
throw new Error("Invalid Chain");
|
||||
}
|
||||
if (usePocketGateway()) {
|
||||
chainId = reverseMapChainId(chainId as string);
|
||||
if (!chainId) {
|
||||
throw new Error("Invalid Chain");
|
||||
}
|
||||
|
||||
let provider: RpcProviderMethod | boolean =
|
||||
gatewayProviders[chainId as string] || false;
|
||||
if (!provider) {
|
||||
provider = getRpcProvider(chainId as string);
|
||||
}
|
||||
gatewayProviders[chainId as string] = provider;
|
||||
return await provider(method, args);
|
||||
}
|
||||
let provider: RpcProviderMethod | boolean =
|
||||
gatewayProviders[chainId as string] || false;
|
||||
if (!provider) {
|
||||
provider = getRpcProvider(chainId as string);
|
||||
}
|
||||
gatewayProviders[chainId as string] = provider;
|
||||
return await provider(method, args);
|
||||
}
|
||||
|
||||
return await sendRelay(
|
||||
JSON.stringify(args),
|
||||
<string>chainId,
|
||||
getAat() as unknown as PocketAAT
|
||||
);
|
||||
};
|
||||
return await sendRelay(
|
||||
JSON.stringify(args),
|
||||
<string>chainId,
|
||||
getAat() as unknown as PocketAAT
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
// Call this every time you want to fetch RPC data
|
||||
async function sendRelay(
|
||||
rpcQuery: string,
|
||||
blockchain: string,
|
||||
pocketAAT: PocketAAT
|
||||
rpcQuery: string,
|
||||
blockchain: string,
|
||||
pocketAAT: PocketAAT
|
||||
) {
|
||||
try {
|
||||
return await (getPocketServer() as unknown as Pocket).sendRelay(
|
||||
rpcQuery,
|
||||
blockchain,
|
||||
pocketAAT
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
throw e;
|
||||
}
|
||||
try {
|
||||
return await (getPocketServer() as unknown as Pocket).sendRelay(
|
||||
rpcQuery,
|
||||
blockchain,
|
||||
pocketAAT
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
function getRpcProvider(chain: string): RpcProviderMethod {
|
||||
if (chain in gatewayMethods) {
|
||||
return gatewayMethods[chain](chain);
|
||||
}
|
||||
if (chain in gatewayMethods) {
|
||||
return gatewayMethods[chain](chain);
|
||||
}
|
||||
|
||||
return gatewayMethods.default(chain);
|
||||
return gatewayMethods.default(chain);
|
||||
}
|
||||
|
|
142
src/rpc/dns.ts
142
src/rpc/dns.ts
|
@ -1,16 +1,16 @@
|
|||
import {isIp} from "../util.js";
|
||||
import {RpcMethodList} from "./index.js";
|
||||
import {createRequire} from "module";
|
||||
import { isIp } from "../util.js";
|
||||
import { RpcMethodList } from "./index.js";
|
||||
import { createRequire } from "module";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const bns = require("bns");
|
||||
const {StubResolver, RecursiveResolver} = bns;
|
||||
const { StubResolver, RecursiveResolver } = bns;
|
||||
|
||||
const resolverOpt = {
|
||||
tcp: true,
|
||||
inet6: false,
|
||||
edns: true,
|
||||
dnssec: true,
|
||||
tcp: true,
|
||||
inet6: false,
|
||||
edns: true,
|
||||
dnssec: true,
|
||||
};
|
||||
|
||||
const globalResolver = new RecursiveResolver(resolverOpt);
|
||||
|
@ -18,90 +18,90 @@ globalResolver.hints.setDefault();
|
|||
globalResolver.open();
|
||||
|
||||
async function resolveNameServer(ns: string): Promise<string | boolean> {
|
||||
if (isIp(ns)) {
|
||||
return ns;
|
||||
}
|
||||
let result = await getDnsRecords(ns, "A");
|
||||
if (isIp(ns)) {
|
||||
return ns;
|
||||
}
|
||||
let result = await getDnsRecords(ns, "A");
|
||||
|
||||
if (result.length) {
|
||||
return result[0];
|
||||
}
|
||||
if (result.length) {
|
||||
return result[0];
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
async function getDnsRecords(
|
||||
domain: string,
|
||||
type: string,
|
||||
authority: boolean = false,
|
||||
resolver = globalResolver
|
||||
domain: string,
|
||||
type: string,
|
||||
authority: boolean = false,
|
||||
resolver = globalResolver
|
||||
): Promise<string[]> {
|
||||
let result;
|
||||
let result;
|
||||
|
||||
try {
|
||||
result = await resolver.lookup(domain, type);
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
result = await resolver.lookup(domain, type);
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let prop = authority ? "authority" : "answer";
|
||||
let prop = authority ? "authority" : "answer";
|
||||
|
||||
if (!result || !result[prop].length) {
|
||||
return [];
|
||||
}
|
||||
if (!result || !result[prop].length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return result[prop].map(
|
||||
(item: object) =>
|
||||
// @ts-ignore
|
||||
item.data.address ?? item.data.target ?? item.data.ns ?? null
|
||||
);
|
||||
return result[prop].map(
|
||||
(item: object) =>
|
||||
// @ts-ignore
|
||||
item.data.address ?? item.data.target ?? item.data.ns ?? null
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
dnslookup: async function (args: any) {
|
||||
let dnsResults: string[] = [];
|
||||
let domain = args.domain;
|
||||
let ns = args.nameserver;
|
||||
let dnsResolver = ns ? new StubResolver(resolverOpt) : globalResolver;
|
||||
await dnsResolver.open();
|
||||
dnslookup: async function (args: any) {
|
||||
let dnsResults: string[] = [];
|
||||
let domain = args.domain;
|
||||
let ns = args.nameserver;
|
||||
let dnsResolver = ns ? new StubResolver(resolverOpt) : globalResolver;
|
||||
await dnsResolver.open();
|
||||
|
||||
if (ns) {
|
||||
let nextNs = ns;
|
||||
let prevNs = null;
|
||||
if (ns) {
|
||||
let nextNs = ns;
|
||||
let prevNs = null;
|
||||
|
||||
while (nextNs) {
|
||||
nextNs = await resolveNameServer(nextNs);
|
||||
if (!nextNs) {
|
||||
nextNs = prevNs;
|
||||
}
|
||||
|
||||
dnsResolver.setServers([nextNs]);
|
||||
|
||||
if (nextNs === prevNs) {
|
||||
break;
|
||||
}
|
||||
let result = await getDnsRecords(domain, "NS", true, dnsResolver);
|
||||
prevNs = nextNs;
|
||||
nextNs = result.length ? result[0] : false;
|
||||
}
|
||||
while (nextNs) {
|
||||
nextNs = await resolveNameServer(nextNs);
|
||||
if (!nextNs) {
|
||||
nextNs = prevNs;
|
||||
}
|
||||
|
||||
for (const queryType of ["CNAME", "A"]) {
|
||||
let result = await getDnsRecords(domain, queryType, false, dnsResolver);
|
||||
dnsResolver.setServers([nextNs]);
|
||||
|
||||
if (result) {
|
||||
dnsResults = dnsResults.concat(result);
|
||||
}
|
||||
if (nextNs === prevNs) {
|
||||
break;
|
||||
}
|
||||
let result = await getDnsRecords(domain, "NS", true, dnsResolver);
|
||||
prevNs = nextNs;
|
||||
nextNs = result.length ? result[0] : false;
|
||||
}
|
||||
}
|
||||
|
||||
await dnsResolver.close();
|
||||
for (const queryType of ["CNAME", "A"]) {
|
||||
let result = await getDnsRecords(domain, queryType, false, dnsResolver);
|
||||
|
||||
dnsResults = dnsResults.filter(Boolean);
|
||||
if (result) {
|
||||
dnsResults = dnsResults.concat(result);
|
||||
}
|
||||
}
|
||||
|
||||
if (dnsResults.length) {
|
||||
return dnsResults[0];
|
||||
}
|
||||
await dnsResolver.close();
|
||||
|
||||
return false;
|
||||
},
|
||||
dnsResults = dnsResults.filter(Boolean);
|
||||
|
||||
if (dnsResults.length) {
|
||||
return dnsResults[0];
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
} as RpcMethodList;
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import {proxyRpcMethod} from "./common.js";
|
||||
import {RpcMethodList} from "./index.js";
|
||||
import { proxyRpcMethod } from "./common.js";
|
||||
import { RpcMethodList } from "./index.js";
|
||||
|
||||
const rpcMethods: RpcMethodList = {};
|
||||
|
||||
function proxyEvmRpcMethod(method: string): Function {
|
||||
return proxyRpcMethod(method);
|
||||
return proxyRpcMethod(method);
|
||||
}
|
||||
|
||||
["eth_call", "eth_chainId", "net_version"].forEach((method) => {
|
||||
rpcMethods[method] = proxyEvmRpcMethod(method);
|
||||
rpcMethods[method] = proxyEvmRpcMethod(method);
|
||||
});
|
||||
|
||||
export default rpcMethods;
|
||||
|
|
|
@ -1,88 +1,88 @@
|
|||
import {RpcMethodList} from "./index.js";
|
||||
import { RpcMethodList } from "./index.js";
|
||||
// @ts-ignore
|
||||
import rand from "random-key";
|
||||
// @ts-ignore
|
||||
import SPVNode from "hsd/lib/node/spvnode.js";
|
||||
import config from "../config.js";
|
||||
import {createRequire} from "module";
|
||||
import { createRequire } from "module";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const {NodeClient} = require("hs-client");
|
||||
const { NodeClient } = require("hs-client");
|
||||
|
||||
let hsdServer: SPVNode;
|
||||
|
||||
let clientArgs = {
|
||||
network: "main",
|
||||
host: "127.0.0.1",
|
||||
port: 12037,
|
||||
apiKey: rand.generate(),
|
||||
network: "main",
|
||||
host: "127.0.0.1",
|
||||
port: 12037,
|
||||
apiKey: rand.generate(),
|
||||
};
|
||||
|
||||
if (!config.bool("hsd-use-external-node")) {
|
||||
hsdServer = new SPVNode({
|
||||
config: false,
|
||||
argv: false,
|
||||
env: true,
|
||||
noDns: true,
|
||||
memory: false,
|
||||
httpHost: "127.0.0.1",
|
||||
apiKey: clientArgs.apiKey,
|
||||
logFile: false,
|
||||
logConsole: true,
|
||||
logLevel: "info",
|
||||
workers: true,
|
||||
network: "main",
|
||||
});
|
||||
hsdServer = new SPVNode({
|
||||
config: false,
|
||||
argv: false,
|
||||
env: true,
|
||||
noDns: true,
|
||||
memory: false,
|
||||
httpHost: "127.0.0.1",
|
||||
apiKey: clientArgs.apiKey,
|
||||
logFile: false,
|
||||
logConsole: true,
|
||||
logLevel: "info",
|
||||
workers: true,
|
||||
network: "main",
|
||||
});
|
||||
|
||||
console.log(`HSD API KEY: ${clientArgs.apiKey}`);
|
||||
console.log(`HSD API KEY: ${clientArgs.apiKey}`);
|
||||
|
||||
hsdServer.on("abort", async (err: any) => {
|
||||
const timeout = setTimeout(() => {
|
||||
console.error("Shutdown is taking a long time. Exiting.");
|
||||
process.exit(3);
|
||||
}, 5000);
|
||||
hsdServer.on("abort", async (err: any) => {
|
||||
const timeout = setTimeout(() => {
|
||||
console.error("Shutdown is taking a long time. Exiting.");
|
||||
process.exit(3);
|
||||
}, 5000);
|
||||
|
||||
timeout.unref();
|
||||
|
||||
try {
|
||||
console.error("Shutting down...");
|
||||
await hsdServer.close();
|
||||
clearTimeout(timeout);
|
||||
console.error((err as Error).stack);
|
||||
process.exit(2);
|
||||
} catch (e: any) {
|
||||
console.error(`Error occurred during shutdown: ${(e as Error).message}`);
|
||||
process.exit(3);
|
||||
}
|
||||
});
|
||||
timeout.unref();
|
||||
|
||||
try {
|
||||
await hsdServer.ensure();
|
||||
await hsdServer.open();
|
||||
await hsdServer.connect();
|
||||
|
||||
hsdServer.startSync();
|
||||
console.error("Shutting down...");
|
||||
await hsdServer.close();
|
||||
clearTimeout(timeout);
|
||||
console.error((err as Error).stack);
|
||||
process.exit(2);
|
||||
} catch (e: any) {
|
||||
console.error((e as Error).stack);
|
||||
console.error(`Error occurred during shutdown: ${(e as Error).message}`);
|
||||
process.exit(3);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await hsdServer.ensure();
|
||||
await hsdServer.open();
|
||||
await hsdServer.connect();
|
||||
|
||||
hsdServer.startSync();
|
||||
} catch (e: any) {
|
||||
console.error((e as Error).stack);
|
||||
}
|
||||
} else {
|
||||
clientArgs = {
|
||||
network: config.str("hsd-network-type"),
|
||||
host: config.str("hsd-host"),
|
||||
port: config.uint("hsd-port"),
|
||||
apiKey: config.str("hsd-api-key"),
|
||||
};
|
||||
clientArgs = {
|
||||
network: config.str("hsd-network-type"),
|
||||
host: config.str("hsd-host"),
|
||||
port: config.uint("hsd-port"),
|
||||
apiKey: config.str("hsd-api-key"),
|
||||
};
|
||||
}
|
||||
|
||||
const hnsClient = new NodeClient(clientArgs);
|
||||
|
||||
export default {
|
||||
getnameresource: async function (args: any, context: object) {
|
||||
// @ts-ignore
|
||||
if ("hns" !== context.chain) {
|
||||
throw new Error("Invalid Chain");
|
||||
}
|
||||
getnameresource: async function (args: any, context: object) {
|
||||
// @ts-ignore
|
||||
if ("hns" !== context.chain) {
|
||||
throw new Error("Invalid Chain");
|
||||
}
|
||||
|
||||
return await hnsClient.execute("getnameresource", args);
|
||||
},
|
||||
return await hnsClient.execute("getnameresource", args);
|
||||
},
|
||||
} as RpcMethodList;
|
||||
|
|
|
@ -2,17 +2,17 @@ export type RpcMethodList = { [name: string]: Function };
|
|||
|
||||
export * from "./common.js";
|
||||
|
||||
import {default as DnsMethods} from "./dns.js";
|
||||
import {default as EvmMethods} from "./evm.js";
|
||||
import {default as HnsMethods} from "./handshake.js";
|
||||
import {default as SolMethods} from "./solana.js";
|
||||
import {default as AlgoMethods} from "./algorand.js";
|
||||
import { default as DnsMethods } from "./dns.js";
|
||||
import { default as EvmMethods } from "./evm.js";
|
||||
import { default as HnsMethods } from "./handshake.js";
|
||||
import { default as SolMethods } from "./solana.js";
|
||||
import { default as AlgoMethods } from "./algorand.js";
|
||||
|
||||
export const rpcMethods: RpcMethodList = Object.assign(
|
||||
{},
|
||||
DnsMethods,
|
||||
EvmMethods,
|
||||
HnsMethods,
|
||||
SolMethods,
|
||||
AlgoMethods
|
||||
{},
|
||||
DnsMethods,
|
||||
EvmMethods,
|
||||
HnsMethods,
|
||||
SolMethods,
|
||||
AlgoMethods
|
||||
);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {proxyRpcMethod} from "./common.js";
|
||||
import {RpcMethodList} from "./index.js";
|
||||
import * as chainNetworks from "../networks.json" assert {type: "json"};
|
||||
import { proxyRpcMethod } from "./common.js";
|
||||
import { RpcMethodList } from "./index.js";
|
||||
import * as chainNetworks from "../networks.json" assert { type: "json" };
|
||||
|
||||
export default {
|
||||
getAccountInfo: proxyRpcMethod("getAccountInfo", [
|
||||
chainNetworks["sol-mainnet"],
|
||||
]),
|
||||
getAccountInfo: proxyRpcMethod("getAccountInfo", [
|
||||
chainNetworks["sol-mainnet"],
|
||||
]),
|
||||
} as RpcMethodList;
|
||||
|
|
74
src/util.ts
74
src/util.ts
|
@ -1,66 +1,66 @@
|
|||
import * as chainNetworks from "./networks.json" assert {type: "json"};
|
||||
import {Buffer} from "buffer";
|
||||
import {blake2b} from "libskynet";
|
||||
import * as chainNetworks from "./networks.json" assert { type: "json" };
|
||||
import { Buffer } from "buffer";
|
||||
import { blake2b } from "libskynet";
|
||||
|
||||
type networks = { [net: string]: string };
|
||||
|
||||
export function errorExit(msg: string): void {
|
||||
console.error(msg);
|
||||
process.exit(1);
|
||||
console.error(msg);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
export function maybeMapChainId(chain: string): string | boolean {
|
||||
if (chain in chainNetworks) {
|
||||
return (chainNetworks as networks)[chain];
|
||||
}
|
||||
if (chain in chainNetworks) {
|
||||
return (chainNetworks as networks)[chain];
|
||||
}
|
||||
|
||||
if (
|
||||
[parseInt(chain, 16).toString(), parseInt(chain, 10).toString()].includes(
|
||||
chain.toLowerCase()
|
||||
)
|
||||
) {
|
||||
return chain;
|
||||
}
|
||||
if (
|
||||
[parseInt(chain, 16).toString(), parseInt(chain, 10).toString()].includes(
|
||||
chain.toLowerCase()
|
||||
)
|
||||
) {
|
||||
return chain;
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
export function reverseMapChainId(chainId: string): string | boolean {
|
||||
let vals = Object.values(chainNetworks);
|
||||
if (!vals.includes(chainId)) {
|
||||
return false;
|
||||
}
|
||||
let vals = Object.values(chainNetworks);
|
||||
if (!vals.includes(chainId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Object.keys(chainNetworks)[vals.indexOf(chainId)];
|
||||
return Object.keys(chainNetworks)[vals.indexOf(chainId)];
|
||||
}
|
||||
|
||||
export function isIp(ip: string) {
|
||||
return /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(
|
||||
ip
|
||||
);
|
||||
return /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(
|
||||
ip
|
||||
);
|
||||
}
|
||||
|
||||
export function hashDataKey(dataKey: string): Uint8Array {
|
||||
return blake2b(encodeUtf8String(dataKey));
|
||||
return blake2b(encodeUtf8String(dataKey));
|
||||
}
|
||||
|
||||
function encodeUtf8String(str: string): Uint8Array {
|
||||
const byteArray = stringToUint8ArrayUtf8(str);
|
||||
const encoded = new Uint8Array(8 + byteArray.length);
|
||||
encoded.set(encodeNumber(byteArray.length));
|
||||
encoded.set(byteArray, 8);
|
||||
return encoded;
|
||||
const byteArray = stringToUint8ArrayUtf8(str);
|
||||
const encoded = new Uint8Array(8 + byteArray.length);
|
||||
encoded.set(encodeNumber(byteArray.length));
|
||||
encoded.set(byteArray, 8);
|
||||
return encoded;
|
||||
}
|
||||
|
||||
function stringToUint8ArrayUtf8(str: string): Uint8Array {
|
||||
return Uint8Array.from(Buffer.from(str, "utf-8"));
|
||||
return Uint8Array.from(Buffer.from(str, "utf-8"));
|
||||
}
|
||||
|
||||
function encodeNumber(num: number): Uint8Array {
|
||||
const encoded = new Uint8Array(8);
|
||||
for (let index = 0; index < encoded.length; index++) {
|
||||
encoded[index] = num & 0xff;
|
||||
num = num >> 8;
|
||||
}
|
||||
return encoded;
|
||||
const encoded = new Uint8Array(8);
|
||||
for (let index = 0; index < encoded.length; index++) {
|
||||
encoded[index] = num & 0xff;
|
||||
num = num >> 8;
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue