// Copyright 2019-2022 The Hush Developers // Released under the GPLv3 #include #include "rpc.h" #include "addressbook.h" #include "settings.h" #include "senttxstore.h" #include "version.h" #include #include "sd.h" extern bool isdragonx; RPC::RPC(MainWindow* main) { auto cl = new ConnectionLoader(main, this); // Execute the load connection async, so we can set up the rest of RPC properly. QTimer::singleShot(1, [=]() { cl->loadConnection(); }); this->main = main; this->ui = main->ui; // Setup balances table model balancesTableModel = new BalancesTableModel(main->ui->balancesTable); main->ui->balancesTable->setModel(balancesTableModel); // Setup transactions table model transactionsTableModel = new TxTableModel(ui->transactionsTable); main->ui->transactionsTable->setModel(transactionsTableModel); main->ui->transactionsTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); peersTableModel = new PeersTableModel(ui->peersTable); main->ui->peersTable->setModel(peersTableModel); // tls ciphersuite is wide main->ui->peersTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); bannedPeersTableModel = new BannedPeersTableModel(ui->bannedPeersTable); main->ui->bannedPeersTable->setModel(bannedPeersTableModel); main->ui->bannedPeersTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); qDebug() << __func__ << "Done settings up TableModels"; // Set up timer to refresh Price priceTimer = new QTimer(main); QObject::connect(priceTimer, &QTimer::timeout, [=]() { qDebug() << "Refreshing price data"; refreshPrice(); }); priceTimer->start(Settings::priceRefreshSpeed); qDebug() << __func__ << ": started price refresh at speed=" << Settings::priceRefreshSpeed; qDebug() << "setting up timer for rescan data"; rescanTimer = new QTimer(main); // PreciseTimer is needed for timely GUI updates rescanTimer->setTimerType(Qt::PreciseTimer); QObject::connect(rescanTimer, &QTimer::timeout, [=]() { qDebug() << "Refreshing rescan data"; refreshRescan(); }); rescanTimer->start(10000); qDebug() << __func__ << ": started rescanTimer"; qDebug() << __func__ << ": Setting up a timer to refresh the UI every few seconds"; timer = new QTimer(main); QObject::connect(timer, &QTimer::timeout, [=]() { qDebug() << "Refreshing main UI"; refresh(); }); timer->start(Settings::updateSpeed); // PreciseTimer is needed for timely GUI updates timer->setTimerType(Qt::PreciseTimer); qDebug() << __func__ << ": Set up the timer to watch for tx status"; txTimer = new QTimer(main); QObject::connect(txTimer, &QTimer::timeout, [=]() { //qDebug() << "Watching tx status"; watchTxStatus(); }); qDebug() << __func__ << "Done settings up all timers"; usedAddresses = new QMap(); auto lang = Settings::getInstance()->get_language(); qDebug() << __func__ << ": found lang="<< lang << " in config file"; main->loadLanguage(lang); qDebug() << __func__ << ": setting UI to lang="<< lang << " found in config file"; } RPC::~RPC() { qDebug() << "Deleting stuff"; delete timer; delete txTimer; delete priceTimer; delete transactionsTableModel; delete balancesTableModel; delete peersTableModel; delete bannedPeersTableModel; delete utxos; delete allBalances; delete usedAddresses; delete zaddresses; delete taddresses; delete conn; } void RPC::setEHushd(std::shared_ptr p) { ehushd = p; } void RPC::pauseTimer() { qDebug() << "pausing main timer"; timer->stop(); } void RPC::restartTimer() { qDebug() << "restarting main timer"; timer->start(Settings::updateSpeed); } // Called when a connection to hushd is available. void RPC::setConnection(Connection* c) { if (c == nullptr) return; delete conn; this->conn = c; ui->statusBar->showMessage("Ready! Thank you for helping secure the Hush network by running a full node."); // See if we need to remove the reindex/rescan flags from the conf file auto hushConfLocation = Settings::getInstance()->getHushdConfLocation(); Settings::removeFromHushConf(hushConfLocation, "rescan"); Settings::removeFromHushConf(hushConfLocation, "reindex"); // Refresh the UI refreshPrice(); //checkForUpdate(); // Force update, because this might be coming from a settings update // where we need to immediately refresh refresh(true); } QJsonValue RPC::makePayload(QString method, QString param, QString param2) { // qDebug() << __func__ << ": method=" << method << " payload=" << param << ", " << param2; QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42" }, {"method", method }, {"params", QJsonArray {param, param2}} }; return payload; } QJsonValue RPC::makePayload(QString method, QString param) { // qDebug() << __func__ << ": method=" << method << " " << "payload=" << param; QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42" }, {"method", method }, {"params", QJsonArray {param}} }; return payload; } QJsonValue RPC::makePayload(QString method, int param) { // qDebug() << __func__ << ": method=" << method << " payload=" << param; QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42" }, {"method", method }, {"params", QJsonArray{param}} }; return payload; } QJsonValue RPC::makePayload(QString method) { // qDebug() << __func__ << ": method=" << method << " with no payload"; QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42" }, {"method", method }, }; return payload; } //TODO: we can use listaddresses void RPC::getTAddresses(const std::function& cb) { QString method = "getaddressesbyaccount"; QString params = ""; conn->doRPCWithDefaultErrorHandling(makePayload(method, ""), cb); } // full or partial rescan void RPC::rescan(qint64 height, const std::function& cb) { QString method = "rescan"; conn->doRPCWithDefaultErrorHandling(makePayload(method, height), cb); } // get rescan info void RPC::getRescanInfo(const std::function& cb){ QString method = "getrescaninfo"; // do not show an error in case getrescaninfo doesn't exist in this hushd conn->doRPCIgnoreError(makePayload(method), cb); } void RPC::getnetworksolps(const std::function& cb){ QString method = "getnetworksolps"; conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); } void RPC::getlocalsolps(const std::function& cb){ QString method = "getlocalsolps"; conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); } // get help void RPC::help(const std::function& cb){ QString method = "help"; conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); } // add/remove a banned node. ip can include an optional netmask void RPC::setban(QString ip, QString command, const std::function& cb) { QString method = "setban"; conn->doRPCWithDefaultErrorHandling(makePayload(method, ip, command), cb); } //unban all banned peer nodes void RPC::clearBanned(const std::function& cb) { QString method = "clearbanned"; conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); } void RPC::z_sweepstatus(const std::function& cb) { QString method = "z_sweepstatus"; conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); } void RPC::z_consolidationstatus(const std::function& cb) { QString method = "z_consolidationstatus"; conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); } void RPC::getZAddresses(const std::function& cb) { QString method = "z_listaddresses"; conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); } void RPC::getTransparentUnspent(const std::function& cb) { QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42"}, {"method", "listunspent"}, {"params", QJsonArray {0}} // Get UTXOs with 0 confirmations as well. }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::getZUnspent(const std::function& cb) { QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42"}, {"method", "z_listunspent"}, {"params", QJsonArray {0}} // Get UTXOs with 0 confirmations as well. }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::z_viewtransaction(QString txid, const std::function& cb) { QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42"}, {"method", "z_viewtransaction"}, {"params", QJsonArray {txid}} }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::getrawtransaction(QString txid, const std::function& cb) { QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42"}, {"method", "getrawtransaction"}, {"params", QJsonArray {txid, 1}} }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::newZaddr(const std::function& cb) { QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42"}, {"method", "z_getnewaddress"}, {"params", QJsonArray { "sapling" }}, }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::newTaddr(const std::function& cb) { QString method = "getnewaddress"; conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); } void RPC::setGenerate(int proclimit, const std::function& cb) { QString method = "setgenerate"; QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42" }, {"method", method }, {"params", QJsonArray {true, proclimit}} }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::stopGenerate(int proclimit, const std::function& cb) { QString method = "setgenerate"; QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42" }, {"method", method }, {"params", QJsonArray {false, proclimit}} }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::getZViewKey(QString addr, const std::function& cb) { QString method = "z_exportviewingkey"; conn->doRPCWithDefaultErrorHandling(makePayload(method, addr), cb); } void RPC::getZPrivKey(QString addr, const std::function& cb) { QString method = "z_exportkey"; conn->doRPCWithDefaultErrorHandling(makePayload(method, addr), cb); } void RPC::getTPrivKey(QString addr, const std::function& cb) { QString method = "dumpprivkey"; conn->doRPCWithDefaultErrorHandling(makePayload(method, addr), cb); } void RPC::importZPrivKey(QString privkey, bool rescan, const std::function& cb) { QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42"}, {"method", "z_importkey"}, {"params", QJsonArray { privkey, (rescan ? "yes" : "no") }}, }; conn->doRPCWithDefaultErrorHandling(payload, cb); } // TODO: support rescan height and prefix void RPC::importTPrivKey(QString privkey, bool rescan, const std::function& cb) { QJsonObject payload; // If privkey starts with 5, K or L, use old-style Hush params, same as BTC+ZEC if( privkey.startsWith("5") || privkey.startsWith("K") || privkey.startsWith("L") ) { DEBUG("Detected old-style taddr HUSH WIF"); payload = { {"jsonrpc", "1.0"}, {"id", "42"}, {"method", "importprivkey"}, {"params", QJsonArray { privkey, "", rescan , "0", "128" }}, }; } else { DEBUG("Detected new-style taddr HUSH WIF"); payload = { {"jsonrpc", "1.0"}, {"id", "42"}, {"method", "importprivkey"}, {"params", QJsonArray { privkey, "", rescan }}, }; } DEBUG("Importing taddr WIF with rescan=" << rescan); conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::validateAddress(QString address, const std::function& cb) { QString method = address.startsWith("z") ? "z_validateaddress" : "validateaddress"; conn->doRPCWithDefaultErrorHandling(makePayload(method, address), cb); } void RPC::getBlock(QString height, const std::function& cb) { conn->doRPCWithDefaultErrorHandling(makePayload("getblock", height), cb); } void RPC::getBalance(const std::function& cb) { QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42"}, {"method", "z_gettotalbalance"}, {"params", QJsonArray {0}} // Get Unconfirmed balance as well. }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::getPeerInfo(const std::function& cb) { QString method = "getpeerinfo"; conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); } void RPC::listBanned(const std::function& cb) { QString method = "listbanned"; conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); } void RPC::getTransactions(const std::function& cb) { QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42"}, {"method", "listtransactions"}, {"params", QJsonArray { "*", 99999 }} }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::mergeToAddress(QJsonArray ¶ms, const std::function& cb, const std::function& err) { QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42"}, {"method", "z_mergetoaddress"}, {"params", params} }; conn->doRPC(payload, cb, [=] (QNetworkReply *reply, const QJsonValue &parsed) { if (!parsed.isUndefined() && !parsed["error"].toObject()["message"].isNull()) { err(parsed["error"].toObject()["message"].toString()); } else { err(reply->errorString()); } }); } void RPC::shieldCoinbase(QJsonArray ¶ms, const std::function& cb, const std::function& err) { QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42"}, {"method", "z_shieldcoinbase"}, {"params", params} }; conn->doRPC(payload, cb, [=] (QNetworkReply *reply, const QJsonValue &parsed) { if (!parsed.isUndefined() && !parsed["error"].toObject()["message"].isNull()) { err(parsed["error"].toObject()["message"].toString()); } else { err(reply->errorString()); } }); } void RPC::sendZTransaction(QJsonValue params, const std::function& cb, const std::function& err) { QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42"}, {"method", "z_sendmany"}, {"params", params} }; conn->doRPC(payload, cb, [=] (QNetworkReply *reply, const QJsonValue &parsed) { if (!parsed.isUndefined() && !parsed["error"].toObject()["message"].isNull()) { err(parsed["error"].toObject()["message"].toString()); } else { err(reply->errorString()); } }); } /** * Method to get all the private keys for both z and t addresses. It will make 2 batch calls, * combine the result, and call the callback with a single list containing both the t-addr and z-addr * private keys */ void RPC::getAllPrivKeys(const std::function>)> cb) { if (conn == nullptr) { // No connection, just return return; } // A special function that will call the callback when two lists have been added auto holder = new QPair>>(); holder->first = 0; // This is the number of times the callback has been called, initialized to 0 auto fnCombineTwoLists = [=] (QList> list) { // Increment the callback counter holder->first++; // Add all std::copy(list.begin(), list.end(), std::back_inserter(holder->second)); // And if the caller has been called twice, do the parent callback with the // collected list if (holder->first == 2) { // Sort so z addresses are on top std::sort(holder->second.begin(), holder->second.end(), [=] (auto a, auto b) { return a.first > b.first; }); cb(holder->second); delete holder; } }; // A utility fn to do the batch calling auto fnDoBatchGetPrivKeys = [=](QJsonValue getAddressPayload, QString privKeyDumpMethodName) { conn->doRPCWithDefaultErrorHandling(getAddressPayload, [=] (QJsonValue resp) { QList addrs; for (auto addr : resp.toArray()) { addrs.push_back(addr.toString()); } // Then, do a batch request to get all the private keys conn->doBatchRPC( addrs, [=] (auto addr) { QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42"}, {"method", privKeyDumpMethodName}, {"params", QJsonArray { addr }}, }; return payload; }, [=] (QMap* privkeys) { QList> allTKeys; for (QString addr: privkeys->keys()) { allTKeys.push_back( QPair( addr, privkeys->value(addr).toString())); } fnCombineTwoLists(allTKeys); delete privkeys; } ); }); }; // First get all the t and z addresses. QJsonObject payloadT = { {"jsonrpc", "1.0"}, {"id", "42"}, {"method", "getaddressesbyaccount"}, {"params", QJsonArray {""} } }; QJsonObject payloadZ = { {"jsonrpc", "1.0"}, {"id", "42"}, {"method", "z_listaddresses"} }; fnDoBatchGetPrivKeys(payloadT, "dumpprivkey"); fnDoBatchGetPrivKeys(payloadZ, "z_exportkey"); } // Build the RPC JSON Parameters for this tx void RPC::fillTxJsonParams(QJsonArray& params, Tx tx) { Q_ASSERT(QJsonValue(params).isArray()); // Get all the addresses and amounts QJsonArray allRecepients; // For each addr/amt/memo, construct the JSON and also build the confirm dialog box for (int i=0; i < tx.toAddrs.size(); i++) { auto toAddr = tx.toAddrs[i]; // Construct the JSON params QJsonObject rec; rec["address"] = toAddr.addr; // Force it through string for rounding. Without this, decimal points beyond 8 places // will appear, causing an "invalid amount" error rec["amount"] = Settings::getDecimalString(toAddr.amount); //.toDouble(); if (toAddr.addr.startsWith("z") && !toAddr.encodedMemo.trimmed().isEmpty()) rec["memo"] = toAddr.encodedMemo; allRecepients.push_back(rec); } // Add sender params.push_back(tx.fromAddr); params.push_back(allRecepients); // Add fees if custom fees are allowed. if (Settings::getInstance()->getAllowCustomFees()) { params.push_back(1); // minconf params.push_back(tx.fee); } } void RPC::noConnection() { QIcon i = QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical); main->statusIcon->setPixmap(i.pixmap(16, 16)); main->statusIcon->setToolTip(""); main->statusLabel->setText(QObject::tr("No Connection")); main->statusLabel->setToolTip(""); main->ui->statusBar->showMessage(QObject::tr("No Connection"), 1000); // Clear balances table. QMap emptyBalances; QList emptyOutputs; balancesTableModel->setNewData(&emptyBalances, &emptyOutputs); // Clear Transactions table. QList emptyTxs; transactionsTableModel->addTData(emptyTxs); transactionsTableModel->addZRecvData(emptyTxs); transactionsTableModel->addZSentData(emptyTxs); // Clear balances ui->balSheilded->setText(""); ui->balTransparent->setText(""); ui->balTotal->setText(""); ui->balUSDTotal->setText(""); ui->balSheilded->setToolTip(""); ui->balTransparent->setToolTip(""); ui->balTotal->setToolTip(""); // Clear send tab from address ui->inputsCombo->clear(); } // Refresh received z txs by calling z_listreceivedbyaddress/gettransaction void RPC::refreshReceivedZTrans(QList zaddrs) { if (conn == nullptr) return noConnection(); // We'll only refresh the received Z txs if settings allows us. if (!Settings::getInstance()->getSaveZtxs()) { QList emptylist; transactionsTableModel->addZRecvData(emptylist); return; } // This method is complicated because z_listreceivedbyaddress only returns the txid, and // we have to make a follow up call to gettransaction to get details of that transaction. // Additionally, it has to be done in batches, because there are multiple z-Addresses, // and each z-Addr can have multiple received txs. // 1. For each z-Addr, get list of received txs conn->doBatchRPC(zaddrs, [=] (QString zaddr) { QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "z_lrba"}, {"method", "z_listreceivedbyaddress"}, {"params", QJsonArray {zaddr, 0}} // Accept 0 conf as well. }; return payload; }, [=] (QMap* zaddrTxids) { // Process all txids, removing duplicates. This can happen if the same address // appears multiple times in a single tx's outputs. QSet txids; QMap memos; for (auto it = zaddrTxids->constBegin(); it != zaddrTxids->constEnd(); it++) { auto zaddr = it.key(); for (const auto& i : it.value().toArray()) { // Mark the address as used usedAddresses->insert(zaddr, true); // Filter out change txs if (! i.toObject()["change"].toBool()) { auto txid = i.toObject()["txid"].toString(); txids.insert(txid); // Check for Memos QString memoBytes = i.toObject()["memo"].toString(); if (!memoBytes.startsWith("f600")) { QString memo(QByteArray::fromHex( i.toObject()["memo"].toString().toUtf8())); if (!memo.trimmed().isEmpty()) memos[zaddr + txid] = memo; } } } } // 2. For all txids, go and get the details of that txid. conn->doBatchRPC(txids.toList(), [=] (QString txid) { QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "gettx"}, {"method", "gettransaction"}, {"params", QJsonArray {txid}} }; return payload; }, [=] (QMap* txidDetails) { QList txdata; // Combine them both together. For every zAddr's txid, get the amount, fee, confirmations and time for (auto it = zaddrTxids->constBegin(); it != zaddrTxids->constEnd(); it++) { for (const auto& i : it.value().toArray()) { // Filter out change txs if (i.toObject()["change"].toBool()) continue; auto zaddr = it.key(); auto txid = i.toObject()["txid"].toString(); // Lookup txid in the map auto txidInfo = txidDetails->value(txid); qint64 timestamp; if (!txidInfo.toObject()["time"].isUndefined()) { timestamp = txidInfo.toObject()["time"].toInt(); } else { timestamp = txidInfo.toObject()["blocktime"].toInt(); } auto amount = i.toObject()["amount"].toDouble(); auto confirmations = (unsigned long)txidInfo["confirmations"].toInt(); TransactionItem tx{ QString("receive"), timestamp, zaddr, txid, amount, confirmations, "", memos.value(zaddr + txid, "") }; txdata.push_front(tx); } } transactionsTableModel->addZRecvData(txdata); // Cleanup both responses; delete zaddrTxids; delete txidDetails; } ); } ); } /// This will refresh all the balance data from hushd void RPC::refresh(bool force) { if (conn == nullptr) return noConnection(); getInfoThenRefresh(force); } void RPC::getInfoThenRefresh(bool force) { //qDebug() << "getinfo"; if (conn == nullptr) return noConnection(); static bool prevCallSucceeded = false; QString method = "getinfo"; conn->doRPC(makePayload(method), [=] (const QJsonValue& reply) { prevCallSucceeded = true; // Testnet? if (!reply["testnet"].isNull()) { Settings::getInstance()->setTestnet(reply["testnet"].toBool()); }; // TODO: checkmark only when getinfo.synced == true! // Connected, so display checkmark. QIcon i(":/icons/connected.gif"); main->statusIcon->setPixmap(i.pixmap(16, 16)); static int lastBlock = 0; int curBlock = reply["blocks"].toInt(); int longestchain = reply["longestchain"].toInt(); int version = reply["version"].toInt(); int p2pport = reply["p2pport"].toInt(); int rpcport = reply["rpcport"].toInt(); int protocolversion = reply["protocolversion"].toInt(); // int notarized = reply["notarized"].toInt(); // int lag = curBlock - notarized; // TODO: store all future halvings int blocks_until_halving= 2020000 - curBlock; char halving_days[8]; int blocktime = 75; // seconds if(isdragonx) { blocks_until_halving= 3500000 - curBlock; blocktime = 60; } sprintf(halving_days, "%.2f", (double) (blocks_until_halving * blocktime) / (60*60*24) ); QString ntzhash = reply["notarizedhash"].toString(); QString ntztxid = reply["notarizedtxid"].toString(); Settings::getInstance()->setHushdVersion(version); ui->longestchain->setText(QString::number(longestchain)); // ui->notarizedhashvalue->setText( ntzhash ); // ui->notarizedtxidvalue->setText( ntztxid ); // ui->lagvalue->setText( QString::number(lag) ); ui->version->setText( QString::number(version) ); ui->protocolversion->setText( QString::number(protocolversion) ); ui->p2pport->setText( QString::number(p2pport) ); ui->rpcport->setText( QString::number(rpcport) ); ui->halving->setText( QString::number(blocks_until_halving) % " blocks, " % QString::fromStdString(halving_days) % " days" ); if ( force || (curBlock != lastBlock) ) { // Something changed, so refresh everything. lastBlock = curBlock; refreshBalances(); refreshPeers(); refreshAddresses(); // This calls refreshZSentTransactions() and refreshReceivedZTrans() refreshTransactions(); } int connections = reply["connections"].toInt(); bool hasTLS = !reply["tls_connections"].isNull(); qDebug() << "Local node TLS support = " << hasTLS; int tlsconnections = hasTLS ? reply["tls_connections"].toInt() : 0; Settings::getInstance()->setPeers(connections); if (connections == 0) { // If there are no peers connected, then the internet is probably off or something else is wrong. QIcon i = QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning); main->statusIcon->setPixmap(i.pixmap(16, 16)); } ui->numconnections->setText(QString::number(connections) + " (" + QString::number(tlsconnections) + " TLS)" ); ui->tlssupport->setText(hasTLS ? "Yes" : "No"); /* // Get network sol/s QString method = "getnetworksolps"; conn->doRPCIgnoreError(makePayload(method), [=](const QJsonValue& reply) { qint64 solrate = reply.toInt(); //TODO: format decimal if(isdragonx) { ui->solrate->setText(QString::number((double)solrate) % " Hash/s"); } else { ui->solrate->setText(QString::number((double)solrate / 1000000) % " MegaSol/s"); } // find a QLCDNumber child of tabWidget named networkhashrate auto hashrate = ui->tabWidget->findChild("networkhashrate"); qDebug() << "solrate=" << QString::number(solrate); if(hashrate != nullptr) { hashrate->display( QString::number( solrate, 'f', 2 ) ); } else { qDebug() << "no widget named networkhashrate found"; } // hashrate->display( QString::number( (double) solrate) ); }); // only look at local hashrate for dragonx if(isdragonx) { QString method = "getlocalsolps"; DEBUG("calling getlocalsolps"); conn->doRPCIgnoreError(makePayload(method), [=](const QJsonValue& reply) { qDebug() << "reply=" << reply; double solrate = reply.toDouble(); DEBUG("solrate=" % QString::number(solrate)); // find a QLCDNumber child of tabWidget named localhashrate auto hashrate = ui->tabWidget->findChild("localhashrate"); if(hashrate != nullptr) { hashrate->display( QString::number( (double) solrate, 'f', 2 ) ); } else { qDebug() << "no widget named localhashrate found"; } }); } */ // Get mining info // This is better+faster than calling multiple RPCs such as getlocalsolps/getnetworksolps/getgenerate // and getlocalsolps returns non-zero values even when not mining, which is not what we want conn->doRPCIgnoreError(makePayload("getmininginfo"), [=](const QJsonValue& reply) { QString localhashps = QString::number( reply["localhashps"].toDouble() ); QString networkhashps = QString::number( reply["networkhashps"].toDouble() ); QString generate = QString::number( reply["generate"].toBool() ); QString difficulty = QString::number( reply["difficulty"].toDouble() ); QString genproclimit = QString::number( reply["genproclimit"].toInt() ); // Update network hashrate in "Node Info" tab if(isdragonx) { ui->solrate->setText(QString::number(networkhashps.toDouble()) % " Hash/s"); } else { ui->solrate->setText(QString::number(networkhashps.toDouble() / 1000000) % " MegaSol/s"); // The rest of this callback is for DRAGONX return; } if ( genproclimit == "-1" ) { // Showing users they are mining with -1 threads by default is confusing genproclimit = QString::number(0); } auto stopbutton = ui->tabWidget->findChild("stopmining"); auto startbutton = ui->tabWidget->findChild("startmining"); if (stopbutton == nullptr) { return; } if (startbutton == nullptr) { return; } if(generate == "1") { // already mining stopbutton->setEnabled(true); startbutton->setEnabled(false); DEBUG("enabled stop mining button, disabled start mining button"); } else { // not yet mining startbutton->setEnabled(true); stopbutton->setEnabled(false); DEBUG("enabled start mining button, disabled stop mining button"); } // find a QLCDNumber child of tabWidget named localhashrate auto localhashrate = ui->tabWidget->findChild("localhashrate"); if(localhashrate != nullptr) { localhashrate->display( QString::number( localhashps.toDouble(), 'f', 2 ) ); } else { qDebug() << "no widget named localhashrate found"; } // find a QLCDNumber child of tabWidget named networkhashrate auto nethashrate = ui->tabWidget->findChild("networkhashrate"); if(nethashrate != nullptr) { nethashrate->display( QString::number( networkhashps.toInt() ) ); } else { qDebug() << "no widget named networkhashrate found"; } // find a QLCDNumber child of tabWidget named difficulty auto diff = ui->tabWidget->findChild("difficulty"); if(nethashrate != nullptr) { diff->display( QString::number( difficulty.toDouble(), 'f', 2 ) ); } else { qDebug() << "no widget named difficulty found"; } // find a QLCDNumber child of tabWidget named miningthreads auto miningthreads = ui->tabWidget->findChild("miningthreads"); if(miningthreads != nullptr) { miningthreads->display( QString::number( genproclimit.toInt() ) ); } else { qDebug() << "no widget named difficulty found"; } // find a QLCDNumber child of tabWidget named luck auto luck = ui->tabWidget->findChild("luck"); if(luck != nullptr) { if( generate == "0" ) { // not mining, luck is not applicable luck->display( QString("-") ); } else { // luck = current estimate of time to find a block given current localhash and nethash //TODO: maybe use this as a tooltip double percentOfNetHash = localhashps.toDouble() / networkhashps.toDouble(); DEBUG( "% of nethash=" << percentOfNetHash ); if (localhashps.toDouble() > 0) { //TODO: this is only for DRAGONX int blocktime = 36; double luckInSeconds = (networkhashps.toDouble()/localhashps.toDouble())*blocktime; double luckInHours = luckInSeconds / (60*60); luck->display( QString::number( luckInHours , 'f', 2 ) ); } } } else { qDebug() << "no widget named luck found"; } }); // Get network info conn->doRPCIgnoreError(makePayload("getnetworkinfo"), [=](const QJsonValue& reply) { QString clientname = reply["subversion"].toString(); QString localservices = reply["localservices"].toString(); ui->clientname->setText(clientname); ui->localservices->setText(localservices); }); conn->doRPCIgnoreError(makePayload("getwalletinfo"), [=](const QJsonValue& reply) { int txcount = reply["txcount"].toInt(); ui->txcount->setText(QString::number(txcount)); }); //TODO: If -zindex is enabled, show stats conn->doRPCIgnoreError(makePayload("getchaintxstats"), [=](const QJsonValue& reply) { int txcount = reply["txcount"].toInt(); ui->chaintxcount->setText(QString::number(txcount)); }); // Call to see if the blockchain is syncing. conn->doRPCIgnoreError(makePayload("getblockchaininfo"), [=](const QJsonValue& reply) { auto progress = reply["verificationprogress"].toDouble(); // TODO: use getinfo.synced bool isSyncing = progress < 0.9999; // 99.99% int blockNumber = reply["blocks"].toInt(); int estimatedheight = 0; if (!reply.toObject()["estimatedheight"].isUndefined()) { estimatedheight = reply["estimatedheight"].toInt(); } auto s = Settings::getInstance(); s->setSyncing(isSyncing); s->setBlockNumber(blockNumber); QString ticker = s->get_currency_name(); // Update hushd tab if (isSyncing) { QString txt = QString::number(blockNumber); if (estimatedheight > 0) { txt = txt % " / ~" % QString::number(estimatedheight); // If estimated height is available, then use the download blocks // as the progress instead of verification progress. progress = (double)blockNumber / (double)estimatedheight; } txt = txt % " ( " % QString::number(progress * 100, 'f', 2) % "% )"; ui->blockheight->setText(txt); ui->heightLabel->setText(QObject::tr("Downloading blocks")); } else { ui->blockheight->setText(QString::number(blockNumber)); ui->heightLabel->setText(QObject::tr("Block height")); } QString extra = ""; QString price = ""; // No price data for dragonx for now if (!isdragonx) { auto ticker_price = s->get_price(ticker); if(ticker_price > 0 && ticker != "BTC") { extra = QString::number( s->getBTCPrice() ) % "sat"; } if (ticker_price > 0) { price = QString(", ") % "HUSH" % "=" % QString::number( (double)ticker_price,'f',8) % " " % ticker % " " % extra; } } // Update the status bar QString statusText = QString() % (isSyncing ? QObject::tr("Syncing") : QObject::tr("Connected")) % " (" % (s->isTestnet() ? QObject::tr("testnet:") : "") % QString::number(blockNumber) % (isSyncing ? ("/" % QString::number(progress*100, 'f', 2) % "%") : QString()) % ") "; // % " Lag: " % QString::number(blockNumber - notarized) % price; main->statusLabel->setText(statusText); auto hushPrice = Settings::getUSDFormat(1); QString tooltip; if (connections > 0) { tooltip = QObject::tr("Connected"); } else { tooltip = QObject::tr("No peer connections! Network issues?"); } tooltip = tooltip % "(v" % QString::number(Settings::getInstance()->getHushdVersion()) % ")"; if (!isdragonx && !hushPrice.isEmpty()) { tooltip = "1 HUSH = " % hushPrice % "\n" % tooltip; } main->statusLabel->setToolTip(tooltip); main->statusIcon->setToolTip(tooltip); }); }, [=](QNetworkReply* reply, const QJsonValue&) { // hushd has probably disappeared. this->noConnection(); // Prevent multiple dialog boxes, because these are called async static bool shown = false; if (!shown && prevCallSucceeded) { // show error only first time shown = true; QMessageBox::critical(main, QObject::tr("Connection Error"), QObject::tr("There was an error connecting to hushd. The error was") + ": \n\n" + reply->errorString(), QMessageBox::StandardButton::Ok); shown = false; } prevCallSucceeded = false; }); } void RPC::refreshAddresses() { if (conn == nullptr) return noConnection(); auto newzaddresses = new QList(); getZAddresses([=] (QJsonValue reply) { for (const auto& it : reply.toArray()) { auto addr = it.toString(); newzaddresses->push_back(addr); } delete zaddresses; zaddresses = newzaddresses; // Refresh the sent and received txs from all these z-addresses refreshSentZTrans(); refreshReceivedZTrans(*zaddresses); }); auto newtaddresses = new QList(); getTAddresses([=] (QJsonValue reply) { for (const auto& it : reply.toArray()) { auto addr = it.toString(); if (Settings::isTAddress(addr)) newtaddresses->push_back(addr); } delete taddresses; taddresses = newtaddresses; }); } // Function to create the data model and update the views, used below. void RPC::updateUI(bool anyUnconfirmed) { ui->unconfirmedWarning->setVisible(anyUnconfirmed); // Update balances model data, which will update the table too balancesTableModel->setNewData(allBalances, utxos); // Update from address main->updateFromCombo(); }; // Function to process reply of the listunspent and z_listunspent API calls, used below. bool RPC::processUnspent(const QJsonValue& reply, QMap* balancesMap, QList* newUtxos) { bool anyUnconfirmed = false; for (const auto& it : reply.toArray()) { QString qsAddr = it.toObject()["address"].toString(); auto confirmations = it.toObject()["confirmations"].toInt(); if (confirmations == 0) { anyUnconfirmed = true; } newUtxos->push_back( UnspentOutput{ qsAddr, it.toObject()["txid"].toString(), Settings::getDecimalString(it.toObject()["amount"].toDouble()), (int)confirmations, it.toObject()["spendable"].toBool() }); (*balancesMap)[qsAddr] = (*balancesMap)[qsAddr] + it.toObject()["amount"].toDouble(); } return anyUnconfirmed; }; void RPC::refreshBalances() { if (conn == nullptr) return noConnection(); // 1. Get the Balances getBalance([=] (QJsonValue reply) { auto balT = reply["transparent"].toString().toDouble(); auto balZ = reply["private"].toString().toDouble(); auto balTotal = reply["total"].toString().toDouble(); ui->balSheilded ->setText(Settings::getDisplayFormat(balZ)); ui->balTransparent->setText(Settings::getDisplayFormat(balT)); ui->balTotal ->setText(Settings::getDisplayFormat(balTotal)); ui->balSheilded ->setToolTip(Settings::getUSDFormat(balZ)); ui->balTransparent->setToolTip(Settings::getUSDFormat(balT)); ui->balTotal ->setToolTip(Settings::getUSDFormat(balTotal)); ui->balUSDTotal ->setText(Settings::getUSDFormat(balTotal)); ui->balUSDTotal ->setToolTip(Settings::getUSDFormat(balTotal)); }); // 2. Get the UTXOs // First, create a new UTXO list. It will be replacing the existing list when everything is processed. auto newUtxos = new QList(); auto newBalances = new QMap(); // Call the Transparent and Z unspent APIs serially and then, once they're done, update the UI getTransparentUnspent([=] (QJsonValue reply) { auto anyTUnconfirmed = processUnspent(reply, newBalances, newUtxos); getZUnspent([=] (QJsonValue reply) { auto anyZUnconfirmed = processUnspent(reply, newBalances, newUtxos); // Swap out the balances and UTXOs delete allBalances; delete utxos; allBalances = newBalances; utxos = newUtxos; updateUI(anyTUnconfirmed || anyZUnconfirmed); main->balancesReady(); }); }); } void RPC::refreshRescan() { qDebug() << __func__; if (conn == nullptr) { qDebug() << __func__ << ": no connection"; return noConnection(); } getRescanInfo([=] (QJsonValue response){ qDebug() << "got getrescaninfo json=" << response; auto rescanning = response.toObject().value("rescanning").toBool(); auto rescan_progress = response.toObject().value("rescan_progress").toString(); auto rescan_start_height = (qint64)response.toObject().value("rescan_start_height").toInt(); auto rescan_height = (qint64)response.toObject().value("rescan_height").toInt(); double percent = QString(rescan_progress).toDouble() * 100; qDebug() << __func__ << ": getrescaninfo" << rescanning << " " << percent << " " << rescan_start_height << " " << rescan_height; if(rescanning){ // pauseTimer(); qDebug() << __func__ << ": Rescanning at " << percent << "e"; ui->statusBar->showMessage(QObject::tr("Rescanning... ") + QString::number(percent)+ "% " + QObject::tr("at height") + " " + QString::number(rescan_height)); } else{ qDebug() << __func__ << ": not rescanning"; } }); } void RPC::refreshPeers() { qDebug() << __func__; if (conn == nullptr) return noConnection(); /* [ { "address": "199.247.28.148/255.255.255.255", "banned_until": 1612869516 }, { "address": "2001:19f0:5001:d26:5400:3ff:fe18:f6c2/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "banned_until": 1612870431 } ] */ listBanned([=] (QJsonValue reply) { QList peerdata; for (const auto& it : reply.toArray()) { auto addr = it.toObject()["address"].toString(); auto asn = (qint64)it.toObject()["mapped_as"].toInt(); auto bantime = (qint64)it.toObject()["banned_until"].toInt(); auto parts = addr.split("/"); auto ip = parts[0]; auto subnet = parts[1]; BannedPeerItem peer { ip, subnet, bantime, asn }; qDebug() << "Adding banned peer with address=" << addr; peerdata.push_back(peer); } bannedPeersTableModel->addData(peerdata); }); getPeerInfo([=] (QJsonValue reply) { QList peerdata; for (const auto& it : reply.toArray()) { auto addr = it.toObject()["addr"].toString(); auto asn = (qint64)it.toObject()["mapped_as"].toInt(); auto recv = (qint64)it.toObject()["bytesrecv"].toInt(); auto sent = (qint64)it.toObject()["bytessent"].toInt(); PeerItem peer { it.toObject()["id"].toInt(), // peerid "", // type (qint64)it.toObject()["conntime"].toInt(), addr, asn, it.toObject()["tls_cipher"].toString(), // don't convert the JS string "false" to a bool, which is true, lulz it.toObject()["tls_verified"].toString() == 'true' ? true : false, (qint64)(it.toObject()["banscore"].toInt()), (qint64)(it.toObject()["version"].toInt()), // protocolversion it.toObject()["subver"].toString(), recv, sent, it.toObject()["pingtime"].toDouble(), }; qDebug() << "Adding peer with address=" << addr << " and AS=" << asn << " bytesrecv=" << recv << " bytessent=" << sent; peerdata.push_back(peer); } //qDebug() << peerdata; // Update model data, which updates the table view peersTableModel->addData(peerdata); }); } void RPC::refreshTransactions() { if (conn == nullptr) return noConnection(); // Show statusBar message ui->statusBar->showMessage(QObject::tr("Transaction data is loading...")); getTransactions([=] (QJsonValue reply) { QList txdata; for (const auto& it : reply.toArray()) { double fee = 0; if (!it.toObject()["fee"].isNull()) { fee = it.toObject()["fee"].toDouble(); } QString address = (it.toObject()["address"].isNull() ? "" : it.toObject()["address"].toString()); TransactionItem tx{ it.toObject()["category"].toString(), (qint64)it.toObject()["time"].toInt(), address, it.toObject()["txid"].toString(), it.toObject()["amount"].toDouble() + fee, (unsigned long)it.toObject()["confirmations"].toInt(), "", "" }; txdata.push_back(tx); if (!address.isEmpty()) usedAddresses->insert(address, true); } // Update model data, which updates the table view qDebug() << "refreshTransactions"; transactionsTableModel->addTData(txdata); // Update statusBar message ui->statusBar->showMessage(QObject::tr("Transaction data loaded"), 3 * 1000); }); } // Read sent Z transactions from the file. void RPC::refreshSentZTrans() { if (conn == nullptr) return noConnection(); auto sentZTxs = SentTxStore::readSentTxFile(); // If there are no sent z txs, then empty the table. // This happens when you clear history. if (sentZTxs.isEmpty()) { transactionsTableModel->addZSentData(sentZTxs); return; } QList txids; for (auto sentTx: sentZTxs) { txids.push_back(sentTx.txid); } // Look up all the txids to get the confirmation count for them. conn->doBatchRPC(txids, [=] (QString txid) { QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "senttxid"}, {"method", "gettransaction"}, {"params", QJsonArray {txid}} }; return payload; }, [=] (QMap* txidList) { auto newSentZTxs = sentZTxs; // Update the original sent list with the confirmation count // TODO: This whole thing is kinda inefficient. We should probably just update the file // with the confirmed block number, so we don't have to keep calling gettransaction for the // sent items. for (TransactionItem& sentTx: newSentZTxs) { auto j = txidList->value(sentTx.txid); if (j.isNull()) continue; auto error = j["confirmations"].isNull(); if (!error) sentTx.confirmations = j["confirmations"].toInt(); } transactionsTableModel->addZSentData(newSentZTxs); delete txidList; } ); } void RPC::addNewTxToWatch(const QString& newOpid, WatchedTx wtx) { watchingOps.insert(newOpid, wtx); watchTxStatus(); } // Execute a transaction! void RPC::executeTransaction(Tx tx, const std::function submitted, const std::function computed, const std::function error) { // First, create the json params QJsonArray params; fillTxJsonParams(params, tx); //std::cout << std::setw(2) << params << std::endl; sendZTransaction(params, [=](const QJsonValue& reply) { QString opid = reply.toString(); // And then start monitoring the transaction addNewTxToWatch( opid, WatchedTx { opid, tx, computed, error} ); submitted(opid); }, [=](QString errStr) { error("", errStr); }); } void RPC::watchTxStatus() { if (conn == nullptr) return noConnection(); // Make an RPC to load pending operation statues conn->doRPCIgnoreError(makePayload("z_getoperationstatus"), [=] (const QJsonValue& reply) { // conn->doRPCIgnoreError(payload, [=] (const json& reply) { // There's an array for each item in the status for (const auto& it : reply.toArray()) { // If we were watching this Tx and its status became "success", then we'll show a status bar alert QString id = it.toObject()["id"].toString(); if (watchingOps.contains(id)) { // log any txs we are watching // "creation_time": 1515969376, // "execution_secs": 50.416337, // And if it ended up successful QString status = it.toObject()["status"].toString(); main->loadingLabel->setVisible(false); if (status == "success") { auto txid = it.toObject()["result"].toObject()["txid"].toString(); SentTxStore::addToSentTx(watchingOps[id].tx, txid); auto wtx = watchingOps[id]; watchingOps.remove(id); wtx.completed(id, txid); qDebug() << "opid "<< id << " started at "<stop(); } else { // Keep polling for updates txTimer->start(Settings::quickUpdateSpeed); } } // If there is some op that we are watching, then show the loading bar, otherwise hide it if (watchingOps.empty()) { main->loadingLabel->setVisible(false); } else { main->loadingLabel->setVisible(true); main->loadingLabel->setToolTip(QString::number(watchingOps.size()) + QObject::tr(" transaction computing.")); } }); } void RPC::checkForUpdate(bool silent) { // Disable update checks for now // TODO: Use Gitea API return; qDebug() << "checking for updates"; if (conn == nullptr) return noConnection(); QUrl cmcURL("https://git.hush.is/hush/SilentDragon/releases"); QNetworkRequest req; req.setUrl(cmcURL); QNetworkReply *reply = conn->restclient->get(req); QObject::connect(reply, &QNetworkReply::finished, [=] { reply->deleteLater(); try { if (reply->error() == QNetworkReply::NoError) { auto releases = QJsonDocument::fromJson(reply->readAll()).array(); QVersionNumber maxVersion(0, 0, 0); for (QJsonValue rel : releases) { if (!rel.toObject().contains("tag_name")) continue; QString tag = rel.toObject()["tag_name"].toString(); if (tag.startsWith("v")) tag = tag.right(tag.length() - 1); if (!tag.isEmpty()) { auto v = QVersionNumber::fromString(tag); if (v > maxVersion) maxVersion = v; } } auto currentVersion = QVersionNumber::fromString(APP_VERSION); // Get the max version that the user has hidden updates for QSettings s; auto maxHiddenVersion = QVersionNumber::fromString(s.value("update/lastversion", "0.0.0").toString()); qDebug() << "Version check: Current " << currentVersion << ", Available " << maxVersion; if (maxVersion > currentVersion && (!silent || maxVersion > maxHiddenVersion)) { auto ans = QMessageBox::information(main, QObject::tr("Update Available"), QObject::tr("A new release v%1 is available! You have v%2.\n\nWould you like to visit the releases page?") .arg(maxVersion.toString()) .arg(currentVersion.toString()), QMessageBox::Yes, QMessageBox::Cancel); if (ans == QMessageBox::Yes) { QDesktopServices::openUrl(QUrl("https://git.hush.is/hush/SilentDragon/releases")); } else { // If the user selects cancel, don't bother them again for this version s.setValue("update/lastversion", maxVersion.toString()); } } else { if (!silent) { QMessageBox::information(main, QObject::tr("No updates available"), QObject::tr("You already have the latest release v%1") .arg(currentVersion.toString())); } } } } catch (const std::exception& e) { // If anything at all goes wrong, move on qDebug() << QString("Exception checking for updates!"); } }); } // Get the HUSH prices void RPC::refreshPrice() { if (conn == nullptr) return noConnection(); if (isdragonx) { return; } auto s = Settings::getInstance(); if (s->getAllowFetchPrices() == false) { qDebug() << "Price fetching disabled"; return; } QString price_feed = "https://api.coingecko.com/api/v3/simple/price?ids=hush&vs_currencies=btc%2Cusd%2Ceur%2Ceth%2Cgbp%2Ccny%2Cjpy%2Cidr%2Crub%2Ccad%2Csgd%2Cchf%2Cinr%2Caud%2Cinr%2Ckrw%2Cthb%2Cnzd%2Czar%2Cvef%2Cxau%2Cxag%2Cvnd%2Csar%2Ctwd%2Caed%2Cars%2Cbdt%2Cbhd%2Cbmd%2Cbrl%2Cclp%2Cczk%2Cdkk%2Chuf%2Cils%2Ckwd%2Clkr%2Cpkr%2Cnok%2Ctry%2Csek%2Cmxn%2Cuah%2Chkd&include_market_cap=true&include_24hr_vol=true&include_24hr_change=true"; qDebug() << "Requesting price feed data via " << price_feed; QUrl cmcURL(price_feed); QNetworkRequest req; req.setUrl(cmcURL); qDebug() << "Created price request object"; QNetworkReply *reply = conn->restclient->get(req); qDebug() << "Created QNetworkReply"; QObject::connect(reply, &QNetworkReply::finished, [=] { reply->deleteLater(); try { QByteArray ba_raw_reply = reply->readAll(); QString raw_reply = QString::fromUtf8(ba_raw_reply); QByteArray unescaped_raw_reply = raw_reply.toUtf8(); QJsonDocument jd_reply = QJsonDocument::fromJson(unescaped_raw_reply); QJsonObject parsed = jd_reply.object(); if (reply->error() != QNetworkReply::NoError) { qDebug() << "Parsing price feed response"; if (!parsed.isEmpty() && !parsed["error"].toObject()["message"].isNull()) { qDebug() << parsed["error"].toObject()["message"].toString(); } else { qDebug() << reply->errorString(); } s->setHUSHPrice(0); s->setBTCPrice(0); return; } qDebug() << "No network errors"; if (parsed.isEmpty()) { s->setHUSHPrice(0); s->setBTCPrice(0); return; } qDebug() << "Parsed JSON"; const QJsonValue& item = parsed; const QJsonValue& hush = item["hush"].toObject(); QString ticker = s->get_currency_name(); ticker = ticker.toLower(); fprintf(stderr,"ticker=%s\n", ticker.toLocal8Bit().data()); //qDebug() << "Ticker = " + ticker; if (!hush[ticker].isUndefined()) { qDebug() << "Found hush key in price json"; //QString price = hush["usd"].toString()); qDebug() << "HUSH = $" << QString::number(hush["usd"].toDouble()) << " USD"; qDebug() << "HUSH = " << QString::number(hush["eur"].toDouble()) << " EUR"; qDebug() << "HUSH = " << QString::number((int) 100000000 * hush["btc"].toDouble()) << " sat "; s->setHUSHPrice( hush[ticker].toDouble() ); s->setBTCPrice( (unsigned int) 100000000 * hush["btc"].toDouble() ); ticker = ticker.toLower(); qDebug() << "ticker=" << ticker; // TODO: work harder to prevent coredumps! auto price = hush[ticker].toDouble(); auto vol = hush[ticker + "_24h_vol"].toDouble(); auto mcap = hush[ticker + "_market_cap"].toDouble(); //auto btcprice = hush["btc"].toDouble(); auto btcvol = hush["btc_24h_vol"].toDouble(); auto btcmcap = hush["btc_market_cap"].toDouble(); s->set_price(ticker, price); s->set_volume(ticker, vol); s->set_volume("BTC", btcvol); s->set_marketcap(ticker, mcap); qDebug() << "Volume = " << (double) vol; ticker = ticker.toUpper(); ui->volume->setText( QString::number((double) vol, 'f', 2) + " " + ticker ); ui->volumeBTC->setText( QString::number((double) btcvol, 'f', 2) + " BTC" ); ticker = ticker.toUpper(); // We don't get an actual HUSH volume stat, so we calculate it if (price > 0) ui->volumeLocal->setText( QString::number((double) vol / (double) price) + " HUSH"); qDebug() << "Mcap = " << (double) mcap; ui->marketcap->setText( QString::number( (double) mcap, 'f', 2) + " " + ticker ); ui->marketcapBTC->setText( QString::number((double) btcmcap, 'f', 2) + " BTC" ); //ui->marketcapLocal->setText( QString::number((double) mcap * (double) price) + " " + ticker ); refresh(true); return; } else { qDebug() << "No hush key found in JSON! API might be down or we are rate-limited\n"; } } catch (const std::exception& e) { // If anything at all goes wrong, just set the price to 0 and move on. qDebug() << QString("Price feed update failure : "); } // If nothing, then set the price to 0; Settings::getInstance()->setHUSHPrice(0); Settings::getInstance()->setBTCPrice(0); }); } void RPC::shutdownHushd() { // Shutdown embedded hushd if it was started if (ehushd == nullptr || ehushd->processId() == 0 || conn == nullptr) { // No hushd running internally, just return return; } QString method = "stop"; conn->doRPCWithDefaultErrorHandling(makePayload(method), [=](auto) {}); conn->shutdown(); QDialog d(main); d.setWindowFlags(d.windowFlags() & ~(Qt::WindowCloseButtonHint | Qt::WindowContextHelpButtonHint)); Ui_ConnectionDialog connD; connD.setupUi(&d); //connD.topIcon->setBasePixmap(QIcon(":/icons/icon.ico").pixmap(256, 256)); if(isdragonx) { d.setWindowTitle("SilentDragonX"); } QMovie *movie1 = new QMovie(":/img/silentdragon-animated-startup-dark.gif");; auto theme = Settings::getInstance()->get_theme_name(); movie1->setScaledSize(QSize(512,512)); connD.topIcon->setMovie(movie1); movie1->start(); if(isdragonx) { connD.status->setText(QObject::tr("Please enhance your calm and wait for SilentDragonX to exit")); connD.statusDetail->setText(QObject::tr("Waiting for dragonxd to exit, y'all")); } else { connD.status->setText(QObject::tr("Please enhance your calm and wait for SilentDragon to exit")); connD.statusDetail->setText(QObject::tr("Waiting for hushd to exit, y'all")); } QTimer waiter(main); // We capture by reference all the local variables because of the d.exec() // below, which blocks this function until we exit. int waitCount = 0; QObject::connect(&waiter, &QTimer::timeout, [&] () { waitCount++; if ((ehushd->atEnd() && ehushd->processId() == 0) || ehushd->state() == QProcess::NotRunning || waitCount > 30 || conn->config->hushDaemon) { // If hushd is daemon, then we don't have to do anything else qDebug() << "Ended"; waiter.stop(); QTimer::singleShot(1000, [&]() { d.accept(); }); } else { qDebug() << "Not ended, continuing to wait..."; } }); waiter.start(1000); // Wait for the hush process to exit. if (!Settings::getInstance()->isHeadless()) { d.exec(); } else { while (waiter.isActive()) { QCoreApplication::processEvents(); QThread::sleep(1); } } } /** * Get a Sapling address from the user's wallet */ QString RPC::getDefaultSaplingAddress() { for (QString addr: *zaddresses) { if (Settings::getInstance()->isSaplingAddress(addr)) return addr; } return QString(); } QString RPC::getDefaultTAddress() { if (getAllTAddresses()->length() > 0) return getAllTAddresses()->at(0); else return QString(); }