diff --git a/src/main.cpp b/src/main.cpp index 76b6873..c86c2af 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,7 @@ #include "mainwindow.h" #include "settings.h" +#include "turnstile.h" + #include "precompiled.h" int main(int argc, char *argv[]) @@ -16,6 +18,8 @@ int main(int argc, char *argv[]) qApp->setFont(QFont("Ubuntu", 11, QFont::Normal, false)); #endif + std::srand(std::time(nullptr)); + QCoreApplication::setOrganizationName("zec-qt-wallet-org"); QCoreApplication::setApplicationName("zec-qt-wallet"); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6ffe036..fba7d67 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2,10 +2,13 @@ #include "ui_mainwindow.h" #include "ui_about.h" #include "ui_settings.h" +#include "ui_turnstile.h" +#include "ui_turnstileprogress.h" #include "rpc.h" #include "balancestablemodel.h" #include "settings.h" #include "utils.h" +#include "turnstile.h" #include "senttxstore.h" #include "precompiled.h" @@ -56,6 +59,7 @@ MainWindow::MainWindow(QWidget *parent) : setupTransactionsTab(); setupRecieveTab(); setupBalancesTab(); + setupTurnstileDialog(); rpc = new RPC(new QNetworkAccessManager(this), this); rpc->refreshZECPrice(); @@ -63,6 +67,178 @@ MainWindow::MainWindow(QWidget *parent) : rpc->refresh(true); // Force refresh first time } +void MainWindow::turnstileProgress() { + Ui_TurnstileProgress progress; + QDialog d(this); + progress.setupUi(&d); + + QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning); + progress.msgIcon->setPixmap(icon.pixmap(64, 64)); + + auto fnUpdateProgressUI = [=] () { + // 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(); + + if (curProgress.step == curProgress.totalSteps) { + 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\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(Utils::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); + 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."); + } + } + }); + + auto accpeted = d.exec(); + if (accpeted == QDialog::Accepted && curProgress.step == curProgress.totalSteps) { + // Finished, so delete the file + rpc->getTurnstile()->removeFile(); + } +} + +void MainWindow::turnstileDoMigration() { + Ui_Turnstile turnstile; + QDialog d(this); + turnstile.setupUi(&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->getAllZAddresses()) { + if (Settings::getInstance()->isSproutAddress(addr)) { + bal += rpc->getAllBalances()->value(addr); + } + } + + return bal; + }; + + //turnstile.migrateZaddList->addItem("All Sprout z-Addrs"); + turnstile.fromBalance->setText(Settings::getInstance()->getZECUSDDisplayFormat(fnGetAllSproutBalance())); + for (auto addr : *rpc->getAllZAddresses()) { + if (Settings::getInstance()->isSaplingAddress(addr)) { + turnstile.migrateTo->addItem(addr); + } else { + if (rpc->getAllBalances()->value(addr) > 0) + turnstile.migrateZaddList->addItem(addr); + } + } + + auto fnUpdateSproutBalance = [=] (QString addr) { + double bal = 0; + if (addr.startsWith("All")) { + bal = fnGetAllSproutBalance(); + } else { + bal = rpc->getAllBalances()->value(addr); + } + + auto balTxt = Settings::getInstance()->getZECUSDDisplayFormat(bal); + + if (bal < Turnstile::minMigrationAmount) { + turnstile.fromBalance->setStyleSheet("color: red;"); + turnstile.fromBalance->setText(balTxt % " [You need at least " + % Settings::getInstance()->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); + } + }; + + 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(QPair(3, 6)); + privOptions.push_back(QPair(5, 10)); + privOptions.push_back(QPair(10, 20)); + + QObject::connect(turnstile.privLevel, QOverload::of(&QComboBox::currentIndexChanged), [=] (auto idx) { + // Update the fees + turnstile.minerFee->setText( + Settings::getInstance()->getZECUSDDisplayFormat(privOptions[idx].first * Utils::getMinerFee())); + }); + + turnstile.privLevel->addItem("Good - 3 tx over 6 blocks"); + turnstile.privLevel->addItem("Excellent - 5 tx over 10 blocks"); + turnstile.privLevel->addItem("Paranoid - 10 tx over 20 blocks"); + + 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(), + privLevel.first, privLevel.second); + + 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 there is current migration that is present, show the progress button + if (rpc->getTurnstile()->isMigrationPresent()) + turnstileProgress(); + else + turnstileDoMigration(); + }); + +} + void MainWindow::setupStatusBar() { // Status Bar loadingLabel = new QLabel(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 9ef337c..19874d6 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -48,6 +48,7 @@ private: void setupRecieveTab(); void setupBalancesTab(); + void setupTurnstileDialog(); void setupSettingsModal(); void setupStatusBar(); @@ -56,7 +57,9 @@ private: Tx createTxFromSendPage(); bool confirmTx(Tx tx, ToFields devFee); - void fillTxJsonParams(json& params, Tx tx); + + void turnstileDoMigration(); + void turnstileProgress(); void cancelButton(); void sendButton(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 0a3e11d..111fe9c 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -22,7 +22,7 @@ - 1 + 0 @@ -362,6 +362,9 @@ 0 + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Amount @@ -723,6 +726,7 @@ File + @@ -772,6 +776,11 @@ Check github.com for Updates + + + Sapling Turnstile + + diff --git a/src/precompiled.h b/src/precompiled.h index c3e837b..8c6b605 100644 --- a/src/precompiled.h +++ b/src/precompiled.h @@ -2,6 +2,10 @@ /* Add C++ includes here */ #include +#include +#include +#include +#include #include #include diff --git a/src/rpc.cpp b/src/rpc.cpp index 41961ae..f8c6fe2 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -2,6 +2,7 @@ #include "utils.h" #include "settings.h" #include "senttxstore.h" +#include "turnstile.h" using json = nlohmann::json; @@ -10,6 +11,8 @@ RPC::RPC(QNetworkAccessManager* client, 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); @@ -46,6 +49,7 @@ RPC::RPC(QNetworkAccessManager* client, MainWindow* main) { }); // Start at every 10s. When an operation is pending, this will change to every second txTimer->start(Utils::updateSpeed); + } RPC::~RPC() { @@ -54,6 +58,7 @@ RPC::~RPC() { delete transactionsTableModel; delete balancesTableModel; + delete turnstile; delete utxos; delete allBalances; @@ -285,53 +290,32 @@ void RPC::handleTxError(const QString& error) { } -void RPC::getBatchRPC( - const QList& payloads, - std::function payloadGenerator, - std::function*)> cb) -{ - auto responses = new QMap(); // zAddr -> list of responses for each call. - int totalSize = payloads.size(); +// Build the RPC JSON Parameters for this tx (with the dev fee included if applicable) +void RPC::fillTxJsonParams(json& params, Tx tx) { + Q_ASSERT(params.is_array()); + // Get all the addresses and amounts + json allRecepients = json::array(); - for (auto item: payloads) { - json payload = payloadGenerator(item); - - QNetworkReply *reply = restclient->post(request, QByteArray::fromStdString(payload.dump())); + // 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]; - QObject::connect(reply, &QNetworkReply::finished, [=] { - reply->deleteLater(); - - auto all = reply->readAll(); - auto parsed = json::parse(all.toStdString(), nullptr, false); + // Construct the JSON params + json rec = json::object(); + rec["address"] = toAddr.addr.toStdString(); + rec["amount"] = QString::number(toAddr.amount, 'f', 8).toDouble(); // Force it through string for rounding + if (toAddr.addr.startsWith("z") && !toAddr.encodedMemo.trimmed().isEmpty()) + rec["memo"] = toAddr.encodedMemo.toStdString(); - 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"]; - } - } - }); + allRecepients.push_back(rec); } - auto waitTimer = new QTimer(main); - QObject::connect(waitTimer, &QTimer::timeout, [=]() { - if (responses->size() == totalSize) { - waitTimer->stop(); - - cb(responses); - - waitTimer->deleteLater(); - } - }); - waitTimer->start(100); + // Add sender + params.push_back(tx.fromAddr.toStdString()); + params.push_back(allRecepients); } + // Refresh received z txs by calling z_listreceivedbyaddress/gettransaction void RPC::refreshReceivedZTrans(QList zaddrs) { @@ -342,14 +326,13 @@ 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 - getBatchRPC(zaddrs, + getBatchRPC(zaddrs, [=] (QString zaddr) { json payload = { {"jsonrpc", "1.0"}, @@ -386,7 +369,7 @@ void RPC::refreshReceivedZTrans(QList zaddrs) { } // 2. For all txids, go and get the details of that txid. - getBatchRPC(txids.toList(), + getBatchRPC(txids.toList(), [=] (QString txid) { json payload = { {"jsonrpc", "1.0"}, @@ -527,6 +510,14 @@ void RPC::refreshAddresses() { // Function to create the data model and update the views, used below. void RPC::updateUI(bool anyUnconfirmed) { + if (Settings::getInstance()->isTestnet()) { + // See if the turnstile migration has any steps that need to be done. + turnstile->executeMigrationStep(); + } else { + // Not available on mainnet yet. + main->ui->actionTurnstile_Migration->setVisible(false); + } + ui->unconfirmedWarning->setVisible(anyUnconfirmed); // Update balances model data, which will update the table too @@ -557,13 +548,9 @@ bool RPC::processUnspent(const json& reply) { } utxos->push_back( - UnspentOutput( - qsAddr, - QString::fromStdString(it["txid"]), - QString::number(it["amount"].get(), 'g', 8), - confirmations - ) - ); + UnspentOutput{ qsAddr, QString::fromStdString(it["txid"]), + QString::number(it["amount"].get(), 'g', 8), + (int)confirmations, it["spendable"].get() }); (*allBalances)[qsAddr] = (*allBalances)[qsAddr] + it["amount"].get(); } @@ -643,7 +630,7 @@ void RPC::refreshSentZTrans() { } // Look up all the txids to get the confirmation count for them. - getBatchRPC(txids, + getBatchRPC(txids, [=] (QString txid) { json payload = { {"jsonrpc", "1.0"}, diff --git a/src/rpc.h b/src/rpc.h index b0e89ef..bbabb75 100644 --- a/src/rpc.h +++ b/src/rpc.h @@ -11,6 +11,7 @@ using json = nlohmann::json; +class Turnstile; struct TransactionItem { QString type; @@ -34,6 +35,7 @@ public: void refreshAddresses(); void refreshZECPrice(); + void fillTxJsonParams(json& params, Tx tx); void sendZTransaction (json params, const std::function& cb); void watchTxStatus(); void addNewTxToWatch(Tx tx, const QString& newOpid); @@ -45,9 +47,60 @@ public: void reloadConnectionInfo(); - void newZaddr (bool sapling, const std::function& cb); - void newTaddr (const std::function& cb); + void newZaddr(bool sapling, const std::function& cb); + void newTaddr(const std::function& cb); + + Turnstile* getTurnstile() { return turnstile; } + // Batch method. Note: Because of the template, it has to be in the header file. + template + void getBatchRPC(const QList& payloads, + std::function payloadGenerator, + std::function*)> cb) { + auto responses = new QMap(); // zAddr -> list of responses for each call. + int totalSize = payloads.size(); + + for (auto item: payloads) { + json payload = payloadGenerator(item); + + QNetworkReply *reply = restclient->post(request, QByteArray::fromStdString(payload.dump())); + + QObject::connect(reply, &QNetworkReply::finished, [=] { + reply->deleteLater(); + + auto all = reply->readAll(); + auto parsed = json::parse(all.toStdString(), nullptr, false); + + if (reply->error() != QNetworkReply::NoError) { + qDebug() << QString::fromStdString(parsed.dump()); + qDebug() << reply->errorString(); + + (*responses)[item] = json::object(); // Empty object + } else { + if (parsed.is_discarded()) { + (*responses)[item] = json::object(); // Empty object + } else { + (*responses)[item] = parsed["result"]; + } + } + }); + } + + auto waitTimer = new QTimer(main); + QObject::connect(waitTimer, &QTimer::timeout, [=]() { + if (responses->size() == totalSize) { + waitTimer->stop(); + + cb(responses); + + waitTimer->deleteLater(); + } + }); + waitTimer->start(100); + } + + + private: void doRPC (const json& payload, const std::function& cb); void doSendRPC (const json& payload, const std::function& cb); @@ -73,11 +126,6 @@ private: void handleConnectionError (const QString& error); void handleTxError (const QString& error); - // Batch - void getBatchRPC(const QList& payloads, - std::function payloadGenerator, - std::function*)> cb); - QNetworkAccessManager* restclient; QNetworkRequest request; @@ -96,6 +144,7 @@ private: Ui::MainWindow* ui; MainWindow* main; + Turnstile* turnstile; // Current balance in the UI. If this number updates, then refresh the UI QString currentBalance; diff --git a/src/sendtab.cpp b/src/sendtab.cpp index f4cae7d..5ffaea0 100644 --- a/src/sendtab.cpp +++ b/src/sendtab.cpp @@ -8,8 +8,6 @@ #include "precompiled.h" -#include -#include using json = nlohmann::json; void MainWindow::setupSendTab() { @@ -474,30 +472,6 @@ bool MainWindow::confirmTx(Tx tx, ToFields devFee) { } } -// Build the RPC JSON Parameters for this tx (with the dev fee included if applicable) -void MainWindow::fillTxJsonParams(json& params, Tx tx) { - // 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(); - rec["amount"] = toAddr.amount; - if (toAddr.addr.startsWith("z") && !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); -} - // Send button clicked void MainWindow::sendButton() { Tx tx = createTxFromSendPage(); @@ -522,7 +496,7 @@ void MainWindow::sendButton() { tx.toAddrs.push_back(devFee); json params = json::array(); - fillTxJsonParams(params, tx); + rpc->fillTxJsonParams(params, tx); std::cout << std::setw(2) << params << std::endl; // And send the Tx diff --git a/src/settings.cpp b/src/settings.cpp index 8c6e6f1..6f90042 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -168,6 +168,14 @@ bool Settings::isSaplingAddress(QString addr) { (!isTestnet() && addr.startsWith("zs")); } +bool Settings::isSproutAddress(QString addr) { + return isZAddress(addr) && !isSaplingAddress(addr); +} + +bool Settings::isZAddress(QString addr) { + return addr.startsWith("z"); +} + bool Settings::isSyncing() { return _isSyncing; } diff --git a/src/settings.h b/src/settings.h index 5142f9d..0c08dc2 100644 --- a/src/settings.h +++ b/src/settings.h @@ -29,6 +29,8 @@ public: void setTestnet(bool isTestnet); bool isSaplingAddress(QString addr); + bool isSproutAddress(QString addr); + bool isZAddress(QString addr); bool isSyncing(); void setSyncing(bool syncing); diff --git a/src/turnstile.cpp b/src/turnstile.cpp new file mode 100644 index 0000000..e69c9d4 --- /dev/null +++ b/src/turnstile.cpp @@ -0,0 +1,369 @@ +#include "turnstile.h" +#include "mainwindow.h" +#include "unspentoutput.h" +#include "rpc.h" +#include "utils.h" +#include "settings.h" + +#include "precompiled.h" + +using json = nlohmann::json; + +Turnstile::Turnstile(RPC* _rpc, MainWindow* mainwindow) { + this->rpc = _rpc; + this->mainwindow = mainwindow; +} + +Turnstile::~Turnstile() { +} + +void printPlan(QList plan) { + for (auto item : plan) { + //qDebug() << item.fromAddr << item.intTAddr + // << item.destAddr << item.amount << item.blockNumber << item.status; + } +} + +QString Turnstile::writeableFile() { + auto filename = QStringLiteral("turnstilemigrationplan.dat"); + + auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); + if (!dir.exists()) + QDir().mkpath(dir.absolutePath()); + + if (Settings::getInstance()->isTestnet()) { + return dir.filePath("testnet-" % filename); + } else { + return dir.filePath(filename); + } +} + +void Turnstile::removeFile() { + QFile(writeableFile()).remove(); +} + +// Data stream write/read methods for migration items +QDataStream &operator<<(QDataStream& ds, const TurnstileMigrationItem& item) { + return ds << QString("v1") << item.fromAddr << item.intTAddr + << item.destAddr << item.amount << item.blockNumber << item.status; +} + +QDataStream &operator>>(QDataStream& ds, TurnstileMigrationItem& item) { + QString version; + return ds >> version >> item.fromAddr >> item.intTAddr + >> item.destAddr >> item.amount >> item.blockNumber >> item.status; +} + +void Turnstile::writeMigrationPlan(QList plan) { + //qDebug() << QString("Writing plan"); + printPlan(plan); + + QFile file(writeableFile()); + file.open(QIODevice::ReadWrite | QIODevice::Truncate); + QDataStream out(&file); // we will serialize the data into the file + out << plan; + file.close(); +} + +QList Turnstile::readMigrationPlan() { + QFile file(writeableFile()); + + QList plan; + if (!file.exists()) return plan; + + file.open(QIODevice::ReadOnly); + QDataStream in(&file); // read the data serialized from the file + in >> plan; + + file.close(); + + // Sort to see when the next step is. + std::sort(plan.begin(), plan.end(), [&] (auto a, auto b) { + return a.blockNumber < b.blockNumber; + }); + + return plan; +} + +void Turnstile::planMigration(QString zaddr, QString destAddr, int numsplits, int numBlocks) { + // First, get the balance and split up the amounts + auto bal = rpc->getAllBalances()->value(zaddr); + auto splits = splitAmount(bal, numsplits); + + // Then, generate an intermediate t-Address for each part using getBatchRPC + rpc->getBatchRPC(splits, + [=] (double /*unused*/) { + json payload = { + {"jsonrpc", "1.0"}, + {"id", "someid"}, + {"method", "getnewaddress"}, + }; + return payload; + }, + [=] (QMap* newAddrs) { + // Get block numbers + auto curBlock = Settings::getInstance()->getBlockNumber(); + auto blockNumbers = getBlockNumbers(curBlock, curBlock + numBlocks, splits.size()); + + // Assign the amounts to the addresses. + QList migItems; + + for (int i=0; i < splits.size(); i++) { + auto tAddr = newAddrs->values()[i].get(); + auto item = TurnstileMigrationItem { zaddr, QString::fromStdString(tAddr), destAddr, + blockNumbers[i], splits[i], + TurnstileMigrationItemStatus::NotStarted }; + migItems.push_back(item); + } + + // The first migration is shifted to the current block, so the user sees something + // happening immediately + if (migItems.size() == 0) { + // Show error and abort + QMessageBox::warning(mainwindow, + "Locked funds", "Could not initiate migration.\nYou either have unconfirmed funds or the balance is too low for an automatic migration."); + return; + } + + migItems[0].blockNumber = curBlock; + + std::sort(migItems.begin(), migItems.end(), [&] (auto a, auto b) { + return a.blockNumber < b.blockNumber; + }); + + writeMigrationPlan(migItems); + rpc->refresh(true); // Force refresh, to start the migration immediately + } + ); +} + + +QList Turnstile::getBlockNumbers(int start, int end, int count) { + QList blocks; + // Generate 'count' numbers between [start, end] + for (int i=0; i < count; i++) { + auto blk = (std::rand() % (end - start)) + start; + blocks.push_back(blk); + } + + return blocks; +} + + // Need at least 0.0005 ZEC for this +double Turnstile::minMigrationAmount = 0.0005; + +QList Turnstile::splitAmount(double amount, int parts) { + QList amounts; + + if (amount < minMigrationAmount) + return amounts; + + fillAmounts(amounts, amount, parts); + //qDebug() << amounts; + + // Ensure they all add up! + double sumofparts = 0; + for (auto a : amounts) { + sumofparts += a; + } + + // Add the Tx fees + sumofparts += amounts.size() * Utils::getMinerFee(); + + //qDebug() << QString::number(sumofparts, 'f', 8) << QString::number(amount, 'f', 8); + //Q_ASSERT(QString::number(sumofparts, 'f', 8) == QString::number(amount, 'f', 8)); + return amounts; +} + +void Turnstile::fillAmounts(QList& amounts, double amount, int count) { + if (count == 1 || amount < 0.01) { + // Also account for the fees needed to send all these transactions + auto actual = amount - (Utils::getMinerFee() * (amounts.size() + 1)); + + amounts.push_back(actual); + return; + } + + // Get a random amount off the amount (between half and full) and call recursively. + // Multiply by hundered, because we'll operate on 0.01 ZEC minimum. We'll divide by 100 later + double curAmount = std::rand() % (int)std::floor(amount * 100); + + // Try to round it off + auto places = (int)std::floor(std::log10(curAmount)); + if (places > 0) { + auto a = std::pow(10, places); + curAmount = std::floor(curAmount / a) * a; + } + + // And divide by 100 + curAmount = curAmount / 100; + + if (curAmount > 0) + amounts.push_back(curAmount); + + fillAmounts(amounts, amount - curAmount, count - 1); +} + +QList::Iterator +Turnstile::getNextStep(QList& plan) { + // Get to the next unexecuted step + auto fnIsEligibleItem = [&] (auto item) { + return item.status == TurnstileMigrationItemStatus::NotStarted || + item.status == TurnstileMigrationItemStatus::SentToT; + }; + + // Find the next step + auto nextStep = std::find_if(plan.begin(), plan.end(), fnIsEligibleItem); + return nextStep; +} + +bool Turnstile::isMigrationPresent() { + auto plan = readMigrationPlan(); + if (plan.isEmpty()) return false; + + return true; +} + +ProgressReport Turnstile::getPlanProgress() { + auto plan = readMigrationPlan(); + + auto nextStep = getNextStep(plan); + + auto step = std::distance(plan.begin(), nextStep) * 2; // 2 steps per item + if (nextStep != plan.end() && + nextStep->status == TurnstileMigrationItemStatus::SentToT) + step++; + + auto total = plan.size(); + + auto nextBlock = nextStep == plan.end() ? 0 : nextStep->blockNumber; + + bool hasErrors = std::find_if(plan.begin(), plan.end(), [=] (auto i) { + return i.status == TurnstileMigrationItemStatus::NotEnoughBalance || + i.status == TurnstileMigrationItemStatus::UnknownError; + }) != plan.end(); + + return ProgressReport{(int)step, total*2, nextBlock, hasErrors}; +} + +void Turnstile::executeMigrationStep() { + auto plan = readMigrationPlan(); + + //qDebug() << QString("Executing step"); + printPlan(plan); + + // Get to the next unexecuted step + auto fnIsEligibleItem = [&] (auto item) { + return item.status == TurnstileMigrationItemStatus::NotStarted || + item.status == TurnstileMigrationItemStatus::SentToT; + }; + + // Fn to find if there are any unconfirmed funds for this address. + auto fnHasUnconfirmed = [=] (QString addr) { + auto utxoset = rpc->getUTXOs(); + return std::find_if(utxoset->begin(), utxoset->end(), [=] (auto utxo) { + return utxo.address == addr && utxo.confirmations == 0 && utxo.spendable; + }) != utxoset->end(); + }; + + // Find the next step + auto nextStep = std::find_if(plan.begin(), plan.end(), fnIsEligibleItem); + + if (nextStep == plan.end()) + return; // Nothing to do + + if (nextStep->blockNumber > Settings::getInstance()->getBlockNumber()) + return; + + // Is this the last step for this address? + auto lastStep = std::find_if(std::next(nextStep), plan.end(), fnIsEligibleItem) == plan.end(); + + // Execute this step + if (nextStep->status == TurnstileMigrationItemStatus::NotStarted) { + // Does this z addr have enough balance? + if (fnHasUnconfirmed(nextStep->fromAddr)) { + //qDebug() << QString("unconfirmed, waiting"); + return; + } + + auto balance = rpc->getAllBalances()->value(nextStep->fromAddr); + if (nextStep->amount > balance) { + qDebug() << "Not enough balance!"; + nextStep->status = TurnstileMigrationItemStatus::NotEnoughBalance; + writeMigrationPlan(plan); + return; + } + + auto to = ToFields{ nextStep->intTAddr, nextStep->amount, "", "" }; + + // If this is the last step, then send the remaining amount instead of the actual amount. + if (lastStep) { + auto remainingAmount = balance - Utils::getMinerFee(); + if (remainingAmount > 0) { + to.amount = remainingAmount; + } + } + + // Create the Tx + auto tx = Tx{ nextStep->fromAddr, { to }, Utils::getMinerFee() }; + + // And send it + doSendTx(tx, [=] () { + // Update status and write plan to disk + nextStep->status = TurnstileMigrationItemStatus::SentToT; + writeMigrationPlan(plan); + }); + + } else if (nextStep->status == TurnstileMigrationItemStatus::SentToT) { + // First thing to do is check to see if the funds are confirmed. + // We'll check both the original sprout address and the intermediate T addr for safety. + if (fnHasUnconfirmed(nextStep->intTAddr) || fnHasUnconfirmed(nextStep->fromAddr)) { + //qDebug() << QString("unconfirmed, waiting"); + return; + } + + if (!rpc->getAllBalances()->keys().contains(nextStep->intTAddr)) { + qDebug() << QString("The intermediate Taddress doesn't have balance, even though it is confirmed"); + return; + } + + // Send it to the final destination address. + auto bal = rpc->getAllBalances()->value(nextStep->intTAddr); + auto sendAmt = bal - Utils::getMinerFee(); + + if (sendAmt < 0) { + qDebug() << "Not enough balance!." << bal << ":" << sendAmt; + nextStep->status = TurnstileMigrationItemStatus::NotEnoughBalance; + writeMigrationPlan(plan); + return; + } + + QList to = { ToFields{ nextStep->destAddr, sendAmt, "", "" } }; + + // Create the Tx + auto tx = Tx{ nextStep->intTAddr, to, Utils::getMinerFee() }; + + // And send it + doSendTx(tx, [=] () { + // Update status and write plan to disk + nextStep->status = TurnstileMigrationItemStatus::SentToZS; + writeMigrationPlan(plan); + }); + } +} + +void Turnstile::doSendTx(Tx tx, std::function cb) { + json params = json::array(); + rpc->fillTxJsonParams(params, tx); + std::cout << std::setw(2) << params << std::endl; + rpc->sendZTransaction(params, [=] (const json& reply) { + QString opid = QString::fromStdString(reply.get()); + //qDebug() << opid; + mainwindow->ui->statusBar->showMessage("Computing Tx: " % opid); + + // And then start monitoring the transaction + rpc->addNewTxToWatch(tx, opid); + + cb(); + }); +} \ No newline at end of file diff --git a/src/turnstile.h b/src/turnstile.h new file mode 100644 index 0000000..6eb5196 --- /dev/null +++ b/src/turnstile.h @@ -0,0 +1,67 @@ +#ifndef TURNSTILE_H +#define TURNSTILE_H + +#include "precompiled.h" + +class RPC; +class MainWindow; +struct Tx; + + +struct TurnstileMigrationItem { + QString fromAddr; + QString intTAddr; + QString destAddr; + int blockNumber; + double amount; + int status; +}; + +enum TurnstileMigrationItemStatus { + NotStarted = 0, + SentToT, + SentToZS, + NotEnoughBalance, + UnknownError +}; + +struct ProgressReport { + int step; + int totalSteps; + int nextBlock; + bool hasErrors; +}; + +class Turnstile +{ +public: + Turnstile(RPC* _rpc, MainWindow* mainwindow); + ~Turnstile(); + + void planMigration(QString zaddr, QString destAddr, int splits, int numBlocks); + QList splitAmount(double amount, int parts); + void fillAmounts(QList& amounts, double amount, int count); + + QList readMigrationPlan(); + void writeMigrationPlan(QList plan); + void removeFile(); + + void executeMigrationStep(); + ProgressReport getPlanProgress(); + bool isMigrationPresent(); + + static double minMigrationAmount; +private: + QList getBlockNumbers(int start, int end, int count); + QString writeableFile(); + + void doSendTx(Tx tx, std::function cb); + + + QList::Iterator getNextStep(QList& plan); + + RPC* rpc; + MainWindow* mainwindow; +}; + +#endif diff --git a/src/turnstile.ui b/src/turnstile.ui new file mode 100644 index 0000000..acf6e64 --- /dev/null +++ b/src/turnstile.ui @@ -0,0 +1,228 @@ + + + Turnstile + + + + 0 + 0 + 565 + 416 + + + + Turnstile Migration + + + + + + Turnstile Migration + + + + + + + + + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + Privacy Level + + + + + + + + 0 + 0 + + + + From + + + + + + + + 0 + 0 + + + + false + + + + + + + + + + + 0 + 0 + + + + + + + + <html><head/><body><p>Funds from Sprout z-Addresses (which start with &quot;zc&quot;) need to be moved to the upgraded Sapling z-Addresses (which start with &quot;zs&quot;). The funds cannot be moved directly, but need to be sent through intermediate &quot;transparent&quot; addresses in privacy-preserving way.</p><p>This migration can be done automatically for you.</p></body></html> + + + true + + + + + + + + 0 + 0 + + + + To + + + + + + + + + + Qt::Horizontal + + + + + + + Balance + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + Miner Fees + + + + + + + + 0 + 0 + + + + 0.0004 ZEC $0.04 + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Total Balance + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Turnstile + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Turnstile + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/turnstileprogress.ui b/src/turnstileprogress.ui new file mode 100644 index 0000000..8deebf4 --- /dev/null +++ b/src/turnstileprogress.ui @@ -0,0 +1,157 @@ + + + TurnstileProgress + + + + 0 + 0 + 400 + 300 + + + + Turnstile Migration Progress + + + + + + 33 + + + + + + + + 0 + 0 + + + + Please ensure you have your wallet.dat backed up! + + + true + + + + + + + Qt::Horizontal + + + + + + + Next Transaction in 4 hours + + + + + + + 4 / 12 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Migration Progress + + + + + + + + 0 + 0 + + + + TextLabel + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close|QDialogButtonBox::Discard|QDialogButtonBox::Ok + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + + + + + + buttonBox + accepted() + TurnstileProgress + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + TurnstileProgress + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/ui_mainwindow.h b/src/ui_mainwindow.h index 4f01b1d..88ffa57 100644 --- a/src/ui_mainwindow.h +++ b/src/ui_mainwindow.h @@ -47,6 +47,7 @@ public: QAction *actionDonate; QAction *actionImport_Private_Keys; QAction *actionCheck_for_Updates; + QAction *actionTurnstile_Migration; QWidget *centralWidget; QGridLayout *gridLayout_3; QTabWidget *tabWidget; @@ -159,6 +160,8 @@ public: actionImport_Private_Keys->setVisible(false); actionCheck_for_Updates = new QAction(MainWindow); actionCheck_for_Updates->setObjectName(QStringLiteral("actionCheck_for_Updates")); + actionTurnstile_Migration = new QAction(MainWindow); + actionTurnstile_Migration->setObjectName(QStringLiteral("actionTurnstile_Migration")); centralWidget = new QWidget(MainWindow); centralWidget->setObjectName(QStringLiteral("centralWidget")); gridLayout_3 = new QGridLayout(centralWidget); @@ -403,6 +406,7 @@ public: Amount1 = new QLineEdit(verticalGroupBox); Amount1->setObjectName(QStringLiteral("Amount1")); Amount1->setBaseSize(QSize(200, 0)); + Amount1->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); horizontalLayout_13->addWidget(Amount1); @@ -667,6 +671,7 @@ public: menuBar->addAction(menuBalance->menuAction()); menuBar->addAction(menuHelp->menuAction()); menuBalance->addAction(actionImport_Private_Keys); + menuBalance->addAction(actionTurnstile_Migration); menuBalance->addAction(actionSettings); menuBalance->addSeparator(); menuBalance->addAction(actionExit); @@ -676,7 +681,7 @@ public: retranslateUi(MainWindow); - tabWidget->setCurrentIndex(1); + tabWidget->setCurrentIndex(0); QMetaObject::connectSlotsByName(MainWindow); @@ -691,6 +696,7 @@ public: actionDonate->setText(QApplication::translate("MainWindow", "Donate", nullptr)); actionImport_Private_Keys->setText(QApplication::translate("MainWindow", "Import Private Keys", nullptr)); actionCheck_for_Updates->setText(QApplication::translate("MainWindow", "Check github.com for Updates", nullptr)); + actionTurnstile_Migration->setText(QApplication::translate("MainWindow", "Sapling Turnstile", nullptr)); groupBox->setTitle(QApplication::translate("MainWindow", "Summary", nullptr)); label->setText(QApplication::translate("MainWindow", "Shielded", nullptr)); balSheilded->setText(QString()); diff --git a/src/ui_turnstile.h b/src/ui_turnstile.h new file mode 100644 index 0000000..fe1d77c --- /dev/null +++ b/src/ui_turnstile.h @@ -0,0 +1,205 @@ +/******************************************************************************** +** Form generated from reading UI file 'turnstile.ui' +** +** Created by: Qt User Interface Compiler version 5.11.2 +** +** WARNING! All changes made in this file will be lost when recompiling UI file! +********************************************************************************/ + +#ifndef UI_TURNSTILE_H +#define UI_TURNSTILE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Ui_Turnstile +{ +public: + QVBoxLayout *verticalLayout; + QGroupBox *groupBox; + QVBoxLayout *verticalLayout_2; + QGridLayout *gridLayout; + QLabel *msgIcon; + QLabel *label_2; + QLabel *label; + QComboBox *migrateZaddList; + QComboBox *privLevel; + QLabel *label_8; + QLabel *label_9; + QComboBox *migrateTo; + QFrame *line; + QLabel *fromBalance; + QSpacerItem *verticalSpacer; + QLabel *label_5; + QLabel *minerFee; + QLabel *label_3; + QDialogButtonBox *buttonBox; + + void setupUi(QDialog *Turnstile) + { + if (Turnstile->objectName().isEmpty()) + Turnstile->setObjectName(QStringLiteral("Turnstile")); + Turnstile->resize(565, 416); + verticalLayout = new QVBoxLayout(Turnstile); + verticalLayout->setObjectName(QStringLiteral("verticalLayout")); + groupBox = new QGroupBox(Turnstile); + groupBox->setObjectName(QStringLiteral("groupBox")); + verticalLayout_2 = new QVBoxLayout(groupBox); + verticalLayout_2->setObjectName(QStringLiteral("verticalLayout_2")); + gridLayout = new QGridLayout(); + gridLayout->setObjectName(QStringLiteral("gridLayout")); + msgIcon = new QLabel(groupBox); + msgIcon->setObjectName(QStringLiteral("msgIcon")); + msgIcon->setAlignment(Qt::AlignCenter); + + gridLayout->addWidget(msgIcon, 0, 0, 1, 1); + + label_2 = new QLabel(groupBox); + label_2->setObjectName(QStringLiteral("label_2")); + QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(label_2->sizePolicy().hasHeightForWidth()); + label_2->setSizePolicy(sizePolicy); + + gridLayout->addWidget(label_2, 5, 0, 1, 1); + + label = new QLabel(groupBox); + label->setObjectName(QStringLiteral("label")); + sizePolicy.setHeightForWidth(label->sizePolicy().hasHeightForWidth()); + label->setSizePolicy(sizePolicy); + + gridLayout->addWidget(label, 2, 0, 1, 1); + + migrateZaddList = new QComboBox(groupBox); + migrateZaddList->setObjectName(QStringLiteral("migrateZaddList")); + QSizePolicy sizePolicy1(QSizePolicy::Expanding, QSizePolicy::Fixed); + sizePolicy1.setHorizontalStretch(0); + sizePolicy1.setVerticalStretch(0); + sizePolicy1.setHeightForWidth(migrateZaddList->sizePolicy().hasHeightForWidth()); + migrateZaddList->setSizePolicy(sizePolicy1); + migrateZaddList->setEditable(false); + + gridLayout->addWidget(migrateZaddList, 2, 1, 1, 2); + + privLevel = new QComboBox(groupBox); + privLevel->setObjectName(QStringLiteral("privLevel")); + sizePolicy1.setHeightForWidth(privLevel->sizePolicy().hasHeightForWidth()); + privLevel->setSizePolicy(sizePolicy1); + + gridLayout->addWidget(privLevel, 5, 1, 1, 2); + + label_8 = new QLabel(groupBox); + label_8->setObjectName(QStringLiteral("label_8")); + label_8->setWordWrap(true); + + gridLayout->addWidget(label_8, 0, 1, 1, 2); + + label_9 = new QLabel(groupBox); + label_9->setObjectName(QStringLiteral("label_9")); + sizePolicy.setHeightForWidth(label_9->sizePolicy().hasHeightForWidth()); + label_9->setSizePolicy(sizePolicy); + + gridLayout->addWidget(label_9, 4, 0, 1, 1); + + migrateTo = new QComboBox(groupBox); + migrateTo->setObjectName(QStringLiteral("migrateTo")); + + gridLayout->addWidget(migrateTo, 4, 1, 1, 2); + + line = new QFrame(groupBox); + line->setObjectName(QStringLiteral("line")); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + + gridLayout->addWidget(line, 1, 0, 1, 3); + + fromBalance = new QLabel(groupBox); + fromBalance->setObjectName(QStringLiteral("fromBalance")); + + gridLayout->addWidget(fromBalance, 3, 1, 1, 1); + + verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); + + gridLayout->addItem(verticalSpacer, 7, 0, 2, 3); + + label_5 = new QLabel(groupBox); + label_5->setObjectName(QStringLiteral("label_5")); + sizePolicy.setHeightForWidth(label_5->sizePolicy().hasHeightForWidth()); + label_5->setSizePolicy(sizePolicy); + + gridLayout->addWidget(label_5, 6, 0, 1, 1); + + minerFee = new QLabel(groupBox); + minerFee->setObjectName(QStringLiteral("minerFee")); + QSizePolicy sizePolicy2(QSizePolicy::Expanding, QSizePolicy::Preferred); + sizePolicy2.setHorizontalStretch(0); + sizePolicy2.setVerticalStretch(0); + sizePolicy2.setHeightForWidth(minerFee->sizePolicy().hasHeightForWidth()); + minerFee->setSizePolicy(sizePolicy2); + minerFee->setAlignment(Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter); + + gridLayout->addWidget(minerFee, 6, 1, 1, 2); + + label_3 = new QLabel(groupBox); + label_3->setObjectName(QStringLiteral("label_3")); + + gridLayout->addWidget(label_3, 3, 0, 1, 1); + + + verticalLayout_2->addLayout(gridLayout); + + + verticalLayout->addWidget(groupBox); + + buttonBox = new QDialogButtonBox(Turnstile); + buttonBox->setObjectName(QStringLiteral("buttonBox")); + buttonBox->setOrientation(Qt::Horizontal); + buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); + + verticalLayout->addWidget(buttonBox); + + + retranslateUi(Turnstile); + QObject::connect(buttonBox, SIGNAL(accepted()), Turnstile, SLOT(accept())); + QObject::connect(buttonBox, SIGNAL(rejected()), Turnstile, SLOT(reject())); + + QMetaObject::connectSlotsByName(Turnstile); + } // setupUi + + void retranslateUi(QDialog *Turnstile) + { + Turnstile->setWindowTitle(QApplication::translate("Turnstile", "Turnstile Migration", nullptr)); + groupBox->setTitle(QApplication::translate("Turnstile", "Turnstile Migration", nullptr)); + msgIcon->setText(QString()); + label_2->setText(QApplication::translate("Turnstile", "Privacy Level", nullptr)); + label->setText(QApplication::translate("Turnstile", "From", nullptr)); + migrateZaddList->setCurrentText(QString()); + label_8->setText(QApplication::translate("Turnstile", "

Funds from Sprout z-Addresses (which start with "zc") need to be moved to the upgraded Sapling z-Addresses (which start with "zs"). The funds cannot be moved directly, but need to be sent through intermediate "transparent" addresses in privacy-preserving way.

This migration can be done automatically for you.

", nullptr)); + label_9->setText(QApplication::translate("Turnstile", "To", nullptr)); + fromBalance->setText(QApplication::translate("Turnstile", "Balance", nullptr)); + label_5->setText(QApplication::translate("Turnstile", "Miner Fees", nullptr)); + minerFee->setText(QApplication::translate("Turnstile", "0.0004 ZEC $0.04", nullptr)); + label_3->setText(QApplication::translate("Turnstile", "Total Balance", nullptr)); + } // retranslateUi + +}; + +namespace Ui { + class Turnstile: public Ui_Turnstile {}; +} // namespace Ui + +QT_END_NAMESPACE + +#endif // UI_TURNSTILE_H diff --git a/src/ui_turnstileprogress.h b/src/ui_turnstileprogress.h new file mode 100644 index 0000000..5c15e70 --- /dev/null +++ b/src/ui_turnstileprogress.h @@ -0,0 +1,142 @@ +/******************************************************************************** +** Form generated from reading UI file 'turnstileprogress.ui' +** +** Created by: Qt User Interface Compiler version 5.11.2 +** +** WARNING! All changes made in this file will be lost when recompiling UI file! +********************************************************************************/ + +#ifndef UI_TURNSTILEPROGRESS_H +#define UI_TURNSTILEPROGRESS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Ui_TurnstileProgress +{ +public: + QGridLayout *gridLayout; + QProgressBar *progressBar; + QLabel *label_4; + QFrame *line; + QLabel *nextTx; + QLabel *progressTxt; + QLabel *label_2; + QLabel *msgIcon; + QDialogButtonBox *buttonBox; + QSpacerItem *verticalSpacer; + QFrame *line_2; + + void setupUi(QDialog *TurnstileProgress) + { + if (TurnstileProgress->objectName().isEmpty()) + TurnstileProgress->setObjectName(QStringLiteral("TurnstileProgress")); + TurnstileProgress->resize(400, 300); + gridLayout = new QGridLayout(TurnstileProgress); + gridLayout->setObjectName(QStringLiteral("gridLayout")); + progressBar = new QProgressBar(TurnstileProgress); + progressBar->setObjectName(QStringLiteral("progressBar")); + progressBar->setValue(33); + + gridLayout->addWidget(progressBar, 3, 0, 1, 3); + + label_4 = new QLabel(TurnstileProgress); + label_4->setObjectName(QStringLiteral("label_4")); + QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(label_4->sizePolicy().hasHeightForWidth()); + label_4->setSizePolicy(sizePolicy); + label_4->setWordWrap(true); + + gridLayout->addWidget(label_4, 7, 1, 1, 2); + + line = new QFrame(TurnstileProgress); + line->setObjectName(QStringLiteral("line")); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + + gridLayout->addWidget(line, 4, 0, 1, 3); + + nextTx = new QLabel(TurnstileProgress); + nextTx->setObjectName(QStringLiteral("nextTx")); + + gridLayout->addWidget(nextTx, 5, 0, 1, 3); + + progressTxt = new QLabel(TurnstileProgress); + progressTxt->setObjectName(QStringLiteral("progressTxt")); + progressTxt->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + + gridLayout->addWidget(progressTxt, 2, 2, 1, 1); + + label_2 = new QLabel(TurnstileProgress); + label_2->setObjectName(QStringLiteral("label_2")); + + gridLayout->addWidget(label_2, 2, 0, 1, 2); + + msgIcon = new QLabel(TurnstileProgress); + msgIcon->setObjectName(QStringLiteral("msgIcon")); + QSizePolicy sizePolicy1(QSizePolicy::Minimum, QSizePolicy::Preferred); + sizePolicy1.setHorizontalStretch(0); + sizePolicy1.setVerticalStretch(0); + sizePolicy1.setHeightForWidth(msgIcon->sizePolicy().hasHeightForWidth()); + msgIcon->setSizePolicy(sizePolicy1); + msgIcon->setAlignment(Qt::AlignCenter); + + gridLayout->addWidget(msgIcon, 7, 0, 1, 1); + + buttonBox = new QDialogButtonBox(TurnstileProgress); + buttonBox->setObjectName(QStringLiteral("buttonBox")); + buttonBox->setOrientation(Qt::Horizontal); + buttonBox->setStandardButtons(QDialogButtonBox::Close|QDialogButtonBox::Discard|QDialogButtonBox::Ok); + buttonBox->setCenterButtons(false); + + gridLayout->addWidget(buttonBox, 9, 0, 1, 3); + + verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); + + gridLayout->addItem(verticalSpacer, 6, 0, 1, 3); + + line_2 = new QFrame(TurnstileProgress); + line_2->setObjectName(QStringLiteral("line_2")); + line_2->setFrameShape(QFrame::HLine); + line_2->setFrameShadow(QFrame::Sunken); + + gridLayout->addWidget(line_2, 8, 0, 1, 3); + + + retranslateUi(TurnstileProgress); + QObject::connect(buttonBox, SIGNAL(accepted()), TurnstileProgress, SLOT(accept())); + QObject::connect(buttonBox, SIGNAL(rejected()), TurnstileProgress, SLOT(reject())); + + QMetaObject::connectSlotsByName(TurnstileProgress); + } // setupUi + + void retranslateUi(QDialog *TurnstileProgress) + { + TurnstileProgress->setWindowTitle(QApplication::translate("TurnstileProgress", "Turnstile Migration Progress", nullptr)); + label_4->setText(QApplication::translate("TurnstileProgress", "Please ensure you have your wallet.dat backed up!", nullptr)); + nextTx->setText(QApplication::translate("TurnstileProgress", "Next Transaction in 4 hours", nullptr)); + progressTxt->setText(QApplication::translate("TurnstileProgress", "4 / 12", nullptr)); + label_2->setText(QApplication::translate("TurnstileProgress", "Migration Progress", nullptr)); + msgIcon->setText(QApplication::translate("TurnstileProgress", "TextLabel", nullptr)); + } // retranslateUi + +}; + +namespace Ui { + class TurnstileProgress: public Ui_TurnstileProgress {}; +} // namespace Ui + +QT_END_NAMESPACE + +#endif // UI_TURNSTILEPROGRESS_H diff --git a/src/unspentoutput.cpp b/src/unspentoutput.cpp index 67068f3..7b3a2d6 100644 --- a/src/unspentoutput.cpp +++ b/src/unspentoutput.cpp @@ -1,9 +1 @@ #include "unspentoutput.h" - -UnspentOutput::UnspentOutput(QString address, QString txid, QString amount, int confirmations) -{ - this->address = address; - this->txid = txid; - this->amount = amount; - this->confirmations = confirmations; -} diff --git a/src/unspentoutput.h b/src/unspentoutput.h index d6af3b1..e106e5d 100644 --- a/src/unspentoutput.h +++ b/src/unspentoutput.h @@ -3,15 +3,12 @@ #include "precompiled.h" -class UnspentOutput -{ -public: - UnspentOutput(QString address, QString txid, QString amount, int confirmations); - +struct UnspentOutput { QString address; QString txid; QString amount; int confirmations; + bool spendable; }; diff --git a/src/utils.cpp b/src/utils.cpp index fe76e22..3b92c11 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -25,13 +25,17 @@ const QString Utils::getDonationAddr(bool sapling) { return "zcEgrceTwvoiFdEvPWcsJHAMrpLsprMF6aRJiQa3fan5ZphyXLPuHghnEPrEPRoEVzUy65GnMVyCTRdkT6BYBepnXh6NBYs"; } +const QString Utils::getDevSproutAddr() { + return "ztbGDqgkmXQjheivgeirwEvJLD4SUNqsWCGwxwVg4YpGz1ARR8P2rXaptkT14z3NDKamcxNmQuvmvktyokMs7HkutRNSx1D"; +} + // Get the dev fee address based on the transaction const QString Utils::getDevAddr(Tx tx) { auto testnetAddrLookup = [=] (const QString& addr) -> QString { if (addr.startsWith("ztestsapling")) { return "ztestsapling1kdp74adyfsmm9838jaupgfyx3npgw8ut63stjjx757pc248cuc0ymzphqeux60c64qe5qt68ygh"; } else if (addr.startsWith("zt")) { - return "ztbGDqgkmXQjheivgeirwEvJLD4SUNqsWCGwxwVg4YpGz1ARR8P2rXaptkT14z3NDKamcxNmQuvmvktyokMs7HkutRNSx1D"; + return getDevSproutAddr(); } else { return QString(); } diff --git a/src/utils.h b/src/utils.h index 3bb7be5..feaa9ff 100644 --- a/src/utils.h +++ b/src/utils.h @@ -12,6 +12,7 @@ public: static const QString txidStatusMessage; static const QString getTokenName(); + static const QString getDevSproutAddr(); static const QString getDevAddr(Tx tx); static const QString getDonationAddr(bool sapling); diff --git a/zec-qt-wallet.pro b/zec-qt-wallet.pro index 197d3bd..818c676 100644 --- a/zec-qt-wallet.pro +++ b/zec-qt-wallet.pro @@ -50,6 +50,7 @@ SOURCES += \ src/sendtab.cpp \ src/senttxstore.cpp \ src/txtablemodel.cpp \ + src/turnstile.cpp \ src/utils.cpp HEADERS += \ @@ -65,6 +66,7 @@ HEADERS += \ src/settings.h \ src/txtablemodel.h \ src/senttxstore.h \ + src/turnstile.h \ src/utils.h FORMS += \ @@ -72,6 +74,8 @@ FORMS += \ src/settings.ui \ src/about.ui \ src/confirm.ui \ + src/turnstile.ui \ + src/turnstileprogress.ui src/memodialog.ui # Default rules for deployment.