parent
ff7ebb0984
commit
070b7825e2
|
@ -12,20 +12,21 @@
|
||||||
"@hyperswarm/dht": "^6.0.1",
|
"@hyperswarm/dht": "^6.0.1",
|
||||||
"@hyperswarm/dht-relay": "^0.3.0",
|
"@hyperswarm/dht-relay": "^0.3.0",
|
||||||
"@pokt-network/pocket-js": "^0.8.0-rc",
|
"@pokt-network/pocket-js": "^0.8.0-rc",
|
||||||
"@root/greenlock": "^4.0.5",
|
|
||||||
"@solana/web3.js": "^1.47.3",
|
"@solana/web3.js": "^1.47.3",
|
||||||
|
"@types/acme-client": "^3.3.0",
|
||||||
"@types/node": "^18.0.0",
|
"@types/node": "^18.0.0",
|
||||||
"@types/node-cron": "^3.0.2",
|
"@types/node-cron": "^3.0.2",
|
||||||
"@types/ws": "^8.5.3",
|
"@types/ws": "^8.5.3",
|
||||||
"@types/xml2js": "^0.4.11",
|
"@types/xml2js": "^0.4.11",
|
||||||
|
"acme-client": "^4.2.5",
|
||||||
"algosdk": "^1.18.1",
|
"algosdk": "^1.18.1",
|
||||||
"async-mutex": "^0.3.2",
|
"async-mutex": "^0.3.2",
|
||||||
"bcfg": "^0.1.7",
|
"bcfg": "^0.1.7",
|
||||||
|
"date-fns": "^2.28.0",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
"ethers": "^5.6.9",
|
"ethers": "^5.6.9",
|
||||||
"express": "^4.18.1",
|
"express": "^4.18.1",
|
||||||
"greenlock-express": "^4.0.3",
|
"hsd": "https://github.com/LumeWeb/hsd.git#spv-namestate",
|
||||||
"hsd": "^4.0.1",
|
|
||||||
"jayson": "^3.6.6",
|
"jayson": "^3.6.6",
|
||||||
"json-stable-stringify": "^1.0.1",
|
"json-stable-stringify": "^1.0.1",
|
||||||
"libskynet": "^0.0.48",
|
"libskynet": "^0.0.48",
|
||||||
|
@ -44,6 +45,7 @@
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/minimatch": "^3.0.5",
|
"@types/minimatch": "^3.0.5",
|
||||||
"@types/sprintf-js": "^1.1.2",
|
"@types/sprintf-js": "^1.1.2",
|
||||||
|
"esbuild": "^0.14.49",
|
||||||
"hyper-typings": "^1.0.0",
|
"hyper-typings": "^1.0.0",
|
||||||
"prettier": "^2.7.1"
|
"prettier": "^2.7.1"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,36 @@
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import BConfig from "bcfg";
|
import BConfig from "bcfg";
|
||||||
import { errorExit } from "./util.js";
|
import {errorExit} from "./util.js";
|
||||||
|
|
||||||
const config = new BConfig("lumeweb-relay");
|
const config = new BConfig("lumeweb-relay");
|
||||||
|
|
||||||
config.inject({
|
config.inject({
|
||||||
relayPort: 8080,
|
relayPort: 8080,
|
||||||
});
|
});
|
||||||
|
|
||||||
config.load({
|
config.load({
|
||||||
env: true,
|
env: true,
|
||||||
argv: true,
|
argv: true,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
config.open("config.conf");
|
config.open("config.conf");
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
}
|
||||||
|
|
||||||
for (const setting of ["relay-domain", "afraid-username", "relay-seed"]) {
|
for (const setting of ["relay-domain", "afraid-username", "relay-seed"]) {
|
||||||
if (!config.get(setting)) {
|
if (!config.get(setting)) {
|
||||||
errorExit(`Required config option ${setting} not set`);
|
errorExit(`Required config option ${setting} not set`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let usingPocketGateway = true;
|
let usingPocketGateway = true;
|
||||||
|
|
||||||
export function usePocketGateway() {
|
export function usePocketGateway() {
|
||||||
return usingPocketGateway;
|
return usingPocketGateway;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateUsePocketGateway(state: boolean): void {
|
export function updateUsePocketGateway(state: boolean): void {
|
||||||
usingPocketGateway = state;
|
usingPocketGateway = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|
60
src/dht.ts
60
src/dht.ts
|
@ -1,41 +1,57 @@
|
||||||
import { createRequire } from "module";
|
import {createRequire} from "module";
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
const DHT = require("@hyperswarm/dht");
|
const DHT = require("@hyperswarm/dht");
|
||||||
import { errorExit } from "./util.js";
|
import {errorExit} from "./util.js";
|
||||||
import {
|
import {
|
||||||
deriveMyskyRootKeypair,
|
deriveMyskyRootKeypair,
|
||||||
ed25519Keypair,
|
ed25519Keypair,
|
||||||
seedPhraseToSeed,
|
seedPhraseToSeed,
|
||||||
validSeedPhrase,
|
validSeedPhrase,
|
||||||
} from "libskynet";
|
} from "libskynet";
|
||||||
import config from "./config.js";
|
import config from "./config.js";
|
||||||
|
|
||||||
|
let node: {
|
||||||
|
ready: () => any;
|
||||||
|
createServer: () => any;
|
||||||
|
defaultKeyPair: any;
|
||||||
|
on: any;
|
||||||
|
};
|
||||||
let server: {
|
let server: {
|
||||||
listen: (arg0: ed25519Keypair) => void;
|
listen: (arg0: ed25519Keypair) => any;
|
||||||
ready: () => any;
|
on: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function start() {
|
async function start() {
|
||||||
const seed = config.str("relay-seed");
|
const seed = config.str("relay-seed");
|
||||||
|
|
||||||
let [, err] = validSeedPhrase(seed);
|
let [, err] = validSeedPhrase(seed);
|
||||||
if (err !== null) {
|
if (err !== null) {
|
||||||
errorExit("RELAY_SEED is invalid. Aborting.");
|
errorExit("RELAY_SEED is invalid. Aborting.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyPair = deriveMyskyRootKeypair(seedPhraseToSeed(seed)[0]);
|
const keyPair = deriveMyskyRootKeypair(seedPhraseToSeed(seed)[0]);
|
||||||
|
|
||||||
const node = new DHT({ keyPair });
|
node = new DHT({keyPair});
|
||||||
|
|
||||||
await node.ready();
|
await node.ready();
|
||||||
|
|
||||||
return (server = node);
|
server = node.createServer();
|
||||||
|
await server.listen(keyPair);
|
||||||
|
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function get() {
|
export async function get(
|
||||||
if (!server) {
|
ret: "server" | "dht" = "dht"
|
||||||
return start();
|
): Promise<typeof server | typeof node> {
|
||||||
}
|
if (!node) {
|
||||||
|
await start();
|
||||||
|
}
|
||||||
|
|
||||||
return server;
|
if (ret == "server") {
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
144
src/dns.ts
144
src/dns.ts
|
@ -1,122 +1,96 @@
|
||||||
import cron from "node-cron";
|
import cron from "node-cron";
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import { get as getDHT } from "./dht.js";
|
import {get as getDHT} from "./dht.js";
|
||||||
import { overwriteRegistryEntry } from "libskynetnode";
|
import {overwriteRegistryEntry} from "libskynetnode";
|
||||||
import { Buffer } from "buffer";
|
import {Buffer} from "buffer";
|
||||||
import { blake2b } from "libskynet";
|
import {Parser} from "xml2js";
|
||||||
import { Parser } from "xml2js";
|
import {URL} from "url";
|
||||||
import { URL } from "url";
|
import {errorExit, hashDataKey} from "./util.js";
|
||||||
import { errorExit } from "./util.js";
|
import {pack} from "msgpackr";
|
||||||
import { pack } from "msgpackr";
|
|
||||||
import config from "./config.js";
|
import config from "./config.js";
|
||||||
|
|
||||||
const { createHash } = await import("crypto");
|
const {createHash} = await import("crypto");
|
||||||
|
|
||||||
let activeIp: string;
|
let activeIp: string;
|
||||||
|
|
||||||
const REGISTRY_DHT_KEY = "lumeweb-dht-relay";
|
const REGISTRY_NODE_KEY = "lumeweb-dht-node";
|
||||||
|
|
||||||
async function ipUpdate() {
|
async function ipUpdate() {
|
||||||
let currentIp = await getCurrentIp();
|
let currentIp = await getCurrentIp();
|
||||||
|
|
||||||
if (activeIp && currentIp === activeIp) {
|
if (activeIp && currentIp === activeIp) {
|
||||||
return;
|
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() {
|
export async function start() {
|
||||||
const dht = await getDHT();
|
const dht = (await getDHT()) as any;
|
||||||
|
|
||||||
await ipUpdate();
|
await ipUpdate();
|
||||||
|
|
||||||
await overwriteRegistryEntry(
|
await overwriteRegistryEntry(
|
||||||
dht.defaultKeyPair,
|
dht.defaultKeyPair,
|
||||||
hashDataKey(REGISTRY_DHT_KEY),
|
hashDataKey(REGISTRY_NODE_KEY),
|
||||||
pack(`${config.str("relay-domain")}:${config.uint("relay-port")}`)
|
pack(`${config.str("relay-domain")}:${config.uint("relay-port")}`)
|
||||||
);
|
);
|
||||||
|
|
||||||
cron.schedule("0 * * * *", ipUpdate);
|
console.log(
|
||||||
}
|
"node pubkey:",
|
||||||
|
Buffer.from(dht.defaultKeyPair.publicKey).toString("hex")
|
||||||
|
);
|
||||||
|
|
||||||
function hashDataKey(dataKey: string): Uint8Array {
|
cron.schedule("0 * * * *", ipUpdate);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function stringToUint8ArrayUtf8(str: string): Uint8Array {
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDomainInfo() {
|
async function getDomainInfo() {
|
||||||
const relayDomain = config.str("relay-domain");
|
const relayDomain = config.str("relay-domain");
|
||||||
const parser = new Parser();
|
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("action", "getdyndns");
|
||||||
params.append("v", "2");
|
params.append("v", "2");
|
||||||
params.append("style", "xml");
|
params.append("style", "xml");
|
||||||
|
|
||||||
const hash = createHash("sha1");
|
const hash = createHash("sha1");
|
||||||
hash.update(
|
hash.update(
|
||||||
`${config.str("afraid-username")}|${config.str("afraid-password")}`
|
`${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)) {
|
if (/could not authenticate/i.test(response)) {
|
||||||
errorExit("Failed to authenticate to afraid.org");
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!domain) {
|
const json = await parser.parseStringPromise(response);
|
||||||
errorExit(`Domain ${relayDomain} not found in afraid.org account`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return domain;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getCurrentIp(): Promise<string> {
|
async function getCurrentIp(): Promise<string> {
|
||||||
const response = await (await fetch("http://checkip.dyndns.org")).text();
|
return await (await fetch("http://ip1.dynupdate.no-ip.com/")).text();
|
||||||
const parser = new Parser();
|
|
||||||
|
|
||||||
const html = await parser.parseStringPromise(response.trim());
|
|
||||||
|
|
||||||
return html.html.body[0].split(":").pop();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { start as startDns } from "./dns.js";
|
import {start as startDns} from "./dns.js";
|
||||||
import { start as startRpc } from "./rpc.js";
|
import {start as startRpc} from "./rpc.js";
|
||||||
import { start as startRelay } from "./relay.js";
|
import {start as startRelay} from "./relay.js";
|
||||||
|
|
||||||
await startDns();
|
await startDns();
|
||||||
await startRpc();
|
await startRpc();
|
||||||
await startRelay();
|
await startRelay();
|
||||||
|
|
||||||
process.on("uncaughtException", function (err) {
|
process.on("uncaughtException", function (err) {
|
||||||
console.log("Caught exception: " + err);
|
console.log("Caught exception: " + err);
|
||||||
});
|
});
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|
145
src/relay.ts
145
src/relay.ts
|
@ -1,69 +1,118 @@
|
||||||
import WS from "ws";
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import DHT from "@hyperswarm/dht";
|
import DHT from "@hyperswarm/dht";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { relay } from "@hyperswarm/dht-relay";
|
import {relay} from "@hyperswarm/dht-relay";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import Stream from "@hyperswarm/dht-relay/ws";
|
import Stream from "@hyperswarm/dht-relay/ws";
|
||||||
import { get as getDHT } from "./dht.js";
|
import express, {Express} from "express";
|
||||||
// @ts-ignore
|
|
||||||
import GLE from "greenlock-express";
|
|
||||||
// @ts-ignore
|
|
||||||
import Greenlock from "@root/greenlock";
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { fileURLToPath } from "url";
|
import {fileURLToPath} from "url";
|
||||||
import config from "./config.js";
|
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 cron from "node-cron";
|
||||||
|
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";
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
let sslCtx: tls.SecureContext = tls.createSecureContext();
|
||||||
const __dirname = path.dirname(__filename);
|
const sslParams: tls.SecureContextOptions = {};
|
||||||
|
|
||||||
const sslConfig = {
|
const sslPrivateKey = await acme.forge.createPrivateKey();
|
||||||
packageRoot: path.dirname(__dirname),
|
const acmeClient = new acme.Client({
|
||||||
maintainerEmail: "contact@lumeweb.com",
|
accountKey: sslPrivateKey,
|
||||||
configDir: path.resolve(config.prefix, "ssl"),
|
directoryUrl: acme.directory.letsencrypt.production,
|
||||||
cluster: false,
|
});
|
||||||
agreeToTerms: true,
|
|
||||||
staging: true,
|
let app: Express;
|
||||||
};
|
let router = express.Router();
|
||||||
|
|
||||||
export async function start() {
|
export async function start() {
|
||||||
const relayDomain = config.str("relay-domain");
|
const relayPort = config.str("relay-port");
|
||||||
const relayPort = config.str("relay-port");
|
app = express();
|
||||||
const greenlock = Greenlock.create(sslConfig);
|
app.use(function (req, res, next) {
|
||||||
await greenlock.add({
|
router(req, res, next);
|
||||||
subject: relayDomain,
|
|
||||||
altnames: [relayDomain],
|
|
||||||
});
|
|
||||||
// @ts-ignore
|
|
||||||
config.greenlock = greenlock;
|
|
||||||
GLE.init(sslConfig).ready(async (GLEServer: any) => {
|
|
||||||
let httpsServer = GLEServer.httpsServer();
|
|
||||||
var httpServer = GLEServer.httpServer();
|
|
||||||
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
httpServer.listen(80, "0.0.0.0", function () {
|
|
||||||
console.info("HTTP Listening on ", httpServer.address());
|
|
||||||
resolve(null);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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();
|
const dht = await getDHT();
|
||||||
|
|
||||||
let wsServer = new WS.Server({ server: httpServer });
|
let wsServer = new WS.Server({server: httpsServer});
|
||||||
|
|
||||||
wsServer.on("connection", (socket: any) => {
|
wsServer.on("connection", (socket: any) => {
|
||||||
relay(dht, new Stream(false, socket));
|
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 greenlock.get({
|
await new Promise((resolve) => {
|
||||||
servername: relayDomain,
|
httpsServer.listen(relayPort, "0.0.0.0", function () {
|
||||||
|
console.info("Relay started on ", httpsServer.address());
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
await createOrRenewSSl();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createOrRenewSSl() {
|
||||||
|
if (sslParams.cert) {
|
||||||
|
const expires = (
|
||||||
|
await acme.forge.readCertificateInfo(sslParams.cert as Buffer)
|
||||||
|
).notAfter;
|
||||||
|
|
||||||
|
let duration = intervalToDuration({start: new Date(), end: expires});
|
||||||
|
|
||||||
|
let daysLeft = (duration.months as number) * 30 + (duration.days as number);
|
||||||
|
|
||||||
|
if (daysLeft > 30) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
console.log("SSL Certificate Updated");
|
||||||
}
|
}
|
||||||
|
|
372
src/rpc.ts
372
src/rpc.ts
|
@ -1,27 +1,28 @@
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import jayson from "jayson/promise/index.js";
|
import jayson from "jayson/promise/index.js";
|
||||||
import { pack, unpack } from "msgpackr";
|
import {pack, unpack} from "msgpackr";
|
||||||
import { Mutex } from "async-mutex";
|
import {Mutex} from "async-mutex";
|
||||||
import { createRequire } from "module";
|
import {createRequire} from "module";
|
||||||
import NodeCache from "node-cache";
|
import NodeCache from "node-cache";
|
||||||
import { get as getDHT } from "./dht.js";
|
import {get as getDHT} from "./dht.js";
|
||||||
import { rpcMethods } from "./rpc/index.js";
|
import {rpcMethods} from "./rpc/index.js";
|
||||||
import PocketPKG from "@pokt-network/pocket-js";
|
import PocketPKG from "@pokt-network/pocket-js";
|
||||||
const { Configuration, HttpRpcProvider, PocketAAT, Pocket } = PocketPKG;
|
|
||||||
|
const {Configuration, HttpRpcProvider, PocketAAT, Pocket} = PocketPKG;
|
||||||
import {
|
import {
|
||||||
JSONRPCRequest,
|
JSONRPCRequest,
|
||||||
JSONRPCResponseWithError,
|
JSONRPCResponseWithError,
|
||||||
JSONRPCResponseWithResult,
|
JSONRPCResponseWithResult,
|
||||||
} from "jayson";
|
} from "jayson";
|
||||||
import config, { updateUsePocketGateway, usePocketGateway } from "./config.js";
|
import config, {updateUsePocketGateway, usePocketGateway} from "./config.js";
|
||||||
import { errorExit } from "./util.js";
|
import {errorExit} from "./util.js";
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
const stringify = require("json-stable-stringify");
|
const stringify = require("json-stable-stringify");
|
||||||
const pendingRequests = new NodeCache();
|
const pendingRequests = new NodeCache();
|
||||||
const processedRequests = new NodeCache({
|
const processedRequests = new NodeCache({
|
||||||
stdTTL: 60 * 60 * 12,
|
stdTTL: 60 * 60 * 12,
|
||||||
});
|
});
|
||||||
|
|
||||||
type PocketAATObject = typeof PocketAAT;
|
type PocketAATObject = typeof PocketAAT;
|
||||||
|
@ -31,232 +32,235 @@ let _aat: PocketAATObject;
|
||||||
let jsonServer: jayson.Server;
|
let jsonServer: jayson.Server;
|
||||||
|
|
||||||
interface RPCRequest {
|
interface RPCRequest {
|
||||||
force: boolean;
|
force: boolean;
|
||||||
chain: string;
|
chain: string;
|
||||||
query: string;
|
query: string;
|
||||||
data: string;
|
data: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RPCResponse {
|
interface RPCResponse {
|
||||||
updated: number;
|
updated: number;
|
||||||
data:
|
data:
|
||||||
| any
|
| any
|
||||||
| {
|
| {
|
||||||
error: string | boolean;
|
error: string | boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function hash(data: string): string {
|
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) {
|
function getRequestId(request: RPCRequest) {
|
||||||
const clonedRequest = Object.assign({}, request);
|
const clonedRequest = Object.assign({}, request);
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
delete clonedRequest.force;
|
delete clonedRequest.force;
|
||||||
|
|
||||||
return hash(stringify(clonedRequest));
|
return hash(stringify(clonedRequest));
|
||||||
}
|
}
|
||||||
|
|
||||||
function maybeProcessRequest(request: RPCRequest) {
|
function maybeProcessRequest(request: RPCRequest) {
|
||||||
if (!request.chain) {
|
if (!request.chain) {
|
||||||
throw new Error("RPC chain missing");
|
throw new Error("RPC chain missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!request.data) {
|
if (!request.data) {
|
||||||
throw new Error("RPC data missing");
|
throw new Error("RPC data missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
return processRequest(request);
|
return processRequest(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processRequest(request: RPCRequest): Promise<RPCResponse> {
|
async function processRequest(request: RPCRequest): Promise<RPCResponse> {
|
||||||
const reqId = getRequestId(request);
|
const reqId = getRequestId(request);
|
||||||
|
|
||||||
let lock: Mutex = pendingRequests.get(reqId) as Mutex;
|
let lock: Mutex = pendingRequests.get(reqId) as Mutex;
|
||||||
const lockExists = !!lock;
|
const lockExists = !!lock;
|
||||||
|
|
||||||
if (!lockExists) {
|
if (!lockExists) {
|
||||||
lock = new Mutex();
|
lock = new Mutex();
|
||||||
pendingRequests.set(reqId, lock);
|
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,
|
|
||||||
} 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rpcResp = rpcResp as unknown as JSONRPCResponseWithError;
|
if (lock.isLocked()) {
|
||||||
|
await lock.waitForUnlock();
|
||||||
if (rpcResp.error) {
|
return processedRequests.get(reqId) as RPCResponse;
|
||||||
// @ts-ignore
|
|
||||||
error = rpcResp.error.message;
|
|
||||||
}
|
}
|
||||||
}
|
await lock.acquire();
|
||||||
|
|
||||||
dbData.data = error
|
if (!request.force && processedRequests.get(reqId)) {
|
||||||
? { error }
|
return processedRequests.get(reqId) as RPCResponse;
|
||||||
: (rpcResp as unknown as JSONRPCResponseWithResult).result;
|
}
|
||||||
|
|
||||||
if (!processedRequests.get(reqId) || request.force) {
|
let rpcResp;
|
||||||
processedRequests.set(reqId, dbData);
|
|
||||||
}
|
|
||||||
|
|
||||||
await lock.release();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
return dbData;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateAat(aat: PocketAATObject): void {
|
export function updateAat(aat: PocketAATObject): void {
|
||||||
_aat = aat;
|
_aat = aat;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAat(): PocketAATObject {
|
export function getAat(): PocketAATObject {
|
||||||
return _aat;
|
return _aat;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPocketServer(): typeof Pocket {
|
export function getPocketServer(): typeof Pocket {
|
||||||
return pocketServer;
|
return pocketServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function unlockAccount(
|
export async function unlockAccount(
|
||||||
accountPrivateKey: string,
|
accountPrivateKey: string,
|
||||||
accountPublicKey: string,
|
accountPublicKey: string,
|
||||||
accountPassphrase: string
|
accountPassphrase: string
|
||||||
): Promise<PocketAATObject> {
|
): Promise<PocketAATObject> {
|
||||||
try {
|
try {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const account = await pocketServer.keybase.importAccount(
|
const account = await pocketServer.keybase.importAccount(
|
||||||
Buffer.from(accountPrivateKey, "hex"),
|
Buffer.from(accountPrivateKey, "hex"),
|
||||||
accountPassphrase
|
accountPassphrase
|
||||||
);
|
);
|
||||||
|
|
||||||
if (account instanceof Error) {
|
if (account instanceof Error) {
|
||||||
// noinspection ExceptionCaughtLocallyJS
|
// noinspection ExceptionCaughtLocallyJS
|
||||||
throw account;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @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(
|
export async function processRpcRequest(
|
||||||
request: JSONRPCRequest,
|
request: JSONRPCRequest,
|
||||||
chain: string
|
chain: string
|
||||||
): Promise<JSONRPCResponseWithResult | JSONRPCResponseWithError | undefined> {
|
): Promise<JSONRPCResponseWithResult | JSONRPCResponseWithError | undefined> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
jsonServer.call(
|
jsonServer.call(
|
||||||
request,
|
request,
|
||||||
{ chain },
|
{chain},
|
||||||
(
|
(
|
||||||
err?: JSONRPCResponseWithError | null,
|
err?: JSONRPCResponseWithError | null,
|
||||||
result?: JSONRPCResponseWithResult
|
result?: JSONRPCResponseWithResult
|
||||||
): void => {
|
): void => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return resolve(err);
|
return resolve(err);
|
||||||
}
|
}
|
||||||
resolve(result);
|
resolve(result);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function start() {
|
export async function start() {
|
||||||
if (!config.str("pocket-app-id") || !config.str("pocket-app-key")) {
|
if (!config.str("pocket-app-id") || !config.str("pocket-app-key")) {
|
||||||
const pocketHost = config.str("pocket-host");
|
const pocketHost = config.str("pocket-host");
|
||||||
const pocketPort = config.uint("pocket-port");
|
const pocketPort = config.uint("pocket-port");
|
||||||
if (!pocketHost || !pocketPort) {
|
if (!pocketHost || !pocketPort) {
|
||||||
errorExit(
|
errorExit(
|
||||||
"Please set pocket-host and pocket-port config options if you do not have an API key set"
|
"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);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dispatchURL = new URL(
|
if (!usePocketGateway()) {
|
||||||
`http://${config.str("pocket-host")}:${config.uint("pocket-port")}`
|
updateAat(
|
||||||
);
|
await unlockAccount(
|
||||||
const rpcProvider = new HttpRpcProvider(dispatchURL);
|
<string>config.str("pocket-account-private-key"),
|
||||||
const configuration = new Configuration();
|
<string>config.str("pocket-account-public-key"),
|
||||||
// @ts-ignore
|
"0"
|
||||||
pocketServer = new Pocket([dispatchURL], rpcProvider, configuration);
|
)
|
||||||
updateUsePocketGateway(false);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!usePocketGateway()) {
|
jsonServer = new jayson.Server(rpcMethods, {useContext: true});
|
||||||
updateAat(
|
|
||||||
await unlockAccount(
|
|
||||||
<string>config.str("pocket-account-private-key"),
|
|
||||||
<string>config.str("pocket-account-public-key"),
|
|
||||||
"0"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonServer = new jayson.Server(rpcMethods, { useContext: true });
|
(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;
|
||||||
|
}
|
||||||
|
|
||||||
(await getDHT()).on("connection", (socket: any) => {
|
try {
|
||||||
socket.on("data", async (data: any) => {
|
socket.write(pack(await maybeProcessRequest(request)));
|
||||||
let request: RPCRequest;
|
} catch (error) {
|
||||||
try {
|
console.trace(error);
|
||||||
request = unpack(data) as RPCRequest;
|
socket.write(pack({error}));
|
||||||
} catch (e) {
|
}
|
||||||
return;
|
socket.end();
|
||||||
}
|
});
|
||||||
|
|
||||||
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";
|
import minimatch from "minimatch";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import HTTPClient from "algosdk/dist/cjs/src/client/client.js";
|
import HTTPClient from "algosdk/dist/cjs/src/client/client.js";
|
||||||
import { sprintf } from "sprintf-js";
|
import {sprintf} from "sprintf-js";
|
||||||
import { RpcMethodList } from "./index.js";
|
import {RpcMethodList} from "./index.js";
|
||||||
import config from "../config.js";
|
import config from "../config.js";
|
||||||
|
|
||||||
const allowedEndpoints: { [endpoint: string]: ("GET" | "POST")[] } = {
|
const allowedEndpoints: { [endpoint: string]: ("GET" | "POST")[] } = {
|
||||||
"/v2/teal/compile": ["POST"],
|
"/v2/teal/compile": ["POST"],
|
||||||
"/v2/accounts/*": ["GET"],
|
"/v2/accounts/*": ["GET"],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function proxyRestMethod(
|
export function proxyRestMethod(
|
||||||
apiServer: string,
|
apiServer: string,
|
||||||
matchChainId: string
|
matchChainId: string
|
||||||
): Function {
|
): Function {
|
||||||
return async function (args: any, context: object) {
|
return async function (args: any, context: object) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
let chain = context.chain;
|
let chain = context.chain;
|
||||||
let chainId = maybeMapChainId(chain);
|
let chainId = maybeMapChainId(chain);
|
||||||
|
|
||||||
if (!chainId) {
|
if (!chainId) {
|
||||||
throw new Error("Invalid Chain");
|
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = await client.post(endpoint, data, { ...fullHeaders });
|
chainId = reverseMapChainId(chainId as string);
|
||||||
break;
|
if (!chainId || chainId !== matchChainId) {
|
||||||
default:
|
throw new Error("Invalid Chain");
|
||||||
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;
|
|
||||||
};
|
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 JSON.parse(JSON.stringify(resp, getCircularReplacer()));
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
algorand_rest_request: proxyRestMethod(
|
algorand_rest_request: proxyRestMethod(
|
||||||
"http://mainnet-api.algonode.network",
|
"http://mainnet-api.algonode.network",
|
||||||
"algorand-mainnet"
|
"algorand-mainnet"
|
||||||
),
|
),
|
||||||
//'algorand_rest_request': proxyRestMethod("https://%s.gateway.pokt.network/v1/lb/%s", "algorand-mainnet"),
|
//'algorand_rest_request': proxyRestMethod("https://%s.gateway.pokt.network/v1/lb/%s", "algorand-mainnet"),
|
||||||
algorand_rest_indexer_request: proxyRestMethod(
|
algorand_rest_indexer_request: proxyRestMethod(
|
||||||
"http://mainnet-idx.algonode.network",
|
"http://mainnet-idx.algonode.network",
|
||||||
"algorand-mainnet-indexer"
|
"algorand-mainnet-indexer"
|
||||||
),
|
),
|
||||||
} as RpcMethodList;
|
} as RpcMethodList;
|
||||||
|
|
|
@ -1,106 +1,106 @@
|
||||||
import { ethers } from "ethers";
|
import {ethers} from "ethers";
|
||||||
import { Pocket, PocketAAT } from "@pokt-network/pocket-js";
|
import {Pocket, PocketAAT} from "@pokt-network/pocket-js";
|
||||||
import { maybeMapChainId, reverseMapChainId } from "../util.js";
|
import {maybeMapChainId, reverseMapChainId} from "../util.js";
|
||||||
import { Connection } from "@solana/web3.js";
|
import {Connection} from "@solana/web3.js";
|
||||||
import { getAat, getPocketServer } from "../rpc.js";
|
import {getAat, getPocketServer} from "../rpc.js";
|
||||||
import config, { usePocketGateway } from "../config.js";
|
import config, {usePocketGateway} from "../config.js";
|
||||||
|
|
||||||
type RpcProviderMethod = (method: string, params: Array<any>) => Promise<any>;
|
type RpcProviderMethod = (method: string, params: Array<any>) => Promise<any>;
|
||||||
|
|
||||||
const gatewayProviders: { [name: string]: RpcProviderMethod } = {};
|
const gatewayProviders: { [name: string]: RpcProviderMethod } = {};
|
||||||
|
|
||||||
const gatewayMethods: {
|
const gatewayMethods: {
|
||||||
[name: string]: (chainId: string) => RpcProviderMethod;
|
[name: string]: (chainId: string) => RpcProviderMethod;
|
||||||
} = {
|
} = {
|
||||||
default: (chainId: string): RpcProviderMethod => {
|
default: (chainId: string): RpcProviderMethod => {
|
||||||
const provider = new ethers.providers.JsonRpcProvider({
|
const provider = new ethers.providers.JsonRpcProvider({
|
||||||
url: `https://${chainId}.gateway.pokt.network/v1/lb/${config.str(
|
url: `https://${chainId}.gateway.pokt.network/v1/lb/${config.str(
|
||||||
"pocket-api-id"
|
"pocket-api-id"
|
||||||
)}`,
|
)}`,
|
||||||
password: config.str("pocket-api-key"),
|
password: config.str("pocket-api-key"),
|
||||||
});
|
});
|
||||||
return provider.send.bind(provider);
|
return provider.send.bind(provider);
|
||||||
},
|
},
|
||||||
"sol-mainnet": (chainId: string): RpcProviderMethod => {
|
"sol-mainnet": (chainId: string): RpcProviderMethod => {
|
||||||
const provider = new Connection(
|
const provider = new Connection(
|
||||||
`https://solana-mainnet.gateway.pokt.network/v1/lb/${config.str(
|
`https://solana-mainnet.gateway.pokt.network/v1/lb/${config.str(
|
||||||
"pocket-api-id"
|
"pocket-api-id"
|
||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return provider._rpcRequest.bind(provider);
|
return provider._rpcRequest.bind(provider);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function proxyRpcMethod(
|
export function proxyRpcMethod(
|
||||||
method: string,
|
method: string,
|
||||||
chains: string[] = []
|
chains: string[] = []
|
||||||
): Function {
|
): Function {
|
||||||
return async function (args: any, context: object) {
|
return async function (args: any, context: object) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
let chain = context.chain;
|
let chain = context.chain;
|
||||||
let chainId = maybeMapChainId(chain);
|
let chainId = maybeMapChainId(chain);
|
||||||
|
|
||||||
let chainMatch = true;
|
let chainMatch = true;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
chains.length > 0 &&
|
chains.length > 0 &&
|
||||||
!chains.includes(chain) &&
|
!chains.includes(chain) &&
|
||||||
!chains.includes(chainId.toString())
|
!chains.includes(chainId.toString())
|
||||||
) {
|
) {
|
||||||
chainMatch = false;
|
chainMatch = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!chainId || !chainMatch) {
|
if (!chainId || !chainMatch) {
|
||||||
throw new Error("Invalid Chain");
|
throw new Error("Invalid Chain");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usePocketGateway()) {
|
if (usePocketGateway()) {
|
||||||
chainId = reverseMapChainId(chainId as string);
|
chainId = reverseMapChainId(chainId as string);
|
||||||
if (!chainId) {
|
if (!chainId) {
|
||||||
throw new Error("Invalid Chain");
|
throw new Error("Invalid Chain");
|
||||||
}
|
}
|
||||||
|
|
||||||
let provider: RpcProviderMethod | boolean =
|
let provider: RpcProviderMethod | boolean =
|
||||||
gatewayProviders[chainId as string] || false;
|
gatewayProviders[chainId as string] || false;
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
provider = getRpcProvider(chainId as string);
|
provider = getRpcProvider(chainId as string);
|
||||||
}
|
}
|
||||||
gatewayProviders[chainId as string] = provider;
|
gatewayProviders[chainId as string] = provider;
|
||||||
return await provider(method, args);
|
return await provider(method, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await sendRelay(
|
return await sendRelay(
|
||||||
JSON.stringify(args),
|
JSON.stringify(args),
|
||||||
<string>chainId,
|
<string>chainId,
|
||||||
getAat() as unknown as PocketAAT
|
getAat() as unknown as PocketAAT
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call this every time you want to fetch RPC data
|
// Call this every time you want to fetch RPC data
|
||||||
async function sendRelay(
|
async function sendRelay(
|
||||||
rpcQuery: string,
|
rpcQuery: string,
|
||||||
blockchain: string,
|
blockchain: string,
|
||||||
pocketAAT: PocketAAT
|
pocketAAT: PocketAAT
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
return await (getPocketServer() as unknown as Pocket).sendRelay(
|
return await (getPocketServer() as unknown as Pocket).sendRelay(
|
||||||
rpcQuery,
|
rpcQuery,
|
||||||
blockchain,
|
blockchain,
|
||||||
pocketAAT
|
pocketAAT
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRpcProvider(chain: string): RpcProviderMethod {
|
function getRpcProvider(chain: string): RpcProviderMethod {
|
||||||
if (chain in gatewayMethods) {
|
if (chain in gatewayMethods) {
|
||||||
return gatewayMethods[chain](chain);
|
return gatewayMethods[chain](chain);
|
||||||
}
|
}
|
||||||
|
|
||||||
return gatewayMethods.default(chain);
|
return gatewayMethods.default(chain);
|
||||||
}
|
}
|
||||||
|
|
178
src/rpc/dns.ts
178
src/rpc/dns.ts
|
@ -1,16 +1,16 @@
|
||||||
import { isIp } from "../util.js";
|
import {isIp} from "../util.js";
|
||||||
import { RpcMethodList } from "./index.js";
|
import {RpcMethodList} from "./index.js";
|
||||||
import { createRequire } from "module";
|
import {createRequire} from "module";
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
const bns = require("bns");
|
const bns = require("bns");
|
||||||
const { StubResolver, RecursiveResolver } = bns;
|
const {StubResolver, RecursiveResolver} = bns;
|
||||||
|
|
||||||
const resolverOpt = {
|
const resolverOpt = {
|
||||||
tcp: true,
|
tcp: true,
|
||||||
inet6: false,
|
inet6: false,
|
||||||
edns: true,
|
edns: true,
|
||||||
dnssec: true,
|
dnssec: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const globalResolver = new RecursiveResolver(resolverOpt);
|
const globalResolver = new RecursiveResolver(resolverOpt);
|
||||||
|
@ -18,90 +18,90 @@ globalResolver.hints.setDefault();
|
||||||
globalResolver.open();
|
globalResolver.open();
|
||||||
|
|
||||||
async function resolveNameServer(ns: string): Promise<string | boolean> {
|
async function resolveNameServer(ns: string): Promise<string | boolean> {
|
||||||
if (isIp(ns)) {
|
if (isIp(ns)) {
|
||||||
return ns;
|
return ns;
|
||||||
}
|
|
||||||
let result = await getDnsRecords(ns, "A");
|
|
||||||
|
|
||||||
if (result.length) {
|
|
||||||
return result[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getDnsRecords(
|
|
||||||
domain: string,
|
|
||||||
type: string,
|
|
||||||
authority: boolean = false,
|
|
||||||
resolver = globalResolver
|
|
||||||
): Promise<string[]> {
|
|
||||||
let result;
|
|
||||||
|
|
||||||
try {
|
|
||||||
result = await resolver.lookup(domain, type);
|
|
||||||
} catch (e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
let prop = authority ? "authority" : "answer";
|
|
||||||
|
|
||||||
if (!result || !result[prop].length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let result = await getDnsRecords(ns, "A");
|
||||||
|
|
||||||
for (const queryType of ["CNAME", "A"]) {
|
if (result.length) {
|
||||||
let result = await getDnsRecords(domain, queryType, false, dnsResolver);
|
return result[0];
|
||||||
|
|
||||||
if (result) {
|
|
||||||
dnsResults = dnsResults.concat(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await dnsResolver.close();
|
|
||||||
|
|
||||||
dnsResults = dnsResults.filter(Boolean);
|
|
||||||
|
|
||||||
if (dnsResults.length) {
|
|
||||||
return dnsResults[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
}
|
||||||
|
|
||||||
|
async function getDnsRecords(
|
||||||
|
domain: string,
|
||||||
|
type: string,
|
||||||
|
authority: boolean = false,
|
||||||
|
resolver = globalResolver
|
||||||
|
): Promise<string[]> {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
result = await resolver.lookup(domain, type);
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let prop = authority ? "authority" : "answer";
|
||||||
|
|
||||||
|
if (!result || !result[prop].length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const queryType of ["CNAME", "A"]) {
|
||||||
|
let result = await getDnsRecords(domain, queryType, false, dnsResolver);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
dnsResults = dnsResults.concat(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await dnsResolver.close();
|
||||||
|
|
||||||
|
dnsResults = dnsResults.filter(Boolean);
|
||||||
|
|
||||||
|
if (dnsResults.length) {
|
||||||
|
return dnsResults[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
} as RpcMethodList;
|
} as RpcMethodList;
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { proxyRpcMethod } from "./common.js";
|
import {proxyRpcMethod} from "./common.js";
|
||||||
import { RpcMethodList } from "./index.js";
|
import {RpcMethodList} from "./index.js";
|
||||||
|
|
||||||
const rpcMethods: RpcMethodList = {};
|
const rpcMethods: RpcMethodList = {};
|
||||||
|
|
||||||
function proxyEvmRpcMethod(method: string): Function {
|
function proxyEvmRpcMethod(method: string): Function {
|
||||||
return proxyRpcMethod(method);
|
return proxyRpcMethod(method);
|
||||||
}
|
}
|
||||||
|
|
||||||
["eth_call", "eth_chainId", "net_version"].forEach((method) => {
|
["eth_call", "eth_chainId", "net_version"].forEach((method) => {
|
||||||
rpcMethods[method] = proxyEvmRpcMethod(method);
|
rpcMethods[method] = proxyEvmRpcMethod(method);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default rpcMethods;
|
export default rpcMethods;
|
||||||
|
|
|
@ -1,55 +1,88 @@
|
||||||
import { RpcMethodList } from "./index.js";
|
import {RpcMethodList} from "./index.js";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import rand from "random-key";
|
import rand from "random-key";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import SPVNode from "hsd/lib/node/spvnode.js";
|
import SPVNode from "hsd/lib/node/spvnode.js";
|
||||||
import config from "../config.js";
|
import config from "../config.js";
|
||||||
import { createRequire } from "module";
|
import {createRequire} from "module";
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
const { NodeClient } = require("hs-client");
|
const {NodeClient} = require("hs-client");
|
||||||
|
|
||||||
let hsdServer: SPVNode;
|
let hsdServer: SPVNode;
|
||||||
|
|
||||||
let clientArgs = {
|
let clientArgs = {
|
||||||
network: "main",
|
network: "main",
|
||||||
host: "127.0.0.1",
|
host: "127.0.0.1",
|
||||||
port: 12037,
|
port: 12037,
|
||||||
apiKey: rand.generate(),
|
apiKey: rand.generate(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!config.bool("hsd-use-extenal-node")) {
|
if (!config.bool("hsd-use-external-node")) {
|
||||||
hsdServer = new SPVNode({
|
hsdServer = new SPVNode({
|
||||||
config: false,
|
config: false,
|
||||||
argv: false,
|
argv: false,
|
||||||
env: true,
|
env: true,
|
||||||
noDns: true,
|
noDns: true,
|
||||||
httpHost: "127.0.0.1",
|
memory: false,
|
||||||
apiKey: clientArgs.apiKey,
|
httpHost: "127.0.0.1",
|
||||||
logFile: false,
|
apiKey: clientArgs.apiKey,
|
||||||
logConsole: false,
|
logFile: false,
|
||||||
logLevel: "info",
|
logConsole: true,
|
||||||
workers: true,
|
logLevel: "info",
|
||||||
network: "main",
|
workers: true,
|
||||||
});
|
network: "main",
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await hsdServer.ensure();
|
||||||
|
await hsdServer.open();
|
||||||
|
await hsdServer.connect();
|
||||||
|
|
||||||
|
hsdServer.startSync();
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error((e as Error).stack);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
clientArgs = {
|
clientArgs = {
|
||||||
network: config.str("hsd-network-type"),
|
network: config.str("hsd-network-type"),
|
||||||
host: config.str("hsd-host"),
|
host: config.str("hsd-host"),
|
||||||
port: config.uint("hsd-port"),
|
port: config.uint("hsd-port"),
|
||||||
apiKey: config.str("hsd-api-key"),
|
apiKey: config.str("hsd-api-key"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const hnsClient = new NodeClient(clientArgs);
|
const hnsClient = new NodeClient(clientArgs);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getnameresource: async function (args: any, context: object) {
|
getnameresource: async function (args: any, context: object) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if ("hns" !== context.chain) {
|
if ("hns" !== context.chain) {
|
||||||
throw new Error("Invalid Chain");
|
throw new Error("Invalid Chain");
|
||||||
}
|
}
|
||||||
|
|
||||||
return await hnsClient.execute("getnameresource", args);
|
return await hnsClient.execute("getnameresource", args);
|
||||||
},
|
},
|
||||||
} as RpcMethodList;
|
} as RpcMethodList;
|
||||||
|
|
|
@ -2,17 +2,17 @@ export type RpcMethodList = { [name: string]: Function };
|
||||||
|
|
||||||
export * from "./common.js";
|
export * from "./common.js";
|
||||||
|
|
||||||
import { default as DnsMethods } from "./dns.js";
|
import {default as DnsMethods} from "./dns.js";
|
||||||
import { default as EvmMethods } from "./evm.js";
|
import {default as EvmMethods} from "./evm.js";
|
||||||
import { default as HnsMethods } from "./handshake.js";
|
import {default as HnsMethods} from "./handshake.js";
|
||||||
import { default as SolMethods } from "./solana.js";
|
import {default as SolMethods} from "./solana.js";
|
||||||
import { default as AlgoMethods } from "./algorand.js";
|
import {default as AlgoMethods} from "./algorand.js";
|
||||||
|
|
||||||
export const rpcMethods: RpcMethodList = Object.assign(
|
export const rpcMethods: RpcMethodList = Object.assign(
|
||||||
{},
|
{},
|
||||||
DnsMethods,
|
DnsMethods,
|
||||||
EvmMethods,
|
EvmMethods,
|
||||||
HnsMethods,
|
HnsMethods,
|
||||||
SolMethods,
|
SolMethods,
|
||||||
AlgoMethods
|
AlgoMethods
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { proxyRpcMethod } from "./common.js";
|
import {proxyRpcMethod} from "./common.js";
|
||||||
import { RpcMethodList } from "./index.js";
|
import {RpcMethodList} from "./index.js";
|
||||||
import * as chainNetworks from "../networks.json" assert { type: "json" };
|
import * as chainNetworks from "../networks.json" assert {type: "json"};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getAccountInfo: proxyRpcMethod("getAccountInfo", [
|
getAccountInfo: proxyRpcMethod("getAccountInfo", [
|
||||||
chainNetworks["sol-mainnet"],
|
chainNetworks["sol-mainnet"],
|
||||||
]),
|
]),
|
||||||
} as RpcMethodList;
|
} as RpcMethodList;
|
||||||
|
|
71
src/util.ts
71
src/util.ts
|
@ -1,39 +1,66 @@
|
||||||
import * as chainNetworks from "./networks.json" assert { type: "json" };
|
import * as chainNetworks from "./networks.json" assert {type: "json"};
|
||||||
|
import {Buffer} from "buffer";
|
||||||
|
import {blake2b} from "libskynet";
|
||||||
|
|
||||||
type networks = { [net: string]: string };
|
type networks = { [net: string]: string };
|
||||||
|
|
||||||
export function errorExit(msg: string): void {
|
export function errorExit(msg: string): void {
|
||||||
console.error(msg);
|
console.error(msg);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function maybeMapChainId(chain: string): string | boolean {
|
export function maybeMapChainId(chain: string): string | boolean {
|
||||||
if (chain in chainNetworks) {
|
if (chain in chainNetworks) {
|
||||||
return (chainNetworks as networks)[chain];
|
return (chainNetworks as networks)[chain];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
[parseInt(chain, 16).toString(), parseInt(chain, 10).toString()].includes(
|
[parseInt(chain, 16).toString(), parseInt(chain, 10).toString()].includes(
|
||||||
chain.toLowerCase()
|
chain.toLowerCase()
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return chain;
|
return chain;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reverseMapChainId(chainId: string): string | boolean {
|
export function reverseMapChainId(chainId: string): string | boolean {
|
||||||
let vals = Object.values(chainNetworks);
|
let vals = Object.values(chainNetworks);
|
||||||
if (!vals.includes(chainId)) {
|
if (!vals.includes(chainId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.keys(chainNetworks)[vals.indexOf(chainId)];
|
return Object.keys(chainNetworks)[vals.indexOf(chainId)];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isIp(ip: string) {
|
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(
|
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
|
ip
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hashDataKey(dataKey: string): Uint8Array {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringToUint8ArrayUtf8(str: string): Uint8Array {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue