diff --git a/src/connection.cpp b/src/connection.cpp index aefcb30..dab9cb4 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -90,7 +90,7 @@ Connection* ConnectionLoader::makeConnection(std::shared_ptr c QString headerData = "Basic " + userpass.toLocal8Bit().toBase64(); request->setRawHeader("Authorization", headerData.toLocal8Bit()); - return new Connection(client, request, config); + return new Connection(main, client, request, config); } void ConnectionLoader::refreshZcashdState(Connection* connection) { @@ -105,7 +105,8 @@ void ConnectionLoader::refreshZcashdState(Connection* connection) { d->hide(); rpc->setConnection(connection); }, - [=] (auto err, auto res) { + [=] (auto reply, auto res) { + auto err = reply->error(); // Failed, see what it is. qDebug() << err << ":" << QString::fromStdString(res.dump()); @@ -251,10 +252,11 @@ std::shared_ptr ConnectionLoader::loadFromSettings() { -Connection::Connection(QNetworkAccessManager* c, QNetworkRequest* r, std::shared_ptr conf) { +Connection::Connection(MainWindow* m, QNetworkAccessManager* c, QNetworkRequest* r, std::shared_ptr conf) { this->restclient = c; this->request = r; this->config = conf; + this->main = m; } Connection::~Connection() { @@ -265,7 +267,7 @@ Connection::~Connection() { void Connection::doRPC(const json& payload, const std::function& cb, - const std::function& ne) { + const std::function& ne) { QNetworkReply *reply = restclient->post(*request, QByteArray::fromStdString(payload.dump())); QObject::connect(reply, &QNetworkReply::finished, [=] { @@ -273,16 +275,45 @@ void Connection::doRPC(const json& payload, const std::function& cb, if (reply->error() != QNetworkReply::NoError) { auto parsed = json::parse(reply->readAll(), nullptr, false); - ne(reply->error(), parsed); + ne(reply, parsed); return; } auto parsed = json::parse(reply->readAll(), nullptr, false); if (parsed.is_discarded()) { - ne(reply->error(), "Unknown error"); + ne(reply, "Unknown error"); } cb(parsed["result"]); }); } + +void Connection::doRPCWithDefaultErrorHandling(const json& payload, const std::function& cb) { + doRPC(payload, cb, [=] (auto reply, auto parsed) { + if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) { + showTxError(QString::fromStdString(parsed["error"]["message"])); + } else { + showTxError(reply->errorString()); + } + }); +} + +void Connection::doRPCIgnoreError(const json& payload, const std::function& cb) { + doRPC(payload, cb, [=] (auto, auto) { + // Ignored error handling + }); +} + +void Connection::showTxError(const QString& error) { + if (error.isNull()) return; + + QMessageBox msg(main); + msg.setIcon(QMessageBox::Icon::Critical); + msg.setWindowTitle("Transaction Error"); + + msg.setText("There was an error sending the transaction. The error was: \n\n" + + error); + + msg.exec(); +} \ No newline at end of file diff --git a/src/connection.h b/src/connection.h index 7664e1e..48ab48f 100644 --- a/src/connection.h +++ b/src/connection.h @@ -1,12 +1,12 @@ #ifndef CONNECTION_H #define CONNECTION_H +#include "mainwindow.h" #include "ui_connection.h" #include "precompiled.h" using json = nlohmann::json; -class MainWindow; class RPC; enum ConnectionType { @@ -59,17 +59,67 @@ private: */ class Connection { public: - Connection(QNetworkAccessManager* c, QNetworkRequest* r, std::shared_ptr conf); + Connection(MainWindow* m, QNetworkAccessManager* c, QNetworkRequest* r, std::shared_ptr conf); ~Connection(); - QNetworkAccessManager* restclient; QNetworkRequest* request; std::shared_ptr config; + MainWindow* main; void doRPC(const json& payload, const std::function& cb, - const std::function& ne); - + const std::function& ne); + void doRPCWithDefaultErrorHandling(const json& payload, const std::function& cb); + void doRPCIgnoreError(const json& payload, const std::function& cb) ; + + void showTxError(const QString& error); + + // Batch method. Note: Because of the template, it has to be in the header file. + template + void doBatchRPC(const QList& payloads, + std::function payloadGenerator, + std::function*)> cb) { + auto responses = new QMap(); // zAddr -> list of responses for each call. + int totalSize = payloads.size(); + + for (auto item: payloads) { + json payload = payloadGenerator(item); + + QNetworkReply *reply = restclient->post(*request, QByteArray::fromStdString(payload.dump())); + + QObject::connect(reply, &QNetworkReply::finished, [=] { + reply->deleteLater(); + + auto all = reply->readAll(); + auto parsed = json::parse(all.toStdString(), nullptr, false); + + if (reply->error() != QNetworkReply::NoError) { + qDebug() << QString::fromStdString(parsed.dump()); + qDebug() << reply->errorString(); + + (*responses)[item] = json::object(); // Empty object + } else { + if (parsed.is_discarded()) { + (*responses)[item] = json::object(); // Empty object + } else { + (*responses)[item] = parsed["result"]; + } + } + }); + } + + auto waitTimer = new QTimer(main); + QObject::connect(waitTimer, &QTimer::timeout, [=]() { + if (responses->size() == totalSize) { + waitTimer->stop(); + + cb(responses); + + waitTimer->deleteLater(); + } + }); + waitTimer->start(100); + } }; #endif \ No newline at end of file diff --git a/src/rpc.cpp b/src/rpc.cpp index ba05f0e..5a5e910 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -71,32 +71,6 @@ void RPC::setConnection(Connection* c) { refresh(); } -void RPC::doRPC(const json& payload, const std::function& cb) { - QNetworkReply *reply = conn->restclient->post(*conn->request, QByteArray::fromStdString(payload.dump())); - - QObject::connect(reply, &QNetworkReply::finished, [=] { - reply->deleteLater(); - - if (reply->error() != QNetworkReply::NoError) { - auto parsed = json::parse(reply->readAll(), nullptr, false); - if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) { - handleConnectionError(QString::fromStdString(parsed["error"]["message"])); - } else { - handleConnectionError(reply->errorString()); - } - - return; - } - - auto parsed = json::parse(reply->readAll(), nullptr, false); - if (parsed.is_discarded()) { - handleConnectionError("Unknown error"); - } - - cb(parsed["result"]); - }); -} - void RPC::getZAddresses(const std::function& cb) { json payload = { {"jsonrpc", "1.0"}, @@ -104,7 +78,7 @@ void RPC::getZAddresses(const std::function& cb) { {"method", "z_listaddresses"}, }; - doRPC(payload, cb); + conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::getTransparentUnspent(const std::function& cb) { @@ -115,7 +89,7 @@ void RPC::getTransparentUnspent(const std::function& cb) { {"params", {0}} // Get UTXOs with 0 confirmations as well. }; - doRPC(payload, cb); + conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::getZUnspent(const std::function& cb) { @@ -126,7 +100,7 @@ void RPC::getZUnspent(const std::function& cb) { {"params", {0}} // Get UTXOs with 0 confirmations as well. }; - doRPC(payload, cb); + conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::newZaddr(bool sapling, const std::function& cb) { @@ -137,7 +111,7 @@ void RPC::newZaddr(bool sapling, const std::function& cb) { {"params", { sapling ? "sapling" : "sprout" }}, }; - doRPC(payload, cb); + conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::newTaddr(const std::function& cb) { @@ -147,7 +121,7 @@ void RPC::newTaddr(const std::function& cb) { {"method", "getnewaddress"}, }; - doRPC(payload, cb); + conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::getZPrivKey(QString addr, const std::function& cb) { @@ -158,7 +132,7 @@ void RPC::getZPrivKey(QString addr, const std::function& cb) { {"params", { addr.toStdString() }}, }; - doRPC(payload, cb); + conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::getTPrivKey(QString addr, const std::function& cb) { @@ -169,7 +143,7 @@ void RPC::getTPrivKey(QString addr, const std::function& cb) { {"params", { addr.toStdString() }}, }; - doRPC(payload, cb); + conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::importZPrivKey(QString addr, bool rescan, const std::function& cb) { @@ -180,7 +154,7 @@ void RPC::importZPrivKey(QString addr, bool rescan, const std::functiondoRPCWithDefaultErrorHandling(payload, cb); } @@ -192,7 +166,7 @@ void RPC::importTPrivKey(QString addr, bool rescan, const std::functiondoRPCWithDefaultErrorHandling(payload, cb); } @@ -204,7 +178,7 @@ void RPC::getBalance(const std::function& cb) { {"params", {0}} // Get Unconfirmed balance as well. }; - doRPC(payload, cb); + conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::getTransactions(const std::function& cb) { @@ -214,39 +188,7 @@ void RPC::getTransactions(const std::function& cb) { {"method", "listtransactions"} }; - doRPC(payload, cb); -} - -void RPC::doSendRPC(const json& payload, const std::function& cb, const std::function& err) { - QNetworkReply *reply = conn->restclient->post(*conn->request, QByteArray::fromStdString(payload.dump())); - - QObject::connect(reply, &QNetworkReply::finished, [=] { - reply->deleteLater(); - - if (reply->error() != QNetworkReply::NoError) { - auto parsed = json::parse(reply->readAll(), nullptr, false); - if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) { - err(QString::fromStdString(parsed["error"]["message"])); - } - else { - err(reply->errorString()); - } - - return; - } - - auto parsed = json::parse(reply->readAll(), nullptr, false); - if (parsed.is_discarded()) { - err("Unknown error"); - } - - cb(parsed["result"]); - }); -} - -// Default implementation of a Send RPC that default shows an error message box with the error. -void RPC::doSendRPC(const json& payload, const std::function& cb) { - doSendRPC(payload, cb, [=](auto error) { this->handleTxError(error); }); + conn->doRPCWithDefaultErrorHandling(payload, cb); } void RPC::sendZTransaction(json params, const std::function& cb) { @@ -257,76 +199,9 @@ void RPC::sendZTransaction(json params, const std::function& cb) { {"params", params} }; - doSendRPC(payload, cb); + conn->doRPCWithDefaultErrorHandling(payload, cb); } -void RPC::handleConnectionError(const QString& error) { - if (error.isNull()) return; - - QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical); - main->statusIcon->setPixmap(icon.pixmap(16, 16)); - main->statusLabel->setText("No Connection"); - - if (firstTime) { - this->firstTime = false; - - QMessageBox msg(main); - msg.setIcon(QMessageBox::Icon::Critical); - msg.setWindowTitle("Connection Error"); - - QString explanation; - if (error.contains("authentication", Qt::CaseInsensitive)) { - explanation = QString() - % "\n\nThis is most likely because of misconfigured rpcuser/rpcpassword. " - % "zcashd needs the following options set in ~/.zcash/zcash.conf\n\n" - % "rpcuser=\n" - % "rpcpassword=\n" - % "\nIf you're connecting to a remote note, you can change the username/password in the " - % "File->Settings menu."; - } else if (error.contains("connection refused", Qt::CaseInsensitive)) { - auto confLocation = Settings::getInstance()->getZcashdConfLocation(); - if (confLocation.isEmpty()) { - explanation = QString() - % "\n\nA zcash.conf was not found on this machine. If you are connecting to a remote/non-standard node " - % "please set the host/port and user/password in the File->Settings menu."; - } - else { - explanation = QString() - % "\n\nA zcash.conf was found at\n" % confLocation - % "\nbut we can't connect to zcashd. Is rpcuser= and rpcpassword= set in the zcash.conf file?"; - } - } else if (error.contains("internal server error", Qt::CaseInsensitive) || - error.contains("rewinding", Qt::CaseInsensitive) || - error.contains("loading", Qt::CaseInsensitive)) { - explanation = QString() - % "\n\nIf you just started zcashd, then it's still loading and you might have to wait a while. If zcashd is ready, then you've run into " - % "something unexpected, and might need to file a bug report here: https://github.com/adityapk00/zec-qt-wallet/issues"; - } else { - explanation = QString() - % "\n\nThis is most likely an internal error. Something unexpected happened. " - % "You might need to file a bug report here: https://github.com/adityapk00/zec-qt-wallet/issues"; - } - - msg.setText("There was a network connection error. The error was: \n\n" - + error + explanation); - - msg.exec(); - return; - } -} - -void RPC::handleTxError(const QString& error) { - if (error.isNull()) return; - - QMessageBox msg(main); - msg.setIcon(QMessageBox::Icon::Critical); - msg.setWindowTitle("Transaction Error"); - - msg.setText("There was an error sending the transaction. The error was: \n\n" - + error); - - msg.exec(); -} // Build the RPC JSON Parameters for this tx (with the dev fee included if applicable) @@ -377,7 +252,7 @@ void RPC::refreshReceivedZTrans(QList zaddrs) { // and each z-Addr can have multiple received txs. // 1. For each z-Addr, get list of received txs - getBatchRPC(zaddrs, + conn->doBatchRPC(zaddrs, [=] (QString zaddr) { json payload = { {"jsonrpc", "1.0"}, @@ -414,7 +289,7 @@ void RPC::refreshReceivedZTrans(QList zaddrs) { } // 2. For all txids, go and get the details of that txid. - getBatchRPC(txids.toList(), + conn->doBatchRPC(txids.toList(), [=] (QString txid) { json payload = { {"jsonrpc", "1.0"}, @@ -488,7 +363,7 @@ void RPC::getInfoThenRefresh(bool force) { {"method", "getinfo"} }; - doRPC(payload, [=] (const json& reply) { + conn->doRPCIgnoreError(payload, [=] (const json& reply) { // Testnet? if (!reply["testnet"].is_null()) { Settings::getInstance()->setTestnet(reply["testnet"].get()); @@ -517,7 +392,7 @@ void RPC::getInfoThenRefresh(bool force) { {"method", "getblockchaininfo"} }; - doRPC(payload, [=](const json& reply) { + conn->doRPCIgnoreError(payload, [=](const json& reply) { auto progress = reply["verificationprogress"].get(); bool isSyncing = progress < 0.999; // 99.9% int blockNumber = reply["blocks"].get(); @@ -693,7 +568,7 @@ void RPC::refreshSentZTrans() { } // Look up all the txids to get the confirmation count for them. - getBatchRPC(txids, + conn->doBatchRPC(txids, [=] (QString txid) { json payload = { {"jsonrpc", "1.0"}, @@ -741,7 +616,7 @@ void RPC::watchTxStatus() { {"method", "z_getoperationstatus"}, }; - doRPC(payload, [=] (const json& reply) { + conn->doRPCWithDefaultErrorHandling(payload, [=] (const json& reply) { // There's an array for each item in the status for (auto& it : reply.get()) { // If we were watching this Tx and it's status became "success", then we'll show a status bar alert diff --git a/src/rpc.h b/src/rpc.h index a1f94c4..9092ce6 100644 --- a/src/rpc.h +++ b/src/rpc.h @@ -56,64 +56,12 @@ public: void importZPrivKey(QString addr, bool rescan, const std::function& cb); void importTPrivKey(QString addr, bool rescan, const std::function& cb); - Turnstile* getTurnstile() { return turnstile; } - - // Batch method. Note: Because of the template, it has to be in the header file. - template - void getBatchRPC(const QList& payloads, - std::function payloadGenerator, - std::function*)> cb) { - auto responses = new QMap(); // zAddr -> list of responses for each call. - int totalSize = payloads.size(); - - for (auto item: payloads) { - json payload = payloadGenerator(item); - - QNetworkReply *reply = conn->restclient->post(*conn->request, QByteArray::fromStdString(payload.dump())); - - QObject::connect(reply, &QNetworkReply::finished, [=] { - reply->deleteLater(); - - auto all = reply->readAll(); - auto parsed = json::parse(all.toStdString(), nullptr, false); - - if (reply->error() != QNetworkReply::NoError) { - qDebug() << QString::fromStdString(parsed.dump()); - qDebug() << reply->errorString(); - - (*responses)[item] = json::object(); // Empty object - } else { - if (parsed.is_discarded()) { - (*responses)[item] = json::object(); // Empty object - } else { - (*responses)[item] = parsed["result"]; - } - } - }); - } - - auto waitTimer = new QTimer(main); - QObject::connect(waitTimer, &QTimer::timeout, [=]() { - if (responses->size() == totalSize) { - waitTimer->stop(); - - cb(responses); - - waitTimer->deleteLater(); - } - }); - waitTimer->start(100); - } - - + Turnstile* getTurnstile() { return turnstile; } + Connection* getConnection() { return conn; } private: void noConnection(); - void doRPC (const json& payload, const std::function& cb); - void doSendRPC(const json& payload, const std::function& cb); - void doSendRPC(const json& payload, const std::function& cb, const std::function& err); - void refreshBalances(); void refreshTransactions(); diff --git a/src/turnstile.cpp b/src/turnstile.cpp index 2c36798..2cd14ed 100644 --- a/src/turnstile.cpp +++ b/src/turnstile.cpp @@ -91,7 +91,7 @@ void Turnstile::planMigration(QString zaddr, QString destAddr, int numsplits, in auto splits = splitAmount(bal, numsplits); // Then, generate an intermediate t-Address for each part using getBatchRPC - rpc->getBatchRPC(splits, + rpc->getConnection()->doBatchRPC(splits, [=] (double /*unused*/) { json payload = { {"jsonrpc", "1.0"},