From cfc2f85b08a2c4049a147046f4bd5a08428e8277 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 30 Oct 2019 13:54:42 -0700 Subject: [PATCH 1/6] Wallet encryption --- lib/Cargo.toml | 2 +- src/controller.cpp | 69 +++++++++++++----- src/controller.h | 48 ++++++++++-- src/datamodel.h | 6 ++ src/encryption.ui | 165 ++++++++++++++++++++++++++++++++++++++++++ src/liteinterface.cpp | 30 ++++++++ src/liteinterface.h | 5 ++ src/mainwindow.cpp | 115 ++++++++++++++++++++++++++++- src/mainwindow.h | 3 + src/mainwindow.ui | 19 ++++- src/settings.h | 8 ++ zecwallet-lite.pro | 1 + 12 files changed, 439 insertions(+), 32 deletions(-) create mode 100644 src/encryption.ui diff --git a/lib/Cargo.toml b/lib/Cargo.toml index fc18947..31b1de2 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -11,4 +11,4 @@ crate-type = ["staticlib"] [dependencies] libc = "0.2.58" lazy_static = "1.4.0" -zecwalletlitelib = { git = "https://github.com/adityapk00/zecwallet-light-cli", rev = "b928d5f09646cc94023ea25f99951fcf1b43e90d" } +zecwalletlitelib = { git = "https://github.com/adityapk00/zecwallet-light-cli", rev = "92d3804a5c67ca7621b141518a1f72451ca6ff40" } diff --git a/src/controller.cpp b/src/controller.cpp index a763e50..7063bf8 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -161,8 +161,6 @@ void Controller::getInfoThenRefresh(bool force) { bool doUpdate = force || (model->getLatestBlock() != curBlock); model->setLatestBlock(curBlock); - qDebug() << "Refreshing. Full update: " << doUpdate; - // Connected, so display checkmark. auto tooltip = Settings::getInstance()->getSettings().server + "\n" + QString::fromStdString(reply.dump()); QIcon i(":/icons/res/connected.gif"); @@ -178,6 +176,14 @@ void Controller::getInfoThenRefresh(bool force) { // See if recurring payments needs anything Recurring::getInstance()->processPending(main); + // Check if the wallet is locked/encrypted + zrpc->fetchWalletEncryptionStatus([=] (const json& reply) { + bool isEncrypted = reply["encrypted"].get(); + bool isLocked = reply["locked"].get(); + + model->setEncryptionStatus(isEncrypted, isLocked); + }); + if ( doUpdate ) { // Something changed, so refresh everything. refreshBalances(); @@ -405,6 +411,31 @@ void Controller::refreshTransactions() { }); } +// If the wallet is encrpyted and locked, we need to unlock it +void Controller::unlockIfEncrypted(std::function cb, std::function error) { + auto encStatus = getModel()->getEncryptionStatus(); + if (encStatus.first && encStatus.second) { + // Wallet is encrypted and locked. Ask for the password and unlock. + QString password = QInputDialog::getText(main, main->tr("Wallet Password"), + main->tr("Please enter your wallet password"), QLineEdit::Password); + + zrpc->unlockWallet(password, [=](json reply) { + if (isJsonSuccess(reply)) { + cb(); + } else { + QMessageBox::critical(main, main->tr("Wallet Decryption Failed"), + QString::fromStdString(reply["error"].get()), + QMessageBox::Ok + ); + error(); + } + }); + } else { + // Not locked, so just call the function + cb(); + } +} + /** * Execute a transaction with the standard UI. i.e., standard status bar message and standard error * handling @@ -430,21 +461,25 @@ void Controller::executeStandardUITransaction(Tx tx) { void Controller::executeTransaction(Tx tx, const std::function submitted, const std::function error) { - // First, create the json params - json params = json::array(); - fillTxJsonParams(params, tx); - std::cout << std::setw(2) << params << std::endl; - - zrpc->sendTransaction(QString::fromStdString(params.dump()), [=](const json& reply) { - if (reply.find("txid") == reply.end()) { - error("", "Couldn't understand Response: " + QString::fromStdString(reply.dump())); - } else { - QString txid = QString::fromStdString(reply["txid"].get()); - submitted(txid); - } - }, - [=](QString errStr) { - error("", errStr); + unlockIfEncrypted([=] () { + // First, create the json params + json params = json::array(); + fillTxJsonParams(params, tx); + std::cout << std::setw(2) << params << std::endl; + + zrpc->sendTransaction(QString::fromStdString(params.dump()), [=](const json& reply) { + if (reply.find("txid") == reply.end()) { + error("", "Couldn't understand Response: " + QString::fromStdString(reply.dump())); + } else { + QString txid = QString::fromStdString(reply["txid"].get()); + submitted(txid); + } + }, + [=](QString errStr) { + error("", errStr); + }); + }, [=]() { + error("", main->tr("Failed to unlock wallet")); }); } diff --git a/src/controller.h b/src/controller.h index 79f8b4f..e885876 100644 --- a/src/controller.h +++ b/src/controller.h @@ -38,8 +38,7 @@ public: void checkForUpdate(bool silent = true); void refreshZECPrice(); - //void getZboardTopics(std::function)> cb); - + void executeStandardUITransaction(Tx tx); void executeTransaction(Tx tx, @@ -54,11 +53,42 @@ public: void noConnection(); bool isEmbedded() { return ezcashd != nullptr; } - void createNewZaddr(bool sapling, const std::function& cb) { zrpc->createNewZaddr(sapling, cb); } - void createNewTaddr(const std::function& cb) { zrpc->createNewTaddr(cb); } - - void fetchPrivKey(QString addr, const std::function& cb) { zrpc->fetchPrivKey(addr, cb); } - void fetchAllPrivKeys(const std::function cb) { zrpc->fetchAllPrivKeys(cb); } + void encryptWallet(QString password, const std::function& cb) { + zrpc->encryptWallet(password, cb); + } + + void removeWalletEncryption(QString password, const std::function& cb) { + zrpc->removeWalletEncryption(password, cb); } + + void saveWallet(const std::function& cb) { zrpc->saveWallet(cb); } + + void createNewZaddr(bool sapling, const std::function& cb) { + unlockIfEncrypted([=] () { + zrpc->createNewZaddr(sapling, cb); + }, [=](){}); + } + void createNewTaddr(const std::function& cb) { + unlockIfEncrypted([=] () { + zrpc->createNewTaddr(cb); + }, [=](){}); + } + + void fetchPrivKey(QString addr, const std::function& cb) { + unlockIfEncrypted([=] () { + zrpc->fetchPrivKey(addr, cb); + }, + [=]() { + cb({ {"error", "Failed to unlock wallet"} }); + }); + } + void fetchAllPrivKeys(const std::function cb) { + unlockIfEncrypted([=] () { + zrpc->fetchAllPrivKeys(cb); + }, + [=]() { + cb({ {"error", "Failed to unlock wallet"} }); + }); + } // void importZPrivKey(QString addr, bool rescan, const std::function& cb) { zrpc->importZPrivKey(addr, rescan, cb); } // void importTPrivKey(QString addr, bool rescan, const std::function& cb) { zrpc->importTPrivKey(addr, rescan, cb); } @@ -74,7 +104,9 @@ private: void processUnspent (const json& reply, QMap* newBalances, QList* newUnspentOutputs); void updateUI (bool anyUnconfirmed); - void getInfoThenRefresh(bool force); + void getInfoThenRefresh (bool force); + + void unlockIfEncrypted (std::function cb, std::function error); QProcess* ezcashd = nullptr; diff --git a/src/datamodel.h b/src/datamodel.h index 2e1764f..e20261a 100644 --- a/src/datamodel.h +++ b/src/datamodel.h @@ -28,6 +28,9 @@ public: void setLatestBlock(int blockHeight); int getLatestBlock() { return this->latestBlock; } + void setEncryptionStatus(bool encrypted, bool locked) { this->isEncrypted = encrypted; this->isLocked = locked; } + QPair getEncryptionStatus() { return qMakePair(this->isEncrypted, this->isLocked); } + const QList getAllZAddresses() { QReadLocker locker(lock); return *zaddresses; } const QList getAllTAddresses() { QReadLocker locker(lock); return *taddresses; } const QList getUTXOs() { QReadLocker locker(lock); return *utxos; } @@ -42,6 +45,9 @@ public: private: int latestBlock; + bool isEncrypted = false; + bool isLocked = false; + QList* utxos = nullptr; QMap* balances = nullptr; QMap* usedAddresses = nullptr; diff --git a/src/encryption.ui b/src/encryption.ui new file mode 100644 index 0000000..b4ab606 --- /dev/null +++ b/src/encryption.ui @@ -0,0 +1,165 @@ + + + encryptionDialog + + + + 0 + 0 + 400 + 300 + + + + Encrypt Your Wallet + + + + + + Qt::Horizontal + + + + + + + Encryption Password: + + + + + + + Confirm Password: + + + + + + + QLineEdit::Password + + + + + + + color: red; + + + Passwords don't match + + + Qt::AlignCenter + + + + + + + QLineEdit::Password + + + + + + + Qt::Horizontal + + + + + + + WARNING: If you forget your password, the only way to recover the wallet is from the seed phrase. + + + Qt::AlignCenter + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + txtPassword + txtConfirmPassword + + + + + buttonBox + accepted() + encryptionDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + encryptionDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/liteinterface.cpp b/src/liteinterface.cpp index 86fc4fe..88628a9 100644 --- a/src/liteinterface.cpp +++ b/src/liteinterface.cpp @@ -77,6 +77,36 @@ void LiteInterface::saveWallet(const std::function& cb) { conn->doRPCWithDefaultErrorHandling("save", "", cb); } +void LiteInterface::unlockWallet(QString password, const std::function& cb) { + if (conn == nullptr) + return; + + conn->doRPCWithDefaultErrorHandling("unlock", password, cb); +} + +void LiteInterface::fetchWalletEncryptionStatus(const std::function& cb) { + if (conn == nullptr) + return; + + conn->doRPCWithDefaultErrorHandling("encryptionstatus", "", cb); +} + +void LiteInterface::encryptWallet(QString password, const std::function& cb) { + if (conn == nullptr) + return; + + conn->doRPCWithDefaultErrorHandling("encrypt", password, cb); +} + + +void LiteInterface::removeWalletEncryption(QString password, const std::function& cb) { + if (conn == nullptr) + return; + + conn->doRPCWithDefaultErrorHandling("decrypt", password, cb); +} + + void LiteInterface::sendTransaction(QString params, const std::function& cb, const std::function& err) { if (conn == nullptr) diff --git a/src/liteinterface.h b/src/liteinterface.h index 67cc782..da04641 100644 --- a/src/liteinterface.h +++ b/src/liteinterface.h @@ -55,6 +55,11 @@ public: void saveWallet(const std::function& cb); + void fetchWalletEncryptionStatus(const std::function& cb); + void encryptWallet(QString password, const std::function& cb); + void unlockWallet(QString password, const std::function& cb); + void removeWalletEncryption(QString password, const std::function& cb); + //void importZPrivKey(QString addr, bool rescan, const std::function& cb); //void importTPrivKey(QString addr, bool rescan, const std::function& cb); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7bb370b..ca424cf 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,6 +1,7 @@ #include "mainwindow.h" #include "addressbook.h" #include "viewalladdresses.h" +#include "ui_encryption.h" #include "ui_mainwindow.h" #include "ui_mobileappconnector.h" #include "ui_addressbook.h" @@ -22,8 +23,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { - - // Include css QString theme_name; try @@ -79,6 +78,15 @@ MainWindow::MainWindow(QWidget *parent) : payZcashURI(); }); + // Wallet encryption + QObject::connect(ui->actionEncrypt_Wallet, &QAction::triggered, [=]() { + encryptWallet(); + }); + + QObject::connect(ui->actionRemove_Wallet_Encryption, &QAction::triggered, [=]() { + removeWalletEncryption(); + }); + // Export All Private Keys QObject::connect(ui->actionExport_All_Private_Keys, &QAction::triggered, this, &MainWindow::exportAllKeys); @@ -211,6 +219,107 @@ void MainWindow::closeEvent(QCloseEvent* event) { QMainWindow::closeEvent(event); } + +void MainWindow::encryptWallet() { + // Check if wallet is already encrypted + auto encStatus = rpc->getModel()->getEncryptionStatus(); + if (encStatus.first) { + QMessageBox::information(this, tr("Wallet is already encrypted"), + tr("Your wallet is already encrypted with a password.\nPlease use 'Remove Wallet Encryption if you want to remove the wallet encryption."), + QMessageBox::Ok + ); + return; + } + + QDialog d(this); + Ui_encryptionDialog ed; + ed.setupUi(&d); + + // Handle edits on the password box + auto fnPasswordEdited = [=](const QString&) { + // Enable the OK button if the passwords match. + if (!ed.txtPassword->text().isEmpty() && + ed.txtPassword->text() == ed.txtConfirmPassword->text()) { + ed.lblPasswordMatch->setText(""); + ed.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); + } else { + ed.lblPasswordMatch->setText(tr("Passwords don't match")); + ed.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + } + }; + + ed.txtPassword->setText(""); + + QObject::connect(ed.txtConfirmPassword, &QLineEdit::textChanged, fnPasswordEdited); + QObject::connect(ed.txtPassword, &QLineEdit::textChanged, fnPasswordEdited); + + auto fnShowError = [=](QString title, const json& res) { + QMessageBox::critical(this, title, + tr("Error was:\n") + QString::fromStdString(res.dump()), + QMessageBox::Ok + ); + }; + + if (d.exec() == QDialog::Accepted) { + rpc->encryptWallet(ed.txtPassword->text(), [=](json res) { + if (isJsonSuccess(res)) { + // Save the wallet + rpc->saveWallet([=] (json reply) { + if (isJsonSuccess(reply)) { + QMessageBox::information(this, tr("Wallet Encrypted"), + tr("Your wallet was successfully encrypted! The password will be needed to send funds or export private keys."), + QMessageBox::Ok + ); + } else { + fnShowError(tr("Wallet Encryption Failed"), reply); + } + }); + } else { + fnShowError(tr("Wallet Encryption Failed"), res); + } + }); + } +} + +void MainWindow::removeWalletEncryption() { + // Check if wallet is already encrypted + auto encStatus = rpc->getModel()->getEncryptionStatus(); + if (!encStatus.first) { + QMessageBox::information(this, tr("Wallet is not encrypted"), + tr("Your wallet is not encrypted with a password."), + QMessageBox::Ok + ); + return; + } + + QString password = QInputDialog::getText(this, tr("Wallet Password"), + tr("Please enter your wallet password"), QLineEdit::Password); + + rpc->removeWalletEncryption(password, [=] (json res) { + if (isJsonSuccess(res)) { + // Save the wallet + rpc->saveWallet([=] (json reply) { + if(isJsonSuccess(reply)) { + QMessageBox::information(this, tr("Wallet Encryption Removed"), + tr("Your wallet was successfully decrypted! You will no longer need a password to send funds or export private keys."), + QMessageBox::Ok + ); + } else { + QMessageBox::critical(this, tr("Wallet Decryption Failed"), + QString::fromStdString(reply["error"].get()), + QMessageBox::Ok + ); + } + }); + } else { + QMessageBox::critical(this, tr("Wallet Decryption Failed"), + QString::fromStdString(res["error"].get()), + QMessageBox::Ok + ); + } + }); +} + void MainWindow::setupStatusBar() { // Status Bar loadingLabel = new QLabel(); @@ -602,7 +711,7 @@ void MainWindow::exportKeys(QString addr) { if (! *(isDialogAlive.get()) ) return; if (reply.is_discarded() || !reply.is_array()) { - pui.privKeyTxt->setPlainText(tr("Error loading private keys")); + pui.privKeyTxt->setPlainText(tr("Error loading private keys: ") + QString::fromStdString(reply.dump())); pui.buttonBox->button(QDialogButtonBox::Save)->setEnabled(false); return; diff --git a/src/mainwindow.h b/src/mainwindow.h index 54dc309..f1f0426 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -96,6 +96,9 @@ private: Tx createTxFromSendPage(); bool confirmTx(Tx tx, RecurringPaymentInfo* rpi); + void encryptWallet(); + void removeWalletEncryption(); + void cancelButton(); void sendButton(); void addAddressSection(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 5a9ac48..7164bb6 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -392,8 +392,8 @@ 0 0 - 1162 - 344 + 1226 + 504 @@ -1088,7 +1088,7 @@ 0 0 1274 - 39 + 22 @@ -1127,6 +1127,9 @@ + + + @@ -1213,6 +1216,16 @@ File a bug... + + + Encrypt Wallet + + + + + Remove Wallet Encryption + + diff --git a/src/settings.h b/src/settings.h index 2fad7e0..94a1e8c 100644 --- a/src/settings.h +++ b/src/settings.h @@ -4,6 +4,8 @@ #include "precompiled.h" #include "camount.h" +using json = nlohmann::json; + struct Config { QString server; }; @@ -119,4 +121,10 @@ private: double zecPrice = 0.0; }; + +inline bool isJsonSuccess(const json& res) { + return res.find("result") != res.end() && + QString::fromStdString(res["result"].get()) == "success"; +} + #endif // SETTINGS_H diff --git a/zecwallet-lite.pro b/zecwallet-lite.pro index 2dcacc4..509837b 100644 --- a/zecwallet-lite.pro +++ b/zecwallet-lite.pro @@ -93,6 +93,7 @@ HEADERS += \ lib/zecwalletlitelib.h FORMS += \ + src/encryption.ui \ src/mainwindow.ui \ src/migration.ui \ src/newseed.ui \ From 1047f60d3c19caa2aea9271a75a2eb27402843c0 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 30 Oct 2019 13:57:38 -0700 Subject: [PATCH 2/6] Handle empty password/cancel --- src/controller.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/controller.cpp b/src/controller.cpp index 75c16e0..da5f522 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -420,7 +420,12 @@ void Controller::unlockIfEncrypted(std::function cb, std::functiontr("Wallet Password"), - main->tr("Please enter your wallet password"), QLineEdit::Password); + main->tr("Your wallet is encrypted.\nPlease enter your wallet password"), QLineEdit::Password); + + if (password.isEmpty()) { + error(); + return; + } zrpc->unlockWallet(password, [=](json reply) { if (isJsonSuccess(reply)) { From bede1093b19a9a81f8fcdd8f1cf963cadc589464 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 30 Oct 2019 15:29:40 -0700 Subject: [PATCH 3/6] Rebuild when lib def changes --- zecwallet-lite.pro | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zecwallet-lite.pro b/zecwallet-lite.pro index 509837b..c067fb5 100644 --- a/zecwallet-lite.pro +++ b/zecwallet-lite.pro @@ -147,6 +147,8 @@ else:win32: librust.target = $$PWD/lib/target/x86_64-pc-windows-gnu/release/z unix: librust.commands = $(MAKE) -C $$PWD/lib else:win32: librust.commands = $(MAKE) -C $$PWD/lib winrelease +librust.depends = lib/Cargo.toml lib/src/lib.rs + librustclean.commands = "rm -rf $$PWD/lib/target" distclean.depends += librustclean From 6b1a41f166ffc806952a27f0b10ebbf8ae5b63da Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 30 Oct 2019 15:30:32 -0700 Subject: [PATCH 4/6] Inc dep --- lib/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cargo.toml b/lib/Cargo.toml index f5ae56d..31b1de2 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -11,4 +11,4 @@ crate-type = ["staticlib"] [dependencies] libc = "0.2.58" lazy_static = "1.4.0" -zecwalletlitelib = { git = "https://github.com/adityapk00/zecwallet-light-cli", rev = "fe82c0b43043867b6cc1ebc462cc7b42f91b1adf" } +zecwalletlitelib = { git = "https://github.com/adityapk00/zecwallet-light-cli", rev = "92d3804a5c67ca7621b141518a1f72451ca6ff40" } From 35a3cff250d94dfc276d5fdffa4cb71739c25d17 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 30 Oct 2019 16:28:30 -0700 Subject: [PATCH 5/6] Don't accept empty passwords --- src/mainwindow.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ca424cf..6143aeb 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -225,7 +225,7 @@ void MainWindow::encryptWallet() { auto encStatus = rpc->getModel()->getEncryptionStatus(); if (encStatus.first) { QMessageBox::information(this, tr("Wallet is already encrypted"), - tr("Your wallet is already encrypted with a password.\nPlease use 'Remove Wallet Encryption if you want to remove the wallet encryption."), + tr("Your wallet is already encrypted with a password.\nPlease use 'Remove Wallet Encryption' if you want to remove the wallet encryption."), QMessageBox::Ok ); return; @@ -248,11 +248,12 @@ void MainWindow::encryptWallet() { } }; - ed.txtPassword->setText(""); - QObject::connect(ed.txtConfirmPassword, &QLineEdit::textChanged, fnPasswordEdited); QObject::connect(ed.txtPassword, &QLineEdit::textChanged, fnPasswordEdited); + ed.txtPassword->setText(""); + ed.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + auto fnShowError = [=](QString title, const json& res) { QMessageBox::critical(this, title, tr("Error was:\n") + QString::fromStdString(res.dump()), From 3d66568ceb3eba8a2990969c9efb801dc7a8b089 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 30 Oct 2019 17:39:11 -0700 Subject: [PATCH 6/6] Handle pending outgoing txns properly --- lib/Cargo.lock | 6 ++--- lib/Cargo.toml | 2 +- src/controller.cpp | 64 ++++++++++++++++++++++++++++++++++------------ src/controller.h | 1 + src/datamodel.cpp | 1 + src/datamodel.h | 24 ++++++++++++++--- 6 files changed, 74 insertions(+), 24 deletions(-) diff --git a/lib/Cargo.lock b/lib/Cargo.lock index a325abe..6f0ae92 100644 --- a/lib/Cargo.lock +++ b/lib/Cargo.lock @@ -1051,7 +1051,7 @@ version = "0.1.0" dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "zecwalletlitelib 0.1.0 (git+https://github.com/adityapk00/zecwallet-light-cli?rev=b928d5f09646cc94023ea25f99951fcf1b43e90d)", + "zecwalletlitelib 0.1.0 (git+https://github.com/adityapk00/zecwallet-light-cli?rev=50d331b0cfe1b3c4b81e33fd6febb4b24264627a)", ] [[package]] @@ -2266,7 +2266,7 @@ dependencies = [ [[package]] name = "zecwalletlitelib" version = "0.1.0" -source = "git+https://github.com/adityapk00/zecwallet-light-cli?rev=b928d5f09646cc94023ea25f99951fcf1b43e90d#b928d5f09646cc94023ea25f99951fcf1b43e90d" +source = "git+https://github.com/adityapk00/zecwallet-light-cli?rev=50d331b0cfe1b3c4b81e33fd6febb4b24264627a#50d331b0cfe1b3c4b81e33fd6febb4b24264627a" dependencies = [ "base58 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "bellman 0.1.0 (git+https://github.com/adityapk00/librustzcash.git?rev=188537ea025fcb7fbdfc11266f307a084a5451e4)", @@ -2562,4 +2562,4 @@ dependencies = [ "checksum zcash_client_backend 0.0.0 (git+https://github.com/adityapk00/librustzcash.git?rev=188537ea025fcb7fbdfc11266f307a084a5451e4)" = "" "checksum zcash_primitives 0.0.0 (git+https://github.com/adityapk00/librustzcash.git?rev=188537ea025fcb7fbdfc11266f307a084a5451e4)" = "" "checksum zcash_proofs 0.0.0 (git+https://github.com/adityapk00/librustzcash.git?rev=188537ea025fcb7fbdfc11266f307a084a5451e4)" = "" -"checksum zecwalletlitelib 0.1.0 (git+https://github.com/adityapk00/zecwallet-light-cli?rev=b928d5f09646cc94023ea25f99951fcf1b43e90d)" = "" +"checksum zecwalletlitelib 0.1.0 (git+https://github.com/adityapk00/zecwallet-light-cli?rev=50d331b0cfe1b3c4b81e33fd6febb4b24264627a)" = "" diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 31b1de2..8987dc1 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -11,4 +11,4 @@ crate-type = ["staticlib"] [dependencies] libc = "0.2.58" lazy_static = "1.4.0" -zecwalletlitelib = { git = "https://github.com/adityapk00/zecwallet-light-cli", rev = "92d3804a5c67ca7621b141518a1f72451ca6ff40" } +zecwalletlitelib = { git = "https://github.com/adityapk00/zecwallet-light-cli", rev = "3e1c61a4b0589be1ff7590cf4ddf025a9160c631" } diff --git a/src/controller.cpp b/src/controller.cpp index da5f522..392043c 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -272,6 +272,34 @@ void Controller::processUnspent(const json& reply, QMap* balan processFn(reply["pending_utxos"].get()); }; +void Controller::updateUIBalances() { + CAmount balT = getModel()->getBalT(); + CAmount balZ = getModel()->getBalZ(); + CAmount balVerified = getModel()->getBalVerified(); + + // Reduce the BalanceZ by the pending outgoing amount. We're adding + // here because totalPending is already negative for outgoing txns. + balZ = balZ + getModel()->getTotalPending(); + + CAmount balTotal = balT + balZ; + CAmount balAvailable = balT + balVerified; + + // Balances table + ui->balSheilded ->setText(balZ.toDecimalZECString()); + ui->balVerified ->setText(balVerified.toDecimalZECString()); + ui->balTransparent->setText(balT.toDecimalZECString()); + ui->balTotal ->setText(balTotal.toDecimalZECString()); + + ui->balSheilded ->setToolTip(balZ.toDecimalUSDString()); + ui->balVerified ->setToolTip(balVerified.toDecimalUSDString()); + ui->balTransparent->setToolTip(balT.toDecimalUSDString()); + ui->balTotal ->setToolTip(balTotal.toDecimalUSDString()); + + // Send tab + ui->txtAvailableZEC->setText(balAvailable.toDecimalZECString()); + ui->txtAvailableUSD->setText(balAvailable.toDecimalUSDString()); +} + void Controller::refreshBalances() { if (!zrpc->haveConnection()) return noConnection(); @@ -282,29 +310,18 @@ void Controller::refreshBalances() { CAmount balZ = CAmount::fromqint64(reply["zbalance"].get()); CAmount balVerified = CAmount::fromqint64(reply["verified_zbalance"].get()); - CAmount balTotal = balT + balZ; - CAmount balAvailable = balT + balVerified; + model->setBalT(balT); + model->setBalZ(balZ); + model->setBalVerified(balVerified); // This is for the websockets AppDataModel::getInstance()->setBalances(balT, balZ); // This is for the datamodel + CAmount balAvailable = balT + balVerified; model->setAvailableBalance(balAvailable); - // Balances table - ui->balSheilded ->setText(balZ.toDecimalZECString()); - ui->balVerified ->setText(balVerified.toDecimalZECString()); - ui->balTransparent->setText(balT.toDecimalZECString()); - ui->balTotal ->setText(balTotal.toDecimalZECString()); - - ui->balSheilded ->setToolTip(balZ.toDecimalUSDString()); - ui->balVerified ->setToolTip(balVerified.toDecimalUSDString()); - ui->balTransparent->setToolTip(balT.toDecimalUSDString()); - ui->balTotal ->setToolTip(balTotal.toDecimalUSDString()); - - // Send tab - ui->txtAvailableZEC->setText(balAvailable.toDecimalZECString()); - ui->txtAvailableUSD->setText(balAvailable.toDecimalUSDString()); + updateUIBalances(); }); // 2. Get the UTXOs @@ -409,6 +426,21 @@ void Controller::refreshTransactions() { } + // Calculate the total unspent amount that's pending. This will need to be + // shown in the UI so the user can keep track of pending funds + CAmount totalPending; + for (auto txitem : txdata) { + if (txitem.confirmations == 0) { + for (auto item: txitem.items) { + totalPending = totalPending + item.amount; + } + } + } + getModel()->setTotalPending(totalPending); + + // Update UI Balance + updateUIBalances(); + // Update model data, which updates the table view transactionsTableModel->replaceData(txdata); }); diff --git a/src/controller.h b/src/controller.h index e885876..c2eb326 100644 --- a/src/controller.h +++ b/src/controller.h @@ -103,6 +103,7 @@ private: void processUnspent (const json& reply, QMap* newBalances, QList* newUnspentOutputs); void updateUI (bool anyUnconfirmed); + void updateUIBalances (); void getInfoThenRefresh (bool force); diff --git a/src/datamodel.cpp b/src/datamodel.cpp index e043466..c1c4d66 100644 --- a/src/datamodel.cpp +++ b/src/datamodel.cpp @@ -24,6 +24,7 @@ DataModel::~DataModel() { } void DataModel::setLatestBlock(int blockHeight) { + QReadLocker locker(lock); this->latestBlock = blockHeight; } diff --git a/src/datamodel.h b/src/datamodel.h index e20261a..4d2ac06 100644 --- a/src/datamodel.h +++ b/src/datamodel.h @@ -26,7 +26,7 @@ public: void markAddressUsed(QString address); void setLatestBlock(int blockHeight); - int getLatestBlock() { return this->latestBlock; } + int getLatestBlock() { QReadLocker locker(lock); return this->latestBlock; } void setEncryptionStatus(bool encrypted, bool locked) { this->isEncrypted = encrypted; this->isLocked = locked; } QPair getEncryptionStatus() { return qMakePair(this->isEncrypted, this->isLocked); } @@ -37,8 +37,20 @@ public: const QMap getAllBalances() { QReadLocker locker(lock); return *balances; } const QMap getUsedAddresses() { QReadLocker locker(lock); return *usedAddresses; } - CAmount getAvailableBalance() { return availableBalance; } - void setAvailableBalance(CAmount a) { this->availableBalance = a; } + CAmount getAvailableBalance() { QReadLocker locker(lock); return availableBalance; } + void setAvailableBalance(CAmount a) { QReadLocker locker(lock); this->availableBalance = a; } + + CAmount getBalT() { QReadLocker locker(lock); return balT; } + void setBalT(CAmount a) { QReadLocker locker(lock); this->balT = a; } + + CAmount getBalZ() { QReadLocker locker(lock); return balZ; } + void setBalZ(CAmount a) { QReadLocker locker(lock); this->balZ = a; } + + CAmount getBalVerified() { QReadLocker locker(lock); return balVerified; } + void setBalVerified(CAmount a) { QReadLocker locker(lock); this->balVerified = a; } + + CAmount getTotalPending() { QReadLocker locker(lock); return totalPending; } + void setTotalPending(CAmount a) { QReadLocker locker(lock); this->totalPending = a; } DataModel(); ~DataModel(); @@ -55,9 +67,13 @@ private: QList* taddresses = nullptr; CAmount availableBalance; + CAmount totalPending; // Outgoing pending is -ve - QReadWriteLock* lock; + CAmount balT; + CAmount balZ; + CAmount balVerified; + QReadWriteLock* lock; }; #endif // DATAMODEL_H \ No newline at end of file