From c6f6b1c12615e8971c655e35564b4c4578757054 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Mon, 7 Oct 2019 12:38:58 -0700 Subject: [PATCH] Split rpc and controller --- .gitignore | 1 + src/controller.h | 1 + src/mainwindow.cpp | 314 +----------------------- src/mainwindow.h | 5 +- src/rpc.cpp | 591 +++++++-------------------------------------- src/rpc.h | 66 ++--- src/zcashdrpc.cpp | 479 ++++++++++++++++++++++++++++++++++++ src/zcashdrpc.h | 70 ++++++ zec-qt-wallet.pro | 6 +- 9 files changed, 671 insertions(+), 862 deletions(-) create mode 100644 src/zcashdrpc.cpp create mode 100644 src/zcashdrpc.h diff --git a/.gitignore b/.gitignore index e68e06b..4ff0d08 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ zcashd IDEWorkspaceChecks.plist *.sln node_modules +zec-qt-wallet.pro.user.4.10-pre1 diff --git a/src/controller.h b/src/controller.h index e69de29..ab0c014 100644 --- a/src/controller.h +++ b/src/controller.h @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index dfcf6df..a49ff74 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -223,207 +223,16 @@ void MainWindow::closeEvent(QCloseEvent* event) { QMainWindow::closeEvent(event); } -void MainWindow::turnstileProgress() { - Ui_TurnstileProgress progress; - QDialog d(this); - progress.setupUi(&d); - Settings::saveRestore(&d); - - QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning); - progress.msgIcon->setPixmap(icon.pixmap(64, 64)); - - bool migrationFinished = false; - auto fnUpdateProgressUI = [=, &migrationFinished] () mutable { - // Get the plan progress - if (rpc->getTurnstile()->isMigrationPresent()) { - auto curProgress = rpc->getTurnstile()->getPlanProgress(); - - progress.progressTxt->setText(QString::number(curProgress.step) % QString(" / ") % QString::number(curProgress.totalSteps)); - progress.progressBar->setValue(100 * curProgress.step / curProgress.totalSteps); - - auto nextTxBlock = curProgress.nextBlock - Settings::getInstance()->getBlockNumber(); - - progress.fromAddr->setText(curProgress.from); - progress.toAddr->setText(curProgress.to); - - if (curProgress.step == curProgress.totalSteps) { - migrationFinished = true; - auto txt = QString("Turnstile migration finished"); - if (curProgress.hasErrors) { - txt = txt + ". There were some errors.\n\nYour funds are all in your wallet, so you should be able to finish moving them manually."; - } - progress.nextTx->setText(txt); - } else { - progress.nextTx->setText(QString("Next transaction in ") - % QString::number(nextTxBlock < 0 ? 0 : nextTxBlock) - % " blocks via " % curProgress.via % "\n" - % (nextTxBlock <= 0 ? "(waiting for confirmations)" : "")); - } - - } else { - progress.progressTxt->setText(""); - progress.progressBar->setValue(0); - progress.nextTx->setText("No turnstile migration is in progress"); - } - }; - - QTimer progressTimer(this); - QObject::connect(&progressTimer, &QTimer::timeout, fnUpdateProgressUI); - progressTimer.start(Settings::updateSpeed); - fnUpdateProgressUI(); - - auto curProgress = rpc->getTurnstile()->getPlanProgress(); - - // Abort button - if (curProgress.step != curProgress.totalSteps) - progress.buttonBox->button(QDialogButtonBox::Discard)->setText("Abort"); - else - progress.buttonBox->button(QDialogButtonBox::Discard)->setVisible(false); - - // Abort button clicked - QObject::connect(progress.buttonBox->button(QDialogButtonBox::Discard), &QPushButton::clicked, [&] () { - if (curProgress.step != curProgress.totalSteps) { - auto abort = QMessageBox::warning(this, "Are you sure you want to Abort?", - "Are you sure you want to abort the migration?\nAll further transactions will be cancelled.\nAll your funds are still in your wallet.", - QMessageBox::Yes, QMessageBox::No); - if (abort == QMessageBox::Yes) { - rpc->getTurnstile()->removeFile(); - d.close(); - ui->statusBar->showMessage("Automatic Sapling turnstile migration aborted."); - } - } - }); - - d.exec(); - if (migrationFinished || curProgress.step == curProgress.totalSteps) { - // Finished, so delete the file - rpc->getTurnstile()->removeFile(); - } -} - -void MainWindow::turnstileDoMigration(QString fromAddr) { - // If a migration is already in progress, show the progress dialog instead - if (rpc->getTurnstile()->isMigrationPresent()) { - turnstileProgress(); - return; - } - - Ui_Turnstile turnstile; - QDialog d(this); - turnstile.setupUi(&d); - Settings::saveRestore(&d); - - QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation); - turnstile.msgIcon->setPixmap(icon.pixmap(64, 64)); - - auto fnGetAllSproutBalance = [=] () { - double bal = 0; - for (auto addr : rpc->getModel()->getAllZAddresses()) { - if (Settings::getInstance()->isSproutAddress(addr)) { - bal += rpc->getModel()->getAllBalances().value(addr); - } - } - - return bal; - }; - - turnstile.fromBalance->setText(Settings::getZECUSDDisplayFormat(fnGetAllSproutBalance())); - for (auto addr : rpc->getModel()->getAllZAddresses()) { - auto bal = rpc->getModel()->getAllBalances().value(addr); - if (Settings::getInstance()->isSaplingAddress(addr)) { - turnstile.migrateTo->addItem(addr, bal); - } else { - turnstile.migrateZaddList->addItem(addr, bal); - } - } - - auto fnUpdateSproutBalance = [=] (QString addr) { - double bal = 0; - - // The currentText contains the balance as well, so strip that. - if (addr.contains("(")) { - addr = addr.left(addr.indexOf("(")); - } - - if (addr.startsWith("All")) { - bal = fnGetAllSproutBalance(); - } else { - bal = rpc->getModel()->getAllBalances().value(addr); - } - - auto balTxt = Settings::getZECUSDDisplayFormat(bal); - - if (bal < Turnstile::minMigrationAmount) { - turnstile.fromBalance->setStyleSheet("color: red;"); - turnstile.fromBalance->setText(balTxt % " [You need at least " - % Settings::getZECDisplayFormat(Turnstile::minMigrationAmount) - % " for automatic migration]"); - turnstile.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - } else { - turnstile.fromBalance->setStyleSheet(""); - turnstile.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - turnstile.fromBalance->setText(balTxt); - } - }; - - if (!fromAddr.isEmpty()) - turnstile.migrateZaddList->setCurrentText(fromAddr); - - fnUpdateSproutBalance(turnstile.migrateZaddList->currentText()); - - // Combo box selection event - QObject::connect(turnstile.migrateZaddList, &QComboBox::currentTextChanged, fnUpdateSproutBalance); - - // Privacy level combobox - // Num tx over num blocks - QList> privOptions; - privOptions.push_back(std::make_tuple(3, 576)); - privOptions.push_back(std::make_tuple(5, 1152)); - privOptions.push_back(std::make_tuple(10, 2304)); - - QObject::connect(turnstile.privLevel, QOverload::of(&QComboBox::currentIndexChanged), [=] (auto idx) { - // Update the fees - turnstile.minerFee->setText( - Settings::getZECUSDDisplayFormat(std::get<0>(privOptions[idx]) * Settings::getMinerFee())); - }); - - for (auto i : privOptions) { - turnstile.privLevel->addItem(QString::number((int)(std::get<1>(i) / 24 / 24)) % " days (" % // 24 blks/hr * 24 hrs per day - QString::number(std::get<1>(i)) % " blocks, ~" % - QString::number(std::get<0>(i)) % " txns)" - ); - } - - turnstile.buttonBox->button(QDialogButtonBox::Ok)->setText("Start"); - - if (d.exec() == QDialog::Accepted) { - auto privLevel = privOptions[turnstile.privLevel->currentIndex()]; - rpc->getTurnstile()->planMigration( - turnstile.migrateZaddList->currentText(), - turnstile.migrateTo->currentText(), - std::get<0>(privLevel), std::get<1>(privLevel)); - - QMessageBox::information(this, "Backup your wallet.dat", - "The migration will now start. You can check progress in the File -> Sapling Turnstile menu.\n\nYOU MUST BACKUP YOUR wallet.dat NOW!\n\nNew Addresses have been added to your wallet which will be used for the migration.", - QMessageBox::Ok); - } -} void MainWindow::setupTurnstileDialog() { // Turnstile migration QObject::connect(ui->actionTurnstile_Migration, &QAction::triggered, [=] () { // If the underlying zcashd has support for the migration and there is no existing migration // in progress, use that. - if (rpc->getMigrationStatus()->available && !rpc->getTurnstile()->isMigrationPresent()) { + if (rpc->getMigrationStatus()->available) { Turnstile::showZcashdMigration(this); } else { - // Else, show the ZecWallet turnstile tool - - // If there is current migration that is present, show the progress button - if (rpc->getTurnstile()->isMigrationPresent()) - turnstileProgress(); - else - turnstileDoMigration(); + // Else, do nothing } }); @@ -718,109 +527,6 @@ void MainWindow::validateAddress() { } -void MainWindow::postToZBoard() { - QDialog d(this); - Ui_zboard zb; - zb.setupUi(&d); - Settings::saveRestore(&d); - - if (rpc->getConnection() == nullptr) - return; - - // Fill the from field with sapling addresses. - for (auto i = rpc->getModel()->getAllBalances().keyBegin(); i != rpc->getModel()->getAllBalances().keyEnd(); i++) { - if (Settings::getInstance()->isSaplingAddress(*i) && rpc->getModel()->getAllBalances().value(*i) > 0) { - zb.fromAddr->addItem(*i); - } - } - - QMap topics; - // Insert the main topic automatically - topics.insert("#Main_Area", Settings::getInstance()->isTestnet() ? Settings::getDonationAddr() : Settings::getZboardAddr()); - zb.topicsList->addItem(topics.firstKey()); - // Then call the API to get topics, and if it returns successfully, then add the rest of the topics - rpc->getZboardTopics([&](QMap topicsMap) { - for (auto t : topicsMap.keys()) { - topics.insert(t, Settings::getInstance()->isTestnet() ? Settings::getDonationAddr() : topicsMap[t]); - zb.topicsList->addItem(t); - } - }); - - // Testnet warning - if (Settings::getInstance()->isTestnet()) { - zb.testnetWarning->setText(tr("You are on testnet, your post won't actually appear on z-board.net")); - } - else { - zb.testnetWarning->setText(""); - } - - QRegExpValidator v(QRegExp("^[a-zA-Z0-9_]{3,20}$"), zb.postAs); - zb.postAs->setValidator(&v); - - zb.feeAmount->setText(Settings::getZECUSDDisplayFormat(Settings::getZboardAmount() + Settings::getMinerFee())); - - auto fnBuildNameMemo = [=]() -> QString { - auto memo = zb.memoTxt->toPlainText().trimmed(); - if (!zb.postAs->text().trimmed().isEmpty()) - memo = zb.postAs->text().trimmed() + ":: " + memo; - return memo; - }; - - auto fnUpdateMemoSize = [=]() { - QString txt = fnBuildNameMemo(); - zb.memoSize->setText(QString::number(txt.toUtf8().size()) + "/512"); - - if (txt.toUtf8().size() <= 512) { - // Everything is fine - zb.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - zb.memoSize->setStyleSheet(""); - } - else { - // Overweight - zb.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - zb.memoSize->setStyleSheet("color: red;"); - } - - // Disallow blank memos - if (zb.memoTxt->toPlainText().trimmed().isEmpty()) { - zb.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - } - else { - zb.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - } - }; - - // Memo text changed - QObject::connect(zb.memoTxt, &QPlainTextEdit::textChanged, fnUpdateMemoSize); - QObject::connect(zb.postAs, &QLineEdit::textChanged, fnUpdateMemoSize); - - zb.memoTxt->setFocus(); - fnUpdateMemoSize(); - - if (d.exec() == QDialog::Accepted) { - // Create a transaction. - Tx tx; - - // Send from your first sapling address that has a balance. - tx.fromAddr = zb.fromAddr->currentText(); - if (tx.fromAddr.isEmpty()) { - QMessageBox::critical(this, "Error Posting Message", tr("You need a sapling address with available balance to post"), QMessageBox::Ok); - return; - } - - auto memo = zb.memoTxt->toPlainText().trimmed(); - if (!zb.postAs->text().trimmed().isEmpty()) - memo = zb.postAs->text().trimmed() + ":: " + memo; - - auto toAddr = topics[zb.topicsList->currentText()]; - tx.toAddrs.push_back(ToFields{ toAddr, Settings::getZboardAmount(), memo, memo.toUtf8().toHex() }); - tx.fee = Settings::getMinerFee(); - - // And send the Tx - rpc->executeStandardUITransaction(tx); - } -} - void MainWindow::doImport(QList* keys) { if (rpc->getConnection() == nullptr) { // No connection, just return @@ -1095,7 +801,7 @@ void MainWindow::exportKeys(QString addr) { }; if (allKeys) { - rpc->getAllPrivKeys(fnUpdateUIWithKeys); + rpc->fetchAllPrivKeys(fnUpdateUIWithKeys); } else { auto fnAddKey = [=](json key) { @@ -1105,10 +811,10 @@ void MainWindow::exportKeys(QString addr) { }; if (Settings::getInstance()->isZAddress(addr)) { - rpc->getZPrivKey(addr, fnAddKey); + rpc->fetchZPrivKey(addr, fnAddKey); } else { - rpc->getTPrivKey(addr, fnAddKey); + rpc->fetchTPrivKey(addr, fnAddKey); } } @@ -1195,12 +901,6 @@ void MainWindow::setupBalancesTab() { }); } - if (Settings::getInstance()->isSproutAddress(addr)) { - menu.addAction(tr("Migrate to Sapling"), [=] () { - this->turnstileDoMigration(addr); - }); - } - menu.exec(ui->balancesTable->viewport()->mapToGlobal(pos)); }); } @@ -1303,7 +1003,7 @@ void MainWindow::setupTransactionsTab() { } void MainWindow::addNewZaddr(bool sapling) { - rpc->newZaddr(sapling, [=] (json reply) { + rpc->createNewZaddr(sapling, [=] (json reply) { QString addr = QString::fromStdString(reply.get()); // Make sure the RPC class reloads the z-addrs for future use rpc->refreshAddresses(); @@ -1354,7 +1054,7 @@ std::function MainWindow::addZAddrsToComboList(bool sapling) { void MainWindow::setupReceiveTab() { auto addNewTAddr = [=] () { - rpc->newTaddr([=] (json reply) { + rpc->createNewTaddr([=] (json reply) { QString addr = QString::fromStdString(reply.get()); // Make sure the RPC class reloads the t-addrs for future use rpc->refreshAddresses(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 923250a..89a890d 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -102,9 +102,6 @@ private: Tx createTxFromSendPage(); bool confirmTx(Tx tx, RecurringPaymentInfo* rpi); - void turnstileDoMigration(QString fromAddr = ""); - void turnstileProgress(); - void cancelButton(); void sendButton(); void inputComboTextChanged(int index); @@ -124,7 +121,7 @@ private: void donate(); void addressBook(); - void postToZBoard(); + //void postToZBoard(); void importPrivKey(); void exportAllKeys(); void exportKeys(QString addr = ""); diff --git a/src/rpc.cpp b/src/rpc.cpp index 66632c6..fb0f84b 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -18,8 +18,6 @@ RPC::RPC(MainWindow* main) { this->main = main; this->ui = main->ui; - this->turnstile = new Turnstile(this, main); - // Setup balances table model balancesTableModel = new BalancesTableModel(main->ui->balancesTable); main->ui->balancesTable->setModel(balancesTableModel); @@ -54,6 +52,9 @@ RPC::RPC(MainWindow* main) { // Create the data model model = new DataModel(); + // Crate the ZcashdRPC + zrpc = new ZcashdRPC(); + // Initialize the migration status to unavailable. this->migrationStatus.available = false; } @@ -64,11 +65,9 @@ RPC::~RPC() { delete transactionsTableModel; delete balancesTableModel; - delete turnstile; delete model; - - delete conn; + delete zrpc; } void RPC::setEZcashd(QProcess* p) { @@ -83,8 +82,7 @@ void RPC::setEZcashd(QProcess* p) { void RPC::setConnection(Connection* c) { if (c == nullptr) return; - delete conn; - this->conn = c; + this->zrpc->setConnection(c); ui->statusBar->showMessage("Ready!"); @@ -106,254 +104,6 @@ void RPC::setConnection(Connection* c) { refresh(true); } -void RPC::getTAddresses(const std::function& cb) { - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "getaddressesbyaccount"}, - {"params", {""}} - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void RPC::getZAddresses(const std::function& cb) { - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_listaddresses"}, - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void RPC::getTransparentUnspent(const std::function& cb) { - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "listunspent"}, - {"params", {0}} // Get UTXOs with 0 confirmations as well. - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void RPC::getZUnspent(const std::function& cb) { - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_listunspent"}, - {"params", {0}} // Get UTXOs with 0 confirmations as well. - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void RPC::newZaddr(bool sapling, const std::function& cb) { - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_getnewaddress"}, - {"params", { sapling ? "sapling" : "sprout" }}, - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void RPC::newTaddr(const std::function& cb) { - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "getnewaddress"}, - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void RPC::getZPrivKey(QString addr, const std::function& cb) { - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_exportkey"}, - {"params", { addr.toStdString() }}, - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void RPC::getTPrivKey(QString addr, const std::function& cb) { - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "dumpprivkey"}, - {"params", { addr.toStdString() }}, - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void RPC::importZPrivKey(QString addr, bool rescan, const std::function& cb) { - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_importkey"}, - {"params", { addr.toStdString(), (rescan? "yes" : "no") }}, - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - - -void RPC::importTPrivKey(QString addr, bool rescan, const std::function& cb) { - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "importprivkey"}, - {"params", { addr.toStdString(), (rescan? "yes" : "no") }}, - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void RPC::validateAddress(QString address, const std::function& cb) { - 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 RPC::getBalance(const std::function& cb) { - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_gettotalbalance"}, - {"params", {0}} // Get Unconfirmed balance as well. - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void RPC::getTransactions(const std::function& cb) { - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "listtransactions"} - }; - - conn->doRPCWithDefaultErrorHandling(payload, cb); -} - -void RPC::sendZTransaction(json params, const std::function& cb, - const std::function& err) { - 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()); - } - }); -} - -/** - * Method to get all the private keys for both z and t addresses. It will make 2 batch calls, - * combine the result, and call the callback with a single list containing both the t-addr and z-addr - * private keys - */ -void RPC::getAllPrivKeys(const std::function>)> cb) { - if (conn == nullptr) { - // No connection, just return - return; - } - - // A special function that will call the callback when two lists have been added - auto holder = new QPair>>(); - holder->first = 0; // This is the number of times the callback has been called, initialized to 0 - auto fnCombineTwoLists = [=] (QList> list) { - // Increment the callback counter - holder->first++; - - // Add all - std::copy(list.begin(), list.end(), std::back_inserter(holder->second)); - - // And if the caller has been called twice, do the parent callback with the - // collected list - if (holder->first == 2) { - // Sort so z addresses are on top - std::sort(holder->second.begin(), holder->second.end(), - [=] (auto a, auto b) { return a.first > b.first; }); - - cb(holder->second); - delete holder; - } - }; - - // A utility fn to do the batch calling - auto fnDoBatchGetPrivKeys = [=](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"); -} - // Build the RPC JSON Parameters for this tx void RPC::fillTxJsonParams(json& params, Tx tx) { @@ -423,7 +173,7 @@ void RPC::noConnection() { // Refresh received z txs by calling z_listreceivedbyaddress/gettransaction void RPC::refreshReceivedZTrans(QList zaddrs) { - if (conn == nullptr) + if (!zrpc->haveConnection()) return noConnection(); // We'll only refresh the received Z txs if settings allows us. @@ -433,109 +183,19 @@ void RPC::refreshReceivedZTrans(QList zaddrs) { 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 - model->markAddressUsed(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); - } - } - - transactionsTableModel->addZRecvData(txdata); - - // Cleanup both responses; - delete zaddrTxids; - delete txidDetails; - } - ); - } + zrpc->fetchReceivedZTrans(zaddrs, + [=] (QString addr) { + model->markAddressUsed(addr); + }, + [=] (QList txdata) { + transactionsTableModel->addZRecvData(txdata); + } ); } /// This will refresh all the balance data from zcashd void RPC::refresh(bool force) { - if (conn == nullptr) + if (!zrpc->haveConnection()) return noConnection(); getInfoThenRefresh(force); @@ -543,17 +203,12 @@ void RPC::refresh(bool force) { void RPC::getInfoThenRefresh(bool force) { - if (conn == nullptr) + if (!zrpc->haveConnection()) return noConnection(); - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "getinfo"} - }; - static bool prevCallSucceeded = false; - conn->doRPC(payload, [=] (const json& reply) { + + zrpc->fetchInfo([=] (const json& reply) { prevCallSucceeded = true; // Testnet? if (!reply["testnet"].is_null()) { @@ -580,9 +235,6 @@ void RPC::getInfoThenRefresh(bool force) { // Something changed, so refresh everything. lastBlock = curBlock; - // See if the turnstile migration has any steps that need to be done. - turnstile->executeMigrationStep(); - refreshBalances(); refreshAddresses(); // This calls refreshZSentTransactions() and refreshReceivedZTrans() refreshTransactions(); @@ -600,28 +252,14 @@ void RPC::getInfoThenRefresh(bool force) { // Get network sol/s if (ezcashd) { - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "getnetworksolps"} - }; - - conn->doRPCIgnoreError(payload, [=](const json& reply) { - qint64 solrate = reply.get(); - + 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. - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "getblockchaininfo"} - }; - - conn->doRPCIgnoreError(payload, [=](const json& reply) { + zrpc->fetchBlockchainInfo([=](const json& reply) { auto progress = reply["verificationprogress"].get(); bool isSyncing = progress < 0.9999; // 99.99% int blockNumber = reply["blocks"].get(); @@ -707,12 +345,12 @@ void RPC::getInfoThenRefresh(bool force) { } void RPC::refreshAddresses() { - if (conn == nullptr) + if (!zrpc->haveConnection()) return noConnection(); auto newzaddresses = new QList(); - getZAddresses([=] (json reply) { + zrpc->fetchZAddresses([=] (json reply) { for (auto& it : reply.get()) { auto addr = QString::fromStdString(it.get()); newzaddresses->push_back(addr); @@ -727,7 +365,7 @@ void RPC::refreshAddresses() { auto newtaddresses = new QList(); - getTAddresses([=] (json reply) { + zrpc->fetchTAddresses([=] (json reply) { for (auto& it : reply.get()) { auto addr = QString::fromStdString(it.get()); if (Settings::isTAddress(addr)) @@ -777,13 +415,7 @@ void RPC::refreshMigration() { if (Settings::getInstance()->getZcashdVersion() < 2000552) return; - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_getmigrationstatus"}, - }; - - conn->doRPCWithDefaultErrorHandling(payload, [=](json reply) { + zrpc->fetchMigrationStatus([=](json reply) { this->migrationStatus.available = true; this->migrationStatus.enabled = reply["enabled"].get(); this->migrationStatus.saplingAddress = QString::fromStdString(reply["destination_address"]); @@ -798,27 +430,12 @@ void RPC::refreshMigration() { }); } -void RPC::setMigrationStatus(bool enabled) { - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_setmigration"}, - {"params", {enabled}} - }; - - conn->doRPCWithDefaultErrorHandling(payload, [=](json) { - // Ignore return value. - }); -} - - - void RPC::refreshBalances() { - if (conn == nullptr) + if (!zrpc->haveConnection()) return noConnection(); // 1. Get the Balances - getBalance([=] (json reply) { + 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(); @@ -841,10 +458,10 @@ void RPC::refreshBalances() { auto newBalances = new QMap(); // Call the Transparent and Z unspent APIs serially and then, once they're done, update the UI - getTransparentUnspent([=] (json reply) { + zrpc->fetchTransparentUnspent([=] (json reply) { auto anyTUnconfirmed = processUnspent(reply, newBalances, newUtxos); - getZUnspent([=] (json reply) { + zrpc->fetchZUnspent([=] (json reply) { auto anyZUnconfirmed = processUnspent(reply, newBalances, newUtxos); // Swap out the balances and UTXOs @@ -859,10 +476,10 @@ void RPC::refreshBalances() { } void RPC::refreshTransactions() { - if (conn == nullptr) + if (!zrpc->haveConnection()) return noConnection(); - getTransactions([=] (json reply) { + zrpc->fetchTransactions([=] (json reply) { QList txdata; for (auto& it : reply.get()) { @@ -894,7 +511,7 @@ void RPC::refreshTransactions() { // Read sent Z transactions from the file. void RPC::refreshSentZTrans() { - if (conn == nullptr) + if (!zrpc->haveConnection()) return noConnection(); auto sentZTxs = SentTxStore::readSentTxFile(); @@ -913,36 +530,9 @@ void RPC::refreshSentZTrans() { } // 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(); - } - - transactionsTableModel->addZSentData(newSentZTxs); - delete txidList; - } - ); + zrpc->fetchReceivedTTrans(txids, sentZTxs, [=](auto newSentZTxs) { + transactionsTableModel->addZSentData(newSentZTxs); + }); } void RPC::addNewTxToWatch(const QString& newOpid, WatchedTx wtx) { @@ -985,7 +575,7 @@ void RPC::executeTransaction(Tx tx, fillTxJsonParams(params, tx); std::cout << std::setw(2) << params << std::endl; - sendZTransaction(params, [=](const json& reply) { + zrpc->sendZTransaction(params, [=](const json& reply) { QString opid = QString::fromStdString(reply.get()); // And then start monitoring the transaction @@ -999,17 +589,10 @@ void RPC::executeTransaction(Tx tx, void RPC::watchTxStatus() { - if (conn == nullptr) + if (!zrpc->haveConnection()) return noConnection(); - // Make an RPC to load pending operation statues - json payload = { - {"jsonrpc", "1.0"}, - {"id", "someid"}, - {"method", "z_getoperationstatus"}, - }; - - conn->doRPCIgnoreError(payload, [=] (const json& reply) { + 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 @@ -1058,7 +641,7 @@ void RPC::watchTxStatus() { } void RPC::checkForUpdate(bool silent) { - if (conn == nullptr) + if (!zrpc->haveConnection()) return noConnection(); QUrl cmcURL("https://api.github.com/repos/ZcashFoundation/zecwallet/releases"); @@ -1066,7 +649,7 @@ void RPC::checkForUpdate(bool silent) { QNetworkRequest req; req.setUrl(cmcURL); - QNetworkReply *reply = conn->restclient->get(req); + QNetworkReply *reply = getConnection()->restclient->get(req); QObject::connect(reply, &QNetworkReply::finished, [=] { reply->deleteLater(); @@ -1130,7 +713,7 @@ void RPC::checkForUpdate(bool silent) { // Get the ZEC->USD price from coinmarketcap using their API void RPC::refreshZECPrice() { - if (conn == nullptr) + if (!zrpc->haveConnection()) return noConnection(); QUrl cmcURL("https://api.coinmarketcap.com/v1/ticker/"); @@ -1138,7 +721,7 @@ void RPC::refreshZECPrice() { QNetworkRequest req; req.setUrl(cmcURL); - QNetworkReply *reply = conn->restclient->get(req); + QNetworkReply *reply = getConnection()->restclient->get(req); QObject::connect(reply, &QNetworkReply::finished, [=] { reply->deleteLater(); @@ -1184,7 +767,7 @@ void RPC::refreshZECPrice() { void RPC::shutdownZcashd() { // Shutdown embedded zcashd if it was started - if (ezcashd == nullptr || ezcashd->processId() == 0 || conn == nullptr) { + if (ezcashd == nullptr || ezcashd->processId() == 0 || ~zrpc->haveConnection()) { // No zcashd running internally, just return return; } @@ -1195,8 +778,8 @@ void RPC::shutdownZcashd() { {"method", "stop"} }; - conn->doRPCWithDefaultErrorHandling(payload, [=](auto) {}); - conn->shutdown(); + getConnection()->doRPCWithDefaultErrorHandling(payload, [=](auto) {}); + getConnection()->shutdown(); QDialog d(main); Ui_ConnectionDialog connD; @@ -1215,7 +798,7 @@ void RPC::shutdownZcashd() { if ((ezcashd->atEnd() && ezcashd->processId() == 0) || waitCount > 30 || - conn->config->zcashDaemon) { // If zcashd is daemon, then we don't have to do anything else + 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(); }); @@ -1238,59 +821,59 @@ void RPC::shutdownZcashd() { } -// Fetch the Z-board topics list -void RPC::getZboardTopics(std::function)> cb) { - if (conn == nullptr) - return noConnection(); +// // Fetch the Z-board topics list +// void RPC::getZboardTopics(std::function)> cb) { +// if (!zrpc->haveConnection()) +// return noConnection(); - QUrl cmcURL("http://z-board.net/listTopics"); +// QUrl cmcURL("http://z-board.net/listTopics"); - QNetworkRequest req; - req.setUrl(cmcURL); +// QNetworkRequest req; +// req.setUrl(cmcURL); - QNetworkReply *reply = conn->restclient->get(req); +// QNetworkReply *reply = conn->restclient->get(req); - QObject::connect(reply, &QNetworkReply::finished, [=] { - reply->deleteLater(); +// 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; - } +// 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 all = reply->readAll(); - auto parsed = json::parse(all, nullptr, false); - if (parsed.is_discarded()) { - return; - } +// 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; +// 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()); +// 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"); - } - }); -} +// 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/rpc.h b/src/rpc.h index da3086f..6ceb10c 100644 --- a/src/rpc.h +++ b/src/rpc.h @@ -8,23 +8,11 @@ #include "txtablemodel.h" #include "ui_mainwindow.h" #include "mainwindow.h" +#include "zcashdrpc.h" #include "connection.h" using json = nlohmann::json; -class Turnstile; - -struct TransactionItem { - QString type; - qint64 datetime; - QString address; - QString txid; - double amount; - long confirmations; - QString fromAddr; - QString memo; -}; - struct WatchedTx { QString opid; Tx tx; @@ -49,19 +37,18 @@ public: DataModel* getModel() { return model; } + Connection* getConnection() { return zrpc->getConnection(); } void setConnection(Connection* c); + void setEZcashd(QProcess* p); const QProcess* getEZcashD() { return ezcashd; } void refresh(bool force = false); - void refreshAddresses(); - - void checkForUpdate(bool silent = true); void refreshZECPrice(); - void getZboardTopics(std::function)> cb); + //void getZboardTopics(std::function)> cb); void executeStandardUITransaction(Tx tx); @@ -71,7 +58,7 @@ public: const std::function error); void fillTxJsonParams(json& params, Tx tx); - void sendZTransaction(json params, const std::function& cb, const std::function& err); + void watchTxStatus(); const QMap getWatchingTxns() { return watchingOps; } @@ -79,30 +66,28 @@ public: const TxTableModel* getTransactionsModel() { return transactionsTableModel; } - 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); - void validateAddress(QString address, const std::function& cb); - void shutdownZcashd(); void noConnection(); bool isEmbedded() { return ezcashd != nullptr; } - QString getDefaultSaplingAddress(); - QString getDefaultTAddress(); + void createNewZaddr(bool sapling, const std::function& cb) { zrpc->createNewZaddr(sapling, cb); } + void createNewTaddr(const std::function& cb) { zrpc->createNewTaddr(cb); } - void getAllPrivKeys(const std::function>)>); + void validateAddress(QString address, const std::function& cb) { zrpc->validateAddress(address, cb); } - Turnstile* getTurnstile() { return turnstile; } - Connection* getConnection() { return conn; } + void fetchZPrivKey(QString addr, const std::function& cb) { zrpc->fetchZPrivKey(addr, cb); } + void fetchTPrivKey(QString addr, const std::function& cb) { zrpc->fetchTPrivKey(addr, cb); } + void fetchAllPrivKeys(const std::function>)> cb) { zrpc->fetchAllPrivKeys(cb); } - const MigrationStatus* getMigrationStatus() { return &migrationStatus; } - void setMigrationStatus(bool enabled); + void importZPrivKey(QString addr, bool rescan, const std::function& cb) { zrpc->importZPrivKey(addr, rescan, cb); } + void importTPrivKey(QString addr, bool rescan, const std::function& cb) { zrpc->importTPrivKey(addr, rescan, cb); } + QString getDefaultSaplingAddress(); + QString getDefaultTAddress(); + + const MigrationStatus* getMigrationStatus() { return &migrationStatus; } + void setMigrationStatus(bool status) { zrpc->setMigrationStatus(status); } + private: void refreshBalances(); @@ -115,16 +100,7 @@ private: void updateUI (bool anyUnconfirmed); void getInfoThenRefresh(bool force); - - void getBalance(const std::function& cb); - - void getTransparentUnspent (const std::function& cb); - void getZUnspent (const std::function& cb); - void getTransactions (const std::function& cb); - void getZAddresses (const std::function& cb); - void getTAddresses (const std::function& cb); - - Connection* conn = nullptr; + QProcess* ezcashd = nullptr; QMap watchingOps; @@ -133,6 +109,7 @@ private: BalancesTableModel* balancesTableModel = nullptr; DataModel* model; + ZcashdRPC* zrpc; QTimer* timer; QTimer* txTimer; @@ -140,7 +117,6 @@ private: Ui::MainWindow* ui; MainWindow* main; - Turnstile* turnstile; // Sapling turnstile migration status (for the zcashd v2.0.5 tool) MigrationStatus migrationStatus; diff --git a/src/zcashdrpc.cpp b/src/zcashdrpc.cpp new file mode 100644 index 0000000..ded1261 --- /dev/null +++ b/src/zcashdrpc.cpp @@ -0,0 +1,479 @@ +#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) { + json payload = { + {"jsonrpc", "1.0"}, + {"id", "someid"}, + {"method", "getaddressesbyaccount"}, + {"params", {""}} + }; + + conn->doRPCWithDefaultErrorHandling(payload, cb); +} + +void ZcashdRPC::fetchZAddresses(const std::function& cb) { + json payload = { + {"jsonrpc", "1.0"}, + {"id", "someid"}, + {"method", "z_listaddresses"}, + }; + + conn->doRPCWithDefaultErrorHandling(payload, cb); +} + +void ZcashdRPC::fetchTransparentUnspent(const std::function& cb) { + 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) { + 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) { + 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) { + json payload = { + {"jsonrpc", "1.0"}, + {"id", "someid"}, + {"method", "getnewaddress"}, + }; + + conn->doRPCWithDefaultErrorHandling(payload, cb); +} + +void ZcashdRPC::fetchZPrivKey(QString addr, const std::function& cb) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + json payload = { + {"jsonrpc", "1.0"}, + {"id", "someid"}, + {"method", "getinfo"} + }; + + conn->doRPC(payload, cb, err); +} + +void ZcashdRPC::fetchBlockchainInfo(const std::function& cb) { + json payload = { + {"jsonrpc", "1.0"}, + {"id", "someid"}, + {"method", "getblockchaininfo"} + }; + + conn->doRPCIgnoreError(payload, cb); +} + +void ZcashdRPC::fetchNetSolOps(const std::function cb) { + 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) { + json payload = { + {"jsonrpc", "1.0"}, + {"id", "someid"}, + {"method", "z_getmigrationstatus"}, + }; + + conn->doRPCWithDefaultErrorHandling(payload, cb); +} + + +void ZcashdRPC::setMigrationStatus(bool enabled) { + 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) { + // 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) { + // 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) { + + // 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; + } + ); + } + ); +} \ No newline at end of file diff --git a/src/zcashdrpc.h b/src/zcashdrpc.h new file mode 100644 index 0000000..0e10501 --- /dev/null +++ b/src/zcashdrpc.h @@ -0,0 +1,70 @@ +#ifndef ZCASHDRPC_H +#define ZCASHDRPC_H + +#include "precompiled.h" + +#include "connection.h" + +using json = nlohmann::json; + +struct TransactionItem { + QString type; + qint64 datetime; + QString address; + QString txid; + double amount; + long confirmations; + QString fromAddr; + QString memo; +}; + + +class ZcashdRPC { +public: + ZcashdRPC(); + ~ZcashdRPC(); + + bool haveConnection(); + void setConnection(Connection* c); + Connection* getConnection() { return conn; } + + void fetchTransparentUnspent (const std::function& cb); + void fetchZUnspent (const std::function& cb); + void fetchTransactions (const std::function& cb); + void fetchZAddresses (const std::function& cb); + void fetchTAddresses (const std::function& cb); + + void fetchReceivedZTrans(QList zaddrs, const std::function usedAddrFn, + const std::function)> txdataFn); + void fetchReceivedTTrans(QList txids, QList sentZtxs, + const std::function)> txdataFn); + + void fetchInfo(const std::function& cb, + const std::function& err); + void fetchBlockchainInfo(const std::function& cb); + void fetchNetSolOps(const std::function cb); + void fetchOpStatus(const std::function& cb); + + void fetchMigrationStatus(const std::function& cb); + void setMigrationStatus(bool enabled); + + void fetchBalance(const std::function& cb); + + void createNewZaddr(bool sapling, const std::function& cb); + void createNewTaddr(const std::function& cb); + + void fetchZPrivKey(QString addr, const std::function& cb); + void fetchTPrivKey(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); + void validateAddress(QString address, const std::function& cb); + + void fetchAllPrivKeys(const std::function>)>); + + void sendZTransaction(json params, const std::function& cb, const std::function& err); + +private: + Connection* conn = nullptr; +}; + +#endif // ZCASHDRPC_H diff --git a/zec-qt-wallet.pro b/zec-qt-wallet.pro index 35fbcd0..61ea805 100644 --- a/zec-qt-wallet.pro +++ b/zec-qt-wallet.pro @@ -62,7 +62,8 @@ SOURCES += \ src/memoedit.cpp \ src/viewalladdresses.cpp \ src/datamodel.cpp \ - src/controller.cpp + src/controller.cpp \ + src/zcashdrpc.cpp HEADERS += \ src/mainwindow.h \ @@ -91,7 +92,8 @@ HEADERS += \ src/memoedit.h \ src/viewalladdresses.h \ src/datamodel.h \ - src/controller.h + src/controller.h \ + src/zcashdrpc.h FORMS += \ src/mainwindow.ui \