app/rpc.js
// @flow
/*jshint esversion: 6 */
/*jslint node: true */
"use strict";
const {ipcRenderer} = require("electron");
const {openTunnel} = require("./ssh_tunneling.js");
const {zenextra} = require("./zenextra.js");
const zencashjs = require("zencashjs");
const delay = require("delay");
let sshServer;
let howManyUseSSH;
function clientCallSync(methodUsed, paramsUsed) {
const rpc = require("node-json-rpc2");
let options = {
port: settings.secureNodePort,
host: "127.0.0.1", // settings.secureNodeFQDN,
user: settings.secureNodeUsername,
password: settings.secureNodePassword,
protocol: "http",
// method:"POST",
path: "/",
strict: true
};
let client = new rpc.Client(options);
return new Promise(function (resolve, reject) {
client.call({
method: methodUsed,
params: paramsUsed, // Will be [] by default
id: "rpcTest", // Optional. By default it's a random id
jsonrpc: "1.0", // Optional. By default it's 2.0
protocol: "http", // Optional. Will be http by default
}, function (error, result) {
if (error) {
reject(error);
}
else {
resolve(result);
}
});
});
}
const clientCallSyncRetry = async (methodUsed, paramsUsed, n, delayMiliSecs) => {
for (let i = 0; i < n; i++) {
if (i > 0) {
await delay(delayMiliSecs);
}
try {
return await clientCallSync(methodUsed, paramsUsed);
} catch (err) {
const isLastAttempt = i + 1 === n;
if (isLastAttempt) throw err;
}
}
};
async function rpcCallCoreSync(methodUsed, paramsUsed) {
let status = "ok";
let outputCore;
if (howManyUseSSH === undefined) {
howManyUseSSH = 1;
} else {
howManyUseSSH = howManyUseSSH + 1;
}
let tunnelToLocalHost = (settings.secureNodeFQDN === "127.0.0.1" || settings.secureNodeFQDN.toLowerCase() === "localhost");
if (sshServer === undefined && howManyUseSSH <= 1 && !tunnelToLocalHost) {
try {
sshServer = await openTunnel();
console.log("SSH Tunnel to Server: Opened");
} catch (error) {
console.log(error);
// console.log("Already open, no problem.");
}
}
// FIXME: colorRpcLEDs - element is not exported
try {
outputCore = await clientCallSyncRetry(methodUsed, paramsUsed, 3, 1000); // 3 is retries, 1000 is 1 sec delay
colorRpcLEDs(true); // false = Red // true = Green
} catch (error) {
outputCore = "rpcCallCoreSync error in method : " + methodUsed + " ";
status = "error";
console.log(outputCore);
colorRpcLEDs(false); // false = Red
//throw new Error("Error using method " + methodUsed);
}
howManyUseSSH = howManyUseSSH - 1;
if (howManyUseSSH === 0 || howManyUseSSH < 0) {
if (!tunnelToLocalHost && !(sshServer === undefined)) { //
sshServer.close();
sshServer = undefined;
console.log("SSH Tunnel to Server: Closed");
}
}
// console.log(outputCore);
return {output: outputCore, status: status}
}
// ====== String Formating ===============================================
function cleanCommandString(string) {
// removes 1st and last white space -- removes double spacing
return string.replace(/\s+$/, "").replace(/ +(?= )/g, "");
}
function removeOneElement(array, element) {
const index = array.indexOf(element);
array.splice(index, 1);
}
function splitCommandString(stringCommand) {
let splitString = stringCommand.split(/\s+/);
let method = splitString[0];
removeOneElement(splitString, method);
// let params = splitString;
return {method: method, params: splitString}
}
//=====================================================
// rpcCallResultSync and rpcCallCoreSync can be combined, but it is more clear to keep them like this
async function rpcCallResultSync(cmd, paramsUsed) {
let status = "error";
let isOK = false;
let respCore;
let outputLast;
respCore = await rpcCallCoreSync(cmd, paramsUsed);
if (respCore.status === "error") {
outputLast = respCore.output;
status = "error";
} else {
outputLast = (respCore.output.result);
status = "ok";
isOK = true;
}
return {output: outputLast, status: status, isOK: isOK}
}
async function helpSync() {
let cmd;
cmd = "help";
let result = await rpcCallResultSync(cmd, []);
console.log(result);
}
async function importPKinSN(pk, address) {
if (pk === undefined) {
return {output: "No PK given.", status: "ok"}
} else {
let cmd;
if (zenextra.isZeroAddr(address)) {
cmd = "z_importkey";
if (zenextra.isPK(pk)) {
pk = zencashjs.zaddress.zSecretKeyToSpendingKey(pk);
}
}
if (zenextra.isTransaparentAddr(address)) {
cmd = "importprivkey";
if (!zenextra.isWif(pk)) {
pk = zencashjs.address.privKeyToWIF(pk);
}
}
return await rpcCallResultSync(cmd, [pk, "no"]);
}
}
async function pingSecureNodeRPC() {
let resp = await rpcCallResultSync("help", []);
return resp.isOK // if resp.isOK = isAlive
}
async function getNewZaddressPK(nameAddress) {
let resp = await rpcCallResultSync("z_getnewaddress", []);
let zAddress = resp.output;
let newResp = await rpcCallResultSync("z_exportkey", [zAddress]); // let spendingKey = newResp.output;
if (newResp.isOK) {
let pkZaddress = zenextra.spendingKeyToSecretKey(newResp.output);
ipcRenderer.send("DB-insert-address", nameAddress, pkZaddress, zAddress);
return {pk: pkZaddress, addr: zAddress, name: nameAddress}
}
}
// Unused for now but can be used in the future
async function getNewTaddressPK(nameAddress) {
let resp = await rpcCallResultSync("getnewaddress", []);
let tAddress = resp.output;
let newResp = await rpcCallResultSync("dumpprivkey", [tAddress]); // let wif = newResp.output;
if (newResp.isOK) {
let pkTaddress = zencashjs.address.WIFToPrivKey(newResp.output);
ipcRenderer.send("DB-insert-address", nameAddress, pkTaddress, tAddress);
return tAddress
}
}
async function getNewTaddressWatchOnly(nameAddress) {
let resp = await rpcCallResultSync("getnewaddress", []);
if (resp.isOK) {
let tAddress = resp.output;
let pkTaddress = "watchOnlyAddrr";
ipcRenderer.send("DB-insert-address", nameAddress, pkTaddress, tAddress);
return tAddress
}
}
async function getSecureNodeTaddressOrGenerate() {
let resp = await rpcCallResultSync("listaddresses", []);
console.log(resp.output);
let theT;
let nameAddress = "My Watch Only Secure Node addr";
let pkTaddress = "watchOnlyAddrr";
if (resp.isOK) {
if (resp.output.length === 0) {
theT = await getNewTaddressWatchOnly(nameAddress)
} else {
theT = resp.output[0];
let respNew = ipcRenderer.sendSync("check-if-address-in-wallet", theT);
let addrExists = respNew.exist;
if (!addrExists) {
ipcRenderer.send("DB-insert-address", nameAddress, pkTaddress, theT);
}
}
return theT
} else {
return false
}
}
async function getOperationStatus(opid) {
let resp = await rpcCallResultSync("z_getoperationstatus", [[opid]]);
return resp.output
}
async function getZaddressBalance(pk, address) {
let balance = -1.0;
let resp = await rpcCallResultSync("z_getbalance", [address]);
if (resp.isOK) {
balance = parseFloat(resp.output); //.toFixed(8)
return {balance: balance, status: resp.status}
} else {
console.log(resp.status);
return {balance: balance, status: resp.status}
}
}
async function getTaddressBalance(address) {
let balance = -1.0;
let resp = await rpcCallResultSync("z_getbalance", [address]);
if (resp.isOK) {
balance = parseFloat(resp.output).toFixed(8);
return {balance: balance, status: resp.status}
} else {
console.log(resp.status);
return {balance: balance, status: resp.status}
}
}
async function updateAllZBalances() {
const valid = ipcRenderer.sendSync("update-Z-old-balance");
const zAddrObjs = ipcRenderer.sendSync("get-all-Z-addresses");
for (const addrObj of zAddrObjs) {
let newBalanceResp = await getZaddressBalance(addrObj.pk, addrObj.addr);
let newBalance = newBalanceResp.balance;
if (newBalance >= 0.0) {
addrObj.lastbalance = newBalance;
let respZ = ipcRenderer.sendSync("update-addr-in-db", addrObj);
}
}
}
async function listAllTAddresses() {
return await rpcCallResultSync("listaddresses", [])
}
async function listAllZAddresses() {
return await rpcCallResultSync("z_listaddresses", [])
}
async function getPKofZAddress(zAddr) {
const cmd = "z_exportkey";
let paramsUsed = [zAddr];
return await rpcCallResultSync(cmd, paramsUsed)
}
async function importAllZAddressesFromSNtoArizen() {
let resp = await listAllZAddresses();
let addrList = resp.output;
let isT = false;
if (resp.isOK) {
if (!(addrList === undefined || addrList.length === 0)) {
for (const addr of addrList) {
let resp = await getPKofZAddress(addr);
// let spendingKey = resp.output;
// spendingKey
let pk = zenextra.spendingKeyToSecretKey(resp.output);
ipcRenderer.send("import-single-key", "My SN Z address", pk, isT);
}
}
}
}
async function importAllZAddressesFromSNtoArizenExcludeExisting() {
let resp = await listAllZAddresses();
let addrList = resp.output;
let isT = false;
if (resp.isOK) {
const zAddrObjsArizen = ipcRenderer.sendSync("get-all-Z-addresses"); // In Arizen
let arizenZs = [];
for (const addrObj of zAddrObjsArizen) {
arizenZs.push(addrObj.addr)
}
if (!(addrList === undefined || addrList.length === 0)) {
for (const addr of addrList) {
if (!(arizenZs.includes(addr))){
let resp = await getPKofZAddress(addr);
// let spendingKey = resp.output;
// spendingKey
let pk = zenextra.spendingKeyToSecretKey(resp.output);
ipcRenderer.send("import-single-key", "My SN Z address", pk, isT);
}
}
}
}
}
async function importAllZAddressesFromArizenToSN() {
const zAddrObjs = ipcRenderer.sendSync("get-all-Z-addresses");
let nullResp;
for (const addrObj of zAddrObjs) {
nullResp = await importPKinSN(addrObj.pk, addrObj.addr);
}
}
async function sendFromOrToZaddress(fromAddressPK, fromAddress, toAddress, amount, fee) {
//let nullResp = await importPKinSN(fromAddressPK, fromAddress);
let minconf = 1;
let amounts = [{"address": toAddress, "amount": amount}];
let cmd = "z_sendmany";
let paramsUsed = [fromAddress, amounts, minconf, fee];
let resp = await rpcCallResultSync(cmd, paramsUsed);
console.log("opid: " + resp.output);
console.log(resp.status);
// FIXME: updateWithdrawalStatus - element is not exported
updateWithdrawalStatus(resp.status, resp.output);
return resp
}
module.exports = {
cleanCommandString: cleanCommandString,
splitCommandString: splitCommandString,
rpcCallResultSync: rpcCallResultSync,
getNewZaddressPK: getNewZaddressPK,
sendFromOrToZaddress: sendFromOrToZaddress,
updateAllZBalances: updateAllZBalances,
importAllZAddressesFromSNtoArizenExcludeExisting: importAllZAddressesFromSNtoArizenExcludeExisting,
importAllZAddressesFromArizenToSN: importAllZAddressesFromArizenToSN,
pingSecureNodeRPC: pingSecureNodeRPC,
getSecureNodeTaddressOrGenerate: getSecureNodeTaddressOrGenerate,
getTaddressBalance: getTaddressBalance
// importAllZAddressesFromSNtoArizen: importAllZAddressesFromSNtoArizen,
// getOperationStatus: getOperationStatus,
// getZaddressBalance: getZaddressBalance,
// rpcCallCoreSync: rpcCallCoreSync,
// importPKinSN: importPKinSN,
// helpSync: helpSync
};