2021-03-10 17:39:53 +11:00
|
|
|
import { ChildProcess, spawn } from 'child_process';
|
2021-03-12 17:08:42 +11:00
|
|
|
import { randomInt } from 'crypto';
|
2021-03-10 17:39:53 +11:00
|
|
|
|
2021-03-15 16:22:26 +11:00
|
|
|
import appRoot from 'app-root-path';
|
2021-03-10 17:39:53 +11:00
|
|
|
import axios from 'axios';
|
2021-04-06 11:00:40 +10:00
|
|
|
import debug from 'debug';
|
2021-04-20 16:51:04 +10:00
|
|
|
import { Multiaddr, multiaddr } from 'multiaddr';
|
2021-03-10 17:39:53 +11:00
|
|
|
import PeerId from 'peer-id';
|
|
|
|
|
|
2021-04-01 11:18:35 +11:00
|
|
|
import { WakuMessage } from '../lib/waku_message';
|
2021-04-01 16:41:49 +11:00
|
|
|
import { RelayDefaultTopic } from '../lib/waku_relay';
|
2021-03-12 10:35:50 +11:00
|
|
|
|
2021-03-15 13:38:36 +11:00
|
|
|
import { existsAsync, mkdirAsync, openAsync } from './async_fs';
|
2021-03-11 11:11:37 +11:00
|
|
|
import waitForLine from './log_file';
|
2021-03-10 17:39:53 +11:00
|
|
|
|
2021-04-06 11:00:40 +10:00
|
|
|
const dbg = debug('nim-waku');
|
|
|
|
|
|
2021-03-12 17:08:42 +11:00
|
|
|
const NIM_WAKU_DEFAULT_P2P_PORT = 60000;
|
|
|
|
|
const NIM_WAKU_DEFAULT_RPC_PORT = 8545;
|
2021-03-15 16:22:26 +11:00
|
|
|
const NIM_WAKU_DIR = appRoot + '/nim-waku';
|
|
|
|
|
const NIM_WAKU_BIN = NIM_WAKU_DIR + '/build/wakunode2';
|
2021-03-10 17:39:53 +11:00
|
|
|
|
2021-03-15 13:38:36 +11:00
|
|
|
const LOG_DIR = './log';
|
|
|
|
|
|
2021-03-11 10:54:35 +11:00
|
|
|
export interface Args {
|
|
|
|
|
staticnode?: string;
|
|
|
|
|
nat?: 'none';
|
|
|
|
|
listenAddress?: string;
|
|
|
|
|
relay?: boolean;
|
|
|
|
|
rpc?: boolean;
|
|
|
|
|
rpcAdmin?: boolean;
|
|
|
|
|
nodekey?: string;
|
2021-03-12 17:08:42 +11:00
|
|
|
portsShift?: number;
|
2021-04-09 11:23:00 +10:00
|
|
|
logLevel?: LogLevel;
|
2021-04-07 11:04:30 +10:00
|
|
|
store?: boolean;
|
2021-04-09 11:23:00 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export enum LogLevel {
|
|
|
|
|
Error = 'error',
|
|
|
|
|
Info = 'info',
|
|
|
|
|
Warn = 'warn',
|
|
|
|
|
Debug = 'debug',
|
|
|
|
|
Trace = 'trace',
|
|
|
|
|
Notice = 'notice',
|
|
|
|
|
Fatal = 'fatal',
|
2021-03-11 10:54:35 +11:00
|
|
|
}
|
|
|
|
|
|
2021-03-10 17:39:53 +11:00
|
|
|
export class NimWaku {
|
|
|
|
|
private process?: ChildProcess;
|
2021-03-26 13:07:47 +11:00
|
|
|
private pid?: number;
|
2021-03-12 17:08:42 +11:00
|
|
|
private portsShift: number;
|
|
|
|
|
private peerId?: PeerId;
|
2021-03-23 11:14:51 +11:00
|
|
|
private multiaddrWithId?: Multiaddr;
|
2021-03-15 13:25:14 +11:00
|
|
|
private logPath: string;
|
2021-03-10 17:39:53 +11:00
|
|
|
|
2021-03-25 15:49:07 +11:00
|
|
|
constructor(logName: string) {
|
2021-03-12 17:08:42 +11:00
|
|
|
this.portsShift = randomInt(0, 5000);
|
2021-03-25 15:49:07 +11:00
|
|
|
this.logPath = `${LOG_DIR}/nim-waku_${logName}.log`;
|
2021-03-15 13:25:14 +11:00
|
|
|
}
|
2021-03-11 11:11:37 +11:00
|
|
|
|
2021-03-23 11:14:51 +11:00
|
|
|
async start(args?: Args) {
|
2021-03-15 13:38:36 +11:00
|
|
|
try {
|
|
|
|
|
await existsAsync(LOG_DIR);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
try {
|
|
|
|
|
await mkdirAsync(LOG_DIR);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// Looks like 2 tests tried to create the director at the same time,
|
|
|
|
|
// it can be ignored
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-15 13:25:14 +11:00
|
|
|
const logFile = await openAsync(this.logPath, 'w');
|
2021-03-11 10:54:35 +11:00
|
|
|
|
2021-03-12 17:08:42 +11:00
|
|
|
const mergedArgs = defaultArgs();
|
|
|
|
|
|
|
|
|
|
// Object.assign overrides the properties with the source (if there are conflicts)
|
2021-04-09 11:23:00 +10:00
|
|
|
Object.assign(
|
|
|
|
|
mergedArgs,
|
|
|
|
|
{ portsShift: this.portsShift, logLevel: LogLevel.Trace },
|
|
|
|
|
args
|
|
|
|
|
);
|
2021-03-12 17:08:42 +11:00
|
|
|
|
|
|
|
|
const argsArray = argsToArray(mergedArgs);
|
|
|
|
|
this.process = spawn(NIM_WAKU_BIN, argsArray, {
|
2021-03-15 16:22:26 +11:00
|
|
|
cwd: NIM_WAKU_DIR,
|
2021-03-11 10:54:35 +11:00
|
|
|
stdio: [
|
|
|
|
|
'ignore', // stdin
|
|
|
|
|
logFile, // stdout
|
|
|
|
|
logFile, // stderr
|
2021-03-10 17:39:53 +11:00
|
|
|
],
|
2021-03-11 10:54:35 +11:00
|
|
|
});
|
2021-03-26 13:07:47 +11:00
|
|
|
this.pid = this.process.pid;
|
2021-04-06 11:00:40 +10:00
|
|
|
dbg(
|
2021-03-26 13:07:47 +11:00
|
|
|
`nim-waku ${
|
|
|
|
|
this.process.pid
|
|
|
|
|
} started at ${new Date().toLocaleTimeString()}`
|
|
|
|
|
);
|
2021-03-10 17:39:53 +11:00
|
|
|
|
2021-03-25 16:29:35 +11:00
|
|
|
this.process.on('exit', (signal) => {
|
2021-04-06 11:00:40 +10:00
|
|
|
dbg(
|
2021-03-26 13:07:47 +11:00
|
|
|
`nim-waku ${
|
|
|
|
|
this.process ? this.process.pid : this.pid
|
|
|
|
|
} process exited with ${signal} at ${new Date().toLocaleTimeString()}`
|
|
|
|
|
);
|
2021-03-25 16:29:35 +11:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.process.on('error', (err) => {
|
2021-03-26 13:07:47 +11:00
|
|
|
console.log(
|
|
|
|
|
`nim-waku ${
|
|
|
|
|
this.process ? this.process.pid : this.pid
|
|
|
|
|
} process encountered an error: ${err} at ${new Date().toLocaleTimeString()}`
|
|
|
|
|
);
|
2021-03-25 16:29:35 +11:00
|
|
|
});
|
|
|
|
|
|
2021-03-15 13:25:14 +11:00
|
|
|
await this.waitForLog('RPC Server started');
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-19 16:07:56 +11:00
|
|
|
public stop() {
|
2021-04-06 11:06:10 +10:00
|
|
|
dbg(
|
2021-03-26 13:07:47 +11:00
|
|
|
`nim-waku ${
|
|
|
|
|
this.process ? this.process.pid : this.pid
|
|
|
|
|
} getting SIGINT at ${new Date().toLocaleTimeString()}`
|
|
|
|
|
);
|
2021-03-19 16:07:56 +11:00
|
|
|
this.process ? this.process.kill('SIGINT') : null;
|
2021-03-22 21:36:44 +11:00
|
|
|
this.process = undefined;
|
2021-03-19 16:07:56 +11:00
|
|
|
}
|
|
|
|
|
|
2021-03-15 13:25:14 +11:00
|
|
|
async waitForLog(msg: string) {
|
|
|
|
|
return waitForLine(this.logPath, msg);
|
2021-03-10 17:39:53 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Calls nim-waku2 JSON-RPC API `get_waku_v2_admin_v1_peers` to check
|
|
|
|
|
* for known peers
|
|
|
|
|
* @throws if nim-waku2 isn't started.
|
|
|
|
|
*/
|
2021-03-11 15:02:29 +11:00
|
|
|
async peers() {
|
2021-03-10 17:39:53 +11:00
|
|
|
this.checkProcess();
|
|
|
|
|
|
|
|
|
|
const res = await this.rpcCall('get_waku_v2_admin_v1_peers', []);
|
|
|
|
|
|
|
|
|
|
return res.result;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-12 17:08:42 +11:00
|
|
|
async info(): Promise<RpcInfoResponse> {
|
2021-03-10 17:39:53 +11:00
|
|
|
this.checkProcess();
|
|
|
|
|
|
|
|
|
|
const res = await this.rpcCall('get_waku_v2_debug_v1_info', []);
|
|
|
|
|
|
|
|
|
|
return res.result;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-01 11:18:35 +11:00
|
|
|
async sendMessage(message: WakuMessage) {
|
2021-03-12 10:35:50 +11:00
|
|
|
this.checkProcess();
|
|
|
|
|
|
2021-03-22 15:34:13 +11:00
|
|
|
if (!message.payload) {
|
|
|
|
|
throw 'Attempting to send empty message';
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-12 10:35:50 +11:00
|
|
|
const rpcMessage = {
|
2021-03-15 16:26:07 +11:00
|
|
|
payload: bufToHex(message.payload),
|
2021-03-12 10:35:50 +11:00
|
|
|
contentTopic: message.contentTopic,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const res = await this.rpcCall('post_waku_v2_relay_v1_message', [
|
2021-04-01 16:41:49 +11:00
|
|
|
RelayDefaultTopic,
|
2021-03-12 10:35:50 +11:00
|
|
|
rpcMessage,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return res.result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async messages() {
|
|
|
|
|
this.checkProcess();
|
|
|
|
|
|
2021-04-01 16:41:49 +11:00
|
|
|
const res = await this.rpcCall('get_waku_v2_relay_v1_messages', [
|
|
|
|
|
RelayDefaultTopic,
|
|
|
|
|
]);
|
2021-03-12 10:35:50 +11:00
|
|
|
|
|
|
|
|
return res.result;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-12 17:08:42 +11:00
|
|
|
async getPeerId(): Promise<PeerId> {
|
2021-03-23 11:14:51 +11:00
|
|
|
return await this.setPeerId().then((res) => res.peerId);
|
|
|
|
|
}
|
2021-03-12 17:08:42 +11:00
|
|
|
|
2021-03-23 11:14:51 +11:00
|
|
|
async getMultiaddrWithId(): Promise<Multiaddr> {
|
|
|
|
|
return await this.setPeerId().then((res) => res.multiaddrWithId);
|
|
|
|
|
}
|
2021-03-12 17:08:42 +11:00
|
|
|
|
2021-03-23 11:14:51 +11:00
|
|
|
private async setPeerId(): Promise<{
|
|
|
|
|
peerId: PeerId;
|
|
|
|
|
multiaddrWithId: Multiaddr;
|
|
|
|
|
}> {
|
|
|
|
|
if (this.peerId && this.multiaddrWithId) {
|
|
|
|
|
return { peerId: this.peerId, multiaddrWithId: this.multiaddrWithId };
|
|
|
|
|
}
|
|
|
|
|
const res = await this.info();
|
|
|
|
|
this.multiaddrWithId = multiaddr(res.listenStr);
|
|
|
|
|
const peerIdStr = this.multiaddrWithId.getPeerId();
|
2021-04-20 16:51:04 +10:00
|
|
|
if (!peerIdStr) throw 'Nim-waku multiaddr does not contain peerId';
|
2021-03-23 11:14:51 +11:00
|
|
|
this.peerId = PeerId.createFromB58String(peerIdStr);
|
|
|
|
|
return { peerId: this.peerId, multiaddrWithId: this.multiaddrWithId };
|
2021-03-10 17:39:53 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get multiaddr(): Multiaddr {
|
2021-03-12 17:08:42 +11:00
|
|
|
const port = NIM_WAKU_DEFAULT_P2P_PORT + this.portsShift;
|
|
|
|
|
return multiaddr(`/ip4/127.0.0.1/tcp/${port}/`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get rpcUrl(): string {
|
|
|
|
|
const port = NIM_WAKU_DEFAULT_RPC_PORT + this.portsShift;
|
|
|
|
|
return `http://localhost:${port}/`;
|
2021-03-10 17:39:53 +11:00
|
|
|
}
|
|
|
|
|
|
2021-04-13 15:22:29 +10:00
|
|
|
private async rpcCall(
|
|
|
|
|
method: string,
|
|
|
|
|
params: Array<string | number | unknown>
|
|
|
|
|
) {
|
2021-03-10 17:39:53 +11:00
|
|
|
const res = await axios.post(
|
2021-03-12 17:08:42 +11:00
|
|
|
this.rpcUrl,
|
2021-03-10 17:39:53 +11:00
|
|
|
{
|
|
|
|
|
jsonrpc: '2.0',
|
|
|
|
|
id: 1,
|
|
|
|
|
method,
|
|
|
|
|
params,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return res.data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private checkProcess() {
|
|
|
|
|
if (!this.process) {
|
|
|
|
|
throw "Nim Waku isn't started";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-11 10:54:35 +11:00
|
|
|
|
|
|
|
|
export function argsToArray(args: Args): Array<string> {
|
|
|
|
|
const array = [];
|
|
|
|
|
|
|
|
|
|
for (const [key, value] of Object.entries(args)) {
|
|
|
|
|
// Change the key from camelCase to kebab-case
|
|
|
|
|
const kebabKey = key.replace(/([A-Z])/, (_, capital) => {
|
|
|
|
|
return '-' + capital.toLowerCase();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const arg = `--${kebabKey}=${value}`;
|
|
|
|
|
array.push(arg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return array;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-12 17:08:42 +11:00
|
|
|
export function defaultArgs(): Args {
|
2021-03-11 10:54:35 +11:00
|
|
|
return {
|
|
|
|
|
nat: 'none',
|
|
|
|
|
listenAddress: '127.0.0.1',
|
|
|
|
|
relay: true,
|
|
|
|
|
rpc: true,
|
|
|
|
|
rpcAdmin: true,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-12 10:44:47 +11:00
|
|
|
export function strToHex(str: string): string {
|
2021-03-12 10:35:50 +11:00
|
|
|
let hex: string;
|
|
|
|
|
try {
|
|
|
|
|
hex = unescape(encodeURIComponent(str))
|
|
|
|
|
.split('')
|
|
|
|
|
.map(function (v) {
|
2021-03-12 10:44:47 +11:00
|
|
|
return v.charCodeAt(0).toString(16);
|
2021-03-12 10:35:50 +11:00
|
|
|
})
|
|
|
|
|
.join('');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
hex = str;
|
|
|
|
|
console.log('invalid text input: ' + str);
|
|
|
|
|
}
|
2021-03-29 14:25:03 +11:00
|
|
|
return hex;
|
2021-03-12 10:35:50 +11:00
|
|
|
}
|
|
|
|
|
|
2021-03-12 10:44:47 +11:00
|
|
|
export function bufToHex(buffer: Uint8Array) {
|
2021-03-29 14:25:03 +11:00
|
|
|
return Array.prototype.map
|
|
|
|
|
.call(buffer, (x) => ('00' + x.toString(16)).slice(-2))
|
|
|
|
|
.join('');
|
2021-03-12 10:35:50 +11:00
|
|
|
}
|
2021-03-12 17:08:42 +11:00
|
|
|
|
|
|
|
|
interface RpcInfoResponse {
|
|
|
|
|
// multiaddr including id.
|
|
|
|
|
listenStr: string;
|
|
|
|
|
}
|