From 08bf0601864eb23668c213d7f12357811a5340c0 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Thu, 1 Nov 2018 11:53:35 -0700 Subject: [PATCH 1/4] Refactor RPC --- src/connection.cpp | 243 +++++++++++++++++++++++++++++++++++++++++++++ src/connection.h | 64 ++++++++++++ src/connection.ui | 29 ++++-- src/main.cpp | 3 +- src/mainwindow.cpp | 26 +++-- src/mainwindow.h | 2 +- src/precompiled.h | 4 +- src/rpc.cpp | 30 ++---- src/rpc.h | 10 +- zec-qt-wallet.pro | 6 +- 10 files changed, 364 insertions(+), 53 deletions(-) create mode 100644 src/connection.cpp create mode 100644 src/connection.h diff --git a/src/connection.cpp b/src/connection.cpp new file mode 100644 index 0000000..089a384 --- /dev/null +++ b/src/connection.cpp @@ -0,0 +1,243 @@ +#include "connection.h" +#include "mainwindow.h" +#include "settings.h" +#include "ui_connection.h" +#include "rpc.h" + +#include "precompiled.h" + +using json = nlohmann::json; + +class LoadingDialog : public QDialog { + //Q_OBJECT +public: + LoadingDialog(QWidget* parent); + ~LoadingDialog(); +public slots: + void reject(); +}; + +LoadingDialog::LoadingDialog(QWidget* parent) : QDialog(parent) {} +LoadingDialog::~LoadingDialog() {} +void LoadingDialog::reject() { + //event->ignore(); +} + +ConnectionLoader::ConnectionLoader(MainWindow* main) { + this->main = main; + + d = new LoadingDialog(main); + connD = new Ui_ConnectionDialog(); + connD->setupUi(d); + + // Center on screen + QRect screenGeometry = QApplication::desktop()->screenGeometry(d); + int x = (screenGeometry.width() - d->width()) / 2; + int y = (screenGeometry.height() - d->height()) / 2; + d->move(x, y); + connD->buttonBox->setEnabled(false); + d->show(); +} + +ConnectionLoader::~ConnectionLoader() { + delete d; + delete connD; +} + +void ConnectionLoader::getConnection(std::function cb) { + + // Priority 1: Try to connect to detect zcash.conf and connect to it. + bool isZcashConfPresent = false; + auto conn = autoDetectZcashConf(); + + // If not autodetected, go and read the UI Settings + if (conn.get() != nullptr) { + isZcashConfPresent = true; + } else { + conn = loadFromSettings(); + + if (conn.get() == nullptr) { + // Nothing configured, show an error + auto explanation = QString() + % "A zcash.conf was not found on this machine.\n\n" + % "If you are connecting to a remote/non-standard node " + % "please set the host/port and user/password in the File->Settings menu."; + + QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical); + connD->icon->setPixmap(icon.pixmap(64, 64)); + connD->status->setText(explanation); + connD->progressBar->setValue(0); + + connD->buttonBox->setEnabled(true); + cb(nullptr); + } + } + + QNetworkAccessManager* client = new QNetworkAccessManager(main); + + QUrl myurl; + myurl.setScheme("http"); + myurl.setHost(Settings::getInstance()->getHost()); + myurl.setPort(Settings::getInstance()->getPort().toInt()); + + QNetworkRequest* request = new QNetworkRequest(); + request->setUrl(myurl); + request->setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); + + QString headerData = "Basic " + Settings::getInstance()->getUsernamePassword().toLocal8Bit().toBase64(); + request->setRawHeader("Authorization", headerData.toLocal8Bit()); + + auto connection = new Connection(client, request); + json payload = { + {"jsonrpc", "1.0"}, + {"id", "someid"}, + {"method", "getinfo"} + }; + connection->doRPC(payload, + [=] (auto result) { + // Success + d->close(); + cb(new RPC(connection, main)); + }, + [=] (auto err, auto res) { + // Failed + auto explanation = QString() + % (isZcashConfPresent ? "A zcash.conf file was found, but a" : "A") + % " connection to zcashd could not be established.\n\n" + % "If you are connecting to a remote/non-standard node " + % "please set the host/port and user/password in the File->Settings menu."; + + QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical); + connD->icon->setPixmap(icon.pixmap(64, 64)); + connD->status->setText(explanation); + connD->progressBar->setValue(0); + + connD->buttonBox->setEnabled(true); + } + ); +} + + +/** + * Try to automatically detect a zcash.conf file in the correct location and load parameters + */ +std::shared_ptr ConnectionLoader::autoDetectZcashConf() { + +#ifdef Q_OS_LINUX + auto confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, ".zcash/zcash.conf"); +#elif defined(Q_OS_DARWIN) + auto confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, "/Library/Application Support/Zcash/zcash.conf"); +#else + auto confLocation = QStandardPaths::locate(QStandardPaths::AppDataLocation, "../../Zcash/zcash.conf"); +#endif + + confLocation = QDir::cleanPath(confLocation); + + if (confLocation.isNull()) { + // No zcash file, just return with nothing + return nullptr; + } + + QFile file(confLocation); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << file.errorString(); + return nullptr; + } + + QTextStream in(&file); + + auto zcashconf = new ConnectionConfig(); + zcashconf->host = "127.0.0.1"; + zcashconf->connType = ConnectionType::DetectedConfExternalZcashD; + + while (!in.atEnd()) { + QString line = in.readLine(); + auto s = line.indexOf("="); + QString name = line.left(s).trimmed().toLower(); + QString value = line.right(line.length() - s - 1).trimmed(); + + if (name == "rpcuser") { + zcashconf->rpcuser = value; + } + if (name == "rpcpassword") { + zcashconf->rpcpassword = value; + } + if (name == "rpcport") { + zcashconf->port = value; + } + if (name == "testnet" && + value == "1" && + zcashconf->port.isEmpty()) { + zcashconf->port = "18232"; + } + } + + // If rpcport is not in the file, and it was not set by the testnet=1 flag, then go to default + if (zcashconf->port.isEmpty()) zcashconf->port = "8232"; + + file.close(); + + return std::make_shared(zcashconf); +} + +/** + * Load connection settings from the UI, which indicates an unknown, external zcashd + */ +std::shared_ptr ConnectionLoader::loadFromSettings() { + // Load from the QT Settings. + QSettings s; + + auto host = s.value("connection/host").toString(); + auto port = s.value("connection/port").toString(); + auto username = s.value("connection/rpcuser").toString(); + auto password = s.value("connection/rpcpassword").toString(); + + if (username.isEmpty() || password.isEmpty()) + return nullptr; + + auto uiConfig = new ConnectionConfig{ host, port, username, password, ConnectionType::UISettingsZCashD }; + + return std::make_shared(uiConfig); +} + + + + + + + + +Connection::Connection(QNetworkAccessManager* c, QNetworkRequest* r) { + this->restclient = c; + this->request = r; +} + +Connection::~Connection() { + delete restclient; + delete request; +} + + + +void Connection::doRPC(const json& payload, const std::function& cb, + const std::function& ne) { + QNetworkReply *reply = restclient->post(*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); + ne(reply->error(), parsed); + + return; + } + + auto parsed = json::parse(reply->readAll(), nullptr, false); + if (parsed.is_discarded()) { + ne(reply->error(), "Unknown error"); + } + + cb(parsed["result"]); + }); +} diff --git a/src/connection.h b/src/connection.h new file mode 100644 index 0000000..3c329fd --- /dev/null +++ b/src/connection.h @@ -0,0 +1,64 @@ +#ifndef CONNECTION_H +#define CONNECTION_H + +#include "ui_connection.h" +#include "precompiled.h" + +using json = nlohmann::json; + +class MainWindow; +class RPC; + +enum ConnectionType { + DetectedConfExternalZcashD = 1, + UISettingsZCashD, + InternalZcashD +}; + +struct ConnectionConfig { + QString host; + QString port; + QString rpcuser; + QString rpcpassword; + + ConnectionType connType; +}; + +class LoadingDialog; + +class ConnectionLoader { + +public: + ConnectionLoader(MainWindow* main); + ~ConnectionLoader(); + + void getConnection(std::function cb); + +private: + std::shared_ptr autoDetectZcashConf(); + std::shared_ptr loadFromSettings(); + + LoadingDialog* d; + Ui_ConnectionDialog* connD; + MainWindow* main; +}; + +/** + * Represents a connection to a zcashd. It may even start a new zcashd if needed. + * This is also a UI class, so it may show a dialog waiting for the connection. +*/ +class Connection { +public: + Connection(QNetworkAccessManager* c, QNetworkRequest* r); + ~Connection(); + + + QNetworkAccessManager* restclient; + QNetworkRequest* request; + + void doRPC(const json& payload, const std::function& cb, + const std::function& ne); + +}; + +#endif \ No newline at end of file diff --git a/src/connection.ui b/src/connection.ui index 7450240..7c83748 100644 --- a/src/connection.ui +++ b/src/connection.ui @@ -11,10 +11,13 @@ - Connecting to zcashd + zec-qt-wallet + + + true - + Qt::Horizontal @@ -24,17 +27,27 @@ - - - - 24 + + + + Connection Status + + + true - + - Connection Status + TextLabel + + + + + + + 24 diff --git a/src/main.cpp b/src/main.cpp index 6f1c5c1..36febd2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,12 +19,11 @@ int main(int argc, char *argv[]) #endif std::srand(std::time(nullptr)); + Settings::init(); QCoreApplication::setOrganizationName("zec-qt-wallet-org"); QCoreApplication::setApplicationName("zec-qt-wallet"); - Settings::init(); - MainWindow w; w.setWindowTitle("zec-qt-wallet v" + QString(APP_VERSION)); w.show(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d5bd7e8..c5ac6c2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -11,6 +11,7 @@ #include "utils.h" #include "turnstile.h" #include "senttxstore.h" +#include "connection.h" #include "precompiled.h" @@ -62,13 +63,17 @@ MainWindow::MainWindow(QWidget *parent) : setupBalancesTab(); setupTurnstileDialog(); - rpc = new RPC(new QNetworkAccessManager(this), this); - rpc->refreshZECPrice(); - - rpc->refresh(true); // Force refresh first time - restoreSavedStates(); + + new ConnectionLoader(this).getConnection([=] (RPC* rpc) { + if (rpc == nullptr) + return; + this->rpc = rpc; + this->rpc->refreshZECPrice(); + this->rpc->refresh(true); // Force refresh first time + }); } + void MainWindow::restoreSavedStates() { QSettings s; @@ -385,11 +390,14 @@ void MainWindow::setupSettingsModal() { settings.rpcuser->text(), settings.rpcpassword->text()); - this->rpc->reloadConnectionInfo(); + auto me = this; + ConnectionLoader(this).getConnection([&me] (auto newrpc) { + delete me->rpc; + me->rpc = newrpc; + // Then refresh everything. + me->rpc->refresh(true); + }); } - - // Then refresh everything. - this->rpc->refresh(true); }; }); diff --git a/src/mainwindow.h b/src/mainwindow.h index 614cbf6..b7c50bd 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -86,7 +86,7 @@ private: void restoreSavedStates(); - RPC* rpc; + RPC* rpc = nullptr; QMovie* loadingMovie; }; diff --git a/src/precompiled.h b/src/precompiled.h index 8c6b605..9c045fd 100644 --- a/src/precompiled.h +++ b/src/precompiled.h @@ -7,7 +7,6 @@ #include #include -#include #include #include #include @@ -23,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +44,8 @@ #include #include #include +#include +#include #include "3rdparty/json/json.hpp" #include "3rdparty/qrcode/QrCode.hpp" diff --git a/src/rpc.cpp b/src/rpc.cpp index 87d0f89..77a2fd2 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -6,8 +6,8 @@ using json = nlohmann::json; -RPC::RPC(QNetworkAccessManager* client, MainWindow* main) { - this->restclient = client; +RPC::RPC(Connection* conn, MainWindow* main) { + this->conn = conn; this->main = main; this->ui = main->ui; @@ -26,8 +26,6 @@ RPC::RPC(QNetworkAccessManager* client, MainWindow* main) { main->ui->transactionsTable->setColumnWidth(2, 200); main->ui->transactionsTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); - reloadConnectionInfo(); - // Set up timer to refresh Price priceTimer = new QTimer(main); QObject::connect(priceTimer, &QTimer::timeout, [=]() { @@ -49,7 +47,6 @@ RPC::RPC(QNetworkAccessManager* client, MainWindow* main) { }); // Start at every 10s. When an operation is pending, this will change to every second txTimer->start(Utils::updateSpeed); - } RPC::~RPC() { @@ -64,27 +61,12 @@ RPC::~RPC() { delete allBalances; delete zaddresses; - delete restclient; + delete conn; } -void RPC::reloadConnectionInfo() { - // Reset for any errors caused. - firstTime = true; - - QUrl myurl; - myurl.setScheme("http"); //https also applicable - myurl.setHost(Settings::getInstance()->getHost()); - myurl.setPort(Settings::getInstance()->getPort().toInt()); - - request.setUrl(myurl); - request.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); - - QString headerData = "Basic " + Settings::getInstance()->getUsernamePassword().toLocal8Bit().toBase64(); - request.setRawHeader("Authorization", headerData.toLocal8Bit()); -} void RPC::doRPC(const json& payload, const std::function& cb) { - QNetworkReply *reply = restclient->post(request, QByteArray::fromStdString(payload.dump())); + QNetworkReply *reply = conn->restclient->post(*conn->request, QByteArray::fromStdString(payload.dump())); QObject::connect(reply, &QNetworkReply::finished, [=] { reply->deleteLater(); @@ -230,7 +212,7 @@ void RPC::getTransactions(const std::function& cb) { } void RPC::doSendRPC(const json& payload, const std::function& cb, const std::function& err) { - QNetworkReply *reply = restclient->post(request, QByteArray::fromStdString(payload.dump())); + QNetworkReply *reply = conn->restclient->post(*conn->request, QByteArray::fromStdString(payload.dump())); QObject::connect(reply, &QNetworkReply::finished, [=] { reply->deleteLater(); @@ -790,7 +772,7 @@ void RPC::refreshZECPrice() { QNetworkRequest req; req.setUrl(cmcURL); - QNetworkReply *reply = restclient->get(req); + QNetworkReply *reply = conn->restclient->get(req); QObject::connect(reply, &QNetworkReply::finished, [=] { reply->deleteLater(); diff --git a/src/rpc.h b/src/rpc.h index c789bcd..887451e 100644 --- a/src/rpc.h +++ b/src/rpc.h @@ -8,6 +8,7 @@ #include "txtablemodel.h" #include "ui_mainwindow.h" #include "mainwindow.h" +#include "connection.h" using json = nlohmann::json; @@ -27,7 +28,7 @@ struct TransactionItem { class RPC { public: - RPC(QNetworkAccessManager* restclient, MainWindow* main); + RPC(Connection* conn, MainWindow* main); ~RPC(); void refresh(bool force = false); @@ -45,8 +46,6 @@ public: const QList* getUTXOs() { return utxos; } const QMap* getAllBalances() { return allBalances; } - void reloadConnectionInfo(); - void newZaddr(bool sapling, const std::function& cb); void newTaddr(const std::function& cb); @@ -69,7 +68,7 @@ public: for (auto item: payloads) { json payload = payloadGenerator(item); - QNetworkReply *reply = restclient->post(request, QByteArray::fromStdString(payload.dump())); + QNetworkReply *reply = conn->restclient->post(*conn->request, QByteArray::fromStdString(payload.dump())); QObject::connect(reply, &QNetworkReply::finished, [=] { reply->deleteLater(); @@ -133,8 +132,7 @@ private: void handleConnectionError (const QString& error); void handleTxError (const QString& error); - QNetworkAccessManager* restclient; - QNetworkRequest request; + Connection* conn = nullptr; QList* utxos = nullptr; QMap* allBalances = nullptr; diff --git a/zec-qt-wallet.pro b/zec-qt-wallet.pro index efd6b04..51f74b7 100644 --- a/zec-qt-wallet.pro +++ b/zec-qt-wallet.pro @@ -53,7 +53,8 @@ SOURCES += \ src/txtablemodel.cpp \ src/turnstile.cpp \ src/utils.cpp \ - src/qrcodelabel.cpp + src/qrcodelabel.cpp \ + src/connection.cpp HEADERS += \ src/mainwindow.h \ @@ -70,7 +71,8 @@ HEADERS += \ src/senttxstore.h \ src/turnstile.h \ src/utils.h \ - src/qrcodelabel.h + src/qrcodelabel.h \ + src/connection.h FORMS += \ src/mainwindow.ui \ From 1b4ced8c8252956014e7a0f8517223599d9d9842 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Thu, 1 Nov 2018 14:10:26 -0700 Subject: [PATCH 2/4] Manage connection --- src/connection.cpp | 133 ++++++++++++++++++++++++++++++--------------- src/connection.h | 29 +++++++--- src/connection.ui | 47 +++++++++++++--- src/mainwindow.cpp | 28 +++------- src/rpc.cpp | 50 ++++++++++++++--- src/rpc.h | 7 ++- src/settings.cpp | 106 ++---------------------------------- src/settings.h | 29 +++------- src/settings.ui | 6 +- 9 files changed, 221 insertions(+), 214 deletions(-) diff --git a/src/connection.cpp b/src/connection.cpp index 089a384..aefcb30 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -7,7 +7,7 @@ #include "precompiled.h" using json = nlohmann::json; - +/* class LoadingDialog : public QDialog { //Q_OBJECT public: @@ -22,11 +22,12 @@ LoadingDialog::~LoadingDialog() {} void LoadingDialog::reject() { //event->ignore(); } - -ConnectionLoader::ConnectionLoader(MainWindow* main) { +*/ +ConnectionLoader::ConnectionLoader(MainWindow* main, RPC* rpc) { this->main = main; + this->rpc = rpc; - d = new LoadingDialog(main); + d = new QDialog(main); connD = new Ui_ConnectionDialog(); connD->setupUi(d); @@ -44,84 +45,124 @@ ConnectionLoader::~ConnectionLoader() { delete connD; } -void ConnectionLoader::getConnection(std::function cb) { - +void ConnectionLoader::loadConnection() { // Priority 1: Try to connect to detect zcash.conf and connect to it. bool isZcashConfPresent = false; - auto conn = autoDetectZcashConf(); + auto config = autoDetectZcashConf(); // If not autodetected, go and read the UI Settings - if (conn.get() != nullptr) { + if (config.get() != nullptr) { isZcashConfPresent = true; } else { - conn = loadFromSettings(); + config = loadFromSettings(); - if (conn.get() == nullptr) { + if (config.get() == nullptr) { // Nothing configured, show an error auto explanation = QString() % "A zcash.conf was not found on this machine.\n\n" % "If you are connecting to a remote/non-standard node " % "please set the host/port and user/password in the File->Settings menu."; - QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical); - connD->icon->setPixmap(icon.pixmap(64, 64)); - connD->status->setText(explanation); - connD->progressBar->setValue(0); + showError(explanation); + rpc->setConnection(nullptr); - connD->buttonBox->setEnabled(true); - cb(nullptr); + return; } } + auto connection = makeConnection(config); + refreshZcashdState(connection); +} + +Connection* ConnectionLoader::makeConnection(std::shared_ptr config) { QNetworkAccessManager* client = new QNetworkAccessManager(main); QUrl myurl; myurl.setScheme("http"); - myurl.setHost(Settings::getInstance()->getHost()); - myurl.setPort(Settings::getInstance()->getPort().toInt()); + myurl.setHost(config.get()->host); + myurl.setPort(config.get()->port.toInt()); QNetworkRequest* request = new QNetworkRequest(); request->setUrl(myurl); request->setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); - QString headerData = "Basic " + Settings::getInstance()->getUsernamePassword().toLocal8Bit().toBase64(); - request->setRawHeader("Authorization", headerData.toLocal8Bit()); + QString userpass = config.get()->rpcuser % ":" % config.get()->rpcpassword; + QString headerData = "Basic " + userpass.toLocal8Bit().toBase64(); + request->setRawHeader("Authorization", headerData.toLocal8Bit()); + + return new Connection(client, request, config); +} - auto connection = new Connection(client, request); +void ConnectionLoader::refreshZcashdState(Connection* connection) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "getinfo"} }; connection->doRPC(payload, - [=] (auto result) { + [=] (auto) { // Success - d->close(); - cb(new RPC(connection, main)); + d->hide(); + rpc->setConnection(connection); }, [=] (auto err, auto res) { - // Failed - auto explanation = QString() - % (isZcashConfPresent ? "A zcash.conf file was found, but a" : "A") - % " connection to zcashd could not be established.\n\n" - % "If you are connecting to a remote/non-standard node " - % "please set the host/port and user/password in the File->Settings menu."; - - QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical); - connD->icon->setPixmap(icon.pixmap(64, 64)); - connD->status->setText(explanation); - connD->progressBar->setValue(0); - - connD->buttonBox->setEnabled(true); + // Failed, see what it is. + qDebug() << err << ":" << QString::fromStdString(res.dump()); + + if (err == QNetworkReply::NetworkError::ConnectionRefusedError) { + auto isZcashConfFound = connection->config.get()->usingZcashConf; + auto explanation = QString() + % (isZcashConfFound ? "A zcash.conf file was found, but a" : "A") + % " connection to zcashd could not be established.\n\n" + % "If you are connecting to a remote/non-standard node " + % "please set the host/port and user/password in the File->Settings menu"; + + showError(explanation); + } else if (err == QNetworkReply::NetworkError::AuthenticationRequiredError) { + auto explanation = QString() + % "Authentication failed. The username / password you specified was " + % "not accepted by zcashd. Try changing it in the File->Settings menu"; + + showError(explanation); + } else if (err == QNetworkReply::NetworkError::InternalServerError && !res.is_discarded()) { + // The server is loading, so just poll until it succeeds + QString status = QString::fromStdString(res["error"]["message"]); + + QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation); + connD->icon->setPixmap(icon.pixmap(128, 128)); + connD->status->setText(status); + + // Refresh after one second + QTimer::singleShot(1000, [=]() { this->refreshZcashdState(connection); }); + } } ); } +void ConnectionLoader::showError(QString explanation) { + QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical); + connD->icon->setPixmap(icon.pixmap(128, 128)); + connD->status->setText(explanation); + connD->title->setText(""); + + connD->buttonBox->setEnabled(true); +} + +/* +int ConnectionLoader::getProgressFromStatus(QString status) { + if (status.startsWith("Loading block")) return 20; + if (status.startsWith("Verifying")) return 40; + if (status.startsWith("Loading Wallet")) return 60; + if (status.startsWith("Activating")) return 80; + if (status.startsWith("Rescanning")) return 90; + return 0; +} +*/ /** * Try to automatically detect a zcash.conf file in the correct location and load parameters */ -std::shared_ptr ConnectionLoader::autoDetectZcashConf() { +std::shared_ptr ConnectionLoader::autoDetectZcashConf() { #ifdef Q_OS_LINUX auto confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, ".zcash/zcash.conf"); @@ -149,6 +190,9 @@ std::shared_ptr ConnectionLoader::autoDetectZcashConf() { auto zcashconf = new ConnectionConfig(); zcashconf->host = "127.0.0.1"; zcashconf->connType = ConnectionType::DetectedConfExternalZcashD; + zcashconf->usingZcashConf = true; + + Settings::getInstance()->setUsingZcashConf(confLocation); while (!in.atEnd()) { QString line = in.readLine(); @@ -177,13 +221,13 @@ std::shared_ptr ConnectionLoader::autoDetectZcashConf() { file.close(); - return std::make_shared(zcashconf); + return std::shared_ptr(zcashconf); } /** * Load connection settings from the UI, which indicates an unknown, external zcashd */ -std::shared_ptr ConnectionLoader::loadFromSettings() { +std::shared_ptr ConnectionLoader::loadFromSettings() { // Load from the QT Settings. QSettings s; @@ -195,9 +239,9 @@ std::shared_ptr ConnectionLoader::loadFromSettings() { if (username.isEmpty() || password.isEmpty()) return nullptr; - auto uiConfig = new ConnectionConfig{ host, port, username, password, ConnectionType::UISettingsZCashD }; + auto uiConfig = new ConnectionConfig{ host, port, username, password, false, ConnectionType::UISettingsZCashD }; - return std::make_shared(uiConfig); + return std::shared_ptr(uiConfig); } @@ -207,9 +251,10 @@ std::shared_ptr ConnectionLoader::loadFromSettings() { -Connection::Connection(QNetworkAccessManager* c, QNetworkRequest* r) { +Connection::Connection(QNetworkAccessManager* c, QNetworkRequest* r, std::shared_ptr conf) { this->restclient = c; - this->request = r; + this->request = r; + this->config = conf; } Connection::~Connection() { diff --git a/src/connection.h b/src/connection.h index 3c329fd..7664e1e 100644 --- a/src/connection.h +++ b/src/connection.h @@ -20,27 +20,37 @@ struct ConnectionConfig { QString port; QString rpcuser; QString rpcpassword; + bool usingZcashConf; ConnectionType connType; }; -class LoadingDialog; +class Connection; class ConnectionLoader { public: - ConnectionLoader(MainWindow* main); + ConnectionLoader(MainWindow* main, RPC* rpc); ~ConnectionLoader(); - void getConnection(std::function cb); + void loadConnection(); private: - std::shared_ptr autoDetectZcashConf(); - std::shared_ptr loadFromSettings(); + std::shared_ptr autoDetectZcashConf(); + std::shared_ptr loadFromSettings(); - LoadingDialog* d; + Connection* makeConnection(std::shared_ptr config); + + void refreshZcashdState(Connection* connection); + int getProgressFromStatus(QString status); + + void showError(QString explanation); + + QDialog* d; Ui_ConnectionDialog* connD; + MainWindow* main; + RPC* rpc; }; /** @@ -49,12 +59,13 @@ private: */ class Connection { public: - Connection(QNetworkAccessManager* c, QNetworkRequest* r); + Connection(QNetworkAccessManager* c, QNetworkRequest* r, std::shared_ptr conf); ~Connection(); - QNetworkAccessManager* restclient; - QNetworkRequest* request; + QNetworkAccessManager* restclient; + QNetworkRequest* request; + std::shared_ptr config; void doRPC(const json& payload, const std::function& cb, const std::function& ne); diff --git a/src/connection.ui b/src/connection.ui index 7c83748..d213b13 100644 --- a/src/connection.ui +++ b/src/connection.ui @@ -7,7 +7,7 @@ 0 0 513 - 286 + 201 @@ -17,40 +17,69 @@ true - + + + + Your zcashd is still loading. Please wait... + + + + Qt::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::Close - + Connection Status + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + true - + TextLabel + + Qt::AlignCenter + + + 20 + - - - - 24 + + + + Qt::Horizontal + + + + Qt::Vertical + + + + 20 + 40 + + + + diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c5ac6c2..d266806 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -63,15 +63,9 @@ MainWindow::MainWindow(QWidget *parent) : setupBalancesTab(); setupTurnstileDialog(); - restoreSavedStates(); + rpc = new RPC(this); - new ConnectionLoader(this).getConnection([=] (RPC* rpc) { - if (rpc == nullptr) - return; - this->rpc = rpc; - this->rpc->refreshZECPrice(); - this->rpc->refresh(true); // Force refresh first time - }); + restoreSavedStates(); } @@ -356,10 +350,11 @@ void MainWindow::setupSettingsModal() { settings.port->setValidator(&validator); // Load current values into the dialog - settings.hostname->setText(Settings::getInstance()->getHost()); - settings.port->setText(Settings::getInstance()->getPort()); - settings.rpcuser->setText(Settings::getInstance()->getUsernamePassword().split(":")[0]); - settings.rpcpassword->setText(Settings::getInstance()->getUsernamePassword().split(":")[1]); + auto conf = Settings::getInstance()->getSettings(); + settings.hostname->setText(conf.host); + settings.port->setText(conf.port); + settings.rpcuser->setText(conf.rpcuser); + settings.rpcpassword->setText(conf.rpcpassword); // If values are coming from zcash.conf, then disable all the fields auto zcashConfLocation = Settings::getInstance()->getZcashdConfLocation(); @@ -390,13 +385,8 @@ void MainWindow::setupSettingsModal() { settings.rpcuser->text(), settings.rpcpassword->text()); - auto me = this; - ConnectionLoader(this).getConnection([&me] (auto newrpc) { - delete me->rpc; - me->rpc = newrpc; - // Then refresh everything. - me->rpc->refresh(true); - }); + auto cl = new ConnectionLoader(this, rpc); + cl->loadConnection(); } }; }); diff --git a/src/rpc.cpp b/src/rpc.cpp index 77a2fd2..ba05f0e 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -6,8 +6,10 @@ using json = nlohmann::json; -RPC::RPC(Connection* conn, MainWindow* main) { - this->conn = conn; +RPC::RPC(MainWindow* main) { + auto cl = new ConnectionLoader(main, this); + cl->loadConnection(); + this->main = main; this->ui = main->ui; @@ -16,15 +18,11 @@ RPC::RPC(Connection* conn, MainWindow* main) { // Setup balances table model balancesTableModel = new BalancesTableModel(main->ui->balancesTable); main->ui->balancesTable->setModel(balancesTableModel); - main->ui->balancesTable->setColumnWidth(0, 300); // Setup transactions table model transactionsTableModel = new TxTableModel(ui->transactionsTable); main->ui->transactionsTable->setModel(transactionsTableModel); main->ui->transactionsTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); - main->ui->transactionsTable->setColumnWidth(1, 350); - main->ui->transactionsTable->setColumnWidth(2, 200); - main->ui->transactionsTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); // Set up timer to refresh Price priceTimer = new QTimer(main); @@ -64,6 +62,14 @@ RPC::~RPC() { delete conn; } +void RPC::setConnection(Connection* c) { + if (c == nullptr) return; + + delete conn; + this->conn = c; + + refresh(); +} void RPC::doRPC(const json& payload, const std::function& cb) { QNetworkReply *reply = conn->restclient->post(*conn->request, QByteArray::fromStdString(payload.dump())); @@ -349,8 +355,14 @@ void RPC::fillTxJsonParams(json& params, Tx tx) { } +void RPC::noConnection() { + ui->statusBar->showMessage("No Connection to zcashd"); +} + // 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()) { @@ -459,11 +471,17 @@ void RPC::refreshReceivedZTrans(QList zaddrs) { /// This will refresh all the balance data from zcashd void RPC::refresh(bool force) { + if (conn == nullptr) + return noConnection(); + getInfoThenRefresh(force); } void RPC::getInfoThenRefresh(bool force) { + if (conn == nullptr) + return noConnection(); + json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, @@ -526,6 +544,9 @@ void RPC::getInfoThenRefresh(bool force) { } void RPC::refreshAddresses() { + if (conn == nullptr) + return noConnection(); + delete zaddresses; zaddresses = new QList(); @@ -591,6 +612,9 @@ bool RPC::processUnspent(const json& reply) { }; void RPC::refreshBalances() { + if (conn == nullptr) + return noConnection(); + // 1. Get the Balances getBalance([=] (json reply) { auto balT = QString::fromStdString(reply["transparent"]).toDouble(); @@ -626,6 +650,9 @@ void RPC::refreshBalances() { } void RPC::refreshTransactions() { + if (conn == nullptr) + return noConnection(); + getTransactions([=] (json reply) { QList txdata; @@ -654,6 +681,9 @@ void RPC::refreshTransactions() { // Read sent Z transactions from the file. void RPC::refreshSentZTrans() { + if (conn == nullptr) + return noConnection(); + auto sentZTxs = SentTxStore::readSentTxFile(); QList txids; @@ -694,13 +724,16 @@ void RPC::refreshSentZTrans() { ); } -void RPC::addNewTxToWatch(Tx tx, const QString& newOpid) { +void RPC::addNewTxToWatch(Tx tx, const QString& newOpid) { watchingOps.insert(newOpid, tx); watchTxStatus(); } void RPC::watchTxStatus() { + if (conn == nullptr) + return noConnection(); + // Make an RPC to load pending operation statues json payload = { {"jsonrpc", "1.0"}, @@ -767,6 +800,9 @@ void RPC::watchTxStatus() { // Get the ZEC->USD price from coinmarketcap using their API void RPC::refreshZECPrice() { + if (conn == nullptr) + return noConnection(); + QUrl cmcURL("https://api.coinmarketcap.com/v1/ticker/"); QNetworkRequest req; diff --git a/src/rpc.h b/src/rpc.h index 887451e..a1f94c4 100644 --- a/src/rpc.h +++ b/src/rpc.h @@ -28,9 +28,11 @@ struct TransactionItem { class RPC { public: - RPC(Connection* conn, MainWindow* main); + RPC(MainWindow* main); ~RPC(); + void setConnection(Connection* c); + void refresh(bool force = false); void refreshAddresses(); @@ -49,7 +51,6 @@ public: void newZaddr(bool sapling, const std::function& cb); void newTaddr(const std::function& cb); - void getZPrivKey(QString addr, const std::function& cb); void getTPrivKey(QString addr, const std::function& cb); void importZPrivKey(QString addr, bool rescan, const std::function& cb); @@ -107,6 +108,8 @@ public: 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); diff --git a/src/settings.cpp b/src/settings.cpp index cb60b74..fa7fa9a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -6,9 +6,6 @@ Settings* Settings::instance = nullptr; Settings::~Settings() { - delete defaults; - delete zcashconf; - delete uisettings; } bool Settings::getSaveZtxs() { @@ -24,27 +21,6 @@ Settings* Settings::init() { if (instance == nullptr) instance = new Settings(); - // There are 3 possible configurations - // 1. The defaults - instance->defaults = new Config{ "127.0.0.1", "8232", "", "" }; - - // 2. From the UI settings - auto settingsFound = instance->loadFromSettings(); - - // 3. From the zcash.conf file - auto confFound = instance->loadFromFile(); - - // zcash.conf (#3) is first priority if it exists - if (confFound) { - instance->currentConfig = instance->zcashconf; - } - else if (settingsFound) { - instance->currentConfig = instance->uisettings; - } - else { - instance->currentConfig = instance->defaults; - } - return instance; } @@ -52,23 +28,7 @@ Settings* Settings::getInstance() { return instance; } - -QString Settings::getHost() { - return currentConfig->host; -} - -QString Settings::getPort() { - return currentConfig->port; -} - - -QString Settings::getUsernamePassword() { - return currentConfig->rpcuser % ":" % currentConfig->rpcpassword; -} - -bool Settings::loadFromSettings() { - delete uisettings; - +Config Settings::getSettings() { // Load from the QT Settings. QSettings s; @@ -77,9 +37,7 @@ bool Settings::loadFromSettings() { auto username = s.value("connection/rpcuser").toString(); auto password = s.value("connection/rpcpassword").toString(); - uisettings = new Config{host, port, username, password}; - - return !username.isEmpty(); + return Config{host, port, username, password}; } void Settings::saveSettings(const QString& host, const QString& port, const QString& username, const QString& password) { @@ -96,63 +54,9 @@ void Settings::saveSettings(const QString& host, const QString& port, const QStr init(); } -bool Settings::loadFromFile() { - delete zcashconf; - -#ifdef Q_OS_LINUX - confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, ".zcash/zcash.conf"); -#elif defined(Q_OS_DARWIN) - confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, "/Library/Application Support/Zcash/zcash.conf"); -#else - confLocation = QStandardPaths::locate(QStandardPaths::AppDataLocation, "../../Zcash/zcash.conf"); -#endif - - confLocation = QDir::cleanPath(confLocation); - - if (confLocation.isNull()) { - // No zcash file, just return with nothing - return false; - } - - QFile file(confLocation); - if (!file.open(QIODevice::ReadOnly)) { - qDebug() << file.errorString(); - return false; - } - - QTextStream in(&file); - - zcashconf = new Config(); - zcashconf->host = defaults->host; - - while (!in.atEnd()) { - QString line = in.readLine(); - auto s = line.indexOf("="); - QString name = line.left(s).trimmed().toLower(); - QString value = line.right(line.length() - s - 1).trimmed(); - - if (name == "rpcuser") { - zcashconf->rpcuser = value; - } - if (name == "rpcpassword") { - zcashconf->rpcpassword = value; - } - if (name == "rpcport") { - zcashconf->port = value; - } - if (name == "testnet" && - value == "1" && - zcashconf->port.isEmpty()) { - zcashconf->port = "18232"; - } - } - - // If rpcport is not in the file, and it was not set by the testnet=1 flag, then go to default - if (zcashconf->port.isEmpty()) zcashconf->port = defaults->port; - - file.close(); - - return true; +void Settings::setUsingZcashConf(QString confLocation) { + if (!confLocation.isEmpty()) + _confLocation = confLocation; } bool Settings::isTestnet() { diff --git a/src/settings.h b/src/settings.h index ccc6358..b771293 100644 --- a/src/settings.h +++ b/src/settings.h @@ -16,14 +16,8 @@ public: static Settings* init(); static Settings* getInstance(); - QString getUsernamePassword(); - QString getHost(); - QString getPort(); - - bool loadFromSettings(); - bool loadFromFile(); - - void saveSettings(const QString& host, const QString& port, const QString& username, const QString& password); + Config getSettings(); + void saveSettings(const QString& host, const QString& port, const QString& username, const QString& password); bool isTestnet(); void setTestnet(bool isTestnet); @@ -43,11 +37,13 @@ public: bool isSaplingActive(); - const QString& getZcashdConfLocation() { return confLocation; } + void setUsingZcashConf(QString confLocation); + const QString& getZcashdConfLocation() { return _confLocation; } void setZECPrice(double p) { zecPrice = p; } double getZECPrice(); + QString getUSDFormat (double bal); QString getZECDisplayFormat (double bal); QString getZECUSDDisplayFormat(double bal); @@ -59,17 +55,10 @@ private: static Settings* instance; - Config* currentConfig; - - Config* defaults = nullptr; - Config* zcashconf = nullptr; - Config* uisettings = nullptr; - - QString confLocation; - - bool _isTestnet = false; - bool _isSyncing = false; - int _blockNumber = 0; + QString _confLocation; + bool _isTestnet = false; + bool _isSyncing = false; + int _blockNumber = 0; double zecPrice = 0.0; }; diff --git a/src/settings.ui b/src/settings.ui index 6c2fe13..19a5050 100644 --- a/src/settings.ui +++ b/src/settings.ui @@ -20,7 +20,7 @@ - 1 + 0 @@ -60,7 +60,7 @@ - 127.0.0.1 + @@ -80,7 +80,7 @@ - 8232 + From 68aeca7f09069d3a1383895ca82800620baa7a97 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Thu, 1 Nov 2018 14:34:44 -0700 Subject: [PATCH 3/4] Move all RPC to connection class --- src/connection.cpp | 43 ++++++++++-- src/connection.h | 60 +++++++++++++++-- src/rpc.cpp | 161 +++++---------------------------------------- src/rpc.h | 56 +--------------- src/turnstile.cpp | 2 +- 5 files changed, 113 insertions(+), 209 deletions(-) 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"}, From c7bda4f87606711b22f8374152c830f84421bfb3 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Thu, 1 Nov 2018 14:44:51 -0700 Subject: [PATCH 4/4] Clear turnstile if migration is done while progress box is opened --- src/mainwindow.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d266806..f1ea439 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -95,7 +95,8 @@ void MainWindow::turnstileProgress() { QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning); progress.msgIcon->setPixmap(icon.pixmap(64, 64)); - auto fnUpdateProgressUI = [=] () { + bool migrationFinished = false; + auto fnUpdateProgressUI = [=, &migrationFinished] () mutable { // Get the plan progress if (rpc->getTurnstile()->isMigrationPresent()) { auto curProgress = rpc->getTurnstile()->getPlanProgress(); @@ -109,6 +110,7 @@ void MainWindow::turnstileProgress() { progress.toAddr->setText(curProgress.to); if (curProgress.step == curProgress.totalSteps) { + migrationFinished = true; auto txt = QString("Turnstile migration finished"); if (curProgress.hasErrors) { txt = txt + ". There were some errors.\n\nYour funds are all in your wallet, so you should be able to finish moving them manually."; @@ -155,7 +157,7 @@ void MainWindow::turnstileProgress() { }); d.exec(); - if (curProgress.step == curProgress.totalSteps) { + if (migrationFinished || curProgress.step == curProgress.totalSteps) { // Finished, so delete the file rpc->getTurnstile()->removeFile(); }