diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index fb050d3..c9bd985 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -381,11 +381,17 @@ void MainWindow::turnstileDoMigration(QString fromAddr) { void MainWindow::setupTurnstileDialog() { // Turnstile migration QObject::connect(ui->actionTurnstile_Migration, &QAction::triggered, [=] () { - // If there is current migration that is present, show the progress button - if (rpc->getTurnstile()->isMigrationPresent()) - turnstileProgress(); - else - turnstileDoMigration(); + // If the underlying zcashd has support for the migration, use that. + // Else, show the ZecWallet turnstile tool + if (rpc->getMigrationStatus()->available) { + Turnstile::showZcashdMigration(this); + } else { + // If there is current migration that is present, show the progress button + if (rpc->getTurnstile()->isMigrationPresent()) + turnstileProgress(); + else + turnstileDoMigration(); + } }); } diff --git a/src/migration.ui b/src/migration.ui new file mode 100644 index 0000000..3addf20 --- /dev/null +++ b/src/migration.ui @@ -0,0 +1,139 @@ + + + MigrationDialog + + + + 0 + 0 + 511 + 498 + + + + Migration Turnstile + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Migration History + + + + + + Migrated Amount + + + + + + + TextLabel + + + + + + + true + + + + + + + TextLabel + + + + + + + Unmigrated Amount + + + + + + + + + + Sprout -> Sapling migration enabled + + + + + + + If enabled, zcashd will slowly migrate your Sprout shielded funds to your Sapling address. + + + true + + + + + + + Qt::Horizontal + + + + + + + + + + + + + + + + buttonBox + accepted() + MigrationDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MigrationDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/rpc.cpp b/src/rpc.cpp index 761ffc1..2c207cd 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -52,6 +52,9 @@ RPC::RPC(MainWindow* main) { txTimer->start(Settings::updateSpeed); usedAddresses = new QMap(); + + // Initialize the migration status to unavailable. + this->migrationStatus.available = false; } RPC::~RPC() { @@ -568,8 +571,9 @@ void RPC::getInfoThenRefresh(bool force) { turnstile->executeMigrationStep(); refreshBalances(); - refreshAddresses(); // This calls refreshZSentTransactions() and refreshReceivedZTrans() + refreshAddresses(); // This calls refreshZSentTransactions() and refreshReceivedZTrans() refreshTransactions(); + refreshMigration(); // Sapling turnstile migration status. } int connections = reply["connections"].get(); @@ -755,6 +759,49 @@ bool RPC::processUnspent(const json& reply, QMap* balancesMap, return anyUnconfirmed; }; +/** + * Refresh the turnstile migration status + */ +void RPC::refreshMigration() { + // Turnstile migration is only supported in zcashd v2.0.5 and above + if (Settings::getInstance()->getZcashdVersion() < 2000552) + return; + + json payload = { + {"jsonrpc", "1.0"}, + {"id", "someid"}, + {"method", "z_getmigrationstatus"}, + }; + + conn->doRPCWithDefaultErrorHandling(payload, [=](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(); + + this->migrationStatus.txids.empty(); + for (auto& it : reply["migration_txids"].get()) { + this->migrationStatus.txids.push_back(QString::fromStdString(it.get())); + } + }); +} + +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) return noConnection(); diff --git a/src/rpc.h b/src/rpc.h index 7cc6610..bd8bd11 100644 --- a/src/rpc.h +++ b/src/rpc.h @@ -31,6 +31,15 @@ struct WatchedTx { std::function error; }; +struct MigrationStatus { + bool available; // Whether the underlying zcashd supports migration? + bool enabled; + QString saplingAddress; + double unmigrated; + double migrated; + QList txids; +}; + class RPC { public: @@ -88,10 +97,14 @@ public: Turnstile* getTurnstile() { return turnstile; } Connection* getConnection() { return conn; } + const MigrationStatus* getMigrationStatus() { return &migrationStatus; } + void setMigrationStatus(bool enabled); + private: void refreshBalances(); void refreshTransactions(); + void refreshMigration(); void refreshSentZTrans(); void refreshReceivedZTrans(QList zaddresses); @@ -108,6 +121,7 @@ private: void getZAddresses (const std::function& cb); void getTAddresses (const std::function& cb); + Connection* conn = nullptr; QProcess* ezcashd = nullptr; @@ -130,6 +144,9 @@ private: MainWindow* main; Turnstile* turnstile; + // Sapling turnstile migration status (for the zcashd v2.0.5 tool) + MigrationStatus migrationStatus; + // Current balance in the UI. If this number updates, then refresh the UI QString currentBalance; }; diff --git a/src/turnstile.cpp b/src/turnstile.cpp index f05940b..96d3b3a 100644 --- a/src/turnstile.cpp +++ b/src/turnstile.cpp @@ -3,6 +3,8 @@ #include "balancestablemodel.h" #include "rpc.h" #include "settings.h" +#include "ui_migration.h" + using json = nlohmann::json; @@ -375,3 +377,91 @@ void Turnstile::doSendTx(Tx tx, std::function cb) { }); } + + +// Methods for zcashd native Migration +void Turnstile::showZcashdMigration(MainWindow* parent) { + // If it is not enabled, don't show the dialog + if (! parent->getRPC()->getMigrationStatus()->available) + return; + + Ui_MigrationDialog md; + QDialog d(parent); + md.setupUi(&d); + Settings::saveRestore(&d); + + MigrationTxns model(md.tblTxids, parent->getRPC()->getMigrationStatus()->txids); + md.tblTxids->setModel(&model); + + // Table right click + md.tblTxids->setContextMenuPolicy(Qt::CustomContextMenu); + QObject::connect(md.tblTxids, &QTableView::customContextMenuRequested, [=, &model] (QPoint pos) { + QModelIndex index = md.tblTxids->indexAt(pos); + if (index.row() < 0) return; + + QMenu menu(parent); + QString txid = model.getTxid(index.row()); + + menu.addAction(QObject::tr("View on block explorer"), [=] () { + QString url; + if (Settings::getInstance()->isTestnet()) { + url = "https://explorer.testnet.z.cash/tx/" + txid; + } else { + url = "https://explorer.zcha.in/transactions/" + txid; + } + QDesktopServices::openUrl(QUrl(url)); + }); + }); + + auto* status = parent->getRPC()->getMigrationStatus(); + + md.chkEnabled->setChecked(status->enabled); + md.lblSaplingAddress->setText(status->saplingAddress); + md.lblUnMigrated->setText(Settings::getZECDisplayFormat(status->unmigrated)); + md.lblMigrated->setText(Settings::getZECDisplayFormat(status->migrated)); + + if (d.exec() == QDialog::Accepted) { + // Update the migration status if it changed + if (md.chkEnabled->isChecked() != status->enabled) { + parent->getRPC()->setMigrationStatus(md.chkEnabled->isChecked()); + } + } +} + + +MigrationTxns::MigrationTxns(QTableView *parent, QList txids) + : QAbstractTableModel(parent) { + headers << tr("Txid"); + this->txids = txids; +} + + +int MigrationTxns::rowCount(const QModelIndex&) const { + return txids.size(); +} + +int MigrationTxns::columnCount(const QModelIndex&) const { + return headers.size(); +} + +QString MigrationTxns::getTxid(int row) const { + return txids.at(row); +} + +QVariant MigrationTxns::data(const QModelIndex &index, int role) const { + if (role == Qt::DisplayRole) { + switch(index.column()) { + case 0: return txids.at(index.row()); + } + } + return QVariant(); +} + + +QVariant MigrationTxns::headerData(int section, Qt::Orientation orientation, int role) const { + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { + return headers.at(section); + } + + return QVariant(); +} diff --git a/src/turnstile.h b/src/turnstile.h index dfa5dae..19643c3 100644 --- a/src/turnstile.h +++ b/src/turnstile.h @@ -52,6 +52,8 @@ public: ProgressReport getPlanProgress(); bool isMigrationPresent(); + static void showZcashdMigration(MainWindow* parent); + static double minMigrationAmount; private: QList getBlockNumbers(int start, int end, int count); @@ -66,4 +68,27 @@ private: MainWindow* mainwindow; }; + +// Classes for zcashd 2.0.5 native migration + + +class MigrationTxns : public QAbstractTableModel { + +public: + MigrationTxns(QTableView* parent, QList txids); + ~MigrationTxns() = default; + + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + + QString getTxid(int row) const; + +private: + QList txids; + QStringList headers; +}; + + #endif diff --git a/zec-qt-wallet.pro b/zec-qt-wallet.pro index f88fe01..8ae6977 100644 --- a/zec-qt-wallet.pro +++ b/zec-qt-wallet.pro @@ -86,6 +86,7 @@ HEADERS += \ FORMS += \ src/mainwindow.ui \ + src/migration.ui \ src/recurringpayments.ui \ src/settings.ui \ src/about.ui \