Browse Source

add sat value and lag to status bar; delete zboard/turnstile stuff

pull/99/head
Jonathan "Duke" Leto 5 years ago
parent
commit
62981fb570
  1. 5
      silentdragon.pro
  2. 1
      src/main.cpp
  3. 333
      src/mainwindow.cpp
  4. 78
      src/rpc.cpp
  5. 18
      src/settings.cpp
  6. 5
      src/settings.h
  7. 372
      src/turnstile.cpp
  8. 69
      src/turnstile.h
  9. 235
      src/turnstile.ui
  10. 192
      src/turnstileprogress.ui

5
silentdragon.pro

@ -46,7 +46,6 @@ SOURCES += \
src/sendtab.cpp \
src/senttxstore.cpp \
src/txtablemodel.cpp \
src/turnstile.cpp \
src/qrcodelabel.cpp \
src/connection.cpp \
src/fillediconlabel.cpp \
@ -73,7 +72,6 @@ HEADERS += \
src/settings.h \
src/txtablemodel.h \
src/senttxstore.h \
src/turnstile.h \
src/qrcodelabel.h \
src/connection.h \
src/fillediconlabel.h \
@ -93,15 +91,12 @@ FORMS += \
src/settings.ui \
src/about.ui \
src/confirm.ui \
src/turnstile.ui \
src/turnstileprogress.ui \
src/privkey.ui \
src/memodialog.ui \
src/viewalladdresses.ui \
src/validateaddress.ui \
src/viewalladdresses.ui \
src/connection.ui \
src/zboard.ui \
src/addressbook.ui \
src/viewalladdresses.ui \
src/mobileappconnector.ui \

1
src/main.cpp

@ -4,7 +4,6 @@
#include "mainwindow.h"
#include "rpc.h"
#include "settings.h"
#include "turnstile.h"
#include "version.h"

333
src/mainwindow.cpp

@ -6,19 +6,15 @@
#include "ui_mainwindow.h"
#include "ui_mobileappconnector.h"
#include "ui_addressbook.h"
#include "ui_zboard.h"
#include "ui_privkey.h"
#include "ui_about.h"
#include "ui_settings.h"
#include "ui_turnstile.h"
#include "ui_turnstileprogress.h"
#include "ui_viewalladdresses.h"
#include "ui_validateaddress.h"
#include "rpc.h"
#include "balancestablemodel.h"
#include "settings.h"
#include "version.h"
#include "turnstile.h"
#include "senttxstore.h"
#include "connection.h"
#include "requestdialog.h"
@ -89,9 +85,6 @@ MainWindow::MainWindow(QWidget *parent) :
// Export transactions
QObject::connect(ui->actionExport_transactions, &QAction::triggered, this, &MainWindow::exportTransactions);
// z-Board.net
QObject::connect(ui->actionz_board_net, &QAction::triggered, this, &MainWindow::postToZBoard);
// Validate Address
QObject::connect(ui->actionValidate_Address, &QAction::triggered, this, &MainWindow::validateAddress);
@ -126,7 +119,6 @@ MainWindow::MainWindow(QWidget *parent) :
setupTransactionsTab();
setupReceiveTab();
setupBalancesTab();
setupTurnstileDialog();
setupZcashdTab();
rpc = new RPC(this);
@ -206,208 +198,6 @@ 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) {
// Return if there is no connection
if (rpc->getAllZAddresses() == nullptr)
return;
// 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->getAllZAddresses()) {
if (Settings::getInstance()->isSproutAddress(addr)) {
bal += rpc->getAllBalances()->value(addr);
}
}
return bal;
};
turnstile.fromBalance->setText(Settings::getZECUSDDisplayFormat(fnGetAllSproutBalance()));
for (auto addr : *rpc->getAllZAddresses()) {
auto bal = rpc->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->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<std::tuple<int, int>> privOptions;
privOptions.push_back(std::make_tuple<int, int>(3, 576));
privOptions.push_back(std::make_tuple<int, int>(5, 1152));
privOptions.push_back(std::make_tuple<int, int>(10, 2304));
QObject::connect(turnstile.privLevel, QOverload<int>::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 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();
@ -727,122 +517,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->getAllBalances()->keyBegin(); i != rpc->getAllBalances()->keyEnd(); i++) {
if (Settings::getInstance()->isSaplingAddress(*i) && rpc->getAllBalances()->value(*i) > 0) {
zb.fromAddr->addItem(*i);
}
}
QMap<QString, QString> 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<QString, QString> 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->executeTransaction(tx, [=] (QString opid) {
ui->statusBar->showMessage(tr("Computing Tx: ") % opid);
},
[=] (QString /*opid*/, QString txid) {
ui->statusBar->showMessage(Settings::txidStatusMessage + " " + txid);
},
[=] (QString opid, QString errStr) {
ui->statusBar->showMessage(QObject::tr(" Tx ") % opid % QObject::tr(" failed"), 15 * 1000);
if (!opid.isEmpty())
errStr = QObject::tr("The transaction with id ") % opid % QObject::tr(" failed. The error was") + ":\n\n" + errStr;
QMessageBox::critical(this, QObject::tr("Transaction Error"), errStr, QMessageBox::Ok);
});
}
}
void MainWindow::doImport(QList<QString>* keys) {
if (rpc->getConnection() == nullptr) {
// No connection, just return
@ -1232,13 +906,6 @@ void MainWindow::setupBalancesTab() {
});
}
//TODO: No sprout UTXOs on the Hush chain, should we remove all turnstile code?
if (Settings::getInstance()->isSproutAddress(addr)) {
menu.addAction(tr("Migrate to Sapling"), [=] () {
this->turnstileDoMigration(addr);
});
}
menu.exec(ui->balancesTable->viewport()->mapToGlobal(pos));
});
}

78
src/rpc.cpp

@ -1,9 +1,9 @@
// Copyright 2019 The Hush Developers
#include "rpc.h"
#include "addressbook.h"
#include "settings.h"
#include "senttxstore.h"
#include "turnstile.h"
#include "version.h"
#include "websockets.h"
@ -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);
@ -62,7 +60,6 @@ RPC::~RPC() {
delete transactionsTableModel;
delete balancesTableModel;
delete turnstile;
delete utxos;
delete allBalances;
@ -591,9 +588,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();
@ -691,9 +685,10 @@ void RPC::getInfoThenRefresh(bool force) {
QString::number(blockNumber) %
(isSyncing ? ("/" % QString::number(progress*100, 'f', 2) % "%") : QString()) %
") " %
" Notarized: " % QString::number(notarized) %
" HUSH/USD=$" % QString::number( (double) Settings::getInstance()->getZECPrice() );
main->statusLabel->setText(statusText);
" Lag: " % QString::number(blockNumber - notarized) %
" HUSH/USD=$" % QString::number( (double) Settings::getInstance()->getZECPrice() ) %
" " % QString::number( Settings::getInstance()->getBTCPrice() ) % "sat";
main->statusLabel->setText(statusText);
auto zecPrice = Settings::getUSDFormat(1);
QString tooltip;
@ -1113,6 +1108,7 @@ void RPC::refreshZECPrice() {
qDebug() << reply->errorString();
}
Settings::getInstance()->setZECPrice(0);
Settings::getInstance()->setBTCPrice(0);
return;
}
@ -1121,6 +1117,7 @@ void RPC::refreshZECPrice() {
auto parsed = json::parse(all, nullptr, false);
if (parsed.is_discarded()) {
Settings::getInstance()->setZECPrice(0);
Settings::getInstance()->setBTCPrice(0);
return;
}
@ -1134,7 +1131,9 @@ void RPC::refreshZECPrice() {
// TODO: support BTC/EUR prices as well
//QString price = QString::fromStdString(hush["usd"].get<json::string_t>());
qDebug() << "HUSH = $" << QString::number((double)hush["usd"]);
qDebug() << "HUSH = " << QString::number((double)hush["btc"]) << " sat ";
Settings::getInstance()->setZECPrice( hush["usd"] );
Settings::getInstance()->setBTCPrice( (unsigned int) 100000000 * (double)hush["btc"] );
return;
} else {
@ -1147,17 +1146,18 @@ void RPC::refreshZECPrice() {
// If nothing, then set the price to 0;
Settings::getInstance()->setZECPrice(0);
Settings::getInstance()->setBTCPrice(0);
});
}
void RPC::shutdownZcashd() {
// Shutdown embedded zcashd if it was started
if (ezcashd == nullptr || ezcashd->processId() == 0 || conn == nullptr) {
// No zcashd running internally, just return
// No hushd running internally, just return
return;
}
std::string method = "stop";
std::string method = "stop";
conn->doRPCWithDefaultErrorHandling(makePayload(method), [=](auto) {});
conn->shutdown();
@ -1203,60 +1203,6 @@ void RPC::shutdownZcashd() {
}
// Fetch the Z-board topics list
void RPC::getZboardTopics(std::function<void(QMap<QString, QString>)> cb) {
if (conn == nullptr)
return noConnection();
QUrl cmcURL("http://z-board.net/listTopics");
QNetworkRequest req;
req.setUrl(cmcURL);
QNetworkReply *reply = conn->restclient->get(req);
QObject::connect(reply, &QNetworkReply::finished, [=] {
reply->deleteLater();
try {
if (reply->error() != QNetworkReply::NoError) {
auto parsed = json::parse(reply->readAll(), nullptr, false);
if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) {
qDebug() << QString::fromStdString(parsed["error"]["message"]);
}
else {
qDebug() << reply->errorString();
}
return;
}
auto all = reply->readAll();
auto parsed = json::parse(all, nullptr, false);
if (parsed.is_discarded()) {
return;
}
QMap<QString, QString> topics;
for (const json& item : parsed["topics"].get<json::array_t>()) {
if (item.find("addr") == item.end() || item.find("topicName") == item.end())
return;
QString addr = QString::fromStdString(item["addr"].get<json::string_t>());
QString topic = QString::fromStdString(item["topicName"].get<json::string_t>());
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
*/

18
src/settings.cpp

@ -159,6 +159,11 @@ double Settings::getZECPrice() {
return zecPrice;
}
unsigned int Settings::getBTCPrice() {
// in satoshis
return btcPrice;
}
bool Settings::getAutoShield() {
// Load from Qt settings
return QSettings().value("options/autoshield", false).toBool();
@ -310,19 +315,6 @@ double Settings::getMinerFee() {
return 0.0001;
}
double Settings::getZboardAmount() {
return 0.0001;
}
QString Settings::getZboardAddr() {
if (Settings::getInstance()->isTestnet()) {
return getDonationAddr();
}
else {
return "zs10m00rvkhfm4f7n23e4sxsx275r7ptnggx39ygl0vy46j9mdll5c97gl6dxgpk0njuptg2mn9w5s";
}
}
bool Settings::isValidSaplingPrivateKey(QString pk) {
if (isTestnet()) {
QRegExp zspkey("^secret-extended-key-test[0-9a-z]{278}$", Qt::CaseInsensitive);

5
src/settings.h

@ -1,3 +1,4 @@
// Copyright 2019 The Hush developers
#ifndef SETTINGS_H
#define SETTINGS_H
@ -88,7 +89,9 @@ public:
const QString& getZcashdConfLocation() { return _confLocation; }
void setZECPrice(double p) { zecPrice = p; }
void setBTCPrice(unsigned int p) { btcPrice = p; }
double getZECPrice();
unsigned int getBTCPrice();
void setPeers(int peers);
int getPeers();
@ -126,6 +129,7 @@ public:
static const QString labelRegExp;
//TODO: add these as advanced options, with sane minimums
static const int updateSpeed = 10 * 1000; // 10 sec
static const int quickUpdateSpeed = 3 * 1000; // 3 sec
static const int priceRefreshSpeed = 15 * 60 * 1000; // 15 mins
@ -148,6 +152,7 @@ private:
int _peerConnections = 0;
double zecPrice = 0.0;
unsigned int btcPrice = 0.0;
};
#endif // SETTINGS_H

372
src/turnstile.cpp

@ -1,372 +0,0 @@
#include "turnstile.h"
#include "mainwindow.h"
#include "balancestablemodel.h"
#include "rpc.h"
#include "settings.h"
using json = nlohmann::json;
Turnstile::Turnstile(RPC* _rpc, MainWindow* mainwindow) {
this->rpc = _rpc;
this->mainwindow = mainwindow;
}
void printPlan(QList<TurnstileMigrationItem> 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<TurnstileMigrationItem> 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<TurnstileMigrationItem> Turnstile::readMigrationPlan() {
QFile file(writeableFile());
QList<TurnstileMigrationItem> 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->getConnection()->doBatchRPC<double>(splits,
[=] (double /*unused*/) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "getnewaddress"},
};
return payload;
},
[=] (QMap<double, json>* newAddrs) {
// Get block numbers
auto curBlock = Settings::getInstance()->getBlockNumber();
auto blockNumbers = getBlockNumbers(curBlock, curBlock + numBlocks, splits.size());
// Assign the amounts to the addresses.
QList<TurnstileMigrationItem> migItems;
for (int i=0; i < splits.size(); i++) {
auto tAddr = newAddrs->values()[i].get<json::string_t>();
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.empty()) {
// Show error and abort
QMessageBox::warning(mainwindow,
QObject::tr("Locked funds"),
QObject::tr("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<int> Turnstile::getBlockNumbers(int start, int end, int count) {
QList<int> 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<double> Turnstile::splitAmount(double amount, int parts) {
QList<double> 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() * Settings::getMinerFee();
return amounts;
}
void Turnstile::fillAmounts(QList<double>& 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 - (Settings::getMinerFee() * (amounts.size() + 1));
amounts.push_back(actual);
return;
}
// Get a random amount off the total amount and call recursively.
// Multiply by hundred, because we'll operate on 0.01 ZEC minimum. We'll divide by 100 later on
// in this function.
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<TurnstileMigrationItem>::Iterator
Turnstile::getNextStep(QList<TurnstileMigrationItem>& 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();
return !plan.isEmpty();
}
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();
auto stepData = (nextStep == plan.end() ? std::prev(nextStep) : nextStep);
return ProgressReport{(int)step, total*2, nextBlock, hasErrors, stepData->fromAddr, stepData->destAddr, stepData->intTAddr};
}
void Turnstile::executeMigrationStep() {
// Do a step only if not syncing, else wait for the blockchain to catch up
if (Settings::getInstance()->isSyncing())
return;
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 - Settings::getMinerFee();
if (remainingAmount > 0) {
to.amount = remainingAmount;
}
}
// Create the Tx
auto tx = Tx{ nextStep->fromAddr, { to }, Settings::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;
}
// Sometimes, we check too quickly, and the unspent UTXO is not updated yet, so we'll
// double check to see if there is enough balance.
if (!rpc->getAllBalances()->keys().contains(nextStep->intTAddr)) {
//qDebug() << QString("The intermediate t-address doesn't have balance, even though it seems to be confirmed");
return;
}
// Send it to the final destination address.
auto bal = rpc->getAllBalances()->value(nextStep->intTAddr);
auto sendAmt = bal - Settings::getMinerFee();
if (sendAmt < 0) {
qDebug() << "Not enough balance!." << bal << ":" << sendAmt;
nextStep->status = TurnstileMigrationItemStatus::NotEnoughBalance;
writeMigrationPlan(plan);
return;
}
QList<ToFields> to = { ToFields{ nextStep->destAddr, sendAmt, "", "" } };
// Create the Tx
auto tx = Tx{ nextStep->intTAddr, to, Settings::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<void(void)> cb) {
rpc->executeTransaction(tx, [=] (QString opid) {
mainwindow->ui->statusBar->showMessage(QObject::tr("Computing Tx: ") % opid);
},
[=] (QString /*opid*/, QString txid) {
mainwindow->ui->statusBar->showMessage(Settings::txidStatusMessage + " " + txid);
cb();
},
[=] (QString opid, QString errStr) {
mainwindow->ui->statusBar->showMessage(QObject::tr(" Tx ") % opid % QObject::tr(" failed"), 15 * 1000);
if (!opid.isEmpty())
errStr = QObject::tr("The transaction with id ") % opid % QObject::tr(" failed. The error was") + ":\n\n" + errStr;
QMessageBox::critical(mainwindow, QObject::tr("Transaction Error"), errStr, QMessageBox::Ok);
});
}

69
src/turnstile.h

@ -1,69 +0,0 @@
#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;
QString from;
QString to;
QString via;
};
class Turnstile
{
public:
Turnstile(RPC* _rpc, MainWindow* mainwindow);
void planMigration(QString zaddr, QString destAddr, int splits, int numBlocks);
QList<double> splitAmount(double amount, int parts);
void fillAmounts(QList<double>& amounts, double amount, int count);
QList<TurnstileMigrationItem> readMigrationPlan();
void writeMigrationPlan(QList<TurnstileMigrationItem> plan);
void removeFile();
void executeMigrationStep();
ProgressReport getPlanProgress();
bool isMigrationPresent();
static double minMigrationAmount;
private:
QList<int> getBlockNumbers(int start, int end, int count);
QString writeableFile();
void doSendTx(Tx tx, std::function<void(void)> cb);
QList<TurnstileMigrationItem>::Iterator getNextStep(QList<TurnstileMigrationItem>& plan);
RPC* rpc;
MainWindow* mainwindow;
};
#endif

235
src/turnstile.ui

@ -1,235 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Turnstile</class>
<widget class="QDialog" name="Turnstile">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>565</width>
<height>416</height>
</rect>
</property>
<property name="windowTitle">
<string>Turnstile Migration</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Turnstile Migration</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="msgIcon">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Migrate over</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>From</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="AddressCombo" name="migrateZaddList">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>false</bool>
</property>
<property name="currentText">
<string/>
</property>
</widget>
</item>
<item row="5" column="1" colspan="2">
<widget class="QComboBox" name="privLevel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QLabel" name="label_8">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Funds from Sprout z-Addresses (which start with &amp;quot;zc&amp;quot;) need to be moved to the upgraded Sapling z-Addresses (which start with &amp;quot;zs&amp;quot;). The funds cannot be moved directly, but need to be sent through intermediate &amp;quot;transparent&amp;quot; addresses in privacy-preserving way.&lt;/p&gt;&lt;p&gt;This migration can be done automatically for you.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_9">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>To</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="AddressCombo" name="migrateTo"/>
</item>
<item row="1" column="0" colspan="3">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="fromBalance">
<property name="text">
<string>Balance</string>
</property>
</widget>
</item>
<item row="7" column="0" rowspan="2" colspan="3">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_5">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Miner Fees</string>
</property>
</widget>
</item>
<item row="6" column="1" colspan="2">
<widget class="QLabel" name="minerFee">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">0.0004 ZEC $0.04</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Total Balance</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>AddressCombo</class>
<extends>QComboBox</extends>
<header>addresscombo.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Turnstile</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Turnstile</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

192
src/turnstileprogress.ui

@ -1,192 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TurnstileProgress</class>
<widget class="QDialog" name="TurnstileProgress">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Turnstile Migration Progress</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>From</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>To</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="QLabel" name="fromAddr">
<property name="text">
<string notr="true">From Address</string>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QLabel" name="progressTxt">
<property name="text">
<string notr="true">4 / 12</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="12" column="1" colspan="2">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Please ensure you have your wallet.dat backed up!</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="9" column="0" colspan="3">
<widget class="QLabel" name="nextTx">
<property name="text">
<string>Next Transaction in 4 hours</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="3">
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>33</number>
</property>
</widget>
</item>
<item row="13" column="0" colspan="3">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Migration Progress</string>
</property>
</widget>
</item>
<item row="14" column="0" colspan="3">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close|QDialogButtonBox::Discard</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="msgIcon">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">TextLabel</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="11" column="0" colspan="3">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="0" colspan="3">
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="3" column="0" colspan="3">
<widget class="QLabel" name="toAddr">
<property name="text">
<string notr="true">To Address</string>
</property>
</widget>
</item>
<item row="10" column="0" colspan="3">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>TurnstileProgress</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>TurnstileProgress</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
Loading…
Cancel
Save