From 306c39bb0963ad41be5ce19f114e97a879773c61 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Tue, 15 Oct 2019 14:01:11 -0700 Subject: [PATCH] Compile fixes --- .gitignore | 4 +- lib/Makefile | 24 + lib/zecwalletlitelib.h | 2 +- src/connection.cpp | 85 +-- src/connection.h | 107 +--- src/controller.cpp | 163 +++-- src/controller.h | 4 +- src/liteinterface.cpp | 545 ++++++++++++++++ src/{zcashdrpc.h => liteinterface.h} | 6 +- src/mainwindow.cpp | 40 +- src/rpc.cpp | 895 --------------------------- src/zcashdrpc.cpp | 545 ---------------- zecwallet-lite.pro | 4 +- 13 files changed, 725 insertions(+), 1699 deletions(-) create mode 100644 lib/Makefile create mode 100644 src/liteinterface.cpp rename src/{zcashdrpc.h => liteinterface.h} (97%) delete mode 100644 src/rpc.cpp delete mode 100644 src/zcashdrpc.cpp diff --git a/.gitignore b/.gitignore index 4ff0d08..478791f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,8 +21,8 @@ zec-qt-wallet.vcxproj* zecwallet.vcxproj* zec-qt-wallet.sln zec-qt-wallet.pro.user -Makefile -Makefile.* +/Makefile +/Makefile.* qrc_application.cpp zec-qt-wallet_plugin_import.cpp zecwallet_plugin_import.cpp diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 0000000..3d91e1c --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,24 @@ +ifeq ($(shell uname),Darwin) + EXT := dylib +else + EXT := a +endif + +all: release + +winrelease: target/x86_64-pc-windows-gnu/release/zecwalletlite.lib + +target/x86_64-pc-windows-gnu/release/zecwalletlite.lib: src/lib.rs Cargo.toml + cargo build --lib --release --target x86_64-pc-windows-gnu + +release: target/release/zecwalletlite.$(EXT) +debug: target/debug/zecwalletlite.$(EXT) + +target/release/zecwalletlite.$(EXT): src/lib.rs Cargo.toml + cargo build --lib --release + +target/debug/zecwalletlite.$(EXT): src/lib.rs Cargo.toml + cargo build --lib + +clean: + rm -rf target \ No newline at end of file diff --git a/lib/zecwalletlitelib.h b/lib/zecwalletlitelib.h index 5c639da..8297964 100644 --- a/lib/zecwalletlitelib.h +++ b/lib/zecwalletlitelib.h @@ -6,7 +6,7 @@ extern "C"{ #endif extern char * litelib_initialze (bool dangerous, const char* server); -extern char * litelib_execute (char* s); +extern char * litelib_execute (const char* s); extern void litelib_rust_free_string (char* s); #ifdef __cplusplus diff --git a/src/connection.cpp b/src/connection.cpp index 9fbdc12..28283eb 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -40,85 +40,19 @@ void ConnectionLoader::doAutoConnect(bool tryEzcashdStart) { // Initialize the library main->logger->write(QObject::tr("Attempting to initialize")); - litelib_initialze(config->dangerous, config->server.toStdString()); + litelib_initialze(config->dangerous, config->server.toStdString().c_str()); auto connection = makeConnection(config); // After the lib is initialized, try to do get info - connection->doRPC("info", [=](auto reply) { + connection->doRPC("info", "", [=](auto reply) { // If success, set the connection d->hide(); main->logger->write("Connection is online."); this->doRPCSetConnection(connection); }, [=](auto err, auto errJson) {}); - - - - if (config.get() != nullptr) { - auto connection = makeConnection(config); - - refreshZcashdState(connection, [=] () { - // Refused connection. So try and start embedded zcashd - if (Settings::getInstance()->useEmbedded()) { - if (tryEzcashdStart) { - this->showInformation(QObject::tr("Starting embedded zcashd")); - if (this->startEmbeddedZcashd()) { - // Embedded zcashd started up. Wait a second and then refresh the connection - main->logger->write("Embedded zcashd started up, trying autoconnect in 1 sec"); - QTimer::singleShot(1000, [=]() { doAutoConnect(); } ); - } else { - if (config->zcashDaemon) { - // zcashd is configured to run as a daemon, so we must wait for a few seconds - // to let it start up. - main->logger->write("zcashd is daemon=1. Waiting for it to start up"); - this->showInformation(QObject::tr("zcashd is set to run as daemon"), QObject::tr("Waiting for zcashd")); - QTimer::singleShot(5000, [=]() { doAutoConnect(/* don't attempt to start ezcashd */ false); }); - } else { - // Something is wrong. - // We're going to attempt to connect to the one in the background one last time - // and see if that works, else throw an error - main->logger->write("Unknown problem while trying to start zcashd"); - QTimer::singleShot(2000, [=]() { doAutoConnect(/* don't attempt to start ezcashd */ false); }); - } - } - } else { - // We tried to start ezcashd previously, and it didn't work. So, show the error. - main->logger->write("Couldn't start embedded zcashd for unknown reason"); - QString explanation; - if (config->zcashDaemon) { - explanation = QString() % QObject::tr("You have zcashd set to start as a daemon, which can cause problems " - "with ZecWallet\n\n." - "Please remove the following line from your zcash.conf and restart ZecWallet\n" - "daemon=1"); - } else { - explanation = QString() % QObject::tr("Couldn't start the embedded zcashd.\n\n" - "Please try restarting.\n\nIf you previously started zcashd with custom arguments, you might need to reset zcash.conf.\n\n" - "If all else fails, please run zcashd manually.") % - (ezcashd ? QObject::tr("The process returned") + ":\n\n" % ezcashd->errorString() : QString("")); - } - - this->showError(explanation); - } - } else { - // zcash.conf exists, there's no connection, and the user asked us not to start zcashd. Error! - main->logger->write("Not using embedded and couldn't connect to zcashd"); - QString explanation = QString() % QObject::tr("Couldn't connect to zcashd configured in zcash.conf.\n\n" - "Not starting embedded zcashd because --no-embedded was passed"); - this->showError(explanation); - } - }); - } else { - if (Settings::getInstance()->useEmbedded()) { - // zcash.conf was not found, so create one - createZcashConf(); - } else { - // Fall back to manual connect - doManualConnect(); - } - } } void ConnectionLoader::doRPCSetConnection(Connection* conn) { - rpc->setEZcashd(ezcashd); rpc->setConnection(conn); d->accept(); @@ -159,8 +93,10 @@ void ConnectionLoader::showError(QString explanation) { d->close(); } + + void Executor::run() { - const char* resp = litelib_execute(this->cmd.toStdString().c_str()); + char* resp = litelib_execute(this->cmd.toStdString().c_str()); QString reply = QString::fromStdString(resp); litelib_rust_free_string(resp); @@ -193,12 +129,13 @@ void Connection::doRPC(const QString cmd, const QString args, const std::functio auto runner = new Executor(cmd, args); QObject::connect(runner, &Executor::responseReady, [=] (json resp) { cb(resp); - }) + }); + QThreadPool::globalInstance()->start(runner); } -void Connection::doRPCWithDefaultErrorHandling(const json& payload, const std::function& cb) { - doRPC(payload, cb, [=] (auto reply, auto parsed) { +void Connection::doRPCWithDefaultErrorHandling(const QString cmd, const QString args, const std::function& cb) { + doRPC(cmd, args, cb, [=] (auto reply, auto parsed) { if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) { this->showTxError(QString::fromStdString(parsed["error"]["message"])); } else { @@ -207,8 +144,8 @@ void Connection::doRPCWithDefaultErrorHandling(const json& payload, const std::f }); } -void Connection::doRPCIgnoreError(const json& payload, const std::function& cb) { - doRPC(payload, cb, [=] (auto, auto) { +void Connection::doRPCIgnoreError(const QString cmd, const QString args, const std::function& cb) { + doRPC(cmd, args, cb, [=] (auto, auto) { // Ignored error handling }); } diff --git a/src/connection.h b/src/connection.h index aceead3..8ef02fa 100644 --- a/src/connection.h +++ b/src/connection.h @@ -12,6 +12,7 @@ class Controller; struct ConnectionConfig { QString server; bool dangerous; + QString proxy; }; class Connection; @@ -31,42 +32,22 @@ private: Connection* makeConnection(std::shared_ptr config); void doAutoConnect(bool tryEzcashdStart = true); - void doManualConnect(); - - void createZcashConf(); - QString locateZcashConfFile(); - QString zcashConfWritableLocation(); - QString zcashParamsDir(); - - bool verifyParams(); - void downloadParams(std::function cb); - void doNextDownload(std::function cb); - bool startEmbeddedZcashd(); - - void refreshZcashdState(Connection* connection, std::function refused); void showError(QString explanation); void showInformation(QString info, QString detail = ""); void doRPCSetConnection(Connection* conn); - QProcess* ezcashd = nullptr; - QDialog* d; Ui_ConnectionDialog* connD; MainWindow* main; Controller* rpc; - - QNetworkReply* currentDownload = nullptr; - QFile* currentOutput = nullptr; - QQueue* downloadQueue = nullptr; - - QNetworkAccessManager* client = nullptr; - QTime downloadTime; }; -class Executor : public QRunnable { +class Executor : public QObject, public QRunnable { + Q_OBJECT + public: Executor(QString cmd, QString args) { this->cmd = cmd; @@ -84,7 +65,7 @@ signals: private: QString cmd; QString args; -} +}; /** * Represents a connection to a zcashd. It may even start a new zcashd if needed. @@ -100,85 +81,13 @@ public: void shutdown(); - void doRPC(const json& payload, const std::function& cb, + void doRPC(const QString cmd, const QString args, 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 doRPCWithDefaultErrorHandling(const QString cmd, const QString args, const std::function& cb); + void doRPCIgnoreError(const QString cmd, const QString args, 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(); - if (totalSize == 0) - return; - - // Keep track of all pending method calls, so as to prevent - // any overlapping calls - static QMap inProgress; - - QString method = QString::fromStdString(payloadGenerator(payloads[0])["method"]); - //if (inProgress.value(method, false)) { - // qDebug() << "In progress batch, skipping"; - // return; - //} - - for (auto item: payloads) { - json payload = payloadGenerator(item); - inProgress[method] = true; - - QNetworkReply *reply = restclient->post(*request, QByteArray::fromStdString(payload.dump())); - - QObject::connect(reply, &QNetworkReply::finished, [=] { - reply->deleteLater(); - if (shutdownInProgress) { - // Ignoring callback because shutdown in progress - return; - } - - 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 (shutdownInProgress) { - waitTimer->stop(); - waitTimer->deleteLater(); - return; - } - - // If all responses have arrived, return - if (responses->size() == totalSize) { - waitTimer->stop(); - - cb(responses); - inProgress[method] = false; - - waitTimer->deleteLater(); - } - }); - waitTimer->start(100); - } - private: bool shutdownInProgress = false; }; diff --git a/src/controller.cpp b/src/controller.cpp index 51b96c1..3382599 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -53,7 +53,7 @@ Controller::Controller(MainWindow* main) { model = new DataModel(); // Crate the ZcashdRPC - zrpc = new ZcashdRPC(); + zrpc = new LiteInterface(); // Initialize the migration status to unavailable. this->migrationStatus.available = false; @@ -283,14 +283,6 @@ void Controller::getInfoThenRefresh(bool force) { ui->blockheight->setText(txt); ui->heightLabel->setText(QObject::tr("Downloading blocks")); } else { - // If syncing is finished, we may have to remove the ibdskiptxverification - // flag from zcash.conf - if (getConnection() != nullptr && getConnection()->config->skiptxverification) { - getConnection()->config->skiptxverification = false; - Settings::removeFromZcashConf(Settings::getInstance()->getZcashdConfLocation(), - "ibdskiptxverification"); - } - ui->blockheight->setText(QString::number(blockNumber)); ui->heightLabel->setText(QObject::tr("Block height")); } @@ -649,10 +641,12 @@ void Controller::checkForUpdate(bool silent) { QNetworkRequest req; req.setUrl(cmcURL); - QNetworkReply *reply = getConnection()->restclient->get(req); + QNetworkAccessManager *manager = new QNetworkAccessManager(this->main); + QNetworkReply *reply = manager->get(req); QObject::connect(reply, &QNetworkReply::finished, [=] { reply->deleteLater(); + manager->deleteLater(); try { if (reply->error() == QNetworkReply::NoError) { @@ -721,10 +715,12 @@ void Controller::refreshZECPrice() { QNetworkRequest req; req.setUrl(cmcURL); - QNetworkReply *reply = getConnection()->restclient->get(req); + QNetworkAccessManager *manager = new QNetworkAccessManager(this->main); + QNetworkReply *reply = manager->get(req); QObject::connect(reply, &QNetworkReply::finished, [=] { reply->deleteLater(); + manager->deleteLater(); try { if (reply->error() != QNetworkReply::NoError) { @@ -767,58 +763,113 @@ void Controller::refreshZECPrice() { void Controller::shutdownZcashd() { // Shutdown embedded zcashd if it was started - if (ezcashd == nullptr || ezcashd->processId() == 0 || !zrpc->haveConnection()) { + if (ezcashd == nullptr || ezcashd->processId() == 0 || ~zrpc->haveConnection()) { // No zcashd running internally, just return return; } - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "stop"} - }; + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "stop"} + // }; - getConnection()->doRPCWithDefaultErrorHandling(payload, [=](auto) {}); - getConnection()->shutdown(); - - QDialog d(main); - Ui_ConnectionDialog connD; - connD.setupUi(&d); - connD.topIcon->setBasePixmap(QIcon(":/icons/res/icon.ico").pixmap(256, 256)); - connD.status->setText(QObject::tr("Please wait for ZecWallet to exit")); - connD.statusDetail->setText(QObject::tr("Waiting for zcashd to exit")); - - QTimer waiter(main); - - // We capture by reference all the local variables because of the d.exec() - // below, which blocks this function until we exit. - int waitCount = 0; - QObject::connect(&waiter, &QTimer::timeout, [&] () { - waitCount++; - - if ((ezcashd->atEnd() && ezcashd->processId() == 0) || - waitCount > 30 || - getConnection()->config->zcashDaemon) { // If zcashd is daemon, then we don't have to do anything else - qDebug() << "Ended"; - waiter.stop(); - QTimer::singleShot(1000, [&]() { d.accept(); }); - } else { - qDebug() << "Not ended, continuing to wait..."; - } - }); - waiter.start(1000); + // getConnection()->doRPCWithDefaultErrorHandling(payload, [=](auto) {}); + // getConnection()->shutdown(); + + // QDialog d(main); + // Ui_ConnectionDialog connD; + // connD.setupUi(&d); + // connD.topIcon->setBasePixmap(QIcon(":/icons/res/icon.ico").pixmap(256, 256)); + // connD.status->setText(QObject::tr("Please wait for ZecWallet to exit")); + // connD.statusDetail->setText(QObject::tr("Waiting for zcashd to exit")); + + // QTimer waiter(main); + + // // We capture by reference all the local variables because of the d.exec() + // // below, which blocks this function until we exit. + // int waitCount = 0; + // QObject::connect(&waiter, &QTimer::timeout, [&] () { + // waitCount++; + + // if ((ezcashd->atEnd() && ezcashd->processId() == 0) || + // waitCount > 30 || + // getConnection()->config->zcashDaemon) { // If zcashd is daemon, then we don't have to do anything else + // qDebug() << "Ended"; + // waiter.stop(); + // QTimer::singleShot(1000, [&]() { d.accept(); }); + // } else { + // qDebug() << "Not ended, continuing to wait..."; + // } + // }); + // waiter.start(1000); + + // // Wait for the zcash process to exit. + // if (!Settings::getInstance()->isHeadless()) { + // d.exec(); + // } else { + // while (waiter.isActive()) { + // QCoreApplication::processEvents(); + + // QThread::sleep(1); + // } + // } +} - // Wait for the zcash process to exit. - if (!Settings::getInstance()->isHeadless()) { - d.exec(); - } else { - while (waiter.isActive()) { - QCoreApplication::processEvents(); - QThread::sleep(1); - } - } -} +// // Fetch the Z-board topics list +// void Controller::getZboardTopics(std::function)> cb) { +// if (!zrpc->haveConnection()) +// return noConnection(); + +// QUrl cmcURL("http://z-board.net/listTopics"); + +// QNetworkRequest req; +// req.setUrl(cmcURL); + +// QNetworkReply *reply = conn->restclient->get(req); + +// QObject::connect(reply, &QNetworkReply::finished, [=] { +// reply->deleteLater(); + +// try { +// if (reply->error() != QNetworkReply::NoError) { +// auto parsed = json::parse(reply->readAll(), nullptr, false); +// if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) { +// qDebug() << QString::fromStdString(parsed["error"]["message"]); +// } +// else { +// qDebug() << reply->errorString(); +// } +// return; +// } + +// auto all = reply->readAll(); + +// auto parsed = json::parse(all, nullptr, false); +// if (parsed.is_discarded()) { +// return; +// } + +// QMap topics; +// for (const json& item : parsed["topics"].get()) { +// if (item.find("addr") == item.end() || item.find("topicName") == item.end()) +// return; + +// QString addr = QString::fromStdString(item["addr"].get()); +// QString topic = QString::fromStdString(item["topicName"].get()); + +// topics.insert(topic, addr); +// } + +// cb(topics); +// } +// catch (...) { +// // If anything at all goes wrong, just set the price to 0 and move on. +// qDebug() << QString("Caught something nasty"); +// } +// }); +// } /** * Get a Sapling address from the user's wallet diff --git a/src/controller.h b/src/controller.h index aef931c..7b05421 100644 --- a/src/controller.h +++ b/src/controller.h @@ -8,7 +8,7 @@ #include "txtablemodel.h" #include "ui_mainwindow.h" #include "mainwindow.h" -#include "zcashdrpc.h" +#include "liteinterface.h" #include "connection.h" using json = nlohmann::json; @@ -109,7 +109,7 @@ private: BalancesTableModel* balancesTableModel = nullptr; DataModel* model; - ZcashdRPC* zrpc; + LiteInterface* zrpc; QTimer* timer; QTimer* txTimer; diff --git a/src/liteinterface.cpp b/src/liteinterface.cpp new file mode 100644 index 0000000..a88af7b --- /dev/null +++ b/src/liteinterface.cpp @@ -0,0 +1,545 @@ +#include "liteinterface.h" + +LiteInterface::LiteInterface() { + +} + +LiteInterface::~LiteInterface() { + delete conn; +} + +void LiteInterface::setConnection(Connection* c) { + if (conn) { + delete conn; + } + + conn = c; +} + +bool LiteInterface::haveConnection() { + return conn != nullptr; +} + +void LiteInterface::fetchTAddresses(const std::function& cb) { + if (conn == nullptr) + return; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "getaddressesbyaccount"}, + // {"params", {""}} + // }; + + // conn->doRPCWithDefaultErrorHandling(payload, cb); +} + +void LiteInterface::fetchZAddresses(const std::function& cb) { + if (conn == nullptr) + return; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "z_listaddresses"}, + // }; + + // conn->doRPCWithDefaultErrorHandling(payload, cb); +} + +void LiteInterface::fetchTransparentUnspent(const std::function& cb) { + if (conn == nullptr) + return; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "listunspent"}, + // {"params", {0}} // Get UTXOs with 0 confirmations as well. + // }; + + // conn->doRPCWithDefaultErrorHandling(payload, cb); +} + +void LiteInterface::fetchZUnspent(const std::function& cb) { + if (conn == nullptr) + return; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "z_listunspent"}, + // {"params", {0}} // Get UTXOs with 0 confirmations as well. + // }; + + // conn->doRPCWithDefaultErrorHandling(payload, cb); +} + +void LiteInterface::createNewZaddr(bool sapling, const std::function& cb) { + if (conn == nullptr) + return; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "z_getnewaddress"}, + // {"params", { sapling ? "sapling" : "sprout" }}, + // }; + + // conn->doRPCWithDefaultErrorHandling(payload, cb); +} + +void LiteInterface::createNewTaddr(const std::function& cb) { + if (conn == nullptr) + return; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "getnewaddress"}, + // }; + + // conn->doRPCWithDefaultErrorHandling(payload, cb); +} + +void LiteInterface::fetchZPrivKey(QString addr, const std::function& cb) { + if (conn == nullptr) + return; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "z_exportkey"}, + // {"params", { addr.toStdString() }}, + // }; + + // conn->doRPCWithDefaultErrorHandling(payload, cb); +} + +void LiteInterface::fetchTPrivKey(QString addr, const std::function& cb) { + if (conn == nullptr) + return; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "dumpprivkey"}, + // {"params", { addr.toStdString() }}, + // }; + + // conn->doRPCWithDefaultErrorHandling(payload, cb); +} + +void LiteInterface::importZPrivKey(QString addr, bool rescan, const std::function& cb) { + if (conn == nullptr) + return; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "z_importkey"}, + // {"params", { addr.toStdString(), (rescan? "yes" : "no") }}, + // }; + + // conn->doRPCWithDefaultErrorHandling(payload, cb); +} + + +void LiteInterface::importTPrivKey(QString addr, bool rescan, const std::function& cb) { + if (conn == nullptr) + return; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "importprivkey"}, + // {"params", { addr.toStdString(), (rescan? "yes" : "no") }}, + // }; + + // conn->doRPCWithDefaultErrorHandling(payload, cb); +} + +void LiteInterface::validateAddress(QString address, const std::function& cb) { + if (conn == nullptr) + return; + + // QString method = Settings::isZAddress(address) ? "z_validateaddress" : "validateaddress"; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", method.toStdString() }, + // {"params", { address.toStdString() } }, + // }; + + // conn->doRPCWithDefaultErrorHandling(payload, cb); +} + +void LiteInterface::fetchBalance(const std::function& cb) { + if (conn == nullptr) + return; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "z_gettotalbalance"}, + // {"params", {0}} // Get Unconfirmed balance as well. + // }; + + // conn->doRPCWithDefaultErrorHandling(payload, cb); +} + +void LiteInterface::fetchTransactions(const std::function& cb) { + if (conn == nullptr) + return; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "listtransactions"} + // }; + + // conn->doRPCWithDefaultErrorHandling(payload, cb); +} + +void LiteInterface::sendZTransaction(json params, const std::function& cb, + const std::function& err) { + if (conn == nullptr) + return; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "z_sendmany"}, + // {"params", params} + // }; + + // conn->doRPC(payload, cb, [=] (auto reply, auto parsed) { + // if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) { + // err(QString::fromStdString(parsed["error"]["message"])); + // } else { + // err(reply->errorString()); + // } + // }); +} + +void LiteInterface::fetchInfo(const std::function& cb, + const std::function& err) { + if (conn == nullptr) + return; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "getinfo"} + // }; + + // conn->doRPC(payload, cb, err); +} + +void LiteInterface::fetchBlockchainInfo(const std::function& cb) { + if (conn == nullptr) + return; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "getblockchaininfo"} + // }; + + // conn->doRPCIgnoreError(payload, cb); +} + +void LiteInterface::fetchNetSolOps(const std::function cb) { + if (conn == nullptr) + return; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "getnetworksolps"} + // }; + + // conn->doRPCIgnoreError(payload, [=](const json& reply) { + // qint64 solrate = reply.get(); + // cb(solrate); + // }); +} + +void LiteInterface::fetchMigrationStatus(const std::function& cb) { + if (conn == nullptr) + return; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "z_getmigrationstatus"}, + // }; + + // conn->doRPCWithDefaultErrorHandling(payload, cb); +} + + +void LiteInterface::setMigrationStatus(bool enabled) { + if (conn == nullptr) + return; + + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "z_setmigration"}, + // {"params", {enabled}} + // }; + + // conn->doRPCWithDefaultErrorHandling(payload, [=](json) { + // // Ignore return value. + // }); +} + + +/** + * Method to get all the private keys for both z and t addresses. It will make 2 batch calls, + * combine the result, and call the callback with a single list containing both the t-addr and z-addr + * private keys + */ +void LiteInterface::fetchAllPrivKeys(const std::function>)> cb) { + if (conn == nullptr) { + // No connection, just return + return; + } + + // // A special function that will call the callback when two lists have been added + // auto holder = new QPair>>(); + // holder->first = 0; // This is the number of times the callback has been called, initialized to 0 + // auto fnCombineTwoLists = [=] (QList> list) { + // // Increment the callback counter + // holder->first++; + + // // Add all + // std::copy(list.begin(), list.end(), std::back_inserter(holder->second)); + + // // And if the caller has been called twice, do the parent callback with the + // // collected list + // if (holder->first == 2) { + // // Sort so z addresses are on top + // std::sort(holder->second.begin(), holder->second.end(), + // [=] (auto a, auto b) { return a.first > b.first; }); + + // cb(holder->second); + // delete holder; + // } + // }; + + // // A utility fn to do the batch calling + // auto fnDoBatchGetPrivKeys = [=](json getAddressPayload, std::string privKeyDumpMethodName) { + // conn->doRPCWithDefaultErrorHandling(getAddressPayload, [=] (json resp) { + // QList addrs; + // for (auto addr : resp.get()) { + // addrs.push_back(QString::fromStdString(addr.get())); + // } + + // // Then, do a batch request to get all the private keys + // conn->doBatchRPC( + // addrs, + // [=] (auto addr) { + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", privKeyDumpMethodName}, + // {"params", { addr.toStdString() }}, + // }; + // return payload; + // }, + // [=] (QMap* privkeys) { + // QList> allTKeys; + // for (QString addr: privkeys->keys()) { + // allTKeys.push_back( + // QPair( + // addr, + // QString::fromStdString(privkeys->value(addr).get()))); + // } + + // fnCombineTwoLists(allTKeys); + // delete privkeys; + // } + // ); + // }); + // }; + + // // First get all the t and z addresses. + // json payloadT = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "getaddressesbyaccount"}, + // {"params", {""} } + // }; + + // json payloadZ = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "z_listaddresses"} + // }; + + // fnDoBatchGetPrivKeys(payloadT, "dumpprivkey"); + // fnDoBatchGetPrivKeys(payloadZ, "z_exportkey"); +} + +void LiteInterface::fetchOpStatus(const std::function& cb) { + if (conn == nullptr) + return; + + // Make an RPC to load pending operation statues + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "someid"}, + // {"method", "z_getoperationstatus"}, + // }; + + // conn->doRPCIgnoreError(payload, cb); +} + +void LiteInterface::fetchReceivedTTrans(QList txids, QList sentZTxs, + const std::function)> txdataFn) { + if (conn == nullptr) + return; + + // Look up all the txids to get the confirmation count for them. + // conn->doBatchRPC(txids, + // [=] (QString txid) { + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "senttxid"}, + // {"method", "gettransaction"}, + // {"params", {txid.toStdString()}} + // }; + + // return payload; + // }, + // [=] (QMap* txidList) { + // auto newSentZTxs = sentZTxs; + // // Update the original sent list with the confirmation count + // // TODO: This whole thing is kinda inefficient. We should probably just update the file + // // with the confirmed block number, so we don't have to keep calling gettransaction for the + // // sent items. + // for (TransactionItem& sentTx: newSentZTxs) { + // auto j = txidList->value(sentTx.txid); + // if (j.is_null()) + // continue; + // auto error = j["confirmations"].is_null(); + // if (!error) + // sentTx.confirmations = j["confirmations"].get(); + // } + + // txdataFn(newSentZTxs); + // delete txidList; + // } + // ); +} + + +// Refresh received z txs by calling z_listreceivedbyaddress/gettransaction +void LiteInterface::fetchReceivedZTrans(QList zaddrs, const std::function usedAddrFn, + const std::function)> txdataFn) { + if (conn == nullptr) + return; + + + // This method is complicated because z_listreceivedbyaddress only returns the txid, and + // we have to make a follow up call to gettransaction to get details of that transaction. + // Additionally, it has to be done in batches, because there are multiple z-Addresses, + // and each z-Addr can have multiple received txs. + + // 1. For each z-Addr, get list of received txs + // conn->doBatchRPC(zaddrs, + // [=] (QString zaddr) { + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "z_lrba"}, + // {"method", "z_listreceivedbyaddress"}, + // {"params", {zaddr.toStdString(), 0}} // Accept 0 conf as well. + // }; + + // return payload; + // }, + // [=] (QMap* zaddrTxids) { + // // Process all txids, removing duplicates. This can happen if the same address + // // appears multiple times in a single tx's outputs. + // QSet txids; + // QMap memos; + // for (auto it = zaddrTxids->constBegin(); it != zaddrTxids->constEnd(); it++) { + // auto zaddr = it.key(); + // for (auto& i : it.value().get()) { + // // Mark the address as used + // usedAddrFn(zaddr); + + // // Filter out change txs + // if (! i["change"].get()) { + // auto txid = QString::fromStdString(i["txid"].get()); + // txids.insert(txid); + + // // Check for Memos + // QString memoBytes = QString::fromStdString(i["memo"].get()); + // if (!memoBytes.startsWith("f600")) { + // QString memo(QByteArray::fromHex( + // QByteArray::fromStdString(i["memo"].get()))); + // if (!memo.trimmed().isEmpty()) + // memos[zaddr + txid] = memo; + // } + // } + // } + // } + + // // 2. For all txids, go and get the details of that txid. + // conn->doBatchRPC(txids.toList(), + // [=] (QString txid) { + // json payload = { + // {"jsonrpc", "1.0"}, + // {"id", "gettx"}, + // {"method", "gettransaction"}, + // {"params", {txid.toStdString()}} + // }; + + // return payload; + // }, + // [=] (QMap* txidDetails) { + // QList txdata; + + // // Combine them both together. For every zAddr's txid, get the amount, fee, confirmations and time + // for (auto it = zaddrTxids->constBegin(); it != zaddrTxids->constEnd(); it++) { + // for (auto& i : it.value().get()) { + // // Filter out change txs + // if (i["change"].get()) + // continue; + + // auto zaddr = it.key(); + // auto txid = QString::fromStdString(i["txid"].get()); + + // // Lookup txid in the map + // auto txidInfo = txidDetails->value(txid); + + // qint64 timestamp; + // if (txidInfo.find("time") != txidInfo.end()) { + // timestamp = txidInfo["time"].get(); + // } else { + // timestamp = txidInfo["blocktime"].get(); + // } + + // auto amount = i["amount"].get(); + // auto confirmations = static_cast(txidInfo["confirmations"].get()); + + // TransactionItem tx{ QString("receive"), timestamp, zaddr, txid, amount, + // confirmations, "", memos.value(zaddr + txid, "") }; + // txdata.push_front(tx); + // } + // } + + // txdataFn(txdata); + + // // Cleanup both responses; + // delete zaddrTxids; + // delete txidDetails; + // } + // ); + // } + // ); +} diff --git a/src/zcashdrpc.h b/src/liteinterface.h similarity index 97% rename from src/zcashdrpc.h rename to src/liteinterface.h index 0e10501..be1b1c4 100644 --- a/src/zcashdrpc.h +++ b/src/liteinterface.h @@ -19,10 +19,10 @@ struct TransactionItem { }; -class ZcashdRPC { +class LiteInterface { public: - ZcashdRPC(); - ~ZcashdRPC(); + LiteInterface(); + ~LiteInterface(); bool haveConnection(); void setConnection(Connection* c); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 07ecf95..c00241e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -715,29 +715,29 @@ void MainWindow::backupWalletDat() { if (!rpc->getConnection()) return; - QDir zcashdir(rpc->getConnection()->config->zcashDir); - QString backupDefaultName = "zcash-wallet-backup-" + QDateTime::currentDateTime().toString("yyyyMMdd") + ".dat"; + // QDir zcashdir(rpc->getConnection()->config->zcashDir); + // QString backupDefaultName = "zcash-wallet-backup-" + QDateTime::currentDateTime().toString("yyyyMMdd") + ".dat"; - if (Settings::getInstance()->isTestnet()) { - zcashdir.cd("testnet3"); - backupDefaultName = "testnet-" + backupDefaultName; - } + // if (Settings::getInstance()->isTestnet()) { + // zcashdir.cd("testnet3"); + // backupDefaultName = "testnet-" + backupDefaultName; + // } - QFile wallet(zcashdir.filePath("wallet.dat")); - if (!wallet.exists()) { - QMessageBox::critical(this, tr("No wallet.dat"), tr("Couldn't find the wallet.dat on this computer") + "\n" + - tr("You need to back it up from the machine zcashd is running on"), QMessageBox::Ok); - return; - } + // QFile wallet(zcashdir.filePath("wallet.dat")); + // if (!wallet.exists()) { + // QMessageBox::critical(this, tr("No wallet.dat"), tr("Couldn't find the wallet.dat on this computer") + "\n" + + // tr("You need to back it up from the machine zcashd is running on"), QMessageBox::Ok); + // return; + // } - QUrl backupName = QFileDialog::getSaveFileUrl(this, tr("Backup wallet.dat"), backupDefaultName, "Data file (*.dat)"); - if (backupName.isEmpty()) - return; - - if (!wallet.copy(backupName.toLocalFile())) { - QMessageBox::critical(this, tr("Couldn't backup"), tr("Couldn't backup the wallet.dat file.") + - tr("You need to back it up manually."), QMessageBox::Ok); - } + // QUrl backupName = QFileDialog::getSaveFileUrl(this, tr("Backup wallet.dat"), backupDefaultName, "Data file (*.dat)"); + // if (backupName.isEmpty()) + // return; + + // if (!wallet.copy(backupName.toLocalFile())) { + // QMessageBox::critical(this, tr("Couldn't backup"), tr("Couldn't backup the wallet.dat file.") + + // tr("You need to back it up manually."), QMessageBox::Ok); + // } } void MainWindow::exportAllKeys() { diff --git a/src/rpc.cpp b/src/rpc.cpp deleted file mode 100644 index 90087c2..0000000 --- a/src/rpc.cpp +++ /dev/null @@ -1,895 +0,0 @@ -#include "controller.h" - -#include "addressbook.h" -#include "settings.h" -#include "senttxstore.h" -#include "turnstile.h" -#include "version.h" -#include "websockets.h" - -using json = nlohmann::json; - -Controller::Controller(MainWindow* main) { - auto cl = new ConnectionLoader(main, this); - - // Execute the load connection async, so we can set up the rest of RPC properly. - QTimer::singleShot(1, [=]() { cl->loadConnection(); }); - - this->main = main; - this->ui = main->ui; - - // Setup balances table model - balancesTableModel = new BalancesTableModel(main->ui->balancesTable); - main->ui->balancesTable->setModel(balancesTableModel); - - // Setup transactions table model - transactionsTableModel = new TxTableModel(ui->transactionsTable); - main->ui->transactionsTable->setModel(transactionsTableModel); - - // Set up timer to refresh Price - priceTimer = new QTimer(main); - QObject::connect(priceTimer, &QTimer::timeout, [=]() { - if (Settings::getInstance()->getAllowFetchPrices()) - refreshZECPrice(); - }); - priceTimer->start(Settings::priceRefreshSpeed); // Every hour - - // Set up a timer to refresh the UI every few seconds - timer = new QTimer(main); - QObject::connect(timer, &QTimer::timeout, [=]() { - refresh(); - }); - timer->start(Settings::updateSpeed); - - // Set up the timer to watch for tx status - txTimer = new QTimer(main); - QObject::connect(txTimer, &QTimer::timeout, [=]() { - watchTxStatus(); - }); - // Start at every 10s. When an operation is pending, this will change to every second - txTimer->start(Settings::updateSpeed); - - // Create the data model - model = new DataModel(); - - // Crate the ZcashdRPC - zrpc = new ZcashdRPC(); - - // Initialize the migration status to unavailable. - this->migrationStatus.available = false; -} - -Controller::~Controller() { - delete timer; - delete txTimer; - - delete transactionsTableModel; - delete balancesTableModel; - - delete model; - delete zrpc; -} - -void Controller::setEZcashd(QProcess* p) { - ezcashd = p; - - if (ezcashd && ui->tabWidget->widget(4) == nullptr) { - ui->tabWidget->addTab(main->zcashdtab, "zcashd"); - } -} - -// Called when a connection to zcashd is available. -void Controller::setConnection(Connection* c) { - if (c == nullptr) return; - - this->zrpc->setConnection(c); - - ui->statusBar->showMessage("Ready!"); - - // See if we need to remove the reindex/rescan flags from the zcash.conf file - auto zcashConfLocation = Settings::getInstance()->getZcashdConfLocation(); - Settings::removeFromZcashConf(zcashConfLocation, "rescan"); - Settings::removeFromZcashConf(zcashConfLocation, "reindex"); - - // If we're allowed to get the Zec Price, get the prices - if (Settings::getInstance()->getAllowFetchPrices()) - refreshZECPrice(); - - // If we're allowed to check for updates, check for a new release - if (Settings::getInstance()->getCheckForUpdates()) - checkForUpdate(); - - // Force update, because this might be coming from a settings update - // where we need to immediately refresh - refresh(true); -} - - -// Build the RPC JSON Parameters for this tx -void Controller::fillTxJsonParams(json& params, Tx tx) { - Q_ASSERT(params.is_array()); - // Get all the addresses and amounts - json allRecepients = json::array(); - - // For each addr/amt/memo, construct the JSON and also build the confirm dialog box - for (int i=0; i < tx.toAddrs.size(); i++) { - auto toAddr = tx.toAddrs[i]; - - // Construct the JSON params - json rec = json::object(); - rec["address"] = toAddr.addr.toStdString(); - // Force it through string for rounding. Without this, decimal points beyond 8 places - // will appear, causing an "invalid amount" error - rec["amount"] = Settings::getDecimalString(toAddr.amount).toStdString(); //.toDouble(); - if (Settings::isZAddress(toAddr.addr) && !toAddr.encodedMemo.trimmed().isEmpty()) - rec["memo"] = toAddr.encodedMemo.toStdString(); - - allRecepients.push_back(rec); - } - - // Add sender - params.push_back(tx.fromAddr.toStdString()); - params.push_back(allRecepients); - - // Add fees if custom fees are allowed. - if (Settings::getInstance()->getAllowCustomFees()) { - params.push_back(1); // minconf - params.push_back(tx.fee); - } -} - - -void Controller::noConnection() { - QIcon i = QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical); - main->statusIcon->setPixmap(i.pixmap(16, 16)); - main->statusIcon->setToolTip(""); - main->statusLabel->setText(QObject::tr("No Connection")); - main->statusLabel->setToolTip(""); - main->ui->statusBar->showMessage(QObject::tr("No Connection"), 1000); - - // Clear balances table. - QMap emptyBalances; - QList emptyOutputs; - balancesTableModel->setNewData(emptyBalances, emptyOutputs); - - // Clear Transactions table. - QList emptyTxs; - transactionsTableModel->addTData(emptyTxs); - transactionsTableModel->addZRecvData(emptyTxs); - transactionsTableModel->addZSentData(emptyTxs); - - // Clear balances - ui->balSheilded->setText(""); - ui->balTransparent->setText(""); - ui->balTotal->setText(""); - - ui->balSheilded->setToolTip(""); - ui->balTransparent->setToolTip(""); - ui->balTotal->setToolTip(""); - - // Clear send tab from address - ui->inputsCombo->clear(); -} - -// Refresh received z txs by calling z_listreceivedbyaddress/gettransaction -void Controller::refreshReceivedZTrans(QList zaddrs) { - if (!zrpc->haveConnection()) - return noConnection(); - - // We'll only refresh the received Z txs if settings allows us. - if (!Settings::getInstance()->getSaveZtxs()) { - QList emptylist; - transactionsTableModel->addZRecvData(emptylist); - return; - } - - zrpc->fetchReceivedZTrans(zaddrs, - [=] (QString addr) { - model->markAddressUsed(addr); - }, - [=] (QList txdata) { - transactionsTableModel->addZRecvData(txdata); - } - ); -} - -/// This will refresh all the balance data from zcashd -void Controller::refresh(bool force) { - if (!zrpc->haveConnection()) - return noConnection(); - - getInfoThenRefresh(force); -} - - -void Controller::getInfoThenRefresh(bool force) { - if (!zrpc->haveConnection()) - return noConnection(); - - static bool prevCallSucceeded = false; - - zrpc->fetchInfo([=] (const json& reply) { - prevCallSucceeded = true; - // Testnet? - if (!reply["testnet"].is_null()) { - Settings::getInstance()->setTestnet(reply["testnet"].get()); - }; - - // Recurring pamynets are testnet only - if (!Settings::getInstance()->isTestnet()) - main->disableRecurring(); - - // Connected, so display checkmark. - QIcon i(":/icons/res/connected.gif"); - main->statusIcon->setPixmap(i.pixmap(16, 16)); - - static int lastBlock = 0; - int curBlock = reply["blocks"].get(); - int version = reply["version"].get(); - Settings::getInstance()->setZcashdVersion(version); - - // See if recurring payments needs anything - Recurring::getInstance()->processPending(main); - - if ( force || (curBlock != lastBlock) ) { - // Something changed, so refresh everything. - lastBlock = curBlock; - - refreshBalances(); - refreshAddresses(); // This calls refreshZSentTransactions() and refreshReceivedZTrans() - refreshTransactions(); - refreshMigration(); // Sapling turnstile migration status. - } - - int connections = reply["connections"].get(); - Settings::getInstance()->setPeers(connections); - - if (connections == 0) { - // If there are no peers connected, then the internet is probably off or something else is wrong. - QIcon i = QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning); - main->statusIcon->setPixmap(i.pixmap(16, 16)); - } - - // Get network sol/s - if (ezcashd) { - zrpc->fetchNetSolOps([=] (qint64 solrate) { - ui->numconnections->setText(QString::number(connections)); - ui->solrate->setText(QString::number(solrate) % " Sol/s"); - }); - } - - // Call to see if the blockchain is syncing. - zrpc->fetchBlockchainInfo([=](const json& reply) { - auto progress = reply["verificationprogress"].get(); - bool isSyncing = progress < 0.9999; // 99.99% - int blockNumber = reply["blocks"].get(); - - int estimatedheight = 0; - if (reply.find("estimatedheight") != reply.end()) { - estimatedheight = reply["estimatedheight"].get(); - } - - Settings::getInstance()->setSyncing(isSyncing); - Settings::getInstance()->setBlockNumber(blockNumber); - - // Update zcashd tab if it exists - if (ezcashd) { - if (isSyncing) { - QString txt = QString::number(blockNumber); - if (estimatedheight > 0) { - txt = txt % " / ~" % QString::number(estimatedheight); - } - txt = txt % " ( " % QString::number(progress * 100, 'f', 2) % "% )"; - ui->blockheight->setText(txt); - ui->heightLabel->setText(QObject::tr("Downloading blocks")); - } else { - // If syncing is finished, we may have to remove the ibdskiptxverification - // flag from zcash.conf - if (getConnection() != nullptr && getConnection()->config->skiptxverification) { - getConnection()->config->skiptxverification = false; - Settings::removeFromZcashConf(Settings::getInstance()->getZcashdConfLocation(), - "ibdskiptxverification"); - } - - ui->blockheight->setText(QString::number(blockNumber)); - ui->heightLabel->setText(QObject::tr("Block height")); - } - } - - // Update the status bar - QString statusText = QString() % - (isSyncing ? QObject::tr("Syncing") : QObject::tr("Connected")) % - " (" % - (Settings::getInstance()->isTestnet() ? QObject::tr("testnet:") : "") % - QString::number(blockNumber) % - (isSyncing ? ("/" % QString::number(progress*100, 'f', 2) % "%") : QString()) % - ")"; - main->statusLabel->setText(statusText); - - // Update the balances view to show a warning if the node is still syncing - ui->lblSyncWarning->setVisible(isSyncing); - ui->lblSyncWarningReceive->setVisible(isSyncing); - - auto zecPrice = Settings::getInstance()->getUSDFromZecAmount(1); - QString tooltip; - if (connections > 0) { - tooltip = QObject::tr("Connected to zcashd"); - } - else { - tooltip = QObject::tr("zcashd has no peer connections"); - } - tooltip = tooltip % "(v " % QString::number(Settings::getInstance()->getZcashdVersion()) % ")"; - - if (!zecPrice.isEmpty()) { - tooltip = "1 " % Settings::getTokenName() % " = " % zecPrice % "\n" % tooltip; - } - main->statusLabel->setToolTip(tooltip); - main->statusIcon->setToolTip(tooltip); - }); - - }, [=](QNetworkReply* reply, const json&) { - // zcashd has probably disappeared. - this->noConnection(); - - // Prevent multiple dialog boxes, because these are called async - static bool shown = false; - if (!shown && prevCallSucceeded) { // show error only first time - shown = true; - QMessageBox::critical(main, QObject::tr("Connection Error"), QObject::tr("There was an error connecting to zcashd. The error was") + ": \n\n" - + reply->errorString(), QMessageBox::StandardButton::Ok); - shown = false; - } - - prevCallSucceeded = false; - }); -} - -void Controller::refreshAddresses() { - if (!zrpc->haveConnection()) - return noConnection(); - - auto newzaddresses = new QList(); - - zrpc->fetchZAddresses([=] (json reply) { - for (auto& it : reply.get()) { - auto addr = QString::fromStdString(it.get()); - newzaddresses->push_back(addr); - } - - model->replaceZaddresses(newzaddresses); - - // Refresh the sent and received txs from all these z-addresses - refreshSentZTrans(); - refreshReceivedZTrans(model->getAllZAddresses()); - }); - - - auto newtaddresses = new QList(); - zrpc->fetchTAddresses([=] (json reply) { - for (auto& it : reply.get()) { - auto addr = QString::fromStdString(it.get()); - if (Settings::isTAddress(addr)) - newtaddresses->push_back(addr); - } - - model->replaceTaddresses(newtaddresses); - }); -} - -// Function to create the data model and update the views, used below. -void Controller::updateUI(bool anyUnconfirmed) { - ui->unconfirmedWarning->setVisible(anyUnconfirmed); - - // Update balances model data, which will update the table too - balancesTableModel->setNewData(model->getAllBalances(), model->getUTXOs()); - - // Update from address - main->updateFromCombo(); -}; - -// Function to process reply of the listunspent and z_listunspent API calls, used below. -bool Controller::processUnspent(const json& reply, QMap* balancesMap, QList* newUtxos) { - bool anyUnconfirmed = false; - for (auto& it : reply.get()) { - QString qsAddr = QString::fromStdString(it["address"]); - auto confirmations = it["confirmations"].get(); - if (confirmations == 0) { - anyUnconfirmed = true; - } - - newUtxos->push_back( - UnspentOutput{ qsAddr, QString::fromStdString(it["txid"]), - Settings::getDecimalString(it["amount"].get()), - (int)confirmations, it["spendable"].get() }); - - (*balancesMap)[qsAddr] = (*balancesMap)[qsAddr] + it["amount"].get(); - } - return anyUnconfirmed; -}; - -/** - * Refresh the turnstile migration status - */ -void Controller::refreshMigration() { - // Turnstile migration is only supported in zcashd v2.0.5 and above - if (Settings::getInstance()->getZcashdVersion() < 2000552) - return; - - zrpc->fetchMigrationStatus([=](json reply) { - this->migrationStatus.available = true; - this->migrationStatus.enabled = reply["enabled"].get(); - this->migrationStatus.saplingAddress = QString::fromStdString(reply["destination_address"]); - this->migrationStatus.unmigrated = QString::fromStdString(reply["unmigrated_amount"]).toDouble(); - this->migrationStatus.migrated = QString::fromStdString(reply["finalized_migrated_amount"]).toDouble(); - - QList ids; - for (auto& it : reply["migration_txids"].get()) { - ids.push_back(QString::fromStdString(it.get())); - } - this->migrationStatus.txids = ids; - }); -} - -void Controller::refreshBalances() { - if (!zrpc->haveConnection()) - return noConnection(); - - // 1. Get the Balances - zrpc->fetchBalance([=] (json reply) { - auto balT = QString::fromStdString(reply["transparent"]).toDouble(); - auto balZ = QString::fromStdString(reply["private"]).toDouble(); - auto balTotal = QString::fromStdString(reply["total"]).toDouble(); - - AppDataModel::getInstance()->setBalances(balT, balZ); - - ui->balSheilded ->setText(Settings::getZECDisplayFormat(balZ)); - ui->balTransparent->setText(Settings::getZECDisplayFormat(balT)); - ui->balTotal ->setText(Settings::getZECDisplayFormat(balTotal)); - - - ui->balSheilded ->setToolTip(Settings::getZECDisplayFormat(balZ)); - ui->balTransparent->setToolTip(Settings::getZECDisplayFormat(balT)); - ui->balTotal ->setToolTip(Settings::getZECDisplayFormat(balTotal)); - }); - - // 2. Get the UTXOs - // First, create a new UTXO list. It will be replacing the existing list when everything is processed. - auto newUtxos = new QList(); - auto newBalances = new QMap(); - - // Call the Transparent and Z unspent APIs serially and then, once they're done, update the UI - zrpc->fetchTransparentUnspent([=] (json reply) { - auto anyTUnconfirmed = processUnspent(reply, newBalances, newUtxos); - - zrpc->fetchZUnspent([=] (json reply) { - auto anyZUnconfirmed = processUnspent(reply, newBalances, newUtxos); - - // Swap out the balances and UTXOs - model->replaceBalances(newBalances); - model->replaceUTXOs(newUtxos); - - updateUI(anyTUnconfirmed || anyZUnconfirmed); - - main->balancesReady(); - }); - }); -} - -void Controller::refreshTransactions() { - if (!zrpc->haveConnection()) - return noConnection(); - - zrpc->fetchTransactions([=] (json reply) { - QList txdata; - - for (auto& it : reply.get()) { - double fee = 0; - if (!it["fee"].is_null()) { - fee = it["fee"].get(); - } - - QString address = (it["address"].is_null() ? "" : QString::fromStdString(it["address"])); - - TransactionItem tx{ - QString::fromStdString(it["category"]), - (qint64)it["time"].get(), - address, - QString::fromStdString(it["txid"]), - it["amount"].get() + fee, - static_cast(it["confirmations"].get()), - "", "" }; - - txdata.push_back(tx); - if (!address.isEmpty()) - model->markAddressUsed(address); - } - - // Update model data, which updates the table view - transactionsTableModel->addTData(txdata); - }); -} - -// Read sent Z transactions from the file. -void Controller::refreshSentZTrans() { - if (!zrpc->haveConnection()) - return noConnection(); - - auto sentZTxs = SentTxStore::readSentTxFile(); - - // If there are no sent z txs, then empty the table. - // This happens when you clear history. - if (sentZTxs.isEmpty()) { - transactionsTableModel->addZSentData(sentZTxs); - return; - } - - QList txids; - - for (auto sentTx: sentZTxs) { - txids.push_back(sentTx.txid); - } - - // Look up all the txids to get the confirmation count for them. - zrpc->fetchReceivedTTrans(txids, sentZTxs, [=](auto newSentZTxs) { - transactionsTableModel->addZSentData(newSentZTxs); - }); -} - -void Controller::addNewTxToWatch(const QString& newOpid, WatchedTx wtx) { - watchingOps.insert(newOpid, wtx); - - watchTxStatus(); -} - -/** - * Execute a transaction with the standard UI. i.e., standard status bar message and standard error - * handling - */ -void Controller::executeStandardUITransaction(Tx tx) { - executeTransaction(tx, - [=] (QString opid) { - ui->statusBar->showMessage(QObject::tr("Computing Tx: ") % opid); - }, - [=] (QString, QString txid) { - ui->statusBar->showMessage(Settings::txidStatusMessage + " " + txid); - }, - [=] (QString opid, QString errStr) { - ui->statusBar->showMessage(QObject::tr(" Tx ") % opid % QObject::tr(" failed"), 15 * 1000); - - if (!opid.isEmpty()) - errStr = QObject::tr("The transaction with id ") % opid % QObject::tr(" failed. The error was") + ":\n\n" + errStr; - - QMessageBox::critical(main, QObject::tr("Transaction Error"), errStr, QMessageBox::Ok); - } - ); -} - - -// Execute a transaction! -void Controller::executeTransaction(Tx tx, - const std::function submitted, - const std::function computed, - const std::function error) { - // First, create the json params - json params = json::array(); - fillTxJsonParams(params, tx); - std::cout << std::setw(2) << params << std::endl; - - zrpc->sendZTransaction(params, [=](const json& reply) { - QString opid = QString::fromStdString(reply.get()); - - // And then start monitoring the transaction - addNewTxToWatch( opid, WatchedTx { opid, tx, computed, error} ); - submitted(opid); - }, - [=](QString errStr) { - error("", errStr); - }); -} - - -void Controller::watchTxStatus() { - if (!zrpc->haveConnection()) - return noConnection(); - - zrpc->fetchOpStatus([=] (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 its status became "success", then we'll show a status bar alert - QString id = QString::fromStdString(it["id"]); - if (watchingOps.contains(id)) { - // And if it ended up successful - QString status = QString::fromStdString(it["status"]); - main->loadingLabel->setVisible(false); - - if (status == "success") { - auto txid = QString::fromStdString(it["result"]["txid"]); - - SentTxStore::addToSentTx(watchingOps[id].tx, txid); - - auto wtx = watchingOps[id]; - watchingOps.remove(id); - wtx.completed(id, txid); - - // Refresh balances to show unconfirmed balances - refresh(true); - } else if (status == "failed") { - // If it failed, then we'll actually show a warning. - auto errorMsg = QString::fromStdString(it["error"]["message"]); - - auto wtx = watchingOps[id]; - watchingOps.remove(id); - wtx.error(id, errorMsg); - } - } - - if (watchingOps.isEmpty()) { - txTimer->start(Settings::updateSpeed); - } else { - txTimer->start(Settings::quickUpdateSpeed); - } - } - - // If there is some op that we are watching, then show the loading bar, otherwise hide it - if (watchingOps.empty()) { - main->loadingLabel->setVisible(false); - } else { - main->loadingLabel->setVisible(true); - main->loadingLabel->setToolTip(QString::number(watchingOps.size()) + QObject::tr(" tx computing. This can take several minutes.")); - } - }); -} - -void Controller::checkForUpdate(bool silent) { - if (!zrpc->haveConnection()) - return noConnection(); - - QUrl cmcURL("https://api.github.com/repos/ZcashFoundation/zecwallet/releases"); - - QNetworkRequest req; - req.setUrl(cmcURL); - - QNetworkReply *reply = getConnection()->restclient->get(req); - - QObject::connect(reply, &QNetworkReply::finished, [=] { - reply->deleteLater(); - - try { - if (reply->error() == QNetworkReply::NoError) { - - auto releases = QJsonDocument::fromJson(reply->readAll()).array(); - QVersionNumber maxVersion(0, 0, 0); - - for (QJsonValue rel : releases) { - if (!rel.toObject().contains("tag_name")) - continue; - - QString tag = rel.toObject()["tag_name"].toString(); - if (tag.startsWith("v")) - tag = tag.right(tag.length() - 1); - - if (!tag.isEmpty()) { - auto v = QVersionNumber::fromString(tag); - if (v > maxVersion) - maxVersion = v; - } - } - - auto currentVersion = QVersionNumber::fromString(APP_VERSION); - - // Get the max version that the user has hidden updates for - QSettings s; - auto maxHiddenVersion = QVersionNumber::fromString(s.value("update/lastversion", "0.0.0").toString()); - - qDebug() << "Version check: Current " << currentVersion << ", Available " << maxVersion; - - if (maxVersion > currentVersion && (!silent || maxVersion > maxHiddenVersion)) { - auto ans = QMessageBox::information(main, QObject::tr("Update Available"), - QObject::tr("A new release v%1 is available! You have v%2.\n\nWould you like to visit the releases page?") - .arg(maxVersion.toString()) - .arg(currentVersion.toString()), - QMessageBox::Yes, QMessageBox::Cancel); - if (ans == QMessageBox::Yes) { - QDesktopServices::openUrl(QUrl("https://github.com/ZcashFoundation/zecwallet/releases")); - } else { - // If the user selects cancel, don't bother them again for this version - s.setValue("update/lastversion", maxVersion.toString()); - } - } else { - if (!silent) { - QMessageBox::information(main, QObject::tr("No updates available"), - QObject::tr("You already have the latest release v%1") - .arg(currentVersion.toString())); - } - } - } - } - catch (...) { - // If anything at all goes wrong, just set the price to 0 and move on. - qDebug() << QString("Caught something nasty"); - } - }); -} - -// Get the ZEC->USD price from coinmarketcap using their API -void Controller::refreshZECPrice() { - if (!zrpc->haveConnection()) - return noConnection(); - - QUrl cmcURL("https://api.coinmarketcap.com/v1/ticker/"); - - QNetworkRequest req; - req.setUrl(cmcURL); - - QNetworkReply *reply = getConnection()->restclient->get(req); - - QObject::connect(reply, &QNetworkReply::finished, [=] { - reply->deleteLater(); - - try { - if (reply->error() != QNetworkReply::NoError) { - auto parsed = json::parse(reply->readAll(), nullptr, false); - if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) { - qDebug() << QString::fromStdString(parsed["error"]["message"]); - } else { - qDebug() << reply->errorString(); - } - Settings::getInstance()->setZECPrice(0); - return; - } - - auto all = reply->readAll(); - - auto parsed = json::parse(all, nullptr, false); - if (parsed.is_discarded()) { - Settings::getInstance()->setZECPrice(0); - return; - } - - for (const json& item : parsed.get()) { - if (item["symbol"].get() == Settings::getTokenName().toStdString()) { - QString price = QString::fromStdString(item["price_usd"].get()); - qDebug() << Settings::getTokenName() << " Price=" << price; - Settings::getInstance()->setZECPrice(price.toDouble()); - - return; - } - } - } catch (...) { - // If anything at all goes wrong, just set the price to 0 and move on. - qDebug() << QString("Caught something nasty"); - } - - // If nothing, then set the price to 0; - Settings::getInstance()->setZECPrice(0); - }); -} - -void Controller::shutdownZcashd() { - // Shutdown embedded zcashd if it was started - if (ezcashd == nullptr || ezcashd->processId() == 0 || ~zrpc->haveConnection()) { - // No zcashd running internally, just return - return; - } - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "stop"} - }; - - getConnection()->doRPCWithDefaultErrorHandling(payload, [=](auto) {}); - getConnection()->shutdown(); - - QDialog d(main); - Ui_ConnectionDialog connD; - connD.setupUi(&d); - connD.topIcon->setBasePixmap(QIcon(":/icons/res/icon.ico").pixmap(256, 256)); - connD.status->setText(QObject::tr("Please wait for ZecWallet to exit")); - connD.statusDetail->setText(QObject::tr("Waiting for zcashd to exit")); - - QTimer waiter(main); - - // We capture by reference all the local variables because of the d.exec() - // below, which blocks this function until we exit. - int waitCount = 0; - QObject::connect(&waiter, &QTimer::timeout, [&] () { - waitCount++; - - if ((ezcashd->atEnd() && ezcashd->processId() == 0) || - waitCount > 30 || - getConnection()->config->zcashDaemon) { // If zcashd is daemon, then we don't have to do anything else - qDebug() << "Ended"; - waiter.stop(); - QTimer::singleShot(1000, [&]() { d.accept(); }); - } else { - qDebug() << "Not ended, continuing to wait..."; - } - }); - waiter.start(1000); - - // Wait for the zcash process to exit. - if (!Settings::getInstance()->isHeadless()) { - d.exec(); - } else { - while (waiter.isActive()) { - QCoreApplication::processEvents(); - - QThread::sleep(1); - } - } -} - - -// // Fetch the Z-board topics list -// void Controller::getZboardTopics(std::function)> cb) { -// if (!zrpc->haveConnection()) -// return noConnection(); - -// QUrl cmcURL("http://z-board.net/listTopics"); - -// QNetworkRequest req; -// req.setUrl(cmcURL); - -// QNetworkReply *reply = conn->restclient->get(req); - -// QObject::connect(reply, &QNetworkReply::finished, [=] { -// reply->deleteLater(); - -// try { -// if (reply->error() != QNetworkReply::NoError) { -// auto parsed = json::parse(reply->readAll(), nullptr, false); -// if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) { -// qDebug() << QString::fromStdString(parsed["error"]["message"]); -// } -// else { -// qDebug() << reply->errorString(); -// } -// return; -// } - -// auto all = reply->readAll(); - -// auto parsed = json::parse(all, nullptr, false); -// if (parsed.is_discarded()) { -// return; -// } - -// QMap topics; -// for (const json& item : parsed["topics"].get()) { -// if (item.find("addr") == item.end() || item.find("topicName") == item.end()) -// return; - -// QString addr = QString::fromStdString(item["addr"].get()); -// QString topic = QString::fromStdString(item["topicName"].get()); - -// topics.insert(topic, addr); -// } - -// cb(topics); -// } -// catch (...) { -// // If anything at all goes wrong, just set the price to 0 and move on. -// qDebug() << QString("Caught something nasty"); -// } -// }); -// } - -/** - * Get a Sapling address from the user's wallet - */ -QString Controller::getDefaultSaplingAddress() { - for (QString addr: model->getAllZAddresses()) { - if (Settings::getInstance()->isSaplingAddress(addr)) - return addr; - } - - return QString(); -} - -QString Controller::getDefaultTAddress() { - if (model->getAllTAddresses().length() > 0) - return model->getAllTAddresses().at(0); - else - return QString(); -} diff --git a/src/zcashdrpc.cpp b/src/zcashdrpc.cpp deleted file mode 100644 index 52968ad..0000000 --- a/src/zcashdrpc.cpp +++ /dev/null @@ -1,545 +0,0 @@ -#include "zcashdrpc.h" - -ZcashdRPC::ZcashdRPC() { - -} - -ZcashdRPC::~ZcashdRPC() { - delete conn; -} - -void ZcashdRPC::setConnection(Connection* c) { - if (conn) { - delete conn; - } - - conn = c; -} - -bool ZcashdRPC::haveConnection() { - return conn != nullptr; -} - -void ZcashdRPC::fetchTAddresses(const std::function& cb) { - if (conn == nullptr) - return; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "getaddressesbyaccount"}, - {"params", {""}} - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void ZcashdRPC::fetchZAddresses(const std::function& cb) { - if (conn == nullptr) - return; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_listaddresses"}, - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void ZcashdRPC::fetchTransparentUnspent(const std::function& cb) { - if (conn == nullptr) - return; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "listunspent"}, - {"params", {0}} // Get UTXOs with 0 confirmations as well. - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void ZcashdRPC::fetchZUnspent(const std::function& cb) { - if (conn == nullptr) - return; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_listunspent"}, - {"params", {0}} // Get UTXOs with 0 confirmations as well. - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void ZcashdRPC::createNewZaddr(bool sapling, const std::function& cb) { - if (conn == nullptr) - return; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_getnewaddress"}, - {"params", { sapling ? "sapling" : "sprout" }}, - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void ZcashdRPC::createNewTaddr(const std::function& cb) { - if (conn == nullptr) - return; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "getnewaddress"}, - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void ZcashdRPC::fetchZPrivKey(QString addr, const std::function& cb) { - if (conn == nullptr) - return; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_exportkey"}, - {"params", { addr.toStdString() }}, - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void ZcashdRPC::fetchTPrivKey(QString addr, const std::function& cb) { - if (conn == nullptr) - return; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "dumpprivkey"}, - {"params", { addr.toStdString() }}, - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void ZcashdRPC::importZPrivKey(QString addr, bool rescan, const std::function& cb) { - if (conn == nullptr) - return; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_importkey"}, - {"params", { addr.toStdString(), (rescan? "yes" : "no") }}, - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - - -void ZcashdRPC::importTPrivKey(QString addr, bool rescan, const std::function& cb) { - if (conn == nullptr) - return; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "importprivkey"}, - {"params", { addr.toStdString(), (rescan? "yes" : "no") }}, - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void ZcashdRPC::validateAddress(QString address, const std::function& cb) { - if (conn == nullptr) - return; - - QString method = Settings::isZAddress(address) ? "z_validateaddress" : "validateaddress"; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", method.toStdString() }, - {"params", { address.toStdString() } }, - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void ZcashdRPC::fetchBalance(const std::function& cb) { - if (conn == nullptr) - return; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_gettotalbalance"}, - {"params", {0}} // Get Unconfirmed balance as well. - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void ZcashdRPC::fetchTransactions(const std::function& cb) { - if (conn == nullptr) - return; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "listtransactions"} - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void ZcashdRPC::sendZTransaction(json params, const std::function& cb, - const std::function& err) { - if (conn == nullptr) - return; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_sendmany"}, - {"params", params} - }; - - conn->doRPC(payload, cb, [=] (auto reply, auto parsed) { - if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) { - err(QString::fromStdString(parsed["error"]["message"])); - } else { - err(reply->errorString()); - } - }); -} - -void ZcashdRPC::fetchInfo(const std::function& cb, - const std::function& err) { - if (conn == nullptr) - return; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "getinfo"} - }; - - conn->doRPC(payload, cb, err); -} - -void ZcashdRPC::fetchBlockchainInfo(const std::function& cb) { - if (conn == nullptr) - return; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "getblockchaininfo"} - }; - - conn->doRPCIgnoreError(payload, cb); -} - -void ZcashdRPC::fetchNetSolOps(const std::function cb) { - if (conn == nullptr) - return; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "getnetworksolps"} - }; - - conn->doRPCIgnoreError(payload, [=](const json& reply) { - qint64 solrate = reply.get(); - cb(solrate); - }); -} - -void ZcashdRPC::fetchMigrationStatus(const std::function& cb) { - if (conn == nullptr) - return; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_getmigrationstatus"}, - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - - -void ZcashdRPC::setMigrationStatus(bool enabled) { - if (conn == nullptr) - return; - - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_setmigration"}, - {"params", {enabled}} - }; - - conn->doRPCWithDefaultErrorHandling(payload, [=](json) { - // Ignore return value. - }); -} - - -/** - * Method to get all the private keys for both z and t addresses. It will make 2 batch calls, - * combine the result, and call the callback with a single list containing both the t-addr and z-addr - * private keys - */ -void ZcashdRPC::fetchAllPrivKeys(const std::function>)> cb) { - if (conn == nullptr) { - // No connection, just return - return; - } - - // A special function that will call the callback when two lists have been added - auto holder = new QPair>>(); - holder->first = 0; // This is the number of times the callback has been called, initialized to 0 - auto fnCombineTwoLists = [=] (QList> list) { - // Increment the callback counter - holder->first++; - - // Add all - std::copy(list.begin(), list.end(), std::back_inserter(holder->second)); - - // And if the caller has been called twice, do the parent callback with the - // collected list - if (holder->first == 2) { - // Sort so z addresses are on top - std::sort(holder->second.begin(), holder->second.end(), - [=] (auto a, auto b) { return a.first > b.first; }); - - cb(holder->second); - delete holder; - } - }; - - // A utility fn to do the batch calling - auto fnDoBatchGetPrivKeys = [=](json getAddressPayload, std::string privKeyDumpMethodName) { - conn->doRPCWithDefaultErrorHandling(getAddressPayload, [=] (json resp) { - QList addrs; - for (auto addr : resp.get()) { - addrs.push_back(QString::fromStdString(addr.get())); - } - - // Then, do a batch request to get all the private keys - conn->doBatchRPC( - addrs, - [=] (auto addr) { - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", privKeyDumpMethodName}, - {"params", { addr.toStdString() }}, - }; - return payload; - }, - [=] (QMap* privkeys) { - QList> allTKeys; - for (QString addr: privkeys->keys()) { - allTKeys.push_back( - QPair( - addr, - QString::fromStdString(privkeys->value(addr).get()))); - } - - fnCombineTwoLists(allTKeys); - delete privkeys; - } - ); - }); - }; - - // First get all the t and z addresses. - json payloadT = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "getaddressesbyaccount"}, - {"params", {""} } - }; - - json payloadZ = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_listaddresses"} - }; - - fnDoBatchGetPrivKeys(payloadT, "dumpprivkey"); - fnDoBatchGetPrivKeys(payloadZ, "z_exportkey"); -} - -void ZcashdRPC::fetchOpStatus(const std::function& cb) { - if (conn == nullptr) - return; - - // Make an RPC to load pending operation statues - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_getoperationstatus"}, - }; - - conn->doRPCIgnoreError(payload, cb); -} - -void ZcashdRPC::fetchReceivedTTrans(QList txids, QList sentZTxs, - const std::function)> txdataFn) { - if (conn == nullptr) - return; - - // Look up all the txids to get the confirmation count for them. - conn->doBatchRPC(txids, - [=] (QString txid) { - json payload = { - {"jsonrpc", "1.0"}, - {"id", "senttxid"}, - {"method", "gettransaction"}, - {"params", {txid.toStdString()}} - }; - - return payload; - }, - [=] (QMap* txidList) { - auto newSentZTxs = sentZTxs; - // Update the original sent list with the confirmation count - // TODO: This whole thing is kinda inefficient. We should probably just update the file - // with the confirmed block number, so we don't have to keep calling gettransaction for the - // sent items. - for (TransactionItem& sentTx: newSentZTxs) { - auto j = txidList->value(sentTx.txid); - if (j.is_null()) - continue; - auto error = j["confirmations"].is_null(); - if (!error) - sentTx.confirmations = j["confirmations"].get(); - } - - txdataFn(newSentZTxs); - delete txidList; - } - ); -} - - -// Refresh received z txs by calling z_listreceivedbyaddress/gettransaction -void ZcashdRPC::fetchReceivedZTrans(QList zaddrs, const std::function usedAddrFn, - const std::function)> txdataFn) { - if (conn == nullptr) - return; - - - // This method is complicated because z_listreceivedbyaddress only returns the txid, and - // we have to make a follow up call to gettransaction to get details of that transaction. - // Additionally, it has to be done in batches, because there are multiple z-Addresses, - // and each z-Addr can have multiple received txs. - - // 1. For each z-Addr, get list of received txs - conn->doBatchRPC(zaddrs, - [=] (QString zaddr) { - json payload = { - {"jsonrpc", "1.0"}, - {"id", "z_lrba"}, - {"method", "z_listreceivedbyaddress"}, - {"params", {zaddr.toStdString(), 0}} // Accept 0 conf as well. - }; - - return payload; - }, - [=] (QMap* zaddrTxids) { - // Process all txids, removing duplicates. This can happen if the same address - // appears multiple times in a single tx's outputs. - QSet txids; - QMap memos; - for (auto it = zaddrTxids->constBegin(); it != zaddrTxids->constEnd(); it++) { - auto zaddr = it.key(); - for (auto& i : it.value().get()) { - // Mark the address as used - usedAddrFn(zaddr); - - // Filter out change txs - if (! i["change"].get()) { - auto txid = QString::fromStdString(i["txid"].get()); - txids.insert(txid); - - // Check for Memos - QString memoBytes = QString::fromStdString(i["memo"].get()); - if (!memoBytes.startsWith("f600")) { - QString memo(QByteArray::fromHex( - QByteArray::fromStdString(i["memo"].get()))); - if (!memo.trimmed().isEmpty()) - memos[zaddr + txid] = memo; - } - } - } - } - - // 2. For all txids, go and get the details of that txid. - conn->doBatchRPC(txids.toList(), - [=] (QString txid) { - json payload = { - {"jsonrpc", "1.0"}, - {"id", "gettx"}, - {"method", "gettransaction"}, - {"params", {txid.toStdString()}} - }; - - return payload; - }, - [=] (QMap* txidDetails) { - QList txdata; - - // Combine them both together. For every zAddr's txid, get the amount, fee, confirmations and time - for (auto it = zaddrTxids->constBegin(); it != zaddrTxids->constEnd(); it++) { - for (auto& i : it.value().get()) { - // Filter out change txs - if (i["change"].get()) - continue; - - auto zaddr = it.key(); - auto txid = QString::fromStdString(i["txid"].get()); - - // Lookup txid in the map - auto txidInfo = txidDetails->value(txid); - - qint64 timestamp; - if (txidInfo.find("time") != txidInfo.end()) { - timestamp = txidInfo["time"].get(); - } else { - timestamp = txidInfo["blocktime"].get(); - } - - auto amount = i["amount"].get(); - auto confirmations = static_cast(txidInfo["confirmations"].get()); - - TransactionItem tx{ QString("receive"), timestamp, zaddr, txid, amount, - confirmations, "", memos.value(zaddr + txid, "") }; - txdata.push_front(tx); - } - } - - txdataFn(txdata); - - // Cleanup both responses; - delete zaddrTxids; - delete txidDetails; - } - ); - } - ); -} diff --git a/zecwallet-lite.pro b/zecwallet-lite.pro index 93db31d..ab5ad83 100644 --- a/zecwallet-lite.pro +++ b/zecwallet-lite.pro @@ -62,7 +62,7 @@ SOURCES += \ src/viewalladdresses.cpp \ src/datamodel.cpp \ src/controller.cpp \ - src/zcashdrpc.cpp + src/liteinterface.cpp HEADERS += \ src/mainwindow.h \ @@ -91,7 +91,7 @@ HEADERS += \ src/viewalladdresses.h \ src/datamodel.h \ src/controller.h \ - src/zcashdrpc.h \ + src/liteinterface.h \ lib/zecwalletlitelib.h FORMS += \