diff --git a/src/connection.cpp b/src/connection.cpp new file mode 100644 index 0000000..dab9cb4 --- /dev/null +++ b/src/connection.cpp @@ -0,0 +1,319 @@ +#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, RPC* rpc) { + this->main = main; + this->rpc = rpc; + + d = new QDialog(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::loadConnection() { + // Priority 1: Try to connect to detect zcash.conf and connect to it. + bool isZcashConfPresent = false; + auto config = autoDetectZcashConf(); + + // If not autodetected, go and read the UI Settings + if (config.get() != nullptr) { + isZcashConfPresent = true; + } else { + config = loadFromSettings(); + + 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."; + + showError(explanation); + rpc->setConnection(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(config.get()->host); + myurl.setPort(config.get()->port.toInt()); + + QNetworkRequest* request = new QNetworkRequest(); + request->setUrl(myurl); + request->setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); + + QString userpass = config.get()->rpcuser % ":" % config.get()->rpcpassword; + QString headerData = "Basic " + userpass.toLocal8Bit().toBase64(); + request->setRawHeader("Authorization", headerData.toLocal8Bit()); + + return new Connection(main, client, request, config); +} + +void ConnectionLoader::refreshZcashdState(Connection* connection) { + json payload = { + {"jsonrpc", "1.0"}, + {"id", "someid"}, + {"method", "getinfo"} + }; + connection->doRPC(payload, + [=] (auto) { + // Success + d->hide(); + rpc->setConnection(connection); + }, + [=] (auto reply, auto res) { + auto err = reply->error(); + // 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() { + +#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; + zcashconf->usingZcashConf = true; + + Settings::getInstance()->setUsingZcashConf(confLocation); + + 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::shared_ptr(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, false, ConnectionType::UISettingsZCashD }; + + return std::shared_ptr(uiConfig); +} + + + + + + + + +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() { + 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, parsed); + + return; + } + + auto parsed = json::parse(reply->readAll(), nullptr, false); + if (parsed.is_discarded()) { + 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 new file mode 100644 index 0000000..48ab48f --- /dev/null +++ b/src/connection.h @@ -0,0 +1,125 @@ +#ifndef CONNECTION_H +#define CONNECTION_H + +#include "mainwindow.h" +#include "ui_connection.h" +#include "precompiled.h" + +using json = nlohmann::json; + +class RPC; + +enum ConnectionType { + DetectedConfExternalZcashD = 1, + UISettingsZCashD, + InternalZcashD +}; + +struct ConnectionConfig { + QString host; + QString port; + QString rpcuser; + QString rpcpassword; + bool usingZcashConf; + + ConnectionType connType; +}; + +class Connection; + +class ConnectionLoader { + +public: + ConnectionLoader(MainWindow* main, RPC* rpc); + ~ConnectionLoader(); + + void loadConnection(); + +private: + std::shared_ptr autoDetectZcashConf(); + std::shared_ptr loadFromSettings(); + + 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; +}; + +/** + * 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(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); + 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/connection.ui b/src/connection.ui index 7450240..d213b13 100644 --- a/src/connection.ui +++ b/src/connection.ui @@ -7,37 +7,79 @@ 0 0 513 - 286 + 201 - Connecting to zcashd + zec-qt-wallet + + + true - + + + + Your zcashd is still loading. Please wait... + + + + Qt::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::Close - - - - 24 + + + + Connection Status + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + true - - + + - Connection Status + TextLabel + + + Qt::AlignCenter + + + 20 + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + 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 26d2175..f1ea439 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,11 @@ MainWindow::MainWindow(QWidget *parent) : setupBalancesTab(); setupTurnstileDialog(); - rpc = new RPC(new QNetworkAccessManager(this), this); - rpc->refreshZECPrice(); - - rpc->refresh(true); // Force refresh first time + rpc = new RPC(this); restoreSavedStates(); } + void MainWindow::restoreSavedStates() { QSettings s; @@ -353,10 +352,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(); @@ -387,11 +387,9 @@ void MainWindow::setupSettingsModal() { settings.rpcuser->text(), settings.rpcpassword->text()); - this->rpc->reloadConnectionInfo(); + auto cl = new ConnectionLoader(this, rpc); + cl->loadConnection(); } - - // 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..5a5e910 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -6,8 +6,10 @@ using json = nlohmann::json; -RPC::RPC(QNetworkAccessManager* client, MainWindow* main) { - this->restclient = client; +RPC::RPC(MainWindow* main) { + auto cl = new ConnectionLoader(main, this); + cl->loadConnection(); + this->main = main; this->ui = main->ui; @@ -16,17 +18,11 @@ RPC::RPC(QNetworkAccessManager* client, 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); - - reloadConnectionInfo(); // Set up timer to refresh Price priceTimer = new QTimer(main); @@ -49,7 +45,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,49 +59,16 @@ 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::setConnection(Connection* c) { + if (c == nullptr) return; -void RPC::doRPC(const json& payload, const std::function& cb) { - QNetworkReply *reply = restclient->post(request, QByteArray::fromStdString(payload.dump())); + delete conn; + this->conn = c; - 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"]); - }); + refresh(); } void RPC::getZAddresses(const std::function& cb) { @@ -116,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) { @@ -127,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) { @@ -138,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) { @@ -149,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) { @@ -159,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) { @@ -170,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) { @@ -181,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) { @@ -192,7 +154,7 @@ void RPC::importZPrivKey(QString addr, bool rescan, const std::functiondoRPCWithDefaultErrorHandling(payload, cb); } @@ -204,7 +166,7 @@ void RPC::importTPrivKey(QString addr, bool rescan, const std::functiondoRPCWithDefaultErrorHandling(payload, cb); } @@ -216,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) { @@ -226,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 = 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); - 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) { @@ -269,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) @@ -367,8 +230,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()) { @@ -383,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"}, @@ -420,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"}, @@ -477,18 +346,24 @@ 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"}, {"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(); @@ -544,6 +419,9 @@ void RPC::getInfoThenRefresh(bool force) { } void RPC::refreshAddresses() { + if (conn == nullptr) + return noConnection(); + delete zaddresses; zaddresses = new QList(); @@ -609,6 +487,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(); @@ -644,6 +525,9 @@ void RPC::refreshBalances() { } void RPC::refreshTransactions() { + if (conn == nullptr) + return noConnection(); + getTransactions([=] (json reply) { QList txdata; @@ -672,6 +556,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; @@ -681,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"}, @@ -712,13 +599,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"}, @@ -726,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 @@ -785,12 +675,15 @@ 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; 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..9092ce6 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,9 +28,11 @@ struct TransactionItem { class RPC { public: - RPC(QNetworkAccessManager* restclient, MainWindow* main); + RPC(MainWindow* main); ~RPC(); + void setConnection(Connection* c); + void refresh(bool force = false); void refreshAddresses(); @@ -45,72 +48,19 @@ 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); - 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); 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 = 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); - } - - + Turnstile* getTurnstile() { return turnstile; } + Connection* getConnection() { return conn; } private: - 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 noConnection(); void refreshBalances(); @@ -133,8 +83,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/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 + 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"}, 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 \