From 98dee12c3748eb2dc5cfbbb7f5a7ddd334cf4da9 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 22 Aug 2020 00:59:10 -0300 Subject: [PATCH 001/121] Implement IsMine for SaplingPaymentAddress --- src/script/ismine.cpp | 15 +++++++++++++++ src/script/ismine.h | 1 + 2 files changed, 16 insertions(+) diff --git a/src/script/ismine.cpp b/src/script/ismine.cpp index 194b9b6385de..254ab7a402ce 100644 --- a/src/script/ismine.cpp +++ b/src/script/ismine.cpp @@ -34,6 +34,21 @@ isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest) return IsMine(keystore, script); } +isminetype IsMine(const CKeyStore& keystore, const libzcash::SaplingPaymentAddress& pa) +{ + libzcash::SaplingIncomingViewingKey ivk; + libzcash::SaplingExtendedFullViewingKey exfvk; + if (keystore.GetSaplingIncomingViewingKey(pa, ivk) && + keystore.GetSaplingFullViewingKey(ivk, exfvk) && + keystore.HaveSaplingSpendingKey(exfvk)) { + return ISMINE_SPENDABLE_SHIELDED; + } else if (!ivk.IsNull()) { + return ISMINE_WATCH_ONLY_SHIELDED; + } else { + return ISMINE_NO; + } +} + isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey) { std::vector vSolutions; diff --git a/src/script/ismine.h b/src/script/ismine.h index db74502f830a..8d97a3758e10 100644 --- a/src/script/ismine.h +++ b/src/script/ismine.h @@ -37,6 +37,7 @@ typedef uint8_t isminefilter; isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey); isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest); +isminetype IsMine(const CKeyStore& keystore, const libzcash::SaplingPaymentAddress& pa); /** * Cachable amount subdivided into watchonly and spendable parts. From ea47c24bab1d063f83cd34b6ac505acebc7a9be1 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 22 Aug 2020 17:53:54 -0300 Subject: [PATCH 002/121] SSPKM: implemented GetShieldedAddressFrom. Method to decrypt an specific sapling output and return the involved shielded address if possible. --- src/sapling/saplingscriptpubkeyman.cpp | 34 ++++++++++++++++++++++++++ src/sapling/saplingscriptpubkeyman.h | 3 +++ 2 files changed, 37 insertions(+) diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index ddcd22b2e042..9b1e10713f70 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -503,6 +503,40 @@ std::set> SaplingScriptPubKeyMan::G return nullifierSet; } +Optional SaplingScriptPubKeyMan::GetShieldedAddressFrom(const CWalletTx& tx, const SaplingOutPoint& op) +{ + // Try to decrypt it using the note data ivk (if exists) + auto noteAndAddress = tx.DecryptSaplingNote(op); + if (noteAndAddress) { + return Optional(noteAndAddress->second); + } + + // Try to recover it with the ovks + Optional optAddRet = nullopt; + std::set ovks; + ovks.emplace(getCommonOVKFromSeed()); + if (!tx.sapData->vShieldedSpend.empty()) { + const SaplingOutPoint& prevOut = mapSaplingNullifiersToNotes[tx.sapData->vShieldedSpend[0].nullifier]; + const CWalletTx* txPrev = wallet->GetWalletTx(prevOut.hash); + if (!txPrev) return nullopt; + const auto& it = txPrev->mapSaplingNoteData.find(prevOut); + if (it != txPrev->mapSaplingNoteData.end()) { + const SaplingNoteData ¬eData = it->second; + libzcash::SaplingExtendedSpendingKey extsk; + libzcash::SaplingExtendedFullViewingKey extfvk; + if (wallet->GetSaplingFullViewingKey(noteData.ivk, extfvk) && + wallet->GetSaplingSpendingKey(extfvk, extsk)) { + ovks.emplace(extsk.expsk.ovk); + } + } + } + auto optNotePlainAndAddress = tx.RecoverSaplingNote(op, ovks); + if (optNotePlainAndAddress) { + optAddRet = optNotePlainAndAddress->second; + } + return optAddRet; +} + CAmount SaplingScriptPubKeyMan::TryToRecoverAndSetAmount(const CWalletTx& tx, const SaplingOutPoint& op) { CAmount nCredit = 0; diff --git a/src/sapling/saplingscriptpubkeyman.h b/src/sapling/saplingscriptpubkeyman.h index f05664de5929..c9d3d688ad0c 100644 --- a/src/sapling/saplingscriptpubkeyman.h +++ b/src/sapling/saplingscriptpubkeyman.h @@ -262,6 +262,9 @@ class SaplingScriptPubKeyMan { std::set> GetNullifiersForAddresses(const std::set & addresses); bool IsNoteSaplingChange(const std::set>& nullifierSet, const libzcash::PaymentAddress& address, const SaplingOutPoint& entry); + //! Decrypt the encrypted note and return the address if possible + Optional GetShieldedAddressFrom(const CWalletTx& tx, const SaplingOutPoint& op); + //! Try to decrypt the note and load the amount into the always available SaplingNoteData CAmount TryToRecoverAndSetAmount(const CWalletTx& tx, const SaplingOutPoint& op); From d9bdd14e724a5f3e633fb3bcae2718b875defbc5 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 22 Aug 2020 17:54:50 -0300 Subject: [PATCH 003/121] SSPKM: implemented IsMine for an specific sapling outpoint. --- src/sapling/saplingscriptpubkeyman.cpp | 6 ++++++ src/sapling/saplingscriptpubkeyman.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index 9b1e10713f70..2f0c823f3d54 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -552,6 +552,12 @@ CAmount SaplingScriptPubKeyMan::TryToRecoverAndSetAmount(const CWalletTx& tx, co return nCredit; } +isminetype SaplingScriptPubKeyMan::IsMine(const CWalletTx& wtx, const SaplingOutPoint& op) +{ + Optional pa = GetShieldedAddressFrom(wtx, op); + return pa ? ::IsMine(*wallet, *pa) : ISMINE_NO; +} + CAmount SaplingScriptPubKeyMan::GetCredit(const CWalletTx& tx, const SaplingOutPoint& op) { const auto& it = tx.mapSaplingNoteData.find(op); diff --git a/src/sapling/saplingscriptpubkeyman.h b/src/sapling/saplingscriptpubkeyman.h index c9d3d688ad0c..e8d671b1491a 100644 --- a/src/sapling/saplingscriptpubkeyman.h +++ b/src/sapling/saplingscriptpubkeyman.h @@ -268,6 +268,8 @@ class SaplingScriptPubKeyMan { //! Try to decrypt the note and load the amount into the always available SaplingNoteData CAmount TryToRecoverAndSetAmount(const CWalletTx& tx, const SaplingOutPoint& op); + //! Return true if the wallet can decrypt & spend the shielded output. + isminetype IsMine(const CWalletTx& wtx, const SaplingOutPoint& op); //! Return the shielded credit of an specific output description CAmount GetCredit(const CWalletTx& tx, const SaplingOutPoint& op); //! Return the shielded credit of the tx From f9c80080f5ce54d00f86091e83cf3e48ded7482c Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 26 Aug 2020 17:15:38 -0300 Subject: [PATCH 004/121] Wallet::GetAvailableBalance include or not available shielded balance in the response. --- src/script/ismine.h | 2 ++ src/wallet/wallet.cpp | 11 +++++++++-- src/wallet/wallet.h | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/script/ismine.h b/src/script/ismine.h index 8d97a3758e10..d87947e694fe 100644 --- a/src/script/ismine.h +++ b/src/script/ismine.h @@ -27,6 +27,8 @@ enum isminetype { ISMINE_WATCH_ONLY_SHIELDED = 1 << 4, //! Indicates that we have the spending key of a shielded spend/output. ISMINE_SPENDABLE_SHIELDED = 1 << 5, + ISMINE_SPENDABLE_TRANSPARENT = ISMINE_SPENDABLE_DELEGATED | ISMINE_SPENDABLE, + ISMINE_SPENDABLE_NO_DELEGATED = ISMINE_SPENDABLE | ISMINE_SPENDABLE_SHIELDED, ISMINE_SPENDABLE_ALL = ISMINE_SPENDABLE_DELEGATED | ISMINE_SPENDABLE | ISMINE_SPENDABLE_SHIELDED, ISMINE_WATCH_ONLY_ALL = ISMINE_WATCH_ONLY | ISMINE_WATCH_ONLY_SHIELDED, ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE | ISMINE_COLD | ISMINE_SPENDABLE_DELEGATED | ISMINE_SPENDABLE_SHIELDED | ISMINE_WATCH_ONLY_SHIELDED, diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index b47604c73ce6..11228048c71c 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1879,9 +1879,16 @@ CAmount CWallet::loopTxsBalance(std::functionmethod) const; - CAmount GetAvailableBalance(bool fIncludeDelegated = true) const; + CAmount GetAvailableBalance(bool fIncludeDelegated = true, bool fIncludeShielded = true) const; CAmount GetAvailableBalance(isminefilter& filter, bool useCache = false, int minDepth = 1) const; CAmount GetColdStakingBalance() const; // delegated coins for which we have the staking key CAmount GetImmatureColdStakingBalance() const; From 63c43a57c415df2396d392bcb5ae307b433c5db2 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 26 Aug 2020 22:39:18 -0300 Subject: [PATCH 005/121] Decouple TryToRecoverNote to be used in TryToDecrypt and GetShieldedAddressFrom --- src/sapling/saplingscriptpubkeyman.cpp | 27 ++++++++++++++++++++------ src/sapling/saplingscriptpubkeyman.h | 4 ++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index 2f0c823f3d54..926500634069 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -513,6 +513,20 @@ Optional SaplingScriptPubKeyMan::GetShieldedAdd // Try to recover it with the ovks Optional optAddRet = nullopt; + auto optNotePlainAndAddress = TryToRecoverNote(tx, op); + if (optNotePlainAndAddress) { + optAddRet = optNotePlainAndAddress->second; + } + return optAddRet; +} + +Optional> + SaplingScriptPubKeyMan::TryToRecoverNote(const CWalletTx& tx, const SaplingOutPoint& op) +{ + // Try to recover it with the ovks. + // todo: should add all of the wallet's ovk as well. std::set ovks; ovks.emplace(getCommonOVKFromSeed()); if (!tx.sapData->vShieldedSpend.empty()) { @@ -530,11 +544,7 @@ Optional SaplingScriptPubKeyMan::GetShieldedAdd } } } - auto optNotePlainAndAddress = tx.RecoverSaplingNote(op, ovks); - if (optNotePlainAndAddress) { - optAddRet = optNotePlainAndAddress->second; - } - return optAddRet; + return tx.RecoverSaplingNote(op, ovks); } CAmount SaplingScriptPubKeyMan::TryToRecoverAndSetAmount(const CWalletTx& tx, const SaplingOutPoint& op) @@ -542,12 +552,17 @@ CAmount SaplingScriptPubKeyMan::TryToRecoverAndSetAmount(const CWalletTx& tx, co CAmount nCredit = 0; // if amount was not set, let's try to decrypt the note and set it. auto noteAndAddress = tx.DecryptSaplingNote(op); - // todo: if cannot be decrypted, use RecoverSaplingNote. if (noteAndAddress) { const libzcash::SaplingNotePlaintext ¬e = noteAndAddress->first; nCredit = note.value(); // if it's not set, then set it. wallet->mapWallet[tx.GetHash()].mapSaplingNoteData[op].amount = nCredit; + } else { + // if cannot be decrypted, use RecoverSaplingNote. + auto optNotePlainAndAddress = TryToRecoverNote(tx, op); + if (optNotePlainAndAddress) { + nCredit += optNotePlainAndAddress->first.value(); + } } return nCredit; } diff --git a/src/sapling/saplingscriptpubkeyman.h b/src/sapling/saplingscriptpubkeyman.h index e8d671b1491a..d9366760f0ff 100644 --- a/src/sapling/saplingscriptpubkeyman.h +++ b/src/sapling/saplingscriptpubkeyman.h @@ -267,6 +267,10 @@ class SaplingScriptPubKeyMan { //! Try to decrypt the note and load the amount into the always available SaplingNoteData CAmount TryToRecoverAndSetAmount(const CWalletTx& tx, const SaplingOutPoint& op); + //! Try to recover the note using the wallet's ovks (mostly used when the outpoint is a debit) + Optional> TryToRecoverNote(const CWalletTx& tx, const SaplingOutPoint& op); //! Return true if the wallet can decrypt & spend the shielded output. isminetype IsMine(const CWalletTx& wtx, const SaplingOutPoint& op); From 53b15de1bc08bc19c687226271667162f2fd6b8c Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 26 Aug 2020 22:40:08 -0300 Subject: [PATCH 006/121] Implement shielded outpoint GetDebit --- src/sapling/saplingscriptpubkeyman.cpp | 5 +++++ src/sapling/saplingscriptpubkeyman.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index 926500634069..20cec08b056c 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -573,6 +573,11 @@ isminetype SaplingScriptPubKeyMan::IsMine(const CWalletTx& wtx, const SaplingOut return pa ? ::IsMine(*wallet, *pa) : ISMINE_NO; } +CAmount SaplingScriptPubKeyMan::GetDebit(const CWalletTx& tx, const SaplingOutPoint& op) +{ + return TryToRecoverAndSetAmount(tx, op); +} + CAmount SaplingScriptPubKeyMan::GetCredit(const CWalletTx& tx, const SaplingOutPoint& op) { const auto& it = tx.mapSaplingNoteData.find(op); diff --git a/src/sapling/saplingscriptpubkeyman.h b/src/sapling/saplingscriptpubkeyman.h index d9366760f0ff..85d4c26f7128 100644 --- a/src/sapling/saplingscriptpubkeyman.h +++ b/src/sapling/saplingscriptpubkeyman.h @@ -274,6 +274,8 @@ class SaplingScriptPubKeyMan { //! Return true if the wallet can decrypt & spend the shielded output. isminetype IsMine(const CWalletTx& wtx, const SaplingOutPoint& op); + //! Return the shielded debit of an specific output description + CAmount GetDebit(const CWalletTx& tx, const SaplingOutPoint& op); //! Return the shielded credit of an specific output description CAmount GetCredit(const CWalletTx& tx, const SaplingOutPoint& op); //! Return the shielded credit of the tx From be1016d111ee04ad96062e0a815f655c0f3f9a4e Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 22 Aug 2020 18:09:58 -0300 Subject: [PATCH 007/121] GUI: decompose shielded credit transaction into TransactionRecords flow. --- src/qt/transactionrecord.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 5810534b21b2..3ffbfd2870db 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -206,6 +206,27 @@ bool TransactionRecord::decomposeCreditTransaction(const CWallet* wallet, const parts.append(sub); } } + + if (wtx.hasSaplingData()) { + auto sspkm = wallet->GetSaplingScriptPubKeyMan(); + for (int i = 0; i < (int) wtx.sapData->vShieldedOutput.size(); ++i) { + SaplingOutPoint out(sub.hash, i); + auto opAddr = sspkm->GetShieldedAddressFrom(wtx, out); + if (opAddr) { + // skip it if change + if (sspkm->IsNoteSaplingChange(out, *opAddr)) { + continue; + } + + sub.address = (opAddr) ? KeyIO::EncodePaymentAddress(*opAddr) : ""; + sub.type = TransactionRecord::RecvWithShieldedAddress; + sub.credit = sspkm->GetCredit(wtx, out); + sub.idx = i; + parts.append(sub); + } + } + } + return true; } From 5bb9daf362ef66a80dcf7015c2a5299f2fbf877f Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 22 Aug 2020 18:12:11 -0300 Subject: [PATCH 008/121] GUI: implemented decompose shielded sendToSelf transaction into a TransactionRecord flow. --- src/qt/transactionrecord.cpp | 53 +++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 3ffbfd2870db..5dab99151add 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -236,17 +236,20 @@ bool TransactionRecord::decomposeSendToSelfTransaction(const CWalletTx& wtx, con { // Payment to self tx is presented as a single record. TransactionRecord sub(wtx.GetHash(), wtx.GetTxTime(), wtx.GetTotalSize()); - // Payment to self by default - sub.type = TransactionRecord::SendToSelf; sub.address = ""; - - // Label for payment to self - CTxDestination address; - if (ExtractDestination(wtx.vout[0].scriptPubKey, address)) { - sub.address = EncodeDestination(address); - } - CAmount nChange = wtx.GetChange(); + if (!wtx.hasSaplingData()) { + sub.type = TransactionRecord::SendToSelf; + // Label for payment to self + CTxDestination address; + if (ExtractDestination(wtx.vout[0].scriptPubKey, address)) { + sub.address = EncodeDestination(address); + } + } else { + // shielded send to self. + sub.type = TransactionRecord::SendToSelfShieldedAddress; + nChange += wtx.GetShieldedChange(); + } sub.debit = -(nDebit - nChange); sub.credit = nCredit - nChange; @@ -318,6 +321,30 @@ bool TransactionRecord::decomposeDebitTransaction(const CWallet* wallet, const C return true; } +// Check whether all the shielded inputs and outputs are from and send to this wallet +std::pair areInputsAndOutputsFromAndToMe(const CWalletTx& wtx, SaplingScriptPubKeyMan* sspkm, bool& involvesWatchAddress) +{ + // Check if all the shielded spends are from me + bool allShieldedSpendsFromMe = true; + for (const auto& spend : wtx.sapData->vShieldedSpend) { + if (!sspkm->IsSaplingNullifierFromMe(spend.nullifier)) { + allShieldedSpendsFromMe = false; + break; + } + } + + // Check if all the shielded outputs are to me + bool allShieldedOutToMe = true; + for (int i = 0; i < (int) wtx.sapData->vShieldedOutput.size(); ++i) { + SaplingOutPoint op(wtx.GetHash(), i); + isminetype mine = sspkm->IsMine(wtx, op); + if (mine & ISMINE_WATCH_ONLY_SHIELDED) involvesWatchAddress = true; + if (mine != ISMINE_SPENDABLE_SHIELDED) allShieldedOutToMe = false; + } + + return std::make_pair(allShieldedSpendsFromMe, allShieldedOutToMe); +} + /* * Decompose CWallet transaction to model transaction records. */ @@ -365,6 +392,7 @@ QList TransactionRecord::decomposeTransaction(const CWallet* } } + auto sspkm = wallet->GetSaplingScriptPubKeyMan(); // As the tx is not credit, need to check if all the inputs and outputs are from and to this wallet. // If it's true, then it's a sendToSelf. If not, then it's an outgoing tx. @@ -383,8 +411,13 @@ QList TransactionRecord::decomposeTransaction(const CWallet* if (fAllToMe > mine) fAllToMe = mine; } + // Check whether all the shielded spends/outputs are from or to me. + bool allShieldedSpendsFromMe, allShieldedOutToMe = true; + std::tie(allShieldedSpendsFromMe, allShieldedOutToMe) = + areInputsAndOutputsFromAndToMe(wtx, sspkm, involvesWatchAddress); + // Check if this tx is purely a payment to self. - if (fAllFromMe && fAllToMe) { + if (fAllFromMe && fAllToMe && allShieldedOutToMe && allShieldedSpendsFromMe) { // Single record for sendToSelf. if (decomposeSendToSelfTransaction(wtx, nCredit, nDebit, involvesWatchAddress, parts)) { return parts; From d0352d4b1874a715842b4a39846dd687df31e2c6 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 22 Aug 2020 18:12:45 -0300 Subject: [PATCH 009/121] GUI: implemented decompose shielded debit transaction into TransactionRecords flow. --- src/qt/transactionrecord.cpp | 45 ++++++++++++++++++++++++++++++------ src/qt/transactionrecord.h | 3 +++ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 5dab99151add..9f5fe66ee87e 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -258,6 +258,40 @@ bool TransactionRecord::decomposeSendToSelfTransaction(const CWalletTx& wtx, con return true; } +bool TransactionRecord::decomposeShieldedDebitTransaction(const CWallet* wallet, const CWalletTx& wtx, + bool involvesWatchAddress, QList& parts) +{ + // Return early if there are no outputs. + if (wtx.sapData->vShieldedOutput.empty()) { + return false; + } + + TransactionRecord sub(wtx.GetHash(), wtx.GetTxTime(), wtx.GetTotalSize()); + auto sspkm = wallet->GetSaplingScriptPubKeyMan(); + bool feeAdded = false; + for (int i = 0; i < (int) wtx.sapData->vShieldedOutput.size(); ++i) { + SaplingOutPoint out(sub.hash, i); + auto opAddr = sspkm->GetShieldedAddressFrom(wtx, out); + // skip change + if (!opAddr || sspkm->IsNoteSaplingChange(out, *opAddr)) { + continue; + } + sub.idx = i; + sub.involvesWatchAddress = involvesWatchAddress; + sub.type = TransactionRecord::SendToShielded; + sub.address = KeyIO::EncodePaymentAddress(*opAddr); + CAmount nValue = sspkm->GetDebit(wtx, out); + /* Add fee to first output */ + if (!feeAdded) { // future, move from the hardcoded fee. + nValue += COIN; + feeAdded = true; + } + sub.debit = -nValue; + parts.append(sub); + } + return true; +} + /** * Decompose wtx outputs in records. */ @@ -266,7 +300,7 @@ bool TransactionRecord::decomposeDebitTransaction(const CWallet* wallet, const C QList& parts) { // Return early if there are no outputs. - if (wtx.vout.empty()) { + if (wtx.vout.empty() && wtx.sapData->vShieldedOutput.empty()) { return false; } @@ -318,7 +352,9 @@ bool TransactionRecord::decomposeDebitTransaction(const CWallet* wallet, const C parts.append(sub); } - return true; + + // Decompose shielded debit + return decomposeShieldedDebitTransaction(wallet, wtx, involvesWatchAddress, parts); } // Check whether all the shielded inputs and outputs are from and send to this wallet @@ -360,11 +396,6 @@ QList TransactionRecord::decomposeTransaction(const CWallet* fZSpendFromMe = wallet->IsMyZerocoinSpend(zcspend.getCoinSerialNumber()); } - // TODO: Add shielded transactions parsing. - if (wtx.IsShieldedTx()) { - return parts; - } - // Decompose coinstake if needed (if it's not a coinstake, the method will no perform any action). if (decomposeCoinStake(wallet, wtx, nCredit, nDebit, fZSpendFromMe, parts)) { return parts; diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h index 95140295ce60..7df0b777d847 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -139,6 +139,9 @@ class TransactionRecord const CAmount& nDebit, bool involvesWatchAddress, QList& parts); + static bool decomposeShieldedDebitTransaction(const CWallet* wallet, const CWalletTx& wtx, + bool involvesWatchAddress, QList& parts); + static std::string getValueOrReturnEmpty(const std::map& mapValue, const std::string& key); static bool ExtractAddress(const CScript& scriptPubKey, bool fColdStake, std::string& addressStr); static void loadHotOrColdStakeOrContract(const CWallet* wallet, const CWalletTx& wtx, From 10af0f0a9d5a7aff6c2fa28f45d57733b8e7b4c3 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 22 Aug 2020 18:15:00 -0300 Subject: [PATCH 010/121] GUI: TransactionRecord, Shielded transaction types added --- src/qt/transactionrecord.cpp | 31 ++++++++++++++++--------------- src/qt/transactionrecord.h | 6 ++++-- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 9f5fe66ee87e..2308a094d6c5 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -7,6 +7,7 @@ #include "transactionrecord.h" #include "base58.h" +#include "sapling/key_io_sapling.h" #include "timedata.h" #include "wallet/wallet.h" #include "zpivchain.h" @@ -469,6 +470,21 @@ QList TransactionRecord::decomposeTransaction(const CWallet* return parts; } +bool ExtractAddress(const CScript& scriptPubKey, bool fColdStake, std::string& addressStr) { + CTxDestination address; + if (!ExtractDestination(scriptPubKey, address, fColdStake)) { + // this shouldn't happen.. + addressStr = "No available address"; + return false; + } else { + addressStr = EncodeDestination( + address, + (fColdStake ? CChainParams::STAKING_ADDRESS : CChainParams::PUBKEY_ADDRESS) + ); + return true; + } +} + void TransactionRecord::loadUnlockColdStake(const CWallet* wallet, const CWalletTx& wtx, TransactionRecord& record) { record.involvesWatchAddress = false; @@ -552,21 +568,6 @@ void TransactionRecord::loadHotOrColdStakeOrContract( ExtractAddress(p2csUtxo.scriptPubKey, false, record.address); } -bool TransactionRecord::ExtractAddress(const CScript& scriptPubKey, bool fColdStake, std::string& addressStr) { - CTxDestination address; - if (!ExtractDestination(scriptPubKey, address, fColdStake)) { - // this shouldn't happen.. - addressStr = "No available address"; - return false; - } else { - addressStr = EncodeDestination( - address, - (fColdStake ? CChainParams::STAKING_ADDRESS : CChainParams::PUBKEY_ADDRESS) - ); - return true; - } -} - bool IsZPIVType(TransactionRecord::Type type) { switch (type) { diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h index 7df0b777d847..40d863b28414 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -91,7 +91,10 @@ class TransactionRecord P2CSDelegationSent, // Non-spendable P2CS delegated utxo. (coin-owner transferred ownership to external wallet) P2CSDelegationSentOwner, // Spendable P2CS delegated utxo. (coin-owner) P2CSUnlockOwner, // Coin-owner spent the delegated utxo - P2CSUnlockStaker // Staker watching the owner spent the delegated utxo + P2CSUnlockStaker, // Staker watching the owner spent the delegated utxo + SendToShielded, // Shielded send + RecvWithShieldedAddress, // Shielded receive + SendToSelfShieldedAddress // Shielded send to self }; /** Number of confirmation recommended for accepting a transaction */ @@ -143,7 +146,6 @@ class TransactionRecord bool involvesWatchAddress, QList& parts); static std::string getValueOrReturnEmpty(const std::map& mapValue, const std::string& key); - static bool ExtractAddress(const CScript& scriptPubKey, bool fColdStake, std::string& addressStr); static void loadHotOrColdStakeOrContract(const CWallet* wallet, const CWalletTx& wtx, TransactionRecord& record, bool isContract = false); static void loadUnlockColdStake(const CWallet* wallet, const CWalletTx& wtx, TransactionRecord& record); From 4da948dd94e30689ce13e0071f2010dc8b1afdd2 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 22 Aug 2020 18:16:19 -0300 Subject: [PATCH 011/121] GUI: Shielded transactionRecord types support in txRow and txtableModel. --- src/qt/pivx/txrow.cpp | 3 +++ src/qt/transactiontablemodel.cpp | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/qt/pivx/txrow.cpp b/src/qt/pivx/txrow.cpp index 60f922049eda..fbd92aa867e5 100644 --- a/src/qt/pivx/txrow.cpp +++ b/src/qt/pivx/txrow.cpp @@ -75,6 +75,7 @@ void TxRow::setType(bool isLightTheme, int type, bool isConfirmed) case TransactionRecord::RecvWithAddress: case TransactionRecord::RecvFromOther: case TransactionRecord::RecvFromZerocoinSpend: + case TransactionRecord::RecvWithShieldedAddress: path = "://ic-transaction-received"; css = "text-list-amount-receive"; break; @@ -83,10 +84,12 @@ void TxRow::setType(bool isLightTheme, int type, bool isConfirmed) case TransactionRecord::ZerocoinSpend: case TransactionRecord::ZerocoinSpend_Change_zPiv: case TransactionRecord::ZerocoinSpend_FromMe: + case TransactionRecord::SendToShielded: path = "://ic-transaction-sent"; css = "text-list-amount-send"; break; case TransactionRecord::SendToSelf: + case TransactionRecord::SendToSelfShieldedAddress: path = "://ic-transaction-mint"; css = "text-list-amount-send"; break; diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 7686e55f6905..9c859edf296a 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -466,6 +466,8 @@ QString TransactionTableModel::formatTxType(const TransactionRecord* wtx) const return tr("Sent to"); case TransactionRecord::SendToSelf: return tr("Payment to yourself"); + case TransactionRecord::SendToSelfShieldedAddress: + return tr("Shielded send to yourself"); case TransactionRecord::StakeMint: return tr("%1 Stake").arg(CURRENCY_UNIT.c_str()); case TransactionRecord::StakeZPIV: @@ -493,6 +495,10 @@ QString TransactionTableModel::formatTxType(const TransactionRecord* wtx) const return tr("Minted Change as z%1 from z%1 Spend").arg(CURRENCY_UNIT.c_str()); case TransactionRecord::ZerocoinSpend_FromMe: return tr("Converted z%1 to %1").arg(CURRENCY_UNIT.c_str()); + case TransactionRecord::RecvWithShieldedAddress: + return tr("Received with shielded"); + case TransactionRecord::SendToShielded: + return tr("Shielded send to"); default: return QString(); } @@ -539,6 +545,10 @@ QString TransactionTableModel::formatTxToAddress(const TransactionRecord* wtx, b case TransactionRecord::ZerocoinSpend_FromMe: case TransactionRecord::RecvFromZerocoinSpend: return lookupAddress(wtx->address, tooltip); + case TransactionRecord::RecvWithShieldedAddress: + case TransactionRecord::SendToShielded: + // todo: add addressbook support for shielded addresses. + return QString::fromStdString(wtx->address); case TransactionRecord::SendToOther: return QString::fromStdString(wtx->address) + watchAddress; case TransactionRecord::ZerocoinMint: @@ -556,6 +566,9 @@ QString TransactionTableModel::formatTxToAddress(const TransactionRecord* wtx, b QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address)); return label.isEmpty() ? "" : label; } + case TransactionRecord::SendToSelfShieldedAddress: + // Do not show the send to self address. todo: add addressbook for shielded addr + return ""; default: { if (watchAddress.isEmpty()) { return tr("No information"); From 95b119b9178846e7e9cd59960ea26a2124270c19 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 26 Aug 2020 22:44:58 -0300 Subject: [PATCH 012/121] wallet: GetUnconfirmedBalance accepting a filter argument and using GetCredit instead of GetAvailableCredit. --- src/wallet/wallet.cpp | 6 +++--- src/wallet/wallet.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 11228048c71c..3add2165d9b1 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1943,11 +1943,11 @@ CAmount CWallet::GetLockedCoins() const }); } -CAmount CWallet::GetUnconfirmedBalance() const +CAmount CWallet::GetUnconfirmedBalance(isminetype filter) const { - return loopTxsBalance([](const uint256& id, const CWalletTx& pcoin, CAmount& nTotal) { + return loopTxsBalance([filter](const uint256& id, const CWalletTx& pcoin, CAmount& nTotal) { if (!pcoin.IsTrusted() && pcoin.GetDepthInMainChain() == 0 && pcoin.InMempool()) - nTotal += pcoin.GetAvailableCredit(); + nTotal += pcoin.GetCredit(filter); }); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index d5ffd679bbcf..9bf0daff4270 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -624,7 +624,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface CAmount GetDelegatedBalance() const; // delegated coins for which we have the spending key CAmount GetImmatureDelegatedBalance() const; CAmount GetLockedCoins() const; - CAmount GetUnconfirmedBalance() const; + CAmount GetUnconfirmedBalance(isminetype filter = ISMINE_SPENDABLE) const; CAmount GetImmatureBalance() const; CAmount GetWatchOnlyBalance() const; CAmount GetUnconfirmedWatchOnlyBalance() const; From 37501ff370088297065c273e9e9be3286f9ac61d Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 26 Aug 2020 22:46:58 -0300 Subject: [PATCH 013/121] wallet: Implement GetAvailableShieldedBalance and GetUnconfirmedShieldedBalance --- src/wallet/wallet.cpp | 11 +++++++++++ src/wallet/wallet.h | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 3add2165d9b1..007c3cfc0f16 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2035,6 +2035,17 @@ CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth) cons return balance; } +// Sapling +CAmount CWallet::GetAvailableShieldedBalance(bool fUseCache) const +{ + return GetAvailableBalance(ISMINE_SPENDABLE_SHIELDED, fUseCache);; +}; + +CAmount CWallet::GetUnconfirmedShieldedBalance() const +{ + return GetUnconfirmedBalance(ISMINE_SPENDABLE_SHIELDED); +}; + void CWallet::GetAvailableP2CSCoins(std::vector& vCoins) const { vCoins.clear(); { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 9bf0daff4270..d52975ed18b7 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -676,6 +676,10 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool MultiSend(); void AutoCombineDust(CConnman* connman); + // Shielded balances + CAmount GetAvailableShieldedBalance(bool fUseCache = true) const; + CAmount GetUnconfirmedShieldedBalance() const; + static CFeeRate minTxFee; /** * Estimate the minimum fee considering user set parameters From 7dd3d0bc26c0affd79c221a9b60bc6f41e679d7e Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 26 Aug 2020 22:47:58 -0300 Subject: [PATCH 014/121] Add shielded balances in WalletBalance struct + connect it to the GUI topbar. --- src/interface/wallet.cpp | 2 ++ src/interface/wallet.h | 5 ++++- src/qt/pivx/topbar.cpp | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/interface/wallet.cpp b/src/interface/wallet.cpp index d122069d93f9..ca9abcac0c3d 100644 --- a/src/interface/wallet.cpp +++ b/src/interface/wallet.cpp @@ -21,6 +21,8 @@ namespace interfaces { } result.delegate_balance = m_wallet.GetDelegatedBalance(); result.coldstaked_balance = m_wallet.GetColdStakingBalance(); + result.shielded_balance = m_wallet.GetAvailableShieldedBalance(); + result.unconfirmed_shielded_balance = m_wallet.GetUnconfirmedShieldedBalance(); return result; } diff --git a/src/interface/wallet.h b/src/interface/wallet.h index c09d31b142c7..b1d8aa6f363f 100644 --- a/src/interface/wallet.h +++ b/src/interface/wallet.h @@ -23,6 +23,8 @@ struct WalletBalances CAmount immature_watch_only_balance{0}; CAmount delegate_balance{0}; CAmount coldstaked_balance{0}; + CAmount shielded_balance{0}; + CAmount unconfirmed_shielded_balance{0}; bool balanceChanged(const WalletBalances& prev) const { @@ -30,7 +32,8 @@ struct WalletBalances immature_balance != prev.immature_balance || watch_only_balance != prev.watch_only_balance || unconfirmed_watch_only_balance != prev.unconfirmed_watch_only_balance || immature_watch_only_balance != prev.immature_watch_only_balance || - delegate_balance != prev.delegate_balance || coldstaked_balance != prev.coldstaked_balance; + delegate_balance != prev.delegate_balance || coldstaked_balance != prev.coldstaked_balance || + shielded_balance != prev.shielded_balance || unconfirmed_shielded_balance != prev.unconfirmed_shielded_balance; } }; diff --git a/src/qt/pivx/topbar.cpp b/src/qt/pivx/topbar.cpp index f2b792b105bd..5693970e1fba 100644 --- a/src/qt/pivx/topbar.cpp +++ b/src/qt/pivx/topbar.cpp @@ -1,6 +1,6 @@ // Copyright (c) 2019-2020 The PIVX developers // Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. +// file COPYING or https://www.opensource.org/licenses/mit-license.php. #include "qt/pivx/topbar.h" #include "qt/pivx/forms/ui_topbar.h" @@ -636,7 +636,7 @@ void TopBar::updateBalances(const interfaces::WalletBalances& newBalance) ui->labelAmountTopPiv->setText(totalPiv); // Expanded ui->labelAmountPiv->setText(totalPiv); - ui->labelPendingPiv->setText(GUIUtil::formatBalance(newBalance.unconfirmed_balance, nDisplayUnit)); + ui->labelPendingPiv->setText(GUIUtil::formatBalance(newBalance.unconfirmed_balance + newBalance.unconfirmed_shielded_balance, nDisplayUnit)); ui->labelImmaturePiv->setText(GUIUtil::formatBalance(newBalance.immature_balance, nDisplayUnit)); } From 889d040025d2b0ed4daae47fe8df8f7baf1b070b Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 15 Sep 2020 00:19:12 -0300 Subject: [PATCH 015/121] RPC: adding includeShielded flag to getbalance command. --- src/rpc/client.cpp | 1 + src/wallet/rpcwallet.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 5bf1a11f47c4..ca937af76a21 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -57,6 +57,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { { "getbalance", 1 }, { "getbalance", 2 }, { "getbalance", 3 }, + { "getbalance", 4 }, { "getshieldedbalance", 1 }, { "getshieldedbalance", 2 }, { "rawshieldedsendmany", 1 }, diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 3d3fda1d9002..655313a6d559 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1904,7 +1904,7 @@ UniValue getreceivedbylabel(const JSONRPCRequest& request) UniValue getbalance(const JSONRPCRequest& request) { - if (request.fHelp || (request.params.size() > 3 )) + if (request.fHelp || (request.params.size() > 4 )) throw std::runtime_error( "getbalance ( minconf includeWatchonly includeDelegated )\n" "\nReturns the server's total available balance (excluding zerocoins).\n" @@ -1915,6 +1915,7 @@ UniValue getbalance(const JSONRPCRequest& request) "1. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n" "2. includeWatchonly (bool, optional, default=false) Also include balance in watchonly addresses (see 'importaddress')\n" "3. includeDelegated (bool, optional, default=true) Also include balance delegated to cold stakers\n" + "4. includeShielded (bool, optional, default=true) Also include shielded balance\n" "\nResult:\n" "amount (numeric) The total amount in PIV received for this wallet.\n" @@ -1933,7 +1934,7 @@ UniValue getbalance(const JSONRPCRequest& request) const int nMinDepth = (paramsSize > 0 ? request.params[0].get_int() : 0); isminefilter filter = ISMINE_SPENDABLE | (paramsSize > 1 && request.params[1].get_bool() ? ISMINE_WATCH_ONLY : ISMINE_NO); filter |= (paramsSize <= 2 || request.params[2].get_bool() ? ISMINE_SPENDABLE_DELEGATED : ISMINE_NO); - + filter |= (paramsSize <= 3 || request.params[3].get_bool() ? ISMINE_SPENDABLE_SHIELDED : ISMINE_NO); return ValueFromAmount(pwalletMain->GetAvailableBalance(filter, true, nMinDepth)); } From 922a26a522b58b56d9d103dcff3376d4c0e7f418 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 15 Sep 2020 00:21:49 -0300 Subject: [PATCH 016/121] Migrating sapling_wallet_nullifiers.py to use the getbalance shielded argument flag. --- test/functional/sapling_wallet_nullifiers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/functional/sapling_wallet_nullifiers.py b/test/functional/sapling_wallet_nullifiers.py index 894385dcb490..42edcf6bdc69 100755 --- a/test/functional/sapling_wallet_nullifiers.py +++ b/test/functional/sapling_wallet_nullifiers.py @@ -118,7 +118,7 @@ def run_test (self): node3mined = Decimal('6250.0') assert_equal(self.nodes[3].getshieldedbalance(), zsendmany2notevalue) - assert_equal(self.nodes[3].getbalance(), node3mined) + assert_equal(self.nodes[3].getbalance(1, False, True, False), node3mined) # Add node 1 address and node 2 viewing key to node 3 myzvkey = self.nodes[2].exportsaplingviewingkey(myzaddr) @@ -155,10 +155,10 @@ def run_test (self): # Node 3's balances should be unchanged without explicitly requesting # to include watch-only balances assert_equal(self.nodes[3].getshieldedbalance(), zsendmany2notevalue) - assert_equal(self.nodes[3].getbalance(), node3mined) + assert_equal(self.nodes[3].getbalance(1, False, True, False), node3mined) assert_equal(self.nodes[3].getshieldedbalance("*", 1, True), zsendmany2notevalue + zaddrremaining2) - assert_equal(self.nodes[3].getbalance(1, True), node3mined + Decimal('1.0')) + assert_equal(self.nodes[3].getbalance(1, True, True, False), node3mined + Decimal('1.0')) # Check individual balances reflect the above assert_equal(self.nodes[3].getreceivedbyaddress(mytaddr1), Decimal('1.0')) From d3a1d456f6a8890093778f322dca13027a67162b Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 15 Sep 2020 23:36:57 -0300 Subject: [PATCH 017/121] AddressBookPurpose including shielded receive and send addresses. --- src/addressbook.cpp | 7 +++++++ src/addressbook.h | 3 +++ 2 files changed, 10 insertions(+) diff --git a/src/addressbook.cpp b/src/addressbook.cpp index 82d023601bfa..26dba463f88a 100644 --- a/src/addressbook.cpp +++ b/src/addressbook.cpp @@ -15,6 +15,8 @@ namespace AddressBook { const std::string DELEGATOR{"delegator"}; const std::string COLD_STAKING{"coldstaking"}; const std::string COLD_STAKING_SEND{"coldstaking_send"}; + const std::string SHIELDED_RECEIVE{"shielded_receive"}; + const std::string SHIELDED_SEND{"shielded_spend"}; } bool IsColdStakingPurpose(const std::string& purpose) { @@ -29,10 +31,15 @@ namespace AddressBook { bool CAddressBookData::isSendPurpose() const { return purpose == AddressBookPurpose::SEND; } + bool CAddressBookData::isReceivePurpose() const { return purpose == AddressBookPurpose::RECEIVE; } + bool CAddressBookData::isShielded() const { + return purpose == AddressBookPurpose::SHIELDED_RECEIVE || purpose == AddressBookPurpose::SHIELDED_SEND; + } + } diff --git a/src/addressbook.h b/src/addressbook.h index 844a3575b119..47a49d7168a7 100644 --- a/src/addressbook.h +++ b/src/addressbook.h @@ -18,6 +18,8 @@ namespace AddressBook { extern const std::string DELEGATOR; extern const std::string COLD_STAKING; extern const std::string COLD_STAKING_SEND; + extern const std::string SHIELDED_RECEIVE; + extern const std::string SHIELDED_SEND; } bool IsColdStakingPurpose(const std::string& purpose); @@ -39,6 +41,7 @@ namespace AddressBook { bool isSendColdStakingPurpose() const; bool isSendPurpose() const; bool isReceivePurpose() const; + bool isShielded() const; }; } From 10b08b43daab59e1a41dc09db155c3f497fe680f Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 15 Sep 2020 23:43:44 -0300 Subject: [PATCH 018/121] Include Shielded addresses in addressbook (without changing nor adding any other functionality yet) --- src/qt/addresstablemodel.cpp | 10 ++++++++-- src/wallet/rpcwallet.cpp | 13 +++++++++---- src/wallet/wallet.cpp | 13 ++++++++++--- src/wallet/wallet.h | 15 +++++++++------ 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 48ca5012e35e..f9ef7a2890a8 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -133,7 +133,11 @@ class AddressTablePriv const CChainParams::Base58Type addrType = AddressBook::IsColdStakingPurpose(addrBookData.purpose) ? CChainParams::STAKING_ADDRESS : CChainParams::PUBKEY_ADDRESS; - const CTxDestination& address = it.GetKey(); + + auto dest = it.GetCTxDestKey(); + if (!dest) continue; + + const auto& address = *dest; bool fMine = IsMine(*wallet, address); AddressTableEntry::Type addressType = translateTransactionType( @@ -578,7 +582,9 @@ QString AddressTableModel::getAddressToShow() const for (auto it = wallet->NewAddressBookIterator(); it.IsValid(); it.Next()) { if (it.GetValue().purpose == AddressBook::AddressBookPurpose::RECEIVE) { - const auto &address = it.GetKey(); + auto dest = it.GetCTxDestKey(); + if (!dest) continue; + const auto &address = *dest; if (IsValidDestination(address) && IsMine(*wallet, address) && !wallet->IsUsed(address)) { return QString::fromStdString(EncodeDestination(address)); } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 655313a6d559..874ec0b149eb 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -283,8 +283,8 @@ UniValue getaddressesbylabel(const JSONRPCRequest& request) UniValue ret(UniValue::VOBJ); for (auto it = pwalletMain->NewAddressBookIterator(); it.IsValid(); it.Next()) { auto addrBook = it.GetValue(); - if (addrBook.name == label) { - ret.pushKV(EncodeDestination(it.GetKey(), AddressBook::IsColdStakingPurpose(addrBook.purpose)), AddressBookDataToJSON(addrBook, false)); + if (!addrBook.isShielded() && addrBook.name == label) { + ret.pushKV(EncodeDestination(*it.GetCTxDestKey(), AddressBook::IsColdStakingPurpose(addrBook.purpose)), AddressBookDataToJSON(addrBook, false)); } } @@ -731,9 +731,11 @@ UniValue ListaddressesForPurpose(const std::string strPurpose) for (auto it = pwalletMain->NewAddressBookIterator(); it.IsValid(); it.Next()) { auto addrBook = it.GetValue(); if (addrBook.purpose != strPurpose) continue; + auto dest = it.GetCTxDestKey(); + if (!dest) continue; UniValue entry(UniValue::VOBJ); entry.pushKV("label", addrBook.name); - entry.pushKV("address", EncodeDestination(it.GetKey(), addrType)); + entry.pushKV("address", EncodeDestination(*dest, addrType)); ret.push_back(entry); } } @@ -2226,7 +2228,10 @@ UniValue ListReceived(const UniValue& params, bool by_label) for (auto& itAddrBook = itAddr; itAddrBook.IsValid(); itAddrBook.Next()) { - const auto &address = itAddrBook.GetKey(); + auto* dest = itAddrBook.GetCTxDestKey(); + if (!dest) continue; + + const auto &address = *dest; const std::string &label = itAddrBook.GetValue().name; auto it = mapTally.find(address); if (it == mapTally.end() && !fIncludeEmpty) { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 007c3cfc0f16..3e2a60c37ff1 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3278,7 +3278,7 @@ bool CWallet::HasDelegator(const CTxOut& out) const return false; { LOCK(cs_wallet); // mapAddressBook - std::map::const_iterator mi = mapAddressBook.find(delegator); + const auto mi = mapAddressBook.find(delegator); if (mi == mapAddressBook.end()) return false; return (*mi).second.purpose == AddressBook::AddressBookPurpose::DELEGATOR; @@ -3447,8 +3447,9 @@ std::set CWallet::GetLabelAddresses(const std::string& label) co { LOCK(cs_wallet); std::set result; - for (const std::pair& item : mapAddressBook) { - const CTxDestination& address = item.first; + for (const auto& item : mapAddressBook) { + if (item.second.isShielded()) continue; + const auto& address = boost::get(item.first); const std::string& strName = item.second.name; if (strName == label) result.insert(address); @@ -4697,6 +4698,12 @@ CAmount CWalletTx::GetShieldedAvailableCredit(bool fUseCache) const return GetAvailableCredit(fUseCache, ISMINE_SPENDABLE_SHIELDED); } +const CTxDestination* CAddressBookIterator::GetCTxDestKey() +{ + return boost::get(&it->first); +} + + CStakeableOutput::CStakeableOutput(const CWalletTx* txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, const CBlockIndex*& _pindex) : COutput(txIn, iIn, nDepthIn, fSpendableIn, fSolvableIn), pindex(_pindex) {} diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index d52975ed18b7..b566d9cbe0db 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -248,11 +248,14 @@ struct CRecipient {} }; +// Regular + shielded addresses variant. +typedef boost::variant CWDestination; + class CAddressBookIterator { public: - explicit CAddressBookIterator(std::map& _map) : map(_map), it(_map.begin()), itEnd(_map.end()) {} - CTxDestination GetKey() { return it->first; } + explicit CAddressBookIterator(std::map& _map) : map(_map), it(_map.begin()), itEnd(_map.end()) {} + const CTxDestination* GetCTxDestKey(); AddressBook::CAddressBookData GetValue() { return it->second; } bool IsValid() { return it != itEnd; } @@ -272,9 +275,9 @@ class CAddressBookIterator } private: - std::map& map; - std::map::iterator it; - std::map::iterator itEnd; + std::map& map; + std::map::iterator it; + std::map::iterator itEnd; }; template @@ -346,7 +349,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface CzPIVWallet* zwallet{nullptr}; //! Destination --> label/purpose mapping. - std::map mapAddressBook; + std::map mapAddressBook; public: From 5317e5c1ce0b5524a8bc8c7c816b881ce14c99ce Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 16 Sep 2020 00:20:27 -0300 Subject: [PATCH 019/121] Create new variant for regular destinations and shielded addresses, plus create isMine function covering it. This is an initial step towards include shielded addresses in CTxDestination variant. Cannot be done all at once because the scope of the changes would be pretty extensive. --- src/script/ismine.cpp | 24 ++++++++++++++++++++++++ src/script/ismine.h | 2 +- src/script/standard.h | 3 +++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/script/ismine.cpp b/src/script/ismine.cpp index 254ab7a402ce..bf5b17eb867c 100644 --- a/src/script/ismine.cpp +++ b/src/script/ismine.cpp @@ -49,6 +49,30 @@ isminetype IsMine(const CKeyStore& keystore, const libzcash::SaplingPaymentAddre } } +namespace +{ + class CWDestinationVisitor : public boost::static_visitor + { + private: + const CKeyStore& keystore; + public: + CWDestinationVisitor(const CKeyStore& _keystore) : keystore(_keystore) {} + + isminetype operator()(const CTxDestination& dest) const { + return ::IsMine(keystore, dest); + } + + isminetype operator()(const libzcash::SaplingPaymentAddress& pa) const { + return ::IsMine(keystore, pa); + } + }; +} + +isminetype IsMine(const CKeyStore& keystore, const CWDestination& dest) +{ + return boost::apply_visitor(CWDestinationVisitor(keystore), dest); +} + isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey) { std::vector vSolutions; diff --git a/src/script/ismine.h b/src/script/ismine.h index d87947e694fe..a81acf354514 100644 --- a/src/script/ismine.h +++ b/src/script/ismine.h @@ -40,7 +40,7 @@ typedef uint8_t isminefilter; isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey); isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest); isminetype IsMine(const CKeyStore& keystore, const libzcash::SaplingPaymentAddress& pa); - +isminetype IsMine(const CKeyStore& keystore, const CWDestination& dest); /** * Cachable amount subdivided into watchonly and spendable parts. */ diff --git a/src/script/standard.h b/src/script/standard.h index 1e7bfbe718ea..a411f71f0cdf 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -70,6 +70,9 @@ class CNoDestination { */ typedef boost::variant CTxDestination; +// Regular + shielded addresses variant. +typedef boost::variant CWDestination; + /** Check whether a CTxDestination is a CNoDestination. */ bool IsValidDestination(const CTxDestination& dest); From 217542855fad5251e0c8f7f4fc0a464cb58664fd Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 17 Sep 2020 01:45:27 -0300 Subject: [PATCH 020/121] Implement EncodeDestination for CWDestination variant. --- src/script/standard.cpp | 17 ++++++++++++++--- src/script/standard.h | 7 +++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 60383c0a2e67..c1da8703b0f1 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -2,14 +2,13 @@ // Copyright (c) 2009-2014 The Bitcoin developers // Copyright (c) 2017-2020 The PIVX developers // Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. +// file COPYING or https://www.opensource.org/licenses/mit-license.php. #include "script/standard.h" #include "pubkey.h" +#include "sapling/key_io_sapling.h" #include "script/script.h" -#include "util.h" -#include "utilstrencodings.h" typedef std::vector valtype; @@ -339,3 +338,15 @@ CScript GetScriptForOpReturn(const uint256& message) bool IsValidDestination(const CTxDestination& dest) { return dest.which() != 0; } + +namespace Standard { + + std::string EncodeDestination(const CWDestination &address, const CChainParams::Base58Type addrType) { + const CTxDestination *dest = boost::get(&address); + if (!dest) { + return KeyIO::EncodePaymentAddress(*boost::get(&address)); + } + return EncodeDestination(*dest, addrType); + }; + +} // End Standard namespace diff --git a/src/script/standard.h b/src/script/standard.h index a411f71f0cdf..d602a091aa7e 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -7,6 +7,7 @@ #ifndef BITCOIN_SCRIPT_STANDARD_H #define BITCOIN_SCRIPT_STANDARD_H +#include "chainparams.h" #include "script/interpreter.h" #include "uint256.h" @@ -89,4 +90,10 @@ CScript GetScriptForMultisig(int nRequired, const std::vector& keys); CScript GetScriptForStakeDelegation(const CKeyID& stakingKey, const CKeyID& spendingKey); CScript GetScriptForOpReturn(const uint256& message); +namespace Standard { + +std::string EncodeDestination(const CWDestination &address, const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS); + +} // End Standard namespace + #endif // BITCOIN_SCRIPT_STANDARD_H From dbf75e6cfc655b5be4d19cd2e92835b419cb6bec Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 17 Sep 2020 01:50:11 -0300 Subject: [PATCH 021/121] move every inner wallet map addressbook method using CTxDestination key to CWDestination. --- src/wallet/wallet.cpp | 32 +++++++++++++++++++++----------- src/wallet/wallet.h | 22 ++++++++++++---------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 3e2a60c37ff1..1012703c1e22 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -13,6 +13,7 @@ #include "guiinterfaceutil.h" #include "masternode-payments.h" #include "policy/policy.h" +#include "sapling/key_io_sapling.h" #include "script/sign.h" #include "spork.h" #include "util.h" @@ -3185,14 +3186,14 @@ DBErrors CWallet::ZapWalletTx(std::vector& vWtx) return DB_LOAD_OK; } -std::string CWallet::ParseIntoAddress(const CTxDestination& dest, const std::string& purpose) { +std::string CWallet::ParseIntoAddress(const CWDestination& dest, const std::string& purpose) { const CChainParams::Base58Type addrType = AddressBook::IsColdStakingPurpose(purpose) ? CChainParams::STAKING_ADDRESS : CChainParams::PUBKEY_ADDRESS; - return EncodeDestination(dest, addrType); + return Standard::EncodeDestination(dest, addrType); } -bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& strPurpose) +bool CWallet::SetAddressBook(const CWDestination& address, const std::string& strName, const std::string& strPurpose) { bool fUpdated = HasAddressBook(address); { @@ -3211,9 +3212,9 @@ bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& s return CWalletDB(strWalletFile).WriteName(addressStr, strName); } -bool CWallet::DelAddressBook(const CTxDestination& address, const CChainParams::Base58Type addrType) +bool CWallet::DelAddressBook(const CWDestination& address, const CChainParams::Base58Type addrType) { - std::string strAddress = EncodeDestination(address, addrType); + std::string strAddress = Standard::EncodeDestination(address, addrType); std::string purpose = GetPurposeForAddressBookEntry(address); { LOCK(cs_wallet); // mapAddressBook @@ -3235,38 +3236,38 @@ bool CWallet::DelAddressBook(const CTxDestination& address, const CChainParams:: return CWalletDB(strWalletFile).EraseName(strAddress); } -std::string CWallet::GetPurposeForAddressBookEntry(const CTxDestination& address) const +std::string CWallet::GetPurposeForAddressBookEntry(const CWDestination& address) const { LOCK(cs_wallet); auto it = mapAddressBook.find(address); return it != mapAddressBook.end() ? it->second.purpose : ""; } -std::string CWallet::GetNameForAddressBookEntry(const CTxDestination& address) const +std::string CWallet::GetNameForAddressBookEntry(const CWDestination& address) const { LOCK(cs_wallet); auto it = mapAddressBook.find(address); return it != mapAddressBook.end() ? it->second.name : ""; } -Optional CWallet::GetAddressBookEntry(const CTxDestination& dest) const +Optional CWallet::GetAddressBookEntry(const CWDestination& dest) const { LOCK(cs_wallet); auto it = mapAddressBook.find(dest); return it != mapAddressBook.end() ? Optional(it->second) : nullopt; } -void CWallet::LoadAddressBookName(const CTxDestination& dest, const std::string& strName) +void CWallet::LoadAddressBookName(const CWDestination& dest, const std::string& strName) { mapAddressBook[dest].name = strName; } -void CWallet::LoadAddressBookPurpose(const CTxDestination& dest, const std::string& strPurpose) +void CWallet::LoadAddressBookPurpose(const CWDestination& dest, const std::string& strPurpose) { mapAddressBook[dest].purpose = strPurpose; } -bool CWallet::HasAddressBook(const CTxDestination& address) const +bool CWallet::HasAddressBook(const CWDestination& address) const { return WITH_LOCK(cs_wallet, return mapAddressBook.count(address)); } @@ -4703,6 +4704,15 @@ const CTxDestination* CAddressBookIterator::GetCTxDestKey() return boost::get(&it->first); } +const libzcash::SaplingPaymentAddress* CAddressBookIterator::GetShieldedDestKey() +{ + return boost::get(&it->first); +} + +const CWDestination* CAddressBookIterator::GetDestKey() +{ + return &it->first; +} CStakeableOutput::CStakeableOutput(const CWalletTx* txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, const CBlockIndex*& _pindex) : COutput(txIn, iIn, nDepthIn, fSpendableIn, fSolvableIn), diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index b566d9cbe0db..e9040c93ec20 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -255,7 +255,9 @@ class CAddressBookIterator { public: explicit CAddressBookIterator(std::map& _map) : map(_map), it(_map.begin()), itEnd(_map.end()) {} + const CWDestination* GetDestKey(); const CTxDestination* GetCTxDestKey(); + const libzcash::SaplingPaymentAddress* GetShieldedDestKey(); AddressBook::CAddressBookData GetValue() { return it->second; } bool IsValid() { return it != itEnd; } @@ -738,21 +740,21 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface DBErrors LoadWallet(bool& fFirstRunRet); DBErrors ZapWalletTx(std::vector& vWtx); - static std::string ParseIntoAddress(const CTxDestination& dest, const std::string& purpose); + static std::string ParseIntoAddress(const CWDestination& dest, const std::string& purpose); - bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& purpose); - bool DelAddressBook(const CTxDestination& address, const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS); - bool HasAddressBook(const CTxDestination& address) const; + bool SetAddressBook(const CWDestination& address, const std::string& strName, const std::string& purpose); + bool DelAddressBook(const CWDestination& address, const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS); + bool HasAddressBook(const CWDestination& address) const; bool HasDelegator(const CTxOut& out) const; int GetAddressBookSize() const { return mapAddressBook.size(); }; CAddressBookIterator NewAddressBookIterator() { return CAddressBookIterator(mapAddressBook); } - std::string GetPurposeForAddressBookEntry(const CTxDestination& address) const; - std::string GetNameForAddressBookEntry(const CTxDestination& address) const; - Optional GetAddressBookEntry(const CTxDestination& address) const; + std::string GetPurposeForAddressBookEntry(const CWDestination& address) const; + std::string GetNameForAddressBookEntry(const CWDestination& address) const; + Optional GetAddressBookEntry(const CWDestination& address) const; - void LoadAddressBookName(const CTxDestination& dest, const std::string& strName); - void LoadAddressBookPurpose(const CTxDestination& dest, const std::string& strPurpose); + void LoadAddressBookName(const CWDestination& dest, const std::string& strName); + void LoadAddressBookPurpose(const CWDestination& dest, const std::string& strPurpose); bool UpdatedTransaction(const uint256& hashTx); @@ -797,7 +799,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface * Address book entry changed. * @note called with lock cs_wallet held. */ - boost::signals2::signal NotifyAddressBookChanged; + boost::signals2::signal NotifyAddressBookChanged; /** * Wallet transaction added, removed or updated. From 46c0edd026b9d4cfd9b8da9f54d2939523453f9f Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 17 Sep 2020 01:53:56 -0300 Subject: [PATCH 022/121] Standard: implement CWDestination DecodeDestination. --- src/script/standard.cpp | 12 ++++++++++++ src/script/standard.h | 2 ++ 2 files changed, 14 insertions(+) diff --git a/src/script/standard.cpp b/src/script/standard.cpp index c1da8703b0f1..afae56e4da50 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -6,6 +6,7 @@ #include "script/standard.h" +#include "base58.h" #include "pubkey.h" #include "sapling/key_io_sapling.h" #include "script/script.h" @@ -349,4 +350,15 @@ namespace Standard { return EncodeDestination(*dest, addrType); }; + CWDestination DecodeDestination(const std::string& strAddress) + { + CWDestination dest; + CTxDestination regDest = ::DecodeDestination(strAddress); + if (!IsValidDestination(regDest)) { + return KeyIO::DecodeSaplingPaymentAddress(strAddress); + } + return regDest; + + } + } // End Standard namespace diff --git a/src/script/standard.h b/src/script/standard.h index d602a091aa7e..4dd648485552 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -94,6 +94,8 @@ namespace Standard { std::string EncodeDestination(const CWDestination &address, const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS); +CWDestination DecodeDestination(const std::string& strAddress); + } // End Standard namespace #endif // BITCOIN_SCRIPT_STANDARD_H From c590d1d9ed557fa27e30a749b6427aa2575e7cfe Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 17 Sep 2020 01:55:23 -0300 Subject: [PATCH 023/121] walletdb: connect shielded addresses in loadAddressBook --- src/wallet/walletdb.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 5b871b74ab22..5ebc25bf1f2d 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -11,6 +11,7 @@ #include "base58.h" #include "protocol.h" +#include "sapling/key_io_sapling.h" #include "serialize.h" #include "sync.h" #include "txdb.h" @@ -424,13 +425,13 @@ bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CW ssKey >> strAddress; std::string strName; ssValue >> strName; - pwallet->LoadAddressBookName(DecodeDestination(strAddress), strName); + pwallet->LoadAddressBookName(Standard::DecodeDestination(strAddress), strName); } else if (strType == "purpose") { std::string strAddress; ssKey >> strAddress; std::string strPurpose; ssValue >> strPurpose; - pwallet->LoadAddressBookPurpose(DecodeDestination(strAddress), strPurpose); + pwallet->LoadAddressBookPurpose(Standard::DecodeDestination(strAddress), strPurpose); } else if (strType == "tx") { uint256 hash; ssKey >> hash; From 73be51a62ed76a21258f50960fc2ba439f01ff98 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 17 Sep 2020 01:56:31 -0300 Subject: [PATCH 024/121] Implement IsShieldedPurpose(strPurpose). --- src/addressbook.cpp | 7 ++++++- src/addressbook.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/addressbook.cpp b/src/addressbook.cpp index 26dba463f88a..e8f2755cc36f 100644 --- a/src/addressbook.cpp +++ b/src/addressbook.cpp @@ -24,6 +24,11 @@ namespace AddressBook { || purpose == AddressBookPurpose::COLD_STAKING_SEND; } + bool IsShieldedPurpose(const std::string& purpose) { + return purpose == AddressBookPurpose::SHIELDED_RECEIVE + || purpose == AddressBookPurpose::SHIELDED_SEND; + } + bool CAddressBookData::isSendColdStakingPurpose() const { return purpose == AddressBookPurpose::COLD_STAKING_SEND; } @@ -37,7 +42,7 @@ namespace AddressBook { } bool CAddressBookData::isShielded() const { - return purpose == AddressBookPurpose::SHIELDED_RECEIVE || purpose == AddressBookPurpose::SHIELDED_SEND; + return IsShieldedPurpose(purpose); } diff --git a/src/addressbook.h b/src/addressbook.h index 47a49d7168a7..42b1e549f413 100644 --- a/src/addressbook.h +++ b/src/addressbook.h @@ -23,6 +23,7 @@ namespace AddressBook { } bool IsColdStakingPurpose(const std::string& purpose); + bool IsShieldedPurpose(const std::string& purpose); /** Address book data */ class CAddressBookData { From 4e056742deadbb944f41b0057a363a1a3ffa7cbe Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 17 Sep 2020 01:57:39 -0300 Subject: [PATCH 025/121] Enable shielded addresses addressbook capabilities. --- src/wallet/wallet.cpp | 6 +++++- src/wallet/wallet.h | 5 +---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 1012703c1e22..7a5841470353 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4451,7 +4451,11 @@ int CWallet::GetVersion() ///////////////// Sapling Methods ////////////////////////// //////////////////////////////////////////////////////////// -libzcash::SaplingPaymentAddress CWallet::GenerateNewSaplingZKey() { return m_sspk_man->GenerateNewSaplingZKey(); } +libzcash::SaplingPaymentAddress CWallet::GenerateNewSaplingZKey(std::string label) { + auto address = m_sspk_man->GenerateNewSaplingZKey(); + SetAddressBook(address, label, AddressBook::AddressBookPurpose::SHIELDED_RECEIVE); + return address; +} void CWallet::IncrementNoteWitnesses(const CBlockIndex* pindex, const CBlock* pblock, diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index e9040c93ec20..9d6d26547055 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -248,9 +248,6 @@ struct CRecipient {} }; -// Regular + shielded addresses variant. -typedef boost::variant CWDestination; - class CAddressBookIterator { public: @@ -520,7 +517,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool FindNotesDataAndAddMissingIVKToKeystore(const CTransaction& tx, Optional& saplingNoteData); //! Generates new Sapling key - libzcash::SaplingPaymentAddress GenerateNewSaplingZKey(); + libzcash::SaplingPaymentAddress GenerateNewSaplingZKey(std::string label = ""); //! pindex is the new tip being connected. void IncrementNoteWitnesses(const CBlockIndex* pindex, From 852c38748578f70d2b6ee1fb09a8216c2b3bb1b8 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 17 Sep 2020 02:00:05 -0300 Subject: [PATCH 026/121] Missing CTxDestination -> CWDestination methods. --- src/qt/walletmodel.cpp | 4 ++-- src/qt/walletmodel.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index dc8a03c505e1..7cc5452dea6f 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -291,7 +291,7 @@ bool WalletModel::validateAddress(const QString& address, bool fStaking) return IsValidDestinationString(address.toStdString(), fStaking); } -bool WalletModel::updateAddressBookLabels(const CTxDestination& dest, const std::string& strName, const std::string& strPurpose) +bool WalletModel::updateAddressBookLabels(const CWDestination& dest, const std::string& strName, const std::string& strPurpose) { auto optAdd = pwalletMain->GetAddressBookEntry(dest); // Check if we have a new address or an updated label @@ -603,7 +603,7 @@ static void NotifyKeyStoreStatusChanged(WalletModel* walletmodel, CCryptoKeyStor QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); } -static void NotifyAddressBookChanged(WalletModel* walletmodel, CWallet* wallet, const CTxDestination& address, const std::string& label, bool isMine, const std::string& purpose, ChangeType status) +static void NotifyAddressBookChanged(WalletModel* walletmodel, CWallet* wallet, const CWDestination& address, const std::string& label, bool isMine, const std::string& purpose, ChangeType status) { QString strAddress = QString::fromStdString(pwalletMain->ParseIntoAddress(address, purpose)); QString strLabel = QString::fromStdString(label); diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index e8704c23b9c1..829426f4b573 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -366,7 +366,7 @@ public Q_SLOTS: /* Current, immature or unconfirmed balance might have changed - emit 'balanceChanged' if so */ void pollBalanceChanged(); /* Update address book labels in the database */ - bool updateAddressBookLabels(const CTxDestination& address, const std::string& strName, const std::string& strPurpose); + bool updateAddressBookLabels(const CWDestination& address, const std::string& strName, const std::string& strPurpose); }; #endif // PIVX_QT_WALLETMODEL_H From 43e80db48326e98f5d35440034b0c4894e252ec0 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 17 Sep 2020 02:00:39 -0300 Subject: [PATCH 027/121] Enable addressbook label support in walletModel::getNewShieldedAddress --- src/qt/walletmodel.cpp | 8 ++++++++ src/qt/walletmodel.h | 3 +++ 2 files changed, 11 insertions(+) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 7cc5452dea6f..187895407e54 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -15,6 +15,7 @@ #include "base58.h" #include "db.h" #include "keystore.h" +#include "sapling/key_io_sapling.h" #include "spork.h" #include "sync.h" #include "guiinterface.h" @@ -773,6 +774,13 @@ PairResult WalletModel::getNewStakingAddress(Destination& ret,std::string label) return res; } +PairResult WalletModel::getNewShieldedAddress(QString& shieldedAddrRet, std::string strLabel) +{ + shieldedAddrRet = QString::fromStdString( + KeyIO::EncodePaymentAddress(wallet->GenerateNewSaplingZKey(strLabel))); + return PairResult(true); +} + bool WalletModel::whitelistAddressFromColdStaking(const QString &addressStr) { return updateAddressBookPurpose(addressStr, AddressBook::AddressBookPurpose::DELEGATOR); diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 829426f4b573..0e151615f2c5 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -250,6 +250,9 @@ class WalletModel : public QObject */ PairResult getNewStakingAddress(Destination& ret, std::string label = "") const; + //! Return a new shielded address. + PairResult getNewShieldedAddress(QString& shieldedAddrRet, std::string strLabel = ""); + bool whitelistAddressFromColdStaking(const QString &addressStr); bool blacklistAddressFromColdStaking(const QString &address); bool updateAddressBookPurpose(const QString &addressStr, const std::string& purpose); From 5fff0e08758ea61c9ae0f8d3593a2fc42e1b9768 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 17 Sep 2020 02:03:06 -0300 Subject: [PATCH 028/121] GUI: Add shielded addresses support to addressTableModel. --- src/qt/addresstablemodel.cpp | 34 ++++++++++++++++++++++++---------- src/qt/addresstablemodel.h | 2 ++ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index f9ef7a2890a8..d58918075d33 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -14,6 +14,8 @@ #include "wallet/wallet.h" #include "askpassphrasedialog.h" +#include "sapling/key_io_sapling.h" + #include #include @@ -26,6 +28,8 @@ const QString AddressTableModel::Delegator = "D"; const QString AddressTableModel::Delegable = "E"; const QString AddressTableModel::ColdStaking = "C"; const QString AddressTableModel::ColdStakingSend = "T"; +const QString AddressTableModel::ShieldedReceive = "U"; +const QString AddressTableModel::ShieldedSend = "V"; struct AddressTableEntry { enum Type { @@ -36,6 +40,8 @@ struct AddressTableEntry { Delegable, ColdStaking, ColdStakingSend, + ShieldedReceive, + ShieldedSend, Hidden /* QSortFilterProxyModel will filter these out */ }; @@ -82,6 +88,10 @@ static AddressTableEntry::Type translateTransactionType(const QString& strPurpos addressType = AddressTableEntry::ColdStaking; else if (strPurpose == QString::fromStdString(AddressBook::AddressBookPurpose::COLD_STAKING_SEND)) addressType = AddressTableEntry::ColdStakingSend; + else if (strPurpose == QString::fromStdString(AddressBook::AddressBookPurpose::SHIELDED_RECEIVE)) + addressType = AddressTableEntry::ShieldedReceive; + else if (strPurpose == QString::fromStdString(AddressBook::AddressBookPurpose::SHIELDED_SEND)) + addressType = AddressTableEntry::ShieldedSend; else if (strPurpose == "unknown" || strPurpose == "") // if purpose not set, guess addressType = (isMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending); return addressType; @@ -134,25 +144,24 @@ class AddressTablePriv AddressBook::IsColdStakingPurpose(addrBookData.purpose) ? CChainParams::STAKING_ADDRESS : CChainParams::PUBKEY_ADDRESS; - auto dest = it.GetCTxDestKey(); - if (!dest) continue; - - const auto& address = *dest; + const CWDestination& dest = *it.GetDestKey(); + bool fMine = IsMine(*wallet, dest); + QString addressStr = QString::fromStdString(Standard::EncodeDestination(dest, addrType)); + uint creationTime = 0; + if (addrBookData.isReceivePurpose()) { + const auto& address = *boost::get(&dest); + creationTime = static_cast(wallet->GetKeyCreationTime(address)); + } - bool fMine = IsMine(*wallet, address); AddressTableEntry::Type addressType = translateTransactionType( QString::fromStdString(addrBookData.purpose), fMine); const std::string& strName = addrBookData.name; - uint creationTime = 0; - if (addrBookData.isReceivePurpose()) - creationTime = static_cast(wallet->GetKeyCreationTime(address)); - updatePurposeCachedCounted(addrBookData.purpose, true); cachedAddressTable.append( AddressTableEntry(addressType, QString::fromStdString(strName), - QString::fromStdString(EncodeDestination(address, addrType)), + addressStr, creationTime ) ); @@ -164,6 +173,7 @@ class AddressTablePriv std::sort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan()); } + // add shielded addresses num if needed.. void updatePurposeCachedCounted(std::string purpose, bool add) { int *var = nullptr; @@ -358,6 +368,10 @@ QVariant AddressTableModel::data(const QModelIndex& index, int role) const return ColdStaking; case AddressTableEntry::ColdStakingSend: return ColdStakingSend; + case AddressTableEntry::ShieldedReceive: + return ShieldedReceive; + case AddressTableEntry::ShieldedSend: + return ShieldedSend; default: break; } diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h index 1ebd9a0a8285..1f368cc6d8ac 100644 --- a/src/qt/addresstablemodel.h +++ b/src/qt/addresstablemodel.h @@ -53,6 +53,8 @@ class AddressTableModel : public QAbstractTableModel static const QString Delegable; /**< Specifies cold staking addresses which delegated tokens to this wallet*/ static const QString ColdStaking; /**< Specifies cold staking own addresses */ static const QString ColdStakingSend; /**< Specifies send cold staking addresses (simil 'contacts')*/ + static const QString ShieldedReceive; /**< Specifies shielded send address */ + static const QString ShieldedSend; /**< Specifies shielded receive address */ /** @name Methods overridden from QAbstractTableModel @{*/ From b05916e1cbd90295b66105ea3d9778ae10782e67 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 17 Sep 2020 02:03:46 -0300 Subject: [PATCH 029/121] GUI: getAddressToShow() include shielded address support. --- src/qt/addresstablemodel.cpp | 34 +++++++++++++++++++++++----------- src/qt/addresstablemodel.h | 2 +- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index d58918075d33..33a047d44fb8 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -590,26 +590,38 @@ bool AddressTableModel::isWhitelisted(const std::string& address) const * Return an unused address * @return */ -QString AddressTableModel::getAddressToShow() const +QString AddressTableModel::getAddressToShow(bool isShielded) const { LOCK(wallet->cs_wallet); for (auto it = wallet->NewAddressBookIterator(); it.IsValid(); it.Next()) { - if (it.GetValue().purpose == AddressBook::AddressBookPurpose::RECEIVE) { - auto dest = it.GetCTxDestKey(); - if (!dest) continue; - const auto &address = *dest; - if (IsValidDestination(address) && IsMine(*wallet, address) && !wallet->IsUsed(address)) { - return QString::fromStdString(EncodeDestination(address)); + const auto addrData = it.GetValue(); + + if (!isShielded) { + if (addrData.purpose == AddressBook::AddressBookPurpose::RECEIVE) { + const auto &address = *it.GetCTxDestKey(); + if (IsValidDestination(address) && IsMine(*wallet, address) && !wallet->IsUsed(address)) { + return QString::fromStdString(EncodeDestination(address)); + } + } + } else { + // todo: add shielded address support to IsUsed + if (addrData.purpose == AddressBook::AddressBookPurpose::SHIELDED_RECEIVE) { + const auto &address = *it.GetShieldedDestKey(); + if (IsValidPaymentAddress(address) && IsMine(*wallet, address)) { + return QString::fromStdString(KeyIO::EncodePaymentAddress(address)); + } } } } - // For some reason we don't have any address in our address book, let's create one QString addressStr; - Destination newAddress; - if (walletModel->getNewAddress(newAddress, "Default").result) { - addressStr = QString::fromStdString(newAddress.ToString()); + if (!isShielded) { + // For some reason we don't have any address in our address book, let's create one + Destination newAddress; + if (walletModel->getNewAddress(newAddress, "Default").result) { + addressStr = QString::fromStdString(newAddress.ToString()); + } } return addressStr; } diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h index 1f368cc6d8ac..dcb88d987732 100644 --- a/src/qt/addresstablemodel.h +++ b/src/qt/addresstablemodel.h @@ -100,7 +100,7 @@ class AddressTableModel : public QAbstractTableModel /** * Return last unused address */ - QString getAddressToShow() const; + QString getAddressToShow(bool shielded = false) const; EditStatus getEditStatus() const { return editStatus; } From 771dff01f31604014447d556e9fb0d642b680be9 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 17 Sep 2020 02:08:46 -0300 Subject: [PATCH 030/121] [GUI] Receive widget: add shielded addresses capabilities. --- src/qt/pivx/forms/receivewidget.ui | 114 +++++++++++++++++++++++++++++ src/qt/pivx/receivewidget.cpp | 68 +++++++++++++---- src/qt/pivx/receivewidget.h | 5 +- 3 files changed, 172 insertions(+), 15 deletions(-) diff --git a/src/qt/pivx/forms/receivewidget.ui b/src/qt/pivx/forms/receivewidget.ui index da043d141f9b..fed5bc931156 100644 --- a/src/qt/pivx/forms/receivewidget.ui +++ b/src/qt/pivx/forms/receivewidget.ui @@ -97,6 +97,120 @@ + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 120 + 30 + + + + + 120 + 30 + + + + Qt::NoFocus + + + Public + + + true + + + true + + + + + + + + 120 + 30 + + + + + 120 + 30 + + + + Qt::NoFocus + + + Shielded + + + true + + + true + + + true + + + + + + + + + + Accept public PIV or shielded PIV + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + diff --git a/src/qt/pivx/receivewidget.cpp b/src/qt/pivx/receivewidget.cpp index d61b108bf6ea..e33a47cc7a3d 100644 --- a/src/qt/pivx/receivewidget.cpp +++ b/src/qt/pivx/receivewidget.cpp @@ -47,6 +47,12 @@ ReceiveWidget::ReceiveWidget(PIVXGUI* parent) : // Address setCssProperty(ui->labelAddress, "label-address-box"); + /* Button Group */ + setCssProperty(ui->pushLeft, "btn-check-left"); + setCssProperty(ui->pushRight, "btn-check-right"); + setCssSubtitleScreen(ui->labelSubtitle2); + ui->labelSubtitle2->setContentsMargins(0,2,4,0); + setCssSubtitleScreen(ui->labelDate); setCssSubtitleScreen(ui->labelLabel); @@ -100,6 +106,10 @@ ReceiveWidget::ReceiveWidget(PIVXGUI* parent) : connect(ui->listViewAddress, &QListView::clicked, this, &ReceiveWidget::handleAddressClicked); connect(ui->btnRequest, &OptionButton::clicked, this, &ReceiveWidget::onRequestClicked); connect(ui->btnMyAddresses, &OptionButton::clicked, this, &ReceiveWidget::onMyAddressesClicked); + + ui->pushLeft->setChecked(true); + connect(ui->pushLeft, &QPushButton::clicked, [this](){onTransparentSelected(true);}); + connect(ui->pushRight, &QPushButton::clicked, [this](){onTransparentSelected(false);}); } void ReceiveWidget::loadWalletModel() @@ -129,20 +139,37 @@ void ReceiveWidget::refreshView(const QModelIndex& tl, const QModelIndex& br) void ReceiveWidget::refreshView(QString refreshAddress) { try { - QString latestAddress = (refreshAddress.isEmpty()) ? this->addressTableModel->getAddressToShow() : refreshAddress; + QString latestAddress = (refreshAddress.isEmpty()) ? this->addressTableModel->getAddressToShow(shieldedMode) : refreshAddress; + if (latestAddress.isEmpty()) { // new default address - Destination newAddress; - PairResult r = walletModel->getNewAddress(newAddress, "Default"); + PairResult r(false); + if (!shieldedMode) { + Destination newAddress; + r = walletModel->getNewAddress(newAddress, "Default"); + latestAddress = QString::fromStdString(newAddress.ToString()); + } else { + // shielded + r = walletModel->getNewShieldedAddress(latestAddress, "default shielded"); + } + // Check for generation errors if (!r.result) { ui->labelQrImg->setText(tr("No available address, try unlocking the wallet")); inform(tr("Error generating address")); return; } - latestAddress = QString::fromStdString(newAddress.ToString()); } - ui->labelAddress->setText(latestAddress); - int64_t time = walletModel->getKeyCreationTime(DecodeDestination(latestAddress.toStdString())); + + QString addressToShow = latestAddress; + int64_t time = 0; + if (shieldedMode) { + addressToShow = addressToShow.left(20) + "..." + addressToShow.right(20); + // add creation time support for shielded addresses. + } else { + time = walletModel->getKeyCreationTime(DecodeDestination(latestAddress.toStdString())); + } + + ui->labelAddress->setText(addressToShow); ui->labelDate->setText(GUIUtil::dateTimeStr(QDateTime::fromTime_t(static_cast(time)))); updateQr(latestAddress); updateLabel(); @@ -167,7 +194,7 @@ void ReceiveWidget::updateLabel() } } -void ReceiveWidget::updateQr(QString address) +void ReceiveWidget::updateQr(QString& address) { info->address = address; QString uri = GUIUtil::formatBitcoinURI(*info); @@ -200,7 +227,7 @@ void ReceiveWidget::onLabelClicked() dialog->setData(info->address, addressTableModel->labelForAddress(info->address)); if (openDialogWithOpaqueBackgroundY(dialog, window, 3.5, 6)) { QString label = dialog->getLabel(); - const CTxDestination address = DecodeDestination(info->address.toUtf8().constData()); + const CWDestination address = Standard::DecodeDestination(info->address.toUtf8().constData()); if (!label.isEmpty() && walletModel->updateAddressBookLabels( address, label.toUtf8().constData(), @@ -227,18 +254,24 @@ void ReceiveWidget::onNewAddressClicked() inform(tr("Cannot create new address, wallet locked")); return; } - Destination address; - PairResult r = walletModel->getNewAddress(address, ""); - // Check for validity + QString strAddress; + PairResult r(false); + if (!shieldedMode) { + Destination address; + r = walletModel->getNewAddress(address, ""); + strAddress = QString::fromStdString(address.ToString()); + } else { + r = walletModel->getNewShieldedAddress(strAddress, ""); + } + + // Check validity if (!r.result) { inform(r.status->c_str()); return; } - updateQr(QString::fromStdString(address.ToString())); - ui->labelAddress->setText(!info->address.isEmpty() ? info->address : tr("No address")); - updateLabel(); + refreshView(strAddress); inform(tr("New address created")); } catch (const std::runtime_error& error) { // Error generating address @@ -318,6 +351,13 @@ void ReceiveWidget::sortAddresses() this->filter->sort(sortType, sortOrder); } +void ReceiveWidget::onTransparentSelected(bool transparentSelected) +{ + this->shieldedMode = !transparentSelected; + refreshView(); + this->filter->setType(shieldedMode ? AddressTableModel::ShieldedReceive : AddressTableModel::Receive); +}; + void ReceiveWidget::changeTheme(bool isLightTheme, QString& theme) { static_cast(this->delegate->getRowFactory())->isLightTheme = isLightTheme; diff --git a/src/qt/pivx/receivewidget.h b/src/qt/pivx/receivewidget.h index 9e0d0b98db20..2828de94f041 100644 --- a/src/qt/pivx/receivewidget.h +++ b/src/qt/pivx/receivewidget.h @@ -67,12 +67,15 @@ private Q_SLOTS: AddressTableModel::ColumnIndex sortType = AddressTableModel::Label; Qt::SortOrder sortOrder = Qt::AscendingOrder; - void updateQr(QString address); + void updateQr(QString& address); void updateLabel(); void showAddressGenerationDialog(bool isPaymentRequest); void sortAddresses(); + void onTransparentSelected(bool transparentSelected); bool isShowingDialog = false; + // Whether the main section is presenting a shielded address or a regular one + bool shieldedMode = false; }; From c2af18da4365c529b06a1f1764903d1baf5b57f1 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 18 Sep 2020 13:57:23 -0300 Subject: [PATCH 031/121] Include methods: IsValidDestination(CWDestination) and DecodeDestination returning whether the address is for staking or not. --- src/script/standard.cpp | 21 +++++++++++++++++++-- src/script/standard.h | 3 +++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/script/standard.cpp b/src/script/standard.cpp index afae56e4da50..a82fbac1fce0 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -351,14 +351,31 @@ namespace Standard { }; CWDestination DecodeDestination(const std::string& strAddress) + { + bool isStaking = false; + return DecodeDestination(strAddress, isStaking); + } + + CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking) { CWDestination dest; - CTxDestination regDest = ::DecodeDestination(strAddress); + CTxDestination regDest = ::DecodeDestination(strAddress, isStaking); if (!IsValidDestination(regDest)) { - return KeyIO::DecodeSaplingPaymentAddress(strAddress); + const auto sapDest = KeyIO::DecodeSaplingPaymentAddress(strAddress); + if (sapDest) return *sapDest; } return regDest; } + bool IsValidDestination(const CWDestination& address) + { + // Only regular base58 addresses and shielded addresses accepted here for now + const libzcash::SaplingPaymentAddress *dest1 = boost::get(&address); + if (dest1) return true; + + const CTxDestination *dest = boost::get(&address); + return dest && ::IsValidDestination(*dest); + } + } // End Standard namespace diff --git a/src/script/standard.h b/src/script/standard.h index 4dd648485552..e5e441e7ffb9 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -95,6 +95,9 @@ namespace Standard { std::string EncodeDestination(const CWDestination &address, const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS); CWDestination DecodeDestination(const std::string& strAddress); +CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking); + +bool IsValidDestination(const CWDestination& dest); } // End Standard namespace From ca7300484223b4badd677f53b7320976edecc601 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 18 Sep 2020 13:58:24 -0300 Subject: [PATCH 032/121] Implementing GetKeyCreationTime for Sapling addresses. --- src/sapling/saplingscriptpubkeyman.cpp | 6 ++++++ src/sapling/saplingscriptpubkeyman.h | 3 +++ src/wallet/wallet.cpp | 7 +++++++ src/wallet/wallet.h | 1 + 4 files changed, 17 insertions(+) diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index 20cec08b056c..bb9454f2fbc4 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -829,6 +829,12 @@ libzcash::SaplingPaymentAddress SaplingScriptPubKeyMan::GenerateNewSaplingZKey() return xsk.DefaultAddress(); } +int64_t SaplingScriptPubKeyMan::GetKeyCreationTime(const libzcash::SaplingIncomingViewingKey& ivk) +{ + auto it = mapSaplingZKeyMetadata.find(ivk); + return it != mapSaplingZKeyMetadata.end() ? it->second.nCreateTime : 0; +} + void SaplingScriptPubKeyMan::GetConflicts(const CWalletTx& wtx, std::set& result) const { AssertLockHeld(wallet->cs_wallet); diff --git a/src/sapling/saplingscriptpubkeyman.h b/src/sapling/saplingscriptpubkeyman.h index 85d4c26f7128..530a09bd4c6b 100644 --- a/src/sapling/saplingscriptpubkeyman.h +++ b/src/sapling/saplingscriptpubkeyman.h @@ -181,6 +181,9 @@ class SaplingScriptPubKeyMan { void GetConflicts(const CWalletTx& wtx, std::set& result) const; + // Get the ivk creation time (we are only using the ivk's default address) + int64_t GetKeyCreationTime(const libzcash::SaplingIncomingViewingKey& ivk); + // Add full viewing key if it's not already in the wallet KeyAddResult AddViewingKeyToWallet(const libzcash::SaplingExtendedFullViewingKey &extfvk) const; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7a5841470353..57781b0d966f 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -167,6 +167,13 @@ int64_t CWallet::GetKeyCreationTime(const CTxDestination& address) return 0; } +int64_t CWallet::GetKeyCreationTime(const libzcash::SaplingPaymentAddress& address) +{ + libzcash::SaplingIncomingViewingKey ivk; + return GetSaplingIncomingViewingKey(address, ivk) ? + GetSaplingScriptPubKeyMan()->GetKeyCreationTime(ivk) : 0; +} + bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey& pubkey) { AssertLockHeld(cs_wallet); // mapKeyMetadata diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 9d6d26547055..d8419e79a9c6 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -510,6 +510,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface PairResult getNewStakingAddress(CTxDestination& ret, std::string label); int64_t GetKeyCreationTime(CPubKey pubkey); int64_t GetKeyCreationTime(const CTxDestination& address); + int64_t GetKeyCreationTime(const libzcash::SaplingPaymentAddress& address); //////////// Sapling ////////////////// From 7a932c959b7fab938e8422a39d334e80ab09c4da Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 18 Sep 2020 13:59:19 -0300 Subject: [PATCH 033/121] [GUI] WalletModel::getKeyCreationTime connected for shielded addresses. --- src/qt/walletmodel.cpp | 21 ++++++++++++++++++++- src/qt/walletmodel.h | 2 ++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 187895407e54..f3350eacc150 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -758,6 +758,25 @@ int64_t WalletModel::getKeyCreationTime(const CTxDestination& address) return 0; } +int64_t WalletModel::getKeyCreationTime(const std::string& address) +{ + CWDestination dest = Standard::DecodeDestination(address); + const CTxDestination* add = boost::get(&dest); + if (add && IsValidDestination(*add)) { + return getKeyCreationTime(*add); + } else { + return getKeyCreationTime(*boost::get(&dest)); + } +} + +int64_t WalletModel::getKeyCreationTime(const libzcash::SaplingPaymentAddress& address) +{ + if (this->isMine(address)) { + return pwalletMain->GetKeyCreationTime(address); + } + return 0; +} + PairResult WalletModel::getNewAddress(Destination& ret, std::string label) const { CTxDestination dest; @@ -954,7 +973,7 @@ bool WalletModel::saveReceiveRequest(const std::string& sAddress, const int64_t return wallet->AddDestData(dest, key, sRequest); } -bool WalletModel::isMine(const CTxDestination& address) +bool WalletModel::isMine(const CWDestination& address) { return IsMine(*wallet, address); } diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 0e151615f2c5..b1ed83d2683e 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -244,6 +244,8 @@ class WalletModel : public QObject int64_t getCreationTime() const; int64_t getKeyCreationTime(const CPubKey& key); int64_t getKeyCreationTime(const CTxDestination& address); + int64_t getKeyCreationTime(const std::string& address); + int64_t getKeyCreationTime(const libzcash::SaplingPaymentAddress& address); PairResult getNewAddress(Destination& ret, std::string label = "") const; /** * Return a new address used to receive for delegated cold stake purpose. From 2f3f84515eae9334301604065435de04732548c6 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 18 Sep 2020 14:00:01 -0300 Subject: [PATCH 034/121] [GUI] WalletModel::validateAddress now contemplating shielded addresses. --- src/qt/walletmodel.cpp | 8 ++++++-- src/qt/walletmodel.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index f3350eacc150..25c293bbd089 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -283,8 +283,12 @@ void WalletModel::updateWatchOnlyFlag(bool fHaveWatchonly) bool WalletModel::validateAddress(const QString& address) { - // Only regular base58 addresses accepted here - return IsValidDestinationString(address.toStdString(), false); + // Only regular base58 addresses and shielded addresses accepted here + bool isStaking = false; + CWDestination dest = Standard::DecodeDestination(address.toStdString(), isStaking); + const auto regDest = boost::get(&dest); + if (regDest && IsValidDestination(*regDest) && isStaking) return false; + return Standard::IsValidDestination(dest); } bool WalletModel::validateAddress(const QString& address, bool fStaking) diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index b1ed83d2683e..6886bba720b5 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -261,7 +261,7 @@ class WalletModel : public QObject std::string getLabelForAddress(const CTxDestination& address); bool getKeyId(const CTxDestination& address, CKeyID& keyID); - bool isMine(const CTxDestination& address); + bool isMine(const CWDestination& address); bool isMine(const QString& addressStr); bool isUsed(CTxDestination address); void getOutputs(const std::vector& vOutpoints, std::vector& vOutputs); From c56d625fe72997cc7058f69ecb04b164a976a03b Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 18 Sep 2020 14:00:26 -0300 Subject: [PATCH 035/121] [GUI] Receive widget: shielded address creation time connected. --- src/qt/pivx/receivewidget.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/qt/pivx/receivewidget.cpp b/src/qt/pivx/receivewidget.cpp index e33a47cc7a3d..07f417b297f5 100644 --- a/src/qt/pivx/receivewidget.cpp +++ b/src/qt/pivx/receivewidget.cpp @@ -161,12 +161,9 @@ void ReceiveWidget::refreshView(QString refreshAddress) } QString addressToShow = latestAddress; - int64_t time = 0; + int64_t time = walletModel->getKeyCreationTime(latestAddress.toStdString()); if (shieldedMode) { addressToShow = addressToShow.left(20) + "..." + addressToShow.right(20); - // add creation time support for shielded addresses. - } else { - time = walletModel->getKeyCreationTime(DecodeDestination(latestAddress.toStdString())); } ui->labelAddress->setText(addressToShow); From 17021688b4f9d7394abbd05c35b941fca27a7d7b Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 18 Sep 2020 16:37:54 -0300 Subject: [PATCH 036/121] AddressTablemodel move labelForAddress and removeRows to use CWDestination. --- src/qt/addresstablemodel.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 33a047d44fb8..28f6009de6f7 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -547,7 +547,7 @@ bool AddressTableModel::removeRows(int row, int count, const QModelIndex& parent const CChainParams::Base58Type addrType = (rec->type == AddressTableEntry::ColdStakingSend) ? CChainParams::STAKING_ADDRESS : CChainParams::PUBKEY_ADDRESS; { LOCK(wallet->cs_wallet); - return wallet->DelAddressBook(DecodeDestination(rec->address.toStdString()), addrType); + return wallet->DelAddressBook(Standard::DecodeDestination(rec->address.toStdString()), addrType); } } @@ -557,7 +557,7 @@ QString AddressTableModel::labelForAddress(const QString& address) const { // TODO: Check why do we have empty addresses.. if (!address.isEmpty()) { - CTxDestination dest = DecodeDestination(address.toStdString()); + CWDestination dest = Standard::DecodeDestination(address.toStdString()); return QString::fromStdString(wallet->GetNameForAddressBookEntry(dest)); } return QString(); @@ -567,7 +567,7 @@ QString AddressTableModel::labelForAddress(const QString& address) const */ std::string AddressTableModel::purposeForAddress(const std::string& address) const { - return wallet->GetPurposeForAddressBookEntry(DecodeDestination(address)); + return wallet->GetPurposeForAddressBookEntry(Standard::DecodeDestination(address)); } int AddressTableModel::lookupAddress(const QString& address) const From c3ffa2fd964a9f342d1a5aa3f47147f602349a09 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 18 Sep 2020 16:42:41 -0300 Subject: [PATCH 037/121] [GUI] contacts widget: add shielded addresses capabilities. --- src/qt/pivx/addresseswidget.cpp | 17 +++++++++++----- src/qt/walletmodel.cpp | 5 +++++ src/qt/walletmodel.h | 1 + src/sapling/sapling_operation.cpp | 33 +++++++++++++++++++++++++++++++ src/sapling/sapling_operation.h | 6 ++++++ src/wallet/rpcwallet.cpp | 30 +++------------------------- 6 files changed, 60 insertions(+), 32 deletions(-) diff --git a/src/qt/pivx/addresseswidget.cpp b/src/qt/pivx/addresseswidget.cpp index 6d1e5715dfdb..0c0cf1f7bebf 100644 --- a/src/qt/pivx/addresseswidget.cpp +++ b/src/qt/pivx/addresseswidget.cpp @@ -40,6 +40,9 @@ class ContactsHolder : public FurListRow row->updateState(isLightTheme, isHovered, isSelected); QString address = index.data(Qt::DisplayRole).toString(); + if (index.data(AddressTableModel::TypeRole).toString() == AddressTableModel::ShieldedSend) { + address = address.left(26) + "..." + address.right(26); + } QModelIndex sibling = index.sibling(index.row(), AddressTableModel::Label); QString label = sibling.data(Qt::DisplayRole).toString(); @@ -158,7 +161,9 @@ void AddressesWidget::loadWalletModel() { if (walletModel) { addressTablemodel = walletModel->getAddressTableModel(); - this->filter = new AddressFilterProxyModel(QStringList({AddressTableModel::Send, AddressTableModel::ColdStakingSend}), this); + this->filter = new AddressFilterProxyModel( + QStringList({AddressTableModel::Send, AddressTableModel::ColdStakingSend, AddressTableModel::ShieldedSend}), + this); this->filter->setSourceModel(addressTablemodel); this->filter->sort(sortType, sortOrder); ui->listAddresses->setModel(this->filter); @@ -182,9 +187,9 @@ void AddressesWidget::onStoreContactClicked() QString address = ui->lineEditAddress->text(); bool isStakingAddress = false; - auto pivAdd = DecodeDestination(address.toUtf8().constData(), isStakingAddress); + auto pivAdd = Standard::DecodeDestination(address.toUtf8().constData(), isStakingAddress); - if (!IsValidDestination(pivAdd) || isStakingAddress) { + if (!Standard::IsValidDestination(pivAdd) || isStakingAddress) { setCssEditLine(ui->lineEditAddress, false, true); inform(tr("Invalid Contact Address")); return; @@ -203,8 +208,10 @@ void AddressesWidget::onStoreContactClicked() return; } + bool isShielded = walletModel->IsShieldedDestination(pivAdd); if (walletModel->updateAddressBookLabels(pivAdd, label.toUtf8().constData(), - isStakingAddress ? AddressBook::AddressBookPurpose::COLD_STAKING_SEND : AddressBook::AddressBookPurpose::SEND) + isShielded ? AddressBook::AddressBookPurpose::SHIELDED_SEND : + isStakingAddress ? AddressBook::AddressBookPurpose::COLD_STAKING_SEND : AddressBook::AddressBookPurpose::SEND) ) { ui->lineEditAddress->setText(""); ui->lineEditName->setText(""); @@ -231,7 +238,7 @@ void AddressesWidget::onEditClicked() dialog->setData(address, currentLabel); if (openDialogWithOpaqueBackground(dialog, window)) { if (walletModel->updateAddressBookLabels( - DecodeDestination(address.toStdString()), dialog->getLabel().toStdString(), addressTablemodel->purposeForAddress(address.toStdString()))){ + Standard::DecodeDestination(address.toStdString()), dialog->getLabel().toStdString(), addressTablemodel->purposeForAddress(address.toStdString()))){ inform(tr("Contact edited")); } else { inform(tr("Contact edit failed")); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 25c293bbd089..175877547977 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -987,6 +987,11 @@ bool WalletModel::isMine(const QString& addressStr) return IsMine(*wallet, DecodeDestination(addressStr.toStdString())); } +bool WalletModel::IsShieldedDestination(const CWDestination& address) +{ + return boost::get(&address); +} + bool WalletModel::isUsed(CTxDestination address) { return wallet->IsUsed(address); diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 6886bba720b5..884856e1477b 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -263,6 +263,7 @@ class WalletModel : public QObject bool isMine(const CWDestination& address); bool isMine(const QString& addressStr); + bool IsShieldedDestination(const CWDestination& address); bool isUsed(CTxDestination address); void getOutputs(const std::vector& vOutpoints, std::vector& vOutputs); bool getMNCollateralCandidate(COutPoint& outPoint); diff --git a/src/sapling/sapling_operation.cpp b/src/sapling/sapling_operation.cpp index e822d531560e..8a90442e58ab 100644 --- a/src/sapling/sapling_operation.cpp +++ b/src/sapling/sapling_operation.cpp @@ -391,3 +391,36 @@ OperationResult GetMemoFromString(const std::string& s, std::array& recipients, bool fromTaddr) +{ + CMutableTransaction mtx; + mtx.nVersion = CTransaction::TxVersion::SAPLING; + unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING; + + // As a sanity check, estimate and verify that the size of the transaction will be valid. + // Depending on the input notes, the actual tx size may turn out to be larger and perhaps invalid. + size_t nTransparentOuts = 0; + for (const auto& t : recipients) { + if (t.IsTransparent()) { + nTransparentOuts++; + continue; + } + if (IsValidPaymentAddress(t.shieldedRecipient->address)) { + mtx.sapData->vShieldedOutput.emplace_back(); + } else { + return errorOut(strprintf("invalid recipient shielded address %s", + KeyIO::EncodePaymentAddress(t.shieldedRecipient->address))); + } + } + CTransaction tx(mtx); + size_t txsize = GetSerializeSize(tx, SER_NETWORK, tx.nVersion) + CTXOUT_REGULAR_SIZE * nTransparentOuts; + if (fromTaddr) { + txsize += CTXIN_SPEND_DUST_SIZE; + txsize += CTXOUT_REGULAR_SIZE; // There will probably be taddr change + } + if (txsize > max_tx_size) { + return errorOut(strprintf("Too many outputs, size of raw transaction would be larger than limit of %d bytes", max_tx_size)); + } + return OperationResult(true); +} diff --git a/src/sapling/sapling_operation.h b/src/sapling/sapling_operation.h index 351f11cd5e37..83965430fc2c 100644 --- a/src/sapling/sapling_operation.h +++ b/src/sapling/sapling_operation.h @@ -11,6 +11,10 @@ #include "primitives/transaction.h" #include "wallet/wallet.h" +// transaction.h comment: spending taddr output requires CTxIn >= 148 bytes and typical taddr txout is 34 bytes +#define CTXIN_SPEND_DUST_SIZE 148 +#define CTXOUT_REGULAR_SIZE 34 + struct TxValues; struct ShieldedRecipient @@ -124,4 +128,6 @@ class SaplingOperation { OperationResult GetMemoFromString(const std::string& s, std::array& memoRet); +OperationResult CheckTransactionSize(std::vector& recipients, bool fromTaddr); + #endif //PIVX_SAPLING_OPERATION_H diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 874ec0b149eb..1191d3d9641e 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1553,33 +1553,9 @@ static SaplingOperation CreateShieldedTransaction(const JSONRPCRequest& request) } // Now check the transaction - CMutableTransaction mtx; - mtx.nVersion = CTransaction::TxVersion::SAPLING; - unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING; - - // As a sanity check, estimate and verify that the size of the transaction will be valid. - // Depending on the input notes, the actual tx size may turn out to be larger and perhaps invalid. - size_t nTransparentOuts = 0; - for (const auto& t : recipients) { - if (t.IsTransparent()) { - nTransparentOuts++; - continue; - } - if (IsValidPaymentAddress(t.shieldedRecipient->address)) { - mtx.sapData->vShieldedOutput.emplace_back(); - } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("invalid recipient shielded address %s", - KeyIO::EncodePaymentAddress(t.shieldedRecipient->address))); - } - } - CTransaction tx(mtx); - size_t txsize = GetSerializeSize(tx, SER_NETWORK, tx.nVersion) + CTXOUT_REGULAR_SIZE * nTransparentOuts; - if (!fromSapling) { - txsize += CTXIN_SPEND_DUST_SIZE; - txsize += CTXOUT_REGULAR_SIZE; // There will probably be taddr change - } - if (txsize > max_tx_size) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Too many outputs, size of raw transaction would be larger than limit of %d bytes", max_tx_size )); + auto opResult = CheckTransactionSize(recipients, !fromSapling); + if (!opResult) { + throw JSONRPCError(RPC_INVALID_PARAMETER, opResult.getError()); } // Param 2: Minimum confirmations From 600625f6c77e870f81cb02b1a8327b220fa53af5 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 19 Sep 2020 22:15:24 -0300 Subject: [PATCH 038/121] addressTableModel counting shielded send addresses. --- src/qt/addresstablemodel.cpp | 5 +++++ src/qt/addresstablemodel.h | 1 + 2 files changed, 6 insertions(+) diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 28f6009de6f7..2c60af9020cd 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -129,6 +129,7 @@ class AddressTablePriv int recvNum = 0; int dellNum = 0; int coldSendNum = 0; + int shieldedSendNum = 0; AddressTableModel* parent; AddressTablePriv(CWallet* wallet, AddressTableModel* parent) : wallet(wallet), parent(parent) {} @@ -185,6 +186,8 @@ class AddressTablePriv var = &coldSendNum; } else if (purpose == AddressBook::AddressBookPurpose::DELEGABLE || purpose == AddressBook::AddressBookPurpose::DELEGATOR) { var = &dellNum; + } else if (purpose == AddressBook::AddressBookPurpose::SHIELDED_SEND) { + var = &shieldedSendNum; } else { return; } @@ -286,6 +289,7 @@ class AddressTablePriv int sizeRecv() { return recvNum; } int sizeDell() { return dellNum; } int SizeColdSend() { return coldSendNum; } + int sizeShieldedSend() { return shieldedSendNum; } AddressTableEntry* index(int idx) { @@ -325,6 +329,7 @@ int AddressTableModel::sizeSend() const { return priv->sizeSend(); } int AddressTableModel::sizeRecv() const { return priv->sizeRecv(); } int AddressTableModel::sizeDell() const { return priv->sizeDell(); } int AddressTableModel::sizeColdSend() const { return priv->SizeColdSend(); } +int AddressTableModel::sizeShieldedSend() const { return priv->sizeShieldedSend(); } QVariant AddressTableModel::data(const QModelIndex& index, int role) const { diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h index dcb88d987732..196c1485d1ab 100644 --- a/src/qt/addresstablemodel.h +++ b/src/qt/addresstablemodel.h @@ -64,6 +64,7 @@ class AddressTableModel : public QAbstractTableModel int sizeRecv() const; int sizeDell() const; int sizeColdSend() const; + int sizeShieldedSend() const; void notifyChange(const QModelIndex &index); QVariant data(const QModelIndex& index, int role) const; bool setData(const QModelIndex& index, const QVariant& value, int role); From 399f7d49567d1bf7b1c314a66d83130634cbd92a Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 19 Sep 2020 22:16:54 -0300 Subject: [PATCH 039/121] Included shielded address support in WalletModel::validateAddress --- src/qt/walletmodel.cpp | 11 +++++++++++ src/qt/walletmodel.h | 3 +++ 2 files changed, 14 insertions(+) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 175877547977..9dedffa1be7b 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -296,6 +296,17 @@ bool WalletModel::validateAddress(const QString& address, bool fStaking) return IsValidDestinationString(address.toStdString(), fStaking); } +bool WalletModel::validateAddress(const QString& address, bool fStaking, bool& isShielded) +{ + bool isStaking = false; + CWDestination dest = Standard::DecodeDestination(address.toStdString(), isStaking); + if (IsShieldedDestination(dest)) { + isShielded = true; + return true; + } + return Standard::IsValidDestination(dest) && (isStaking == fStaking); +} + bool WalletModel::updateAddressBookLabels(const CWDestination& dest, const std::string& strName, const std::string& strPurpose) { auto optAdd = pwalletMain->GetAddressBookEntry(dest); diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 884856e1477b..0d22b099424a 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -173,6 +173,9 @@ class WalletModel : public QObject bool validateAddress(const QString& address); // Check address for validity and type (whether cold staking address or not) bool validateAddress(const QString& address, bool fStaking); + // Check address for validity and type (whether cold staking address or not), + // plus return isShielded = true if the parsed address is a valid shielded address. + bool validateAddress(const QString& address, bool fStaking, bool& isShielded); // Return status record for SendCoins, contains error id + information struct SendCoinsReturn { From e1714dff41049d13b587d04413b21a53a2244ebc Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 19 Sep 2020 22:33:25 -0300 Subject: [PATCH 040/121] ContactsDropdown accepting several filters at once plus enabled regular and shielded address support in send screen contacts dropdown. --- src/qt/pivx/coldstakingwidget.cpp | 2 +- src/qt/pivx/contactsdropdown.cpp | 4 ++-- src/qt/pivx/contactsdropdown.h | 4 ++-- src/qt/pivx/send.cpp | 2 +- src/qt/pivx/settings/settingsbittoolwidget.cpp | 2 +- src/qt/pivx/settings/settingssignmessagewidgets.cpp | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/qt/pivx/coldstakingwidget.cpp b/src/qt/pivx/coldstakingwidget.cpp index e4cd5dd778bc..1e700c391641 100644 --- a/src/qt/pivx/coldstakingwidget.cpp +++ b/src/qt/pivx/coldstakingwidget.cpp @@ -355,7 +355,7 @@ void ColdStakingWidget::onContactsClicked() return; } - menuContacts->setWalletModel(walletModel, isContactOwnerSelected ? AddressTableModel::Receive : AddressTableModel::ColdStakingSend); + menuContacts->setWalletModel(walletModel, {(isContactOwnerSelected ? AddressTableModel::Receive : AddressTableModel::ColdStakingSend)}); menuContacts->resizeList(width, height); menuContacts->setStyleSheet(styleSheet()); menuContacts->adjustSize(); diff --git a/src/qt/pivx/contactsdropdown.cpp b/src/qt/pivx/contactsdropdown.cpp index bb8a6b6cf332..3abf55d15b76 100644 --- a/src/qt/pivx/contactsdropdown.cpp +++ b/src/qt/pivx/contactsdropdown.cpp @@ -81,7 +81,7 @@ ContactsDropdown::ContactsDropdown(int minWidth, int minHeight, PWidget *parent) connect(list, &QListView::clicked, this, &ContactsDropdown::handleClick); } -void ContactsDropdown::setWalletModel(WalletModel* _model, const QString& type){ +void ContactsDropdown::setWalletModel(WalletModel* _model, const QStringList& type){ if (!model) { model = _model->getAddressTableModel(); this->filter = new AddressFilterProxyModel(type, this); @@ -94,7 +94,7 @@ void ContactsDropdown::setWalletModel(WalletModel* _model, const QString& type){ } } -void ContactsDropdown::setType(const QString& type) { +void ContactsDropdown::setType(const QStringList& type) { if (filter) filter->setType(type); } diff --git a/src/qt/pivx/contactsdropdown.h b/src/qt/pivx/contactsdropdown.h index f4a8c591e94b..0985e249e5ec 100644 --- a/src/qt/pivx/contactsdropdown.h +++ b/src/qt/pivx/contactsdropdown.h @@ -31,8 +31,8 @@ class ContactsDropdown : public PWidget explicit ContactsDropdown(int minWidth, int minHeight, PWidget *parent = nullptr); void resizeList(int minWidth, int mintHeight); - void setWalletModel(WalletModel* _model, const QString& type); - void setType(const QString& type); + void setWalletModel(WalletModel* _model, const QStringList& type); + void setType(const QStringList& type); void changeTheme(bool isLightTheme, QString& theme) override; Q_SIGNALS: void contactSelected(QString address, QString label); diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index f30fcf3346d9..363d6b2328e2 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -578,7 +578,7 @@ void SendWidget::onContactsClicked(SendMultiRow* entry) height, this ); - menuContacts->setWalletModel(walletModel, AddressTableModel::Send); + menuContacts->setWalletModel(walletModel, {AddressTableModel::Send, AddressTableModel::ShieldedSend}); connect(menuContacts, &ContactsDropdown::contactSelected, [this](QString address, QString label) { if (focusedEntry) { if (label != "(no label)") diff --git a/src/qt/pivx/settings/settingsbittoolwidget.cpp b/src/qt/pivx/settings/settingsbittoolwidget.cpp index 65a05727eacb..14e5ecbbc947 100644 --- a/src/qt/pivx/settings/settingsbittoolwidget.cpp +++ b/src/qt/pivx/settings/settingsbittoolwidget.cpp @@ -205,7 +205,7 @@ void SettingsBitToolWidget::onAddressesClicked() height, this ); - menuContacts->setWalletModel(walletModel, AddressTableModel::Receive); + menuContacts->setWalletModel(walletModel, {AddressTableModel::Receive}); connect(menuContacts, &ContactsDropdown::contactSelected, [this](QString address, QString label){ setAddress_ENC(address); }); diff --git a/src/qt/pivx/settings/settingssignmessagewidgets.cpp b/src/qt/pivx/settings/settingssignmessagewidgets.cpp index a65a2ea15cda..671f93285176 100644 --- a/src/qt/pivx/settings/settingssignmessagewidgets.cpp +++ b/src/qt/pivx/settings/settingssignmessagewidgets.cpp @@ -276,7 +276,7 @@ void SettingsSignMessageWidgets::onAddressesClicked() height, this ); - menuContacts->setWalletModel(walletModel, AddressTableModel::Receive); + menuContacts->setWalletModel(walletModel, {AddressTableModel::Receive}); connect(menuContacts, &ContactsDropdown::contactSelected, [this](QString address, QString label){ setAddress_SM(address); }); From 397d062fc5d55e634b83173d95ac6683db70deb1 Mon Sep 17 00:00:00 2001 From: furszy Date: Sun, 20 Sep 2020 03:06:40 -0300 Subject: [PATCH 041/121] Standard::DecodeDestination return whether the decoded address is shielded or not. --- src/script/standard.cpp | 12 +++++++++++- src/script/standard.h | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/script/standard.cpp b/src/script/standard.cpp index a82fbac1fce0..1a33439cea64 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -357,12 +357,22 @@ namespace Standard { } CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking) + { + bool isShielded = false; + return DecodeDestination(strAddress, isStaking, isShielded); + } + + // agregar isShielded + CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking, bool& isShielded) { CWDestination dest; CTxDestination regDest = ::DecodeDestination(strAddress, isStaking); if (!IsValidDestination(regDest)) { const auto sapDest = KeyIO::DecodeSaplingPaymentAddress(strAddress); - if (sapDest) return *sapDest; + if (sapDest) { + isShielded = true; + return *sapDest; + } } return regDest; diff --git a/src/script/standard.h b/src/script/standard.h index e5e441e7ffb9..49d67c08ef14 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -96,6 +96,7 @@ std::string EncodeDestination(const CWDestination &address, const CChainParams:: CWDestination DecodeDestination(const std::string& strAddress); CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking); +CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking, bool& isShielded); bool IsValidDestination(const CWDestination& dest); From d6e0717ef4a06e5217b4daebaa968b4f2aadc641 Mon Sep 17 00:00:00 2001 From: furszy Date: Sun, 20 Sep 2020 03:13:55 -0300 Subject: [PATCH 042/121] send widget: divide send transaction preparation from confirmation and subsequent broadcast. --- src/qt/pivx/send.cpp | 9 +++++++-- src/qt/pivx/send.h | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index 363d6b2328e2..379e6bfae158 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -374,12 +374,17 @@ bool SendWidget::send(QList recipients) return false; } + return sendFinalStep(currentTransaction); +} + +bool SendWidget::sendFinalStep(WalletModelTransaction& currentTransaction) +{ showHideOp(true); const bool fStakeDelegationVoided = currentTransaction.getTransaction()->fStakeDelegationVoided; QString warningStr = QString(); if (fStakeDelegationVoided) warningStr = tr("WARNING:\nTransaction spends a cold-stake delegation, voiding it.\n" - "These coins will no longer be cold-staked."); + "These coins will no longer be cold-staked."); TxDetailDialog* dialog = new TxDetailDialog(window, true, warningStr); dialog->setDisplayUnit(walletModel->getOptionsModel()->getDisplayUnit()); dialog->setData(walletModel, currentTransaction); @@ -408,7 +413,7 @@ bool SendWidget::send(QList recipients) } dialog->deleteLater(); - return false; + return true; } QString SendWidget::recipientsToString(QList recipients) diff --git a/src/qt/pivx/send.h b/src/qt/pivx/send.h index a50bce62cd85..b0988db52b9c 100644 --- a/src/qt/pivx/send.h +++ b/src/qt/pivx/send.h @@ -94,6 +94,7 @@ private Q_SLOTS: QString recipientsToString(QList recipients); SendMultiRow* createEntry(); bool send(QList recipients); + bool sendFinalStep(WalletModelTransaction& currentTransaction); void setFocusOnLastEntry(); void showHideCheckBoxDelegations(); void updateEntryLabels(QList recipients); From 4626fc9356d40a346b5aef7a28230c028b0c93d8 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 21 Sep 2020 16:31:50 -0300 Subject: [PATCH 043/121] WalletModel::sendCoins include shielded address distinction in addressbook labels update. --- src/qt/walletmodel.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 9dedffa1be7b..fc753da40c7c 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -499,8 +499,10 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction& tran // Don't touch the address book when we have a payment request if (!rcp.paymentRequest.IsInitialized()) { bool isStaking = false; - CTxDestination address = DecodeDestination(rcp.address.toStdString(), isStaking); - std::string purpose = isStaking ? AddressBook::AddressBookPurpose::COLD_STAKING_SEND : AddressBook::AddressBookPurpose::SEND; + bool isShielded = false; + auto address = Standard::DecodeDestination(rcp.address.toStdString(), isStaking, isShielded); + std::string purpose = isShielded ? AddressBook::AddressBookPurpose::SHIELDED_SEND : + isStaking ? AddressBook::AddressBookPurpose::COLD_STAKING_SEND : AddressBook::AddressBookPurpose::SEND; std::string strLabel = rcp.label.toStdString(); updateAddressBookLabels(address, strLabel, purpose); } From 7f18b36d8792f5faef8f0fd87a66e59d1945ee75 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 21 Sep 2020 16:36:21 -0300 Subject: [PATCH 044/121] GUI: send caching whether the address is shielded or not. --- src/qt/pivx/send.cpp | 5 ++++- src/qt/pivx/sendmultirow.cpp | 4 +++- src/qt/walletmodel.h | 3 +++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index 379e6bfae158..7c4d1fa9ac82 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -321,12 +321,15 @@ void SendWidget::onSendClicked() return; QList recipients; + bool hasShieldedOutput = false; for (SendMultiRow* entry : entries) { // TODO: Check UTXO splitter here.. // Validate send.. if (entry && entry->validate()) { - recipients.append(entry->getValue()); + auto recipient = entry->getValue(); + if (!hasShieldedOutput) hasShieldedOutput = recipient.isShieldedAddr; + recipients.append(recipient); } else { inform(tr("Invalid entry")); return; diff --git a/src/qt/pivx/sendmultirow.cpp b/src/qt/pivx/sendmultirow.cpp index 31f3fee66a6d..8dee6a708bb3 100644 --- a/src/qt/pivx/sendmultirow.cpp +++ b/src/qt/pivx/sendmultirow.cpp @@ -193,7 +193,9 @@ SendCoinsRecipient SendMultiRow::getValue() // Normal payment recipient.address = getAddress(); recipient.label = ui->lineEditDescription->text(); - recipient.amount = getAmountValue();; + recipient.amount = getAmountValue(); + auto dest = Standard::DecodeDestination(recipient.address.toStdString()); + recipient.isShieldedAddr = boost::get(&dest); return recipient; } diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 0d22b099424a..d9bbd26efc5b 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -59,6 +59,9 @@ class SendCoinsRecipient bool isP2CS = false; QString ownerAddress; + // Quick flag to not have to check the address type more than once. + bool isShieldedAddr{false}; + // Amount CAmount amount; // If from a payment request, this is used for storing the memo From 82807da3c8596cd9d050197611c8c9959aba7d9b Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 21 Sep 2020 16:38:59 -0300 Subject: [PATCH 045/121] WalletModel: PrepareShieldedTransaction method implemented. --- src/qt/walletmodel.cpp | 43 ++++++++++++++++++++++++++++++++++++++++++ src/qt/walletmodel.h | 4 ++++ 2 files changed, 47 insertions(+) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index fc753da40c7c..0e856d1ec437 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -16,6 +16,7 @@ #include "db.h" #include "keystore.h" #include "sapling/key_io_sapling.h" +#include "sapling/sapling_operation.h" #include "spork.h" #include "sync.h" #include "guiinterface.h" @@ -513,6 +514,48 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction& tran return SendCoinsReturn(OK); } +OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction& modelTransaction) +{ + // Basic checks first + + // Check network status + int nextBlockHeight = cachedNumBlocks + 1; + if (!Params().GetConsensus().NetworkUpgradeActive(nextBlockHeight, Consensus::UPGRADE_V5_DUMMY)) { + return errorOut("Error, cannot send transaction. Sapling is not activated"); + } + + // Load shieldedAddrRecipients. + std::vector recipients; + for (const auto& recipient : modelTransaction.getRecipients()) { + if (recipient.isShieldedAddr) { + auto pa = KeyIO::DecodeSaplingPaymentAddress(recipient.address.toStdString()); + if (!pa) return errorOut("Error, invalid shielded address"); + recipients.emplace_back(*pa, recipient.amount, ""); + } else { + auto dest = DecodeDestination(recipient.address.toStdString()); + if (!IsValidDestination(dest)) return errorOut("Error, invalid transparent address"); + recipients.emplace_back(dest, recipient.amount); + } + } + + // Now check the transaction size + auto opResult = CheckTransactionSize(recipients, true); + if (!opResult) return opResult; + + // Create the operation + TransactionBuilder txBuilder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, wallet); + SaplingOperation operation(txBuilder); + auto operationResult = operation.setRecipients(recipients)->build(); + + // load the transaction and key change (if needed) + CWalletTx* newTx = modelTransaction.getTransaction(); + newTx = new CWalletTx(wallet, operation.getFinalTx()); + Optional& optKeyChange = operation.GetTransparentKeyChange(); + CReserveKey* tKeyChange = modelTransaction.getPossibleKeyChange(); + if (optKeyChange) tKeyChange = optKeyChange.get_ptr(); + return opResult; +} + const CWalletTx* WalletModel::getTx(uint256 id) { return wallet->GetWalletTx(id); diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index d9bbd26efc5b..0366467296ba 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -14,6 +14,7 @@ #include "interface/wallet.h" #include "allocators.h" /* for SecureString */ +#include "operationresult.h" #include "wallet/wallet.h" #include "pairresult.h" @@ -204,6 +205,9 @@ class WalletModel : public QObject // Send coins to a list of recipients SendCoinsReturn sendCoins(WalletModelTransaction& transaction); + // Prepare shielded transaction. + OperationResult PrepareShieldedTransaction(WalletModelTransaction& modelTransaction); + // Wallet encryption bool setWalletEncrypted(bool encrypted, const SecureString& passphrase); // Passphrase only needed when unlocking From c4f832132eb81de660b60fa1f04e8448c4ba0f5b Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 21 Sep 2020 16:41:01 -0300 Subject: [PATCH 046/121] GUI: send screen, connected shielded send. --- src/qt/pivx/send.cpp | 22 +++++++++++++++++----- src/qt/pivx/send.h | 3 ++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index 7c4d1fa9ac82..0765dc782cf6 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -348,18 +348,30 @@ void SendWidget::onSendClicked() return; } - if (send(recipients)) { - updateEntryLabels(recipients); + { + WalletModelTransaction tx(recipients); + if (hasShieldedOutput ? sendShielded(tx) : send(tx)) { + updateEntryLabels(recipients); + } } setFocusOnLastEntry(); } -bool SendWidget::send(QList recipients) +bool SendWidget::sendShielded(WalletModelTransaction& currentTransaction) +{ + auto result = walletModel->PrepareShieldedTransaction(currentTransaction); + if (!result) { + inform(result.m_error); + return false; + } + + return sendFinalStep(currentTransaction); +} + +bool SendWidget::send(WalletModelTransaction& currentTransaction) { // prepare transaction for getting txFee earlier - WalletModelTransaction currentTransaction(recipients); WalletModel::SendCoinsReturn prepareStatus; - prepareStatus = walletModel->prepareTransaction(currentTransaction, coinControlDialog->coinControl, fDelegationsChecked); // process prepareStatus and on error generate message shown to user diff --git a/src/qt/pivx/send.h b/src/qt/pivx/send.h index b0988db52b9c..7a479d5f73ed 100644 --- a/src/qt/pivx/send.h +++ b/src/qt/pivx/send.h @@ -93,7 +93,8 @@ private Q_SLOTS: void resizeMenu(); QString recipientsToString(QList recipients); SendMultiRow* createEntry(); - bool send(QList recipients); + bool sendShielded(WalletModelTransaction& tx); + bool send(WalletModelTransaction& tx); bool sendFinalStep(WalletModelTransaction& currentTransaction); void setFocusOnLastEntry(); void showHideCheckBoxDelegations(); From 6b84f0a04f1cca99ef1f58a23198187163720312 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 21 Sep 2020 16:42:26 -0300 Subject: [PATCH 047/121] GUI: send screen: connect shielded contacts size and add shielded addr contact validation. --- src/qt/pivx/send.cpp | 7 ++++--- src/qt/pivx/sendconfirmdialog.cpp | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index 0765dc782cf6..ee1fff730ee0 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -583,7 +583,8 @@ void SendWidget::onContactsClicked(SendMultiRow* entry) menu->hide(); } - int contactsSize = walletModel->getAddressTableModel()->sizeSend(); + int contactsSize = walletModel->getAddressTableModel()->sizeSend() + + walletModel->getAddressTableModel()->sizeShieldedSend(); if (contactsSize == 0) { inform(tr("No contacts available, you can go to the contacts screen and add some there!")); return; @@ -666,9 +667,9 @@ void SendWidget::onContactMultiClicked() } bool isStakingAddr = false; - auto pivAdd = DecodeDestination(address.toStdString(), isStakingAddr); + auto pivAdd = Standard::DecodeDestination(address.toStdString(), isStakingAddr); - if (!IsValidDestination(pivAdd) || isStakingAddr) { + if (!Standard::IsValidDestination(pivAdd) || isStakingAddr) { inform(tr("Invalid address")); return; } diff --git a/src/qt/pivx/sendconfirmdialog.cpp b/src/qt/pivx/sendconfirmdialog.cpp index 33ace12f5fc2..3025cc272bd0 100644 --- a/src/qt/pivx/sendconfirmdialog.cpp +++ b/src/qt/pivx/sendconfirmdialog.cpp @@ -95,7 +95,7 @@ void TxDetailDialog::setData(WalletModel *model, const QModelIndex &index) ui->textId->setText(hash.left(20) + "..." + hash.right(20)); ui->textId->setTextInteractionFlags(Qt::TextSelectableByMouse); if (tx->vout.size() == 1) { - ui->textSendLabel->setText(address); + ui->textSendLabel->setText((address.size() < 40) ? address : address.left(20) + "..." + address.right(20)); } else { ui->textSendLabel->setText(QString::number(tx->vout.size()) + " recipients"); } From 44667fcbd62bae8710060a4d1545873ea1138928 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 21 Sep 2020 23:57:45 -0300 Subject: [PATCH 048/121] GUI: send row, edit box address validating shielded addresses. --- src/qt/pivx/sendmultirow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/qt/pivx/sendmultirow.cpp b/src/qt/pivx/sendmultirow.cpp index 8dee6a708bb3..a2a43801cea3 100644 --- a/src/qt/pivx/sendmultirow.cpp +++ b/src/qt/pivx/sendmultirow.cpp @@ -85,7 +85,8 @@ bool SendMultiRow::addressChanged(const QString& str, bool fOnlyValidate) { if (!str.isEmpty()) { QString trimmedStr = str.trimmed(); - const bool valid = walletModel->validateAddress(trimmedStr, this->onlyStakingAddressAccepted); + bool isShielded = false; + const bool valid = walletModel->validateAddress(trimmedStr, this->onlyStakingAddressAccepted, isShielded); if (!valid) { // check URI SendCoinsRecipient rcp; From b110a2e4749a27c4ff0d11a35cb2575528570911 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 23 Sep 2020 01:01:26 -0300 Subject: [PATCH 049/121] walletModelTransaction: implement setTransaction. --- src/qt/walletmodeltransaction.cpp | 5 +++++ src/qt/walletmodeltransaction.h | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/qt/walletmodeltransaction.cpp b/src/qt/walletmodeltransaction.cpp index e64383a75220..87e8edfdd16a 100644 --- a/src/qt/walletmodeltransaction.cpp +++ b/src/qt/walletmodeltransaction.cpp @@ -31,6 +31,11 @@ CWalletTx* WalletModelTransaction::getTransaction() return walletTransaction; } +void WalletModelTransaction::setTransaction(CWalletTx* tx) +{ + walletTransaction = tx; +} + unsigned int WalletModelTransaction::getTransactionSize() { return (!walletTransaction ? 0 : (::GetSerializeSize(*(CTransaction*)walletTransaction, SER_NETWORK, PROTOCOL_VERSION))); diff --git a/src/qt/walletmodeltransaction.h b/src/qt/walletmodeltransaction.h index 83eb6cbfbf2f..fc7f56b6a700 100644 --- a/src/qt/walletmodeltransaction.h +++ b/src/qt/walletmodeltransaction.h @@ -35,10 +35,12 @@ class WalletModelTransaction void newPossibleKeyChange(CWallet* wallet); CReserveKey* getPossibleKeyChange(); + void setTransaction(CWalletTx* tx); + private: const QList recipients; - CWalletTx* walletTransaction; - CReserveKey* keyChange; + CWalletTx* walletTransaction{nullptr}; + CReserveKey* keyChange{nullptr}; CAmount fee; }; From 024954db609668adfdcfe0bfde001f6063c469ff Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 23 Sep 2020 01:18:12 -0300 Subject: [PATCH 050/121] WalletModel: PrepareShieldedTransaction fully functional. --- src/qt/walletmodel.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 0e856d1ec437..e745d0b15758 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -545,15 +545,18 @@ OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction& // Create the operation TransactionBuilder txBuilder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, wallet); SaplingOperation operation(txBuilder); - auto operationResult = operation.setRecipients(recipients)->build(); + auto operationResult = operation.setRecipients(recipients) + ->setTransparentKeyChange(modelTransaction.getPossibleKeyChange()) + ->setSelectTransparentCoins(true) + ->build(); + + if (!operationResult) { + return operationResult; + } // load the transaction and key change (if needed) - CWalletTx* newTx = modelTransaction.getTransaction(); - newTx = new CWalletTx(wallet, operation.getFinalTx()); - Optional& optKeyChange = operation.GetTransparentKeyChange(); - CReserveKey* tKeyChange = modelTransaction.getPossibleKeyChange(); - if (optKeyChange) tKeyChange = optKeyChange.get_ptr(); - return opResult; + modelTransaction.setTransaction(new CWalletTx(wallet, operation.getFinalTx())); + return operationResult; } const CWalletTx* WalletModel::getTx(uint256 id) From 50b5cf08081dd1a5395064bfe10fa923dd026dd5 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 23 Sep 2020 01:18:57 -0300 Subject: [PATCH 051/121] WalletModel:sendCoins passing key change pointer instead of reference. --- src/qt/walletmodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index e745d0b15758..96ef35869818 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -483,7 +483,7 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction& tran } CReserveKey* keyChange = transaction.getPossibleKeyChange(); - const CWallet::CommitResult& res = wallet->CommitTransaction(*newTx, *keyChange, g_connman.get()); + const CWallet::CommitResult& res = wallet->CommitTransaction(*newTx, keyChange, g_connman.get()); if (res.status != CWallet::CommitStatus::OK) { return SendCoinsReturn(res); } From fbaa554a3a0ff7d0393b709af60f7c599b4c0573 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 31 Oct 2020 17:05:54 -0300 Subject: [PATCH 052/121] WalletModel::sendCoins, sapling active flag connected. --- src/qt/walletmodel.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 96ef35869818..d8448b866395 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -457,10 +457,11 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction& tran } bool fColdStakingActive = isColdStakingNetworkelyEnabled(); + bool fSaplingActive = Params().GetConsensus().NetworkUpgradeActive(cachedNumBlocks, Consensus::UPGRADE_V5_DUMMY); // Double check tx before do anything - CValidationState state; // TODO: Add sapling network active flag check - if (!CheckTransaction(*transaction.getTransaction(), true, true, state, true, fColdStakingActive)) { + CValidationState state; + if (!CheckTransaction(*transaction.getTransaction(), true, true, state, true, fColdStakingActive, fSaplingActive)) { return TransactionCheckFailed; } From 8d06471a1b158c4ccbff0a7ca516d458ea44694c Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 31 Oct 2020 18:03:29 -0300 Subject: [PATCH 053/121] GUI: new double amount transaction row + connection to the shielded send to self records (shield PIVs). --- src/qt/pivx/forms/txrow.ui | 46 +++++++++++++++++++++++++---- src/qt/pivx/res/css/style_dark.css | 5 ++++ src/qt/pivx/res/css/style_light.css | 5 ++++ src/qt/pivx/txrow.cpp | 31 ++++++++++++++----- src/qt/pivx/txrow.h | 4 ++- src/qt/pivx/txviewholder.cpp | 21 +++++++++---- src/qt/transactiontablemodel.cpp | 2 ++ src/qt/transactiontablemodel.h | 2 ++ 8 files changed, 96 insertions(+), 20 deletions(-) diff --git a/src/qt/pivx/forms/txrow.ui b/src/qt/pivx/forms/txrow.ui index 7316680b27aa..116f92ac63b2 100644 --- a/src/qt/pivx/forms/txrow.ui +++ b/src/qt/pivx/forms/txrow.ui @@ -133,13 +133,47 @@ - - - - - - N/A + + + + 80 + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + +0.000585 PIV + + + + + + + + + + -0.000585 PIV + + + + diff --git a/src/qt/pivx/res/css/style_dark.css b/src/qt/pivx/res/css/style_dark.css index 3ed654883d71..ca41d22a012c 100644 --- a/src/qt/pivx/res/css/style_dark.css +++ b/src/qt/pivx/res/css/style_dark.css @@ -934,6 +934,11 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ font-size:16px; } +*[cssClass="text-list-amount-send-small"] { + color:#f84444; + font-size:15px; +} + *[cssClass="text-list-amount-unconfirmed"] { color:#B6B6B6; font-size:16px; diff --git a/src/qt/pivx/res/css/style_light.css b/src/qt/pivx/res/css/style_light.css index 6526c5706b71..60e524c8484d 100644 --- a/src/qt/pivx/res/css/style_light.css +++ b/src/qt/pivx/res/css/style_light.css @@ -939,6 +939,11 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ font-size:16px; } +*[cssClass="text-list-amount-send-small"] { + color:#f84444; + font-size:15px; +} + *[cssClass="text-list-amount-unconfirmed"] { color:#B6B6B6; font-size:16px; diff --git a/src/qt/pivx/txrow.cpp b/src/qt/pivx/txrow.cpp index fbd92aa867e5..3db5da8b9bba 100644 --- a/src/qt/pivx/txrow.cpp +++ b/src/qt/pivx/txrow.cpp @@ -13,6 +13,7 @@ TxRow::TxRow(QWidget *parent) : ui(new Ui::TxRow) { ui->setupUi(this); + ui->lblAmountBottom->setVisible(false); } void TxRow::init(bool isLightTheme) @@ -21,9 +22,15 @@ void TxRow::init(bool isLightTheme) updateStatus(isLightTheme, false, false); } -void TxRow::setConfirmStatus(bool isConfirm) -{ - if (isConfirm) { +void TxRow::showHideSecondAmount(bool show) { + if (show != isDoubleAmount) { + isDoubleAmount = show; + ui->lblAmountBottom->setVisible(show); + } +} + +void TxRow::setConfirmStatus(bool isConfirm){ + if(isConfirm){ setCssProperty(ui->lblAddress, "text-list-body1"); setCssProperty(ui->lblDate, "text-list-caption"); } else { @@ -50,15 +57,17 @@ void TxRow::setLabel(QString str) ui->lblAddress->setText(str); } -void TxRow::setAmount(QString str) +void TxRow::setAmount(QString top, QString bottom) { - ui->lblAmount->setText(str); + ui->lblAmountTop->setText(top); + ui->lblAmountBottom->setText(bottom); } void TxRow::setType(bool isLightTheme, int type, bool isConfirmed) { QString path; QString css; + QString cssAmountBottom; bool sameIcon = false; switch (type) { case TransactionRecord::ZerocoinMint: @@ -89,7 +98,6 @@ void TxRow::setType(bool isLightTheme, int type, bool isConfirmed) css = "text-list-amount-send"; break; case TransactionRecord::SendToSelf: - case TransactionRecord::SendToSelfShieldedAddress: path = "://ic-transaction-mint"; css = "text-list-amount-send"; break; @@ -115,6 +123,13 @@ void TxRow::setType(bool isLightTheme, int type, bool isConfirmed) path = "://ic-transaction-cs-contract"; css = "text-list-amount-send"; break; + + // TODO: Complete me.. + case TransactionRecord::SendToSelfShieldedAddress: + path = "://ic-transaction-mint"; + css = "text-list-amount-unconfirmed"; + cssAmountBottom = "text-list-amount-send-small"; + break; default: path = "://ic-pending"; sameIcon = true; @@ -128,12 +143,14 @@ void TxRow::setType(bool isLightTheme, int type, bool isConfirmed) if (!isConfirmed){ css = "text-list-amount-unconfirmed"; + cssAmountBottom = "text-list-amount-unconfirmed"; path += "-inactive"; setConfirmStatus(false); } else { setConfirmStatus(true); } - setCssProperty(ui->lblAmount, css, true); + setCssProperty(ui->lblAmountTop, css, true); + if (isDoubleAmount) setCssProperty(ui->lblAmountBottom, cssAmountBottom, true); ui->icon->setIcon(QIcon(path)); } diff --git a/src/qt/pivx/txrow.h b/src/qt/pivx/txrow.h index bf3b6255e4ed..44e360cb150b 100644 --- a/src/qt/pivx/txrow.h +++ b/src/qt/pivx/txrow.h @@ -22,17 +22,19 @@ class TxRow : public QWidget ~TxRow(); void init(bool isLightTheme); + void showHideSecondAmount(bool show); void updateStatus(bool isLightTheme, bool isHover, bool isSelected); void setDate(QDateTime); void setLabel(QString); - void setAmount(QString); + void setAmount(QString top, QString bottom); void setType(bool isLightTheme, int type, bool isConfirmed); void setConfirmStatus(bool isConfirmed); private: Ui::TxRow *ui; bool isConfirmed = false; + bool isDoubleAmount = false; }; #endif // TXROW_H diff --git a/src/qt/pivx/txviewholder.cpp b/src/qt/pivx/txviewholder.cpp index 11efd406e778..df3208d62f8e 100644 --- a/src/qt/pivx/txviewholder.cpp +++ b/src/qt/pivx/txviewholder.cpp @@ -16,18 +16,19 @@ QWidget* TxViewHolder::createHolder(int pos) return txRow; } -void TxViewHolder::init(QWidget* holder,const QModelIndex &index, bool isHovered, bool isSelected) const +void TxViewHolder::init(QWidget* holder, const QModelIndex &index, bool isHovered, bool isSelected) const { + QModelIndex rIndex = (filter) ? filter->mapToSource(index) : index; + int type = rIndex.data(TransactionTableModel::TypeRole).toInt(); + TxRow *txRow = static_cast(holder); txRow->updateStatus(isLightTheme, isHovered, isSelected); - QModelIndex rIndex = (filter) ? filter->mapToSource(index) : index; QDateTime date = rIndex.data(TransactionTableModel::DateRole).toDateTime(); - qint64 amount = rIndex.data(TransactionTableModel::AmountRole).toLongLong(); - QString amountText = BitcoinUnits::formatWithUnit(nDisplayUnit, amount, true, BitcoinUnits::separatorAlways); QModelIndex indexType = rIndex.sibling(rIndex.row(),TransactionTableModel::Type); QString label = indexType.data(Qt::DisplayRole).toString(); - int type = rIndex.data(TransactionTableModel::TypeRole).toInt(); + + txRow->showHideSecondAmount(type == TransactionRecord::SendToSelfShieldedAddress); if (type != TransactionRecord::ZerocoinMint && type != TransactionRecord::ZerocoinSpend_Change_zPiv && @@ -42,13 +43,21 @@ void TxViewHolder::init(QWidget* holder,const QModelIndex &index, bool isHovered label += rIndex.data(Qt::DisplayRole).toString(); } + qint64 amountTop = rIndex.data(TransactionTableModel::AmountRole).toLongLong(); int status = rIndex.data(TransactionTableModel::StatusRole).toInt(); bool isUnconfirmed = (status == TransactionStatus::Unconfirmed) || (status == TransactionStatus::Immature) || (status == TransactionStatus::Conflicted) || (status == TransactionStatus::NotAccepted); txRow->setDate(date); txRow->setLabel(label); - txRow->setAmount(amountText); + QString amountText = BitcoinUnits::formatWithUnit(nDisplayUnit, amountTop, true, BitcoinUnits::separatorAlways); + if (type == TransactionRecord::SendToSelfShieldedAddress) { + qint64 amountBottom = rIndex.data(TransactionTableModel::CreditAmountRole).toLongLong(); + QString amountBottomText = BitcoinUnits::formatWithUnit(nDisplayUnit, amountBottom, true, BitcoinUnits::separatorAlways); + txRow->setAmount(amountBottomText, amountText + " fee"); + } else { + txRow->setAmount(amountText, ""); + } txRow->setType(isLightTheme, type, !isUnconfirmed); } diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 9c859edf296a..a44a03b97119 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -753,6 +753,8 @@ QVariant TransactionTableModel::data(const QModelIndex& index, int role) const return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address)); case AmountRole: return qint64(rec->credit + rec->debit); + case CreditAmountRole: + return qint64(rec->credit); case TxIDRole: return rec->getTxID(); case TxHashRole: diff --git a/src/qt/transactiontablemodel.h b/src/qt/transactiontablemodel.h index d1702b8daed1..45fdc0d36e96 100644 --- a/src/qt/transactiontablemodel.h +++ b/src/qt/transactiontablemodel.h @@ -64,6 +64,8 @@ class TransactionTableModel : public QAbstractTableModel FormattedAmountRole, /** Transaction status (TransactionRecord::Status) */ StatusRole, + /** Credit amount of transaction */ + CreditAmountRole, /** Transaction size in bytes */ SizeRole }; From 503f00e16ed0d6ba2a787b301034bce961734ffe Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 4 Nov 2020 14:07:41 -0300 Subject: [PATCH 054/121] Special case for send to self transparent to shielded conversion tx row amount calculation (only show the amount that is being converted). --- src/qt/pivx/txviewholder.cpp | 2 +- src/qt/transactionrecord.cpp | 1 + src/qt/transactionrecord.h | 2 ++ src/qt/transactiontablemodel.cpp | 4 ++-- src/qt/transactiontablemodel.h | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/qt/pivx/txviewholder.cpp b/src/qt/pivx/txviewholder.cpp index df3208d62f8e..d4109c1ccc6f 100644 --- a/src/qt/pivx/txviewholder.cpp +++ b/src/qt/pivx/txviewholder.cpp @@ -52,7 +52,7 @@ void TxViewHolder::init(QWidget* holder, const QModelIndex &index, bool isHovere txRow->setLabel(label); QString amountText = BitcoinUnits::formatWithUnit(nDisplayUnit, amountTop, true, BitcoinUnits::separatorAlways); if (type == TransactionRecord::SendToSelfShieldedAddress) { - qint64 amountBottom = rIndex.data(TransactionTableModel::CreditAmountRole).toLongLong(); + qint64 amountBottom = rIndex.data(TransactionTableModel::ShieldedCreditAmountRole).toLongLong(); QString amountBottomText = BitcoinUnits::formatWithUnit(nDisplayUnit, amountBottom, true, BitcoinUnits::separatorAlways); txRow->setAmount(amountBottomText, amountText + " fee"); } else { diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 2308a094d6c5..606ef8077cdf 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -250,6 +250,7 @@ bool TransactionRecord::decomposeSendToSelfTransaction(const CWalletTx& wtx, con // shielded send to self. sub.type = TransactionRecord::SendToSelfShieldedAddress; nChange += wtx.GetShieldedChange(); + sub.shieldedCredit = wtx.GetShieldedAvailableCredit(); } sub.debit = -(nDebit - nChange); diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h index 40d863b28414..62ef81baa947 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -9,6 +9,7 @@ #include "amount.h" #include "script/script.h" +#include "optional.h" #include "uint256.h" #include @@ -159,6 +160,7 @@ class TransactionRecord CAmount debit; CAmount credit; unsigned int size; + Optional shieldedCredit{nullopt}; /**@}*/ /** Subtransaction index, for sort key */ diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index a44a03b97119..e62262aa2b11 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -753,8 +753,8 @@ QVariant TransactionTableModel::data(const QModelIndex& index, int role) const return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address)); case AmountRole: return qint64(rec->credit + rec->debit); - case CreditAmountRole: - return qint64(rec->credit); + case ShieldedCreditAmountRole: + return rec->shieldedCredit ? qint64(*rec->shieldedCredit) : 0; case TxIDRole: return rec->getTxID(); case TxHashRole: diff --git a/src/qt/transactiontablemodel.h b/src/qt/transactiontablemodel.h index 45fdc0d36e96..4922bc31057f 100644 --- a/src/qt/transactiontablemodel.h +++ b/src/qt/transactiontablemodel.h @@ -65,7 +65,7 @@ class TransactionTableModel : public QAbstractTableModel /** Transaction status (TransactionRecord::Status) */ StatusRole, /** Credit amount of transaction */ - CreditAmountRole, + ShieldedCreditAmountRole, /** Transaction size in bytes */ SizeRole }; From 35a44a0bb47db7d09a008ddbf0bee806b968b62b Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 5 Nov 2020 20:50:31 -0300 Subject: [PATCH 055/121] [BUG][GUI] Fixing GetAvailableShieldedBalance calculation, ismimetype was being taken as a boolean, calling to a different function to the one that was expecting to call. --- src/wallet/wallet.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 57781b0d966f..af02672bb5ab 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2046,7 +2046,8 @@ CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth) cons // Sapling CAmount CWallet::GetAvailableShieldedBalance(bool fUseCache) const { - return GetAvailableBalance(ISMINE_SPENDABLE_SHIELDED, fUseCache);; + isminefilter filter = ISMINE_SPENDABLE_SHIELDED; + return GetAvailableBalance(filter, fUseCache); }; CAmount CWallet::GetUnconfirmedShieldedBalance() const From 5769dff68fb726163234116582b25a7eeaa80d43 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 5 Nov 2020 20:50:54 -0300 Subject: [PATCH 056/121] GUI: Introduce total shielded balance into topbar. --- src/qt/pivx/forms/topbar.ui | 34 ++++++++++++++++++++++++++++++++++ src/qt/pivx/topbar.cpp | 5 +++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/qt/pivx/forms/topbar.ui b/src/qt/pivx/forms/topbar.ui index fd177e74ac04..14e5e03fa7ca 100644 --- a/src/qt/pivx/forms/topbar.ui +++ b/src/qt/pivx/forms/topbar.ui @@ -350,6 +350,40 @@ 0 + + + + + + Shielded + + + + + + + 130.43 PIV + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 40 + 20 + + + + diff --git a/src/qt/pivx/topbar.cpp b/src/qt/pivx/topbar.cpp index 5693970e1fba..655dc55e9f92 100644 --- a/src/qt/pivx/topbar.cpp +++ b/src/qt/pivx/topbar.cpp @@ -44,7 +44,7 @@ TopBar::TopBar(PIVXGUI* _mainWindow, QWidget *parent) : ui->containerTop->setProperty("cssClass", "container-top"); #endif - std::initializer_list lblTitles = {ui->labelTitle1, ui->labelTitle3, ui->labelTitle4}; + std::initializer_list lblTitles = {ui->labelTitle1, ui->labelTitle3, ui->labelTitle4, ui->labelTitle5}; setCssProperty(lblTitles, "text-title-topbar"); QFont font; font.setWeight(QFont::Light); @@ -54,7 +54,7 @@ TopBar::TopBar(PIVXGUI* _mainWindow, QWidget *parent) : ui->widgetTopAmount->setVisible(false); setCssProperty({ui->labelAmountTopPiv}, "amount-small-topbar"); setCssProperty({ui->labelAmountPiv}, "amount-topbar"); - setCssProperty({ui->labelPendingPiv, ui->labelImmaturePiv}, "amount-small-topbar"); + setCssProperty({ui->labelPendingPiv, ui->labelImmaturePiv, ui->labelShieldedPiv}, "amount-small-topbar"); // Progress Sync progressBar = new QProgressBar(ui->layoutSync); @@ -638,6 +638,7 @@ void TopBar::updateBalances(const interfaces::WalletBalances& newBalance) ui->labelAmountPiv->setText(totalPiv); ui->labelPendingPiv->setText(GUIUtil::formatBalance(newBalance.unconfirmed_balance + newBalance.unconfirmed_shielded_balance, nDisplayUnit)); ui->labelImmaturePiv->setText(GUIUtil::formatBalance(newBalance.immature_balance, nDisplayUnit)); + ui->labelShieldedPiv->setText(GUIUtil::formatBalance(newBalance.shielded_balance, nDisplayUnit)); } void TopBar::resizeEvent(QResizeEvent *event) From 7ced8ae5020fe6c3fd5e1be3968392f8b6c541af Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 6 Nov 2020 16:45:05 -0300 Subject: [PATCH 057/121] GUI: balance bubble popup implemented. --- src/Makefile.qt.include | 3 + src/qt/CMakeLists.txt | 1 + src/qt/pivx/balancebubble.cpp | 74 +++++++++++++++++ src/qt/pivx/balancebubble.h | 35 ++++++++ src/qt/pivx/forms/balancebubble.ui | 125 +++++++++++++++++++++++++++++ 5 files changed, 238 insertions(+) create mode 100644 src/qt/pivx/balancebubble.cpp create mode 100644 src/qt/pivx/balancebubble.h create mode 100644 src/qt/pivx/forms/balancebubble.ui diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index a69d3479fcd1..157ee8c81be8 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -24,6 +24,7 @@ QT_FORMS_UI = \ qt/pivx/forms/lockunlock.ui \ qt/pivx/forms/expandablebutton.ui \ qt/pivx/forms/receivedialog.ui \ + qt/pivx/forms/balancebubble.ui \ qt/pivx/forms/topbar.ui \ qt/pivx/forms/txrow.ui \ qt/pivx/forms/dashboardwidget.ui \ @@ -228,6 +229,7 @@ BITCOIN_QT_H = \ qt/pivx/txviewholder.h \ qt/pivx/qtutils.h \ qt/pivx/expandablebutton.h \ + qt/pivx/balancebubble.h \ qt/pivx/topbar.h \ qt/pivx/txrow.h \ qt/pivx/addressholder.h \ @@ -548,6 +550,7 @@ BITCOIN_QT_WALLET_CPP = \ qt/pivx/txviewholder.cpp \ qt/pivx/qtutils.cpp \ qt/pivx/expandablebutton.cpp \ + qt/pivx/balancebubble.cpp \ qt/pivx/topbar.cpp \ qt/pivx/txrow.cpp \ qt/pivx/addressholder.cpp \ diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index 22e76bf8547d..5490ba442920 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -123,6 +123,7 @@ SET(QT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/pivx/furabstractlistitemdelegate.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/txviewholder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/qtutils.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/pivx/balancebubble.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/expandablebutton.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/topbar.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/txrow.cpp diff --git a/src/qt/pivx/balancebubble.cpp b/src/qt/pivx/balancebubble.cpp new file mode 100644 index 000000000000..bf8a3cecab1f --- /dev/null +++ b/src/qt/pivx/balancebubble.cpp @@ -0,0 +1,74 @@ +// Copyright (c) 2020 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#include "qt/pivx/balancebubble.h" +#include "qt/pivx/forms/ui_balancebubble.h" + +#include "qt/pivx/qtutils.h" + +#include +#include +#include +#include + +BalanceBubble::BalanceBubble(QWidget *parent) : + QWidget(parent), + ui(new Ui::BalanceBubble) +{ + ui->setupUi(this); + + ui->frame->setProperty("cssClass", "container-popup"); + setCssProperty({ui->textTransparent, ui->textShielded}, "amount-small-popup"); + + std::initializer_list lblTitles = {ui->lblFirst, ui->lblSecond}; + setCssProperty(lblTitles, "text-title-topbar"); + QFont font; + font.setWeight(QFont::Light); + for (QWidget* w : lblTitles) { w->setFont(font); } +} + +void BalanceBubble::updateValues(int64_t nTransparentBalance, int64_t nShieldedBalance, int unit){ + + ui->textTransparent->setText(BitcoinUnits::formatWithUnit(unit, nTransparentBalance, false, BitcoinUnits::separatorAlways)); + ui->textShielded->setText(BitcoinUnits::formatWithUnit(unit, nShieldedBalance, false, BitcoinUnits::separatorAlways)); +} + +void BalanceBubble::showEvent(QShowEvent *event) +{ + QGraphicsOpacityEffect *eff = new QGraphicsOpacityEffect(this); + this->setGraphicsEffect(eff); + QPropertyAnimation *a = new QPropertyAnimation(eff,"opacity"); + a->setDuration(400); + a->setStartValue(0.1); + a->setEndValue(1); + a->setEasingCurve(QEasingCurve::InBack); + a->start(QPropertyAnimation::DeleteWhenStopped); + + if (!hideTimer) hideTimer = new QTimer(this); + connect(hideTimer, &QTimer::timeout, this, &BalanceBubble::hideTimeout); + hideTimer->start(7000); +} + +void BalanceBubble::hideEvent(QHideEvent *event) +{ + if (hideTimer) hideTimer->stop(); + QGraphicsOpacityEffect *eff = new QGraphicsOpacityEffect(this); + this->setGraphicsEffect(eff); + QPropertyAnimation *a = new QPropertyAnimation(eff,"opacity"); + a->setDuration(800); + a->setStartValue(1); + a->setEndValue(0); + a->setEasingCurve(QEasingCurve::OutBack); + a->start(QPropertyAnimation::DeleteWhenStopped); +} + +void BalanceBubble::hideTimeout() +{ + hide(); +} + +BalanceBubble::~BalanceBubble() +{ + delete ui; +} \ No newline at end of file diff --git a/src/qt/pivx/balancebubble.h b/src/qt/pivx/balancebubble.h new file mode 100644 index 000000000000..12db86fba03c --- /dev/null +++ b/src/qt/pivx/balancebubble.h @@ -0,0 +1,35 @@ +// Copyright (c) 2020 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#ifndef PIVX_BALANCEBUBBLE_H +#define PIVX_BALANCEBUBBLE_H + +#include +#include + +namespace Ui { + class BalanceBubble; +} + +class BalanceBubble : public QWidget +{ + +public: + explicit BalanceBubble(QWidget *parent = nullptr); + ~BalanceBubble(); + + virtual void showEvent(QShowEvent *event) override; + virtual void hideEvent(QHideEvent *event) override; + + void updateValues(int64_t nTransparentBalance, int64_t nShieldedBalance, int unit); + +public Q_SLOTS: + void hideTimeout(); + +private: + Ui::BalanceBubble *ui; + QTimer* hideTimer{nullptr}; +}; + +#endif //PIVX_BALANCEBUBBLE_H diff --git a/src/qt/pivx/forms/balancebubble.ui b/src/qt/pivx/forms/balancebubble.ui new file mode 100644 index 000000000000..27d09eee8245 --- /dev/null +++ b/src/qt/pivx/forms/balancebubble.ui @@ -0,0 +1,125 @@ + + + BalanceBubble + + + + 0 + 0 + 218 + 178 + + + + + 218 + 178 + + + + Form + + + #BalanceBubble{ +background-color:transparent +} + + + + 0 + + + + + + 200 + 160 + + + + + 200 + 160 + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + 10 + + + + 0 + + + + + + + + + + Transparent + + + Qt::AlignCenter + + + + + + + + + + + + + 0.00 pivx + + + Qt::AlignCenter + + + + + + + + + + Shielded + + + Qt::AlignCenter + + + + + + + + + + 0.00 pivx + + + Qt::AlignCenter + + + + + + + + + + + From 925c28a537fa300b985b3d7eb022ac6146979d5d Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 6 Nov 2020 16:50:34 -0300 Subject: [PATCH 058/121] GUI: topbar shielded balance connection, bubble popup connected. --- src/Makefile.qt.include | 4 +- src/qt/pivx.qrc | 2 + src/qt/pivx/forms/topbar.ui | 213 +++++++++++++------ src/qt/pivx/res/css/style_dark.css | 24 ++- src/qt/pivx/res/css/style_light.css | 25 ++- src/qt/pivx/res/img/ic-information-hover.svg | 15 ++ src/qt/pivx/res/img/ic-information.svg | 15 ++ src/qt/pivx/topbar.cpp | 53 ++++- src/qt/pivx/topbar.h | 5 + 9 files changed, 284 insertions(+), 72 deletions(-) create mode 100644 src/qt/pivx/res/img/ic-information-hover.svg create mode 100644 src/qt/pivx/res/img/ic-information.svg diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 157ee8c81be8..be96a7a319ce 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -494,11 +494,11 @@ RES_ICONS = \ qt/pivx/res/img/ic-transaction-cs-contract.svg \ qt/pivx/res/img/ic-transaction-cs-contract-inactive.svg \ qt/pivx/res/img/ic-check-box-indeterminate.svg \ + qt/pivx/res/img/ic-information.svg \ + qt/pivx/res/img/ic-information-hover.svg \ qt/pivx/res/img/ani-loading-dark.gif \ qt/pivx/res/img/ani-loading.gif - - BITCOIN_QT_BASE_CPP = \ qt/bantablemodel.cpp \ qt/bitcoinaddressvalidator.cpp \ diff --git a/src/qt/pivx.qrc b/src/qt/pivx.qrc index 2a9e9b8b0413..99774aede88d 100644 --- a/src/qt/pivx.qrc +++ b/src/qt/pivx.qrc @@ -219,5 +219,7 @@ pivx/res/img/ic-check-cold-staking.svg pivx/res/img/ic-check-cold-staking-off.svg pivx/res/img/ic-check-cold-staking-enabled.svg + pivx/res/img/ic-information.svg + pivx/res/img/ic-information-hover.svg diff --git a/src/qt/pivx/forms/topbar.ui b/src/qt/pivx/forms/topbar.ui index 14e5e03fa7ca..b541101e036e 100644 --- a/src/qt/pivx/forms/topbar.ui +++ b/src/qt/pivx/forms/topbar.ui @@ -312,23 +312,23 @@ 10 - - - 9 + + + 0 + + + 0 + + + 30 - - - 0 - - - 0 - - - 30 - + - + + + 5 + @@ -337,55 +337,105 @@ - + + + + 26 + 26 + + + + + 26 + 26 + + + + true + + + Qt::NoFocus + - 480.0685 PIV + + + + + 24 + 24 + + + + - - - - - - 0 - - - - - - - Shielded - - - - - - - 130.43 PIV - - - - - - + Qt::Horizontal - QSizePolicy::Fixed + QSizePolicy::MinimumExpanding - 40 + 20 20 - + + + + + + 480.0685 PIV + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + @@ -401,25 +451,45 @@ - - - - - Qt::Horizontal + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 0 - - QSizePolicy::Fixed + + 0 - - - 40 - 20 - + + 0 + + + 0 - - - - @@ -435,10 +505,23 @@ - - - - + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + diff --git a/src/qt/pivx/res/css/style_dark.css b/src/qt/pivx/res/css/style_dark.css index ca41d22a012c..d33b8091d614 100644 --- a/src/qt/pivx/res/css/style_dark.css +++ b/src/qt/pivx/res/css/style_dark.css @@ -420,7 +420,12 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ *[cssClass="amount-small-topbar"] { color:#FFFFFF; - font-size:22px; + font-size:21px; +} + +*[cssClass="amount-small-popup"] { + color:#FFFFFF; + font-size:17px; } *[cssClass="container-qr"] { @@ -434,6 +439,18 @@ QPushButton[cssClass="btn-qr"] { background-color:transparent; } +QPushButton[cssClass="btn-info"] { + qproperty-icon: url("://ic-information"); + qproperty-iconSize: 24px 24px; + background-color:transparent; +} + +QPushButton[cssClass="btn-info"]:hover { + qproperty-icon: url("://ic-information-hover"); + qproperty-iconSize: 24px 24px; + background-color:transparent; +} + *[cssClass="sync-status"] { background-color:#505c4b7d; color:#FFFFFF; @@ -2438,6 +2455,11 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ border:1px solid #b088ff; } +*[cssClass="container-popup"] { + background-color:#0f0b16; + border-radius:14px; + border:1px solid #b088ff; +} *[cssClass="text-title-dialog"] { color:#b088ff; diff --git a/src/qt/pivx/res/css/style_light.css b/src/qt/pivx/res/css/style_light.css index 60e524c8484d..d9a6fc71095f 100644 --- a/src/qt/pivx/res/css/style_light.css +++ b/src/qt/pivx/res/css/style_light.css @@ -379,7 +379,12 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ *[cssClass="amount-small-topbar"] { color:#FFFFFF; - font-size:22px; + font-size:21px; +} + +*[cssClass="amount-small-popup"] { + color:#FFFFFF; + font-size:17px; } *[cssClass="container-qr"] { @@ -393,6 +398,18 @@ QPushButton[cssClass="btn-qr"] { background-color:transparent; } +QPushButton[cssClass="btn-info"] { + qproperty-icon: url("://ic-information"); + qproperty-iconSize: 24px 24px; + background-color:transparent; +} + +QPushButton[cssClass="btn-info"]:hover { + qproperty-icon: url("://ic-information-hover"); + qproperty-iconSize: 24px 24px; + background-color:transparent; +} + *[cssClass="sync-status"] { background-color:#505c4b7d; color:#FFFFFF; @@ -2439,6 +2456,12 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ border-radius: 2px; } +*[cssClass="container-popup"] { + background-color:#0f0b16; + border-radius:14px; + border:1px solid #b088ff; +} + *[cssClass="text-title2-dialog"] { color:#5c4b7d; font-size:16px; diff --git a/src/qt/pivx/res/img/ic-information-hover.svg b/src/qt/pivx/res/img/ic-information-hover.svg new file mode 100644 index 000000000000..9877967dd663 --- /dev/null +++ b/src/qt/pivx/res/img/ic-information-hover.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/src/qt/pivx/res/img/ic-information.svg b/src/qt/pivx/res/img/ic-information.svg new file mode 100644 index 000000000000..eeece03862c1 --- /dev/null +++ b/src/qt/pivx/res/img/ic-information.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/src/qt/pivx/topbar.cpp b/src/qt/pivx/topbar.cpp index 655dc55e9f92..b28e8038c9a8 100644 --- a/src/qt/pivx/topbar.cpp +++ b/src/qt/pivx/topbar.cpp @@ -11,6 +11,7 @@ #include "askpassphrasedialog.h" #include "bitcoinunits.h" +#include "qt/pivx/balancebubble.h" #include "clientmodel.h" #include "qt/guiconstants.h" #include "qt/guiutil.h" @@ -27,6 +28,29 @@ #define REQUEST_UPGRADE_WALLET 1 +class ButtonHoverWatcher : public QObject +{ +public: + explicit ButtonHoverWatcher(QObject* parent = nullptr) : + QObject(parent) {} + bool eventFilter(QObject* watched, QEvent* event) override + { + QPushButton* button = qobject_cast(watched); + if (!button) return false; + + if (event->type() == QEvent::Enter) { + button->setIcon(QIcon("://ic-information-hover")); + return true; + } + + if (event->type() == QEvent::Leave){ + button->setIcon(QIcon("://ic-information")); + return true; + } + return false; + } +}; + TopBar::TopBar(PIVXGUI* _mainWindow, QWidget *parent) : PWidget(_mainWindow, parent), ui(new Ui::TopBar) @@ -44,7 +68,7 @@ TopBar::TopBar(PIVXGUI* _mainWindow, QWidget *parent) : ui->containerTop->setProperty("cssClass", "container-top"); #endif - std::initializer_list lblTitles = {ui->labelTitle1, ui->labelTitle3, ui->labelTitle4, ui->labelTitle5}; + std::initializer_list lblTitles = {ui->labelTitle1, ui->labelTitle3, ui->labelTitle4}; setCssProperty(lblTitles, "text-title-topbar"); QFont font; font.setWeight(QFont::Light); @@ -54,7 +78,7 @@ TopBar::TopBar(PIVXGUI* _mainWindow, QWidget *parent) : ui->widgetTopAmount->setVisible(false); setCssProperty({ui->labelAmountTopPiv}, "amount-small-topbar"); setCssProperty({ui->labelAmountPiv}, "amount-topbar"); - setCssProperty({ui->labelPendingPiv, ui->labelImmaturePiv, ui->labelShieldedPiv}, "amount-small-topbar"); + setCssProperty({ui->labelPendingPiv, ui->labelImmaturePiv}, "amount-small-topbar"); // Progress Sync progressBar = new QProgressBar(ui->layoutSync); @@ -103,6 +127,9 @@ TopBar::TopBar(PIVXGUI* _mainWindow, QWidget *parent) : setCssProperty(ui->qrContainer, "container-qr"); setCssProperty(ui->pushButtonQR, "btn-qr"); + setCssProperty(ui->pushButtonBalanceInfo, "btn-info"); + ButtonHoverWatcher * watcher = new ButtonHoverWatcher(this); + ui->pushButtonBalanceInfo->installEventFilter(watcher); // QR image QPixmap pixmap("://img-qr-test"); @@ -119,6 +146,7 @@ TopBar::TopBar(PIVXGUI* _mainWindow, QWidget *parent) : connect(ui->pushButtonQR, &QPushButton::clicked, this, &TopBar::onBtnReceiveClicked); connect(ui->btnQr, &QPushButton::clicked, this, &TopBar::onBtnReceiveClicked); + connect(ui->pushButtonBalanceInfo, &QPushButton::clicked, this, &TopBar::onBtnBalanceInfoClicked); connect(ui->pushButtonLock, &ExpandableButton::Mouse_Pressed, this, &TopBar::onBtnLockClicked); connect(ui->pushButtonTheme, &ExpandableButton::Mouse_Pressed, this, &TopBar::onThemeClicked); connect(ui->pushButtonFAQ, &ExpandableButton::Mouse_Pressed, [this](){window->openFAQ();}); @@ -313,9 +341,29 @@ void TopBar::onBtnReceiveClicked() } } +void TopBar::onBtnBalanceInfoClicked() +{ + if (!walletModel) return; + if (balanceBubble) { + if (balanceBubble->isVisible()) { + balanceBubble->hide(); + return; + } + } else balanceBubble = new BalanceBubble(this); + + const auto& balances = walletModel->GetWalletBalances(); + balanceBubble->updateValues(balances.balance - balances.shielded_balance, balances.shielded_balance, nDisplayUnit); + QPoint pos = this->pos(); + pos.setX(pos.x() + (ui->labelTitle1->width()) + 60); + pos.setY(pos.y() + 20); + balanceBubble->move(pos); + balanceBubble->show(); +} + void TopBar::showTop() { if (ui->bottom_container->isVisible()) { + if (balanceBubble && balanceBubble->isVisible()) balanceBubble->hide(); ui->bottom_container->setVisible(false); ui->widgetTopAmount->setVisible(true); this->setFixedHeight(75); @@ -638,7 +686,6 @@ void TopBar::updateBalances(const interfaces::WalletBalances& newBalance) ui->labelAmountPiv->setText(totalPiv); ui->labelPendingPiv->setText(GUIUtil::formatBalance(newBalance.unconfirmed_balance + newBalance.unconfirmed_shielded_balance, nDisplayUnit)); ui->labelImmaturePiv->setText(GUIUtil::formatBalance(newBalance.immature_balance, nDisplayUnit)); - ui->labelShieldedPiv->setText(GUIUtil::formatBalance(newBalance.shielded_balance, nDisplayUnit)); } void TopBar::resizeEvent(QResizeEvent *event) diff --git a/src/qt/pivx/topbar.h b/src/qt/pivx/topbar.h index c471cfe240b5..88e9970d4412 100644 --- a/src/qt/pivx/topbar.h +++ b/src/qt/pivx/topbar.h @@ -12,6 +12,7 @@ #include #include +class BalanceBubble; class PIVXGUI; class WalletModel; class ClientModel; @@ -61,6 +62,7 @@ public Q_SLOTS: void resizeEvent(QResizeEvent *event) override; private Q_SLOTS: void onBtnReceiveClicked(); + void onBtnBalanceInfoClicked(); void onThemeClicked(); void onBtnLockClicked(); void lockDropdownMouseLeave(); @@ -79,6 +81,9 @@ private Q_SLOTS: QTimer* timerStakingIcon = nullptr; bool isInitializing = true; + // info popup + BalanceBubble* balanceBubble = nullptr; + void updateTorIcon(); }; From 3581b8ee90b38f16dd822e88e92e30392541e817 Mon Sep 17 00:00:00 2001 From: furszy Date: Sun, 8 Nov 2020 20:23:38 -0300 Subject: [PATCH 059/121] GUI: send screen, "send from shielded" process connection. --- src/qt/pivx/forms/send.ui | 118 ++++++++++++++++++++++++- src/qt/pivx/send.cpp | 42 ++++++--- src/qt/pivx/send.h | 4 +- src/qt/walletmodel.cpp | 13 +-- src/qt/walletmodel.h | 6 +- src/sapling/saplingscriptpubkeyman.cpp | 25 +++--- 6 files changed, 173 insertions(+), 35 deletions(-) diff --git a/src/qt/pivx/forms/send.ui b/src/qt/pivx/forms/send.ui index e14da066501f..a2e42bee47e1 100644 --- a/src/qt/pivx/forms/send.ui +++ b/src/qt/pivx/forms/send.ui @@ -66,7 +66,7 @@ - 5 + 0 @@ -81,7 +81,7 @@ - Send public coins (PIV) + Transfer coins publicly or fully private @@ -100,6 +100,120 @@ + + + + 3 + + + + + #groupBox{ +padding-top:2px; +} + + + + + + Qt::AlignCenter + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 120 + 30 + + + + + 120 + 30 + + + + Qt::NoFocus + + + Transparent + + + true + + + true + + + + + + + + 120 + 30 + + + + + 120 + 30 + + + + Qt::NoFocus + + + Shielded + + + true + + + true + + + true + + + + + + + + + + + + + Select which coins do you want to spend + + + Qt::AlignCenter + + + 0 + + + + + diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index ee1fff730ee0..8042a307dd3b 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -42,8 +42,13 @@ SendWidget::SendWidget(PIVXGUI* parent) : setCssProperty(ui->labelTitle, "text-title-screen"); ui->labelTitle->setFont(fontLight); + /* Button Group */ + setCssProperty(ui->pushLeft, "btn-check-left"); + ui->pushLeft->setChecked(true); + setCssProperty(ui->pushRight, "btn-check-right"); + /* Subtitle */ - setCssProperty(ui->labelSubtitle1, "text-subtitle"); + setCssProperty({ui->labelSubtitle1, ui->labelSubtitle2}, "text-subtitle"); /* Address - Amount*/ setCssProperty({ui->labelSubtitleAddress, ui->labelSubtitleAmount}, "text-title"); @@ -108,6 +113,8 @@ SendWidget::SendWidget(PIVXGUI* parent) : setCustomFeeSelected(false); // Connect + connect(ui->pushLeft, &QPushButton::clicked, [this](){onPIVSelected(true);}); + connect(ui->pushRight, &QPushButton::clicked, [this](){onPIVSelected(false);}); connect(ui->pushButtonSave, &QPushButton::clicked, this, &SendWidget::onSendClicked); connect(ui->pushButtonAddRecipient, &QPushButton::clicked, this, &SendWidget::onAddEntryClicked); connect(ui->pushButtonClear, &QPushButton::clicked, [this](){clearAll(true);}); @@ -127,25 +134,29 @@ void SendWidget::refreshAmounts() } nDisplayUnit = walletModel->getOptionsModel()->getDisplayUnit(); - ui->labelAmountSend->setText(GUIUtil::formatBalance(total, nDisplayUnit, false)); + QString type = "transparent"; CAmount totalAmount = 0; if (coinControlDialog->coinControl->HasSelected()) { // Set remaining balance to the sum of the coinControl selected inputs totalAmount = walletModel->getBalance(coinControlDialog->coinControl) - total; ui->labelTitleTotalRemaining->setText(tr("Total remaining from the selected UTXO")); } else { - // Wallet's unlocked balance - totalAmount = walletModel->getUnlockedBalance(nullptr, fDelegationsChecked) - total; + // Wallet's unlocked balance. + if (isTransparent) { + totalAmount = walletModel->getUnlockedBalance(nullptr, fDelegationsChecked, false) - total; + } else { + totalAmount = walletModel->GetWalletBalances().shielded_balance - total; + type = "shielded"; + } ui->labelTitleTotalRemaining->setText(tr("Unlocked remaining")); } ui->labelAmountRemaining->setText( GUIUtil::formatBalance( totalAmount, nDisplayUnit, - false - ) + false) + " " + type ); // show or hide delegations checkbox if need be showHideCheckBoxDelegations(); @@ -302,11 +313,11 @@ void SendWidget::setFocusOnLastEntry() void SendWidget::showHideCheckBoxDelegations() { // Show checkbox only when there is any available owned delegation and - // coincontrol is not selected. + // coincontrol is not selected, and we are trying to spend transparent PIVs. const bool isCControl = coinControlDialog->coinControl->HasSelected(); const bool hasDel = cachedDelegatedBalance > 0; - const bool showCheckBox = !isCControl && hasDel; + const bool showCheckBox = isTransparent && !isCControl && hasDel; ui->checkBoxDelegations->setVisible(showCheckBox); if (showCheckBox) ui->checkBoxDelegations->setToolTip( @@ -350,21 +361,20 @@ void SendWidget::onSendClicked() { WalletModelTransaction tx(recipients); - if (hasShieldedOutput ? sendShielded(tx) : send(tx)) { + if (hasShieldedOutput || !isTransparent ? sendShielded(tx, isTransparent) : send(tx)) { updateEntryLabels(recipients); } } setFocusOnLastEntry(); } -bool SendWidget::sendShielded(WalletModelTransaction& currentTransaction) +bool SendWidget::sendShielded(WalletModelTransaction& currentTransaction, bool fromTransparent) { - auto result = walletModel->PrepareShieldedTransaction(currentTransaction); + auto result = walletModel->PrepareShieldedTransaction(currentTransaction, fromTransparent); if (!result) { inform(result.m_error); return false; } - return sendFinalStep(currentTransaction); } @@ -541,6 +551,7 @@ void SendWidget::onChangeCustomFeeClicked() void SendWidget::onCoinControlClicked() { + // TODO: Implement unspent notes coin control if (walletModel->getBalance() > 0) { coinControlDialog->refreshDialog(); setCoinControlPayAmounts(); @@ -576,6 +587,13 @@ void SendWidget::onCheckBoxChanged() } } +void SendWidget::onPIVSelected(bool _isTransparent) +{ + isTransparent = _isTransparent; + refreshAmounts(); + updateStyle(coinIcon); +} + void SendWidget::onContactsClicked(SendMultiRow* entry) { focusedEntry = entry; diff --git a/src/qt/pivx/send.h b/src/qt/pivx/send.h index 7a479d5f73ed..954991aea60a 100644 --- a/src/qt/pivx/send.h +++ b/src/qt/pivx/send.h @@ -59,6 +59,7 @@ public Q_SLOTS: void showEvent(QShowEvent *event) override; private Q_SLOTS: + void onPIVSelected(bool _isTransparent); void onSendClicked(); void onContactsClicked(SendMultiRow* entry); void onMenuClicked(SendMultiRow* entry); @@ -90,10 +91,11 @@ private Q_SLOTS: // Current focus entry SendMultiRow* focusedEntry = nullptr; + bool isTransparent = true; void resizeMenu(); QString recipientsToString(QList recipients); SendMultiRow* createEntry(); - bool sendShielded(WalletModelTransaction& tx); + bool sendShielded(WalletModelTransaction& tx, bool fromTransparent); bool send(WalletModelTransaction& tx); bool sendFinalStep(WalletModelTransaction& currentTransaction); void setFocusOnLastEntry(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index d8448b866395..dc040490945b 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -95,7 +95,7 @@ bool WalletModel::upgradeWallet(std::string& upgradeError) return wallet->Upgrade(upgradeError, prev_version); } -CAmount WalletModel::getBalance(const CCoinControl* coinControl, bool fIncludeDelegated, bool fUnlockedOnly) const +CAmount WalletModel::getBalance(const CCoinControl* coinControl, bool fIncludeDelegated, bool fUnlockedOnly, bool fIncludeShielded) const { if (coinControl) { CAmount nBalance = 0; @@ -112,12 +112,12 @@ CAmount WalletModel::getBalance(const CCoinControl* coinControl, bool fIncludeDe return nBalance; } - return wallet->GetAvailableBalance(fIncludeDelegated) - (fUnlockedOnly ? wallet->GetLockedCoins() : CAmount(0)); + return wallet->GetAvailableBalance(fIncludeDelegated, fIncludeShielded) - (fUnlockedOnly ? wallet->GetLockedCoins() : CAmount(0)); } -CAmount WalletModel::getUnlockedBalance(const CCoinControl* coinControl, bool fIncludeDelegated) const +CAmount WalletModel::getUnlockedBalance(const CCoinControl* coinControl, bool fIncludeDelegated, bool fIncludeShielded) const { - return getBalance(coinControl, fIncludeDelegated, true); + return getBalance(coinControl, fIncludeDelegated, true, fIncludeShielded); } CAmount WalletModel::getMinColdStakingAmount() const @@ -515,7 +515,7 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction& tran return SendCoinsReturn(OK); } -OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction& modelTransaction) +OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction& modelTransaction, bool fromTransparent) { // Basic checks first @@ -548,7 +548,8 @@ OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction& SaplingOperation operation(txBuilder); auto operationResult = operation.setRecipients(recipients) ->setTransparentKeyChange(modelTransaction.getPossibleKeyChange()) - ->setSelectTransparentCoins(true) + ->setSelectTransparentCoins(fromTransparent) + ->setSelectShieldedCoins(!fromTransparent) ->build(); if (!operationResult) { diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 0366467296ba..37c13eab9ac3 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -160,8 +160,8 @@ class WalletModel : public QObject interfaces::WalletBalances GetWalletBalances() { return m_cached_balances; }; - CAmount getBalance(const CCoinControl* coinControl = nullptr, bool fIncludeDelegated = true, bool fUnlockedOnly = false) const; - CAmount getUnlockedBalance(const CCoinControl* coinControl = nullptr, bool fIncludeDelegated = true) const; + CAmount getBalance(const CCoinControl* coinControl = nullptr, bool fIncludeDelegated = true, bool fUnlockedOnly = false, bool fIncludeShielded = true) const; + CAmount getUnlockedBalance(const CCoinControl* coinControl = nullptr, bool fIncludeDelegated = true, bool fIncludeShielded = true) const; CAmount getLockedBalance() const; bool haveWatchOnly() const; CAmount getDelegatedBalance() const; @@ -206,7 +206,7 @@ class WalletModel : public QObject SendCoinsReturn sendCoins(WalletModelTransaction& transaction); // Prepare shielded transaction. - OperationResult PrepareShieldedTransaction(WalletModelTransaction& modelTransaction); + OperationResult PrepareShieldedTransaction(WalletModelTransaction& modelTransaction, bool fromTransparent); // Wallet encryption bool setWalletEncrypted(bool encrypted, const SecureString& passphrase); diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index bb9454f2fbc4..f4c4ca1c0df4 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -530,17 +530,20 @@ Optional ovks; ovks.emplace(getCommonOVKFromSeed()); if (!tx.sapData->vShieldedSpend.empty()) { - const SaplingOutPoint& prevOut = mapSaplingNullifiersToNotes[tx.sapData->vShieldedSpend[0].nullifier]; - const CWalletTx* txPrev = wallet->GetWalletTx(prevOut.hash); - if (!txPrev) return nullopt; - const auto& it = txPrev->mapSaplingNoteData.find(prevOut); - if (it != txPrev->mapSaplingNoteData.end()) { - const SaplingNoteData ¬eData = it->second; - libzcash::SaplingExtendedSpendingKey extsk; - libzcash::SaplingExtendedFullViewingKey extfvk; - if (wallet->GetSaplingFullViewingKey(noteData.ivk, extfvk) && - wallet->GetSaplingSpendingKey(extfvk, extsk)) { - ovks.emplace(extsk.expsk.ovk); + const auto& it = mapSaplingNullifiersToNotes.find(tx.sapData->vShieldedSpend[0].nullifier); + if (it != mapSaplingNullifiersToNotes.end()) { + const SaplingOutPoint& prevOut = it->second; + const CWalletTx* txPrev = wallet->GetWalletTx(prevOut.hash); + if (!txPrev) return nullopt; + const auto& itPrev = txPrev->mapSaplingNoteData.find(prevOut); + if (itPrev != txPrev->mapSaplingNoteData.end()) { + const SaplingNoteData& noteData = itPrev->second; + libzcash::SaplingExtendedSpendingKey extsk; + libzcash::SaplingExtendedFullViewingKey extfvk; + if (wallet->GetSaplingFullViewingKey(noteData.ivk, extfvk) && + wallet->GetSaplingSpendingKey(extfvk, extsk)) { + ovks.emplace(extsk.expsk.ovk); + } } } } From 76dd3fc1a6af6a0c17caf2f6f4f1811cc179f30a Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 9 Nov 2020 11:38:08 -0300 Subject: [PATCH 060/121] wallet: fix GetAvailableBalance, the "only transparent" option wasn't implemented. --- src/wallet/wallet.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index af02672bb5ab..5366b5467d8c 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1892,10 +1892,12 @@ CAmount CWallet::GetAvailableBalance(bool fIncludeDelegated, bool fIncludeShield isminefilter filter; if (fIncludeDelegated && fIncludeShielded) { filter = ISMINE_SPENDABLE_ALL; - } else if (fIncludeDelegated){ + } else if (fIncludeDelegated) { filter = ISMINE_SPENDABLE_TRANSPARENT; - } else { + } else if (fIncludeShielded) { filter = ISMINE_SPENDABLE_NO_DELEGATED; + } else { + filter = ISMINE_SPENDABLE; } return GetAvailableBalance(filter, true, 0); } From 4828d58ec98970aed70a0fe6d229b1b72261c9c2 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 9 Nov 2020 11:48:10 -0300 Subject: [PATCH 061/121] GUI: collapsed topbar, shielded piv total amount added. --- src/qt/pivx/forms/topbar.ui | 85 ++++++++++++++++++++++++++++++++++++- src/qt/pivx/topbar.cpp | 9 ++-- 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/src/qt/pivx/forms/topbar.ui b/src/qt/pivx/forms/topbar.ui index b541101e036e..31666071d32b 100644 --- a/src/qt/pivx/forms/topbar.ui +++ b/src/qt/pivx/forms/topbar.ui @@ -93,7 +93,7 @@ - 0 + 5 0 @@ -123,6 +123,89 @@ + + + + transparent + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 15 + 20 + + + + + + + + + 1 + 30 + + + + + 1 + 30 + + + + background-color:white; +padding:0px; +border:none; + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 15 + 20 + + + + + + + + + 0 + 36 + + + + 1.000 PIV + + + + + + + shielded + + + diff --git a/src/qt/pivx/topbar.cpp b/src/qt/pivx/topbar.cpp index b28e8038c9a8..b2ad496d4d70 100644 --- a/src/qt/pivx/topbar.cpp +++ b/src/qt/pivx/topbar.cpp @@ -68,7 +68,7 @@ TopBar::TopBar(PIVXGUI* _mainWindow, QWidget *parent) : ui->containerTop->setProperty("cssClass", "container-top"); #endif - std::initializer_list lblTitles = {ui->labelTitle1, ui->labelTitle3, ui->labelTitle4}; + std::initializer_list lblTitles = {ui->labelTitle1, ui->labelTitle3, ui->labelTitle4, ui->labelTrans, ui->labelShield}; setCssProperty(lblTitles, "text-title-topbar"); QFont font; font.setWeight(QFont::Light); @@ -76,7 +76,7 @@ TopBar::TopBar(PIVXGUI* _mainWindow, QWidget *parent) : // Amount information top ui->widgetTopAmount->setVisible(false); - setCssProperty({ui->labelAmountTopPiv}, "amount-small-topbar"); + setCssProperty({ui->labelAmountTopPiv, ui->labelAmountTopShieldedPiv}, "amount-small-topbar"); setCssProperty({ui->labelAmountPiv}, "amount-topbar"); setCssProperty({ui->labelPendingPiv, ui->labelImmaturePiv}, "amount-small-topbar"); @@ -678,10 +678,13 @@ void TopBar::updateBalances(const interfaces::WalletBalances& newBalance) // PIV Total QString totalPiv = GUIUtil::formatBalance(newBalance.balance, nDisplayUnit); + QString totalTransparent = GUIUtil::formatBalance(newBalance.balance - newBalance.shielded_balance); + QString totalShielded = GUIUtil::formatBalance(newBalance.shielded_balance); // PIV // Top - ui->labelAmountTopPiv->setText(totalPiv); + ui->labelAmountTopPiv->setText(totalTransparent); + ui->labelAmountTopShieldedPiv->setText(totalShielded); // Expanded ui->labelAmountPiv->setText(totalPiv); ui->labelPendingPiv->setText(GUIUtil::formatBalance(newBalance.unconfirmed_balance + newBalance.unconfirmed_shielded_balance, nDisplayUnit)); From 91654a5667ba36d3605ca32ba53ac830295214a3 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 9 Nov 2020 12:07:27 -0300 Subject: [PATCH 062/121] GUI: sendconfirmation dialog, formatting shielded address to be fully visible to double check it before confirm the operation. --- src/qt/pivx/sendconfirmdialog.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/qt/pivx/sendconfirmdialog.cpp b/src/qt/pivx/sendconfirmdialog.cpp index 3025cc272bd0..2acc322d7053 100644 --- a/src/qt/pivx/sendconfirmdialog.cpp +++ b/src/qt/pivx/sendconfirmdialog.cpp @@ -118,6 +118,17 @@ void TxDetailDialog::setData(WalletModel *model, const QModelIndex &index) } +QString formatAdressToShow(const QString& address) +{ + QString addressToShow; + if (address.size() > 60) { + addressToShow = address.left(60) + "\n" + address.mid(60); + } else { + addressToShow = address; + } + return addressToShow; +} + void TxDetailDialog::setData(WalletModel *model, WalletModelTransaction &tx) { this->model = model; @@ -132,11 +143,14 @@ void TxDetailDialog::setData(WalletModel *model, WalletModelTransaction &tx) if (recipient.isP2CS) { ui->labelSend->setText(tr("Delegating to")); } + if (recipient.isShieldedAddr) { + ui->labelSend->setText(tr("Shielding to")); + } if (recipient.label.isEmpty()) { // If there is no label, then do not show the blank space. - ui->textSendLabel->setText(recipient.address); ui->textSend->setVisible(false); + ui->textSendLabel->setText(formatAdressToShow(recipient.address)); } else { - ui->textSend->setText(recipient.address); + ui->textSend->setText(formatAdressToShow(recipient.address)); ui->textSendLabel->setText(recipient.label); } ui->pushOutputs->setVisible(false); From 7eaf63b7dee7a25aa4c7d4b25573d0707b8f0af3 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 10 Nov 2020 23:43:51 -0300 Subject: [PATCH 063/121] GUI: loadingDialog custom loading message --- src/qt/pivx/loadingdialog.cpp | 6 +++++- src/qt/pivx/loadingdialog.h | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/qt/pivx/loadingdialog.cpp b/src/qt/pivx/loadingdialog.cpp index 6a47c75084bd..cb4731e95303 100644 --- a/src/qt/pivx/loadingdialog.cpp +++ b/src/qt/pivx/loadingdialog.cpp @@ -25,7 +25,7 @@ void Worker::process(){ Q_EMIT finished(); }; -LoadingDialog::LoadingDialog(QWidget *parent) : +LoadingDialog::LoadingDialog(QWidget *parent, QString loadingMsg) : QDialog(parent), ui(new Ui::LoadingDialog) { @@ -42,6 +42,10 @@ LoadingDialog::LoadingDialog(QWidget *parent) : ui->labelMessage->setProperty("cssClass", "text-loading"); ui->labelDots->setProperty("cssClass", "text-loading"); + + if (!loadingMsg.isEmpty()) { + ui->labelMessage->setText(loadingMsg); + } } void LoadingDialog::execute(Runnable *runnable, int type, std::unique_ptr pctx) diff --git a/src/qt/pivx/loadingdialog.h b/src/qt/pivx/loadingdialog.h index 7e0d0e4a249d..82f912729066 100644 --- a/src/qt/pivx/loadingdialog.h +++ b/src/qt/pivx/loadingdialog.h @@ -64,7 +64,7 @@ class LoadingDialog : public QDialog Q_OBJECT public: - explicit LoadingDialog(QWidget *parent = nullptr); + explicit LoadingDialog(QWidget *parent = nullptr, QString loadingMsg = ""); ~LoadingDialog(); void execute(Runnable *runnable, int type, std::unique_ptr pctx = nullptr); From 7d8f41e9f8d330b10c5ebe959417e843869ed9c9 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 10 Nov 2020 23:56:05 -0300 Subject: [PATCH 064/121] GUI: move transaction creation flow to a worker thread. Shielded transactions require much more processing than regular transactions and were locking the main thread. --- src/qt/pivx/coldstakingwidget.cpp | 2 +- src/qt/pivx/masternodewizarddialog.cpp | 2 +- src/qt/pivx/send.cpp | 94 ++++++++++++++++++++------ src/qt/pivx/send.h | 16 ++++- src/qt/pivx/txviewholder.cpp | 2 +- src/qt/walletmodel.cpp | 22 +++--- src/qt/walletmodel.h | 4 +- src/qt/walletmodeltransaction.h | 3 + 8 files changed, 108 insertions(+), 37 deletions(-) diff --git a/src/qt/pivx/coldstakingwidget.cpp b/src/qt/pivx/coldstakingwidget.cpp index 1e700c391641..8ef5b227df5f 100644 --- a/src/qt/pivx/coldstakingwidget.cpp +++ b/src/qt/pivx/coldstakingwidget.cpp @@ -477,7 +477,7 @@ void ColdStakingWidget::onSendClicked() // Prepare transaction for getting txFee earlier (exlude delegated coins) WalletModelTransaction currentTransaction(recipients); - WalletModel::SendCoinsReturn prepareStatus = walletModel->prepareTransaction(currentTransaction, coinControlDialog->coinControl, false); + WalletModel::SendCoinsReturn prepareStatus = walletModel->prepareTransaction(¤tTransaction, coinControlDialog->coinControl, false); // process prepareStatus and on error generate message shown to user GuiTransactionsUtils::ProcessSendCoinsReturnAndInform( diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index 5c9bdb5c23e6..763693e8c583 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -222,7 +222,7 @@ bool MasterNodeWizardDialog::createMN() WalletModel::SendCoinsReturn prepareStatus; // no coincontrol, no P2CS delegations - prepareStatus = walletModel->prepareTransaction(currentTransaction, nullptr, false); + prepareStatus = walletModel->prepareTransaction(¤tTransaction, nullptr, false); QString returnMsg = tr("Unknown error"); // process prepareStatus and on error generate message shown to user diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index 8042a307dd3b..7348d38a4b02 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -11,13 +11,17 @@ #include "qt/pivx/sendconfirmdialog.h" #include "qt/pivx/myaddressrow.h" #include "qt/pivx/guitransactionsutils.h" +#include "qt/pivx/loadingdialog.h" #include "clientmodel.h" #include "optionsmodel.h" +#include "operationresult.h" #include "addresstablemodel.h" #include "coincontrol.h" #include "script/standard.h" #include "openuridialog.h" +#define REQUEST_PREPARE_TX 1 + SendWidget::SendWidget(PIVXGUI* parent) : PWidget(parent), ui(new Ui::send), @@ -352,54 +356,62 @@ void SendWidget::onSendClicked() return; } - WalletModel::UnlockContext ctx(walletModel->requestUnlock()); - if (!ctx.isValid()) { + auto ptrUnlockedContext = MakeUnique(walletModel->requestUnlock()); + if (!ptrUnlockedContext->isValid()) { // Unlock wallet was cancelled inform(tr("Cannot send, wallet locked")); return; } - { - WalletModelTransaction tx(recipients); - if (hasShieldedOutput || !isTransparent ? sendShielded(tx, isTransparent) : send(tx)) { - updateEntryLabels(recipients); - } + // If tx exists then there is an on-going process being executed, return. + if (ptrModelTx) { + inform(tr("On going process being executed, please wait until it's finished to create a new transaction")); + return; } - setFocusOnLastEntry(); + ptrModelTx = std::make_shared(WalletModelTransaction(recipients)); + ptrModelTx->useV2 = hasShieldedOutput || !isTransparent; + + window->showHide(true); + LoadingDialog *dialog = new LoadingDialog(window, tr("Preparing transaction")); + dialog->execute(this, REQUEST_PREPARE_TX, std::move(ptrUnlockedContext)); + openDialogWithOpaqueBackgroundFullScreen(dialog, window); } -bool SendWidget::sendShielded(WalletModelTransaction& currentTransaction, bool fromTransparent) +OperationResult SendWidget::prepareShielded(WalletModelTransaction* currentTransaction, bool fromTransparent) { auto result = walletModel->PrepareShieldedTransaction(currentTransaction, fromTransparent); if (!result) { - inform(result.m_error); - return false; + return errorOut(result.m_error.toStdString()); } - return sendFinalStep(currentTransaction); + return OperationResult(true); } -bool SendWidget::send(WalletModelTransaction& currentTransaction) +OperationResult SendWidget::prepareTransparent(WalletModelTransaction* currentTransaction) { // prepare transaction for getting txFee earlier WalletModel::SendCoinsReturn prepareStatus; prepareStatus = walletModel->prepareTransaction(currentTransaction, coinControlDialog->coinControl, fDelegationsChecked); // process prepareStatus and on error generate message shown to user - GuiTransactionsUtils::ProcessSendCoinsReturnAndInform( + CClientUIInterface::MessageBoxFlags informType; + QString informMsg = GuiTransactionsUtils::ProcessSendCoinsReturn( this, prepareStatus, walletModel, + informType, BitcoinUnits::formatWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), - currentTransaction.getTransactionFee()), + currentTransaction->getTransactionFee()), true ); - if (prepareStatus.status != WalletModel::OK) { - inform(tr("Cannot create transaction.")); - return false; + if (!informMsg.isEmpty()) { + return errorOut(informMsg.toStdString()); } - return sendFinalStep(currentTransaction); + if (prepareStatus.status != WalletModel::OK) { + return errorOut("Cannot create transaction."); + } + return OperationResult(true); } bool SendWidget::sendFinalStep(WalletModelTransaction& currentTransaction) @@ -441,6 +453,50 @@ bool SendWidget::sendFinalStep(WalletModelTransaction& currentTransaction) return true; } +void SendWidget::resetSendProcess(QString informError) +{ + ptrModelTx.reset(); + isProcessing = false; + + if (!informError.isEmpty()) { + inform(informError); + } +} + +void SendWidget::txBroadcasted() +{ + // broadcast the tx now and update labels. + if (sendFinalStep(*ptrModelTx)) { + updateEntryLabels(ptrModelTx->getRecipients()); + } + setFocusOnLastEntry(); + resetSendProcess(QString()); +} + +void SendWidget::run(int type) +{ + if (type == REQUEST_PREPARE_TX) { + if (!isProcessing) { + isProcessing = true; + OperationResult result(false); + if ((result = ptrModelTx->useV2 ? + prepareShielded(ptrModelTx.get(), isTransparent) : + prepareTransparent(ptrModelTx.get()) + )) { + QMetaObject::invokeMethod(this, "txBroadcasted", Qt::QueuedConnection); + } else { + // error, reset state + QMetaObject::invokeMethod(this, "resetSendProcess", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(result.getError()))); + } + } + } +} + +void SendWidget::onError(QString error, int type) +{ + QMetaObject::invokeMethod(this, "resetSendProcess", Qt::QueuedConnection, Q_ARG(QString, error)); +} + QString SendWidget::recipientsToString(QList recipients) { QString s = ""; diff --git a/src/qt/pivx/send.h b/src/qt/pivx/send.h index 954991aea60a..a25c5ef35431 100644 --- a/src/qt/pivx/send.h +++ b/src/qt/pivx/send.h @@ -16,10 +16,13 @@ #include "coincontroldialog.h" #include "qt/pivx/tooltipmenu.h" +#include + static const int MAX_SEND_POPUP_ENTRIES = 8; class PIVXGUI; class ClientModel; +class OperationResult; class WalletModel; class WalletModelTransaction; @@ -58,6 +61,9 @@ public Q_SLOTS: void resizeEvent(QResizeEvent *event) override; void showEvent(QShowEvent *event) override; + void run(int type) override; + void onError(QString error, int type) override; + private Q_SLOTS: void onPIVSelected(bool _isTransparent); void onSendClicked(); @@ -71,6 +77,8 @@ private Q_SLOTS: void onDeleteClicked(); void onResetCustomOptions(bool fRefreshAmounts); void onResetSettings(); + void txBroadcasted(); + void resetSendProcess(QString informError); private: Ui::send *ui; @@ -86,6 +94,10 @@ private Q_SLOTS: QList entries; CoinControlDialog *coinControlDialog = nullptr; + // Cached tx + std::shared_ptr ptrModelTx; + std::atomic isProcessing{false}; + ContactsDropdown *menuContacts = nullptr; TooltipMenu *menu = nullptr; // Current focus entry @@ -95,8 +107,8 @@ private Q_SLOTS: void resizeMenu(); QString recipientsToString(QList recipients); SendMultiRow* createEntry(); - bool sendShielded(WalletModelTransaction& tx, bool fromTransparent); - bool send(WalletModelTransaction& tx); + OperationResult prepareShielded(WalletModelTransaction* tx, bool fromTransparent); + OperationResult prepareTransparent(WalletModelTransaction* tx); bool sendFinalStep(WalletModelTransaction& currentTransaction); void setFocusOnLastEntry(); void showHideCheckBoxDelegations(); diff --git a/src/qt/pivx/txviewholder.cpp b/src/qt/pivx/txviewholder.cpp index d4109c1ccc6f..c8089c63e2e7 100644 --- a/src/qt/pivx/txviewholder.cpp +++ b/src/qt/pivx/txviewholder.cpp @@ -54,7 +54,7 @@ void TxViewHolder::init(QWidget* holder, const QModelIndex &index, bool isHovere if (type == TransactionRecord::SendToSelfShieldedAddress) { qint64 amountBottom = rIndex.data(TransactionTableModel::ShieldedCreditAmountRole).toLongLong(); QString amountBottomText = BitcoinUnits::formatWithUnit(nDisplayUnit, amountBottom, true, BitcoinUnits::separatorAlways); - txRow->setAmount(amountBottomText, amountText + " fee"); + txRow->setAmount(amountBottomText + " shielded", amountText + " fee"); } else { txRow->setAmount(amountText, ""); } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index dc040490945b..a600d26312a2 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -320,10 +320,10 @@ bool WalletModel::updateAddressBookLabels(const CWDestination& dest, const std:: return false; } -WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction& transaction, const CCoinControl* coinControl, bool fIncludeDelegations) +WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction* transaction, const CCoinControl* coinControl, bool fIncludeDelegations) { CAmount total = 0; - QList recipients = transaction.getRecipients(); + QList recipients = transaction->getRecipients(); std::vector vecSend; if (recipients.empty()) { @@ -406,13 +406,13 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact { LOCK2(cs_main, wallet->cs_wallet); - transaction.newPossibleKeyChange(wallet); + transaction->newPossibleKeyChange(wallet); CAmount nFeeRequired = 0; int nChangePosInOut = -1; std::string strFailReason; - CWalletTx* newTx = transaction.getTransaction(); - CReserveKey* keyChange = transaction.getPossibleKeyChange(); + CWalletTx* newTx = transaction->getTransaction(); + CReserveKey* keyChange = transaction->getPossibleKeyChange(); bool fCreated = wallet->CreateTransaction(vecSend, *newTx, @@ -425,7 +425,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact true, 0, fIncludeDelegations); - transaction.setTransactionFee(nFeeRequired); + transaction->setTransactionFee(nFeeRequired); if (!fCreated) { if ((total + nFeeRequired) > nSpendableBalance) { @@ -441,7 +441,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact } // reject insane fee - if (nFeeRequired > ::minRelayTxFee.GetFee(transaction.getTransactionSize()) * 10000) + if (nFeeRequired > ::minRelayTxFee.GetFee(transaction->getTransactionSize()) * 10000) return InsaneFee; } @@ -515,7 +515,7 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction& tran return SendCoinsReturn(OK); } -OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction& modelTransaction, bool fromTransparent) +OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction* modelTransaction, bool fromTransparent) { // Basic checks first @@ -527,7 +527,7 @@ OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction& // Load shieldedAddrRecipients. std::vector recipients; - for (const auto& recipient : modelTransaction.getRecipients()) { + for (const auto& recipient : modelTransaction->getRecipients()) { if (recipient.isShieldedAddr) { auto pa = KeyIO::DecodeSaplingPaymentAddress(recipient.address.toStdString()); if (!pa) return errorOut("Error, invalid shielded address"); @@ -547,7 +547,7 @@ OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction& TransactionBuilder txBuilder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, wallet); SaplingOperation operation(txBuilder); auto operationResult = operation.setRecipients(recipients) - ->setTransparentKeyChange(modelTransaction.getPossibleKeyChange()) + ->setTransparentKeyChange(modelTransaction->getPossibleKeyChange()) ->setSelectTransparentCoins(fromTransparent) ->setSelectShieldedCoins(!fromTransparent) ->build(); @@ -557,7 +557,7 @@ OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction& } // load the transaction and key change (if needed) - modelTransaction.setTransaction(new CWalletTx(wallet, operation.getFinalTx())); + modelTransaction->setTransaction(new CWalletTx(wallet, operation.getFinalTx())); return operationResult; } diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 37c13eab9ac3..96f76b25fd1a 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -200,13 +200,13 @@ class WalletModel : public QObject const CWalletTx* getTx(uint256 id); // prepare transaction for getting txfee before sending coins - SendCoinsReturn prepareTransaction(WalletModelTransaction& transaction, const CCoinControl* coinControl = NULL, bool fIncludeDelegations = true); + SendCoinsReturn prepareTransaction(WalletModelTransaction* transaction, const CCoinControl* coinControl = NULL, bool fIncludeDelegations = true); // Send coins to a list of recipients SendCoinsReturn sendCoins(WalletModelTransaction& transaction); // Prepare shielded transaction. - OperationResult PrepareShieldedTransaction(WalletModelTransaction& modelTransaction, bool fromTransparent); + OperationResult PrepareShieldedTransaction(WalletModelTransaction* modelTransaction, bool fromTransparent); // Wallet encryption bool setWalletEncrypted(bool encrypted, const SecureString& passphrase); diff --git a/src/qt/walletmodeltransaction.h b/src/qt/walletmodeltransaction.h index fc7f56b6a700..b8bb7fc1710b 100644 --- a/src/qt/walletmodeltransaction.h +++ b/src/qt/walletmodeltransaction.h @@ -37,6 +37,9 @@ class WalletModelTransaction void setTransaction(CWalletTx* tx); + // Whether should create a +v2 tx or go simple and create a v1. + bool useV2{false}; + private: const QList recipients; CWalletTx* walletTransaction{nullptr}; From 4cbfd0e4e0fd8b753919cb256cb696d3b4449e6e Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 11 Nov 2020 18:49:53 -0300 Subject: [PATCH 065/121] [FIXUP with send worker] send process moved properly to a worker thread fixes. --- src/qt/pivx/coldstakingwidget.cpp | 2 +- src/qt/pivx/send.cpp | 66 ++++++++++++++++--------------- src/qt/pivx/send.h | 8 ++-- src/qt/pivx/sendconfirmdialog.cpp | 14 +++---- src/qt/pivx/sendconfirmdialog.h | 4 +- src/qt/walletmodel.cpp | 7 +--- src/qt/walletmodeltransaction.cpp | 3 +- src/qt/walletmodeltransaction.h | 2 +- src/wallet/rpcwallet.cpp | 6 +-- src/wallet/wallet.cpp | 20 +++++----- src/wallet/wallet.h | 4 +- 11 files changed, 69 insertions(+), 67 deletions(-) diff --git a/src/qt/pivx/coldstakingwidget.cpp b/src/qt/pivx/coldstakingwidget.cpp index 8ef5b227df5f..cd5ac7d7e9ed 100644 --- a/src/qt/pivx/coldstakingwidget.cpp +++ b/src/qt/pivx/coldstakingwidget.cpp @@ -496,7 +496,7 @@ void ColdStakingWidget::onSendClicked() showHideOp(true); TxDetailDialog* dialog = new TxDetailDialog(window); dialog->setDisplayUnit(nDisplayUnit); - dialog->setData(walletModel, currentTransaction); + dialog->setData(walletModel, ¤tTransaction); dialog->adjustSize(); openDialogWithOpaqueBackgroundY(dialog, window, 3, 5); diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index 7348d38a4b02..d21ce65713d2 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -364,17 +364,37 @@ void SendWidget::onSendClicked() } // If tx exists then there is an on-going process being executed, return. - if (ptrModelTx) { + if (isProcessing || ptrModelTx) { inform(tr("On going process being executed, please wait until it's finished to create a new transaction")); return; } - ptrModelTx = std::make_shared(WalletModelTransaction(recipients)); + ptrModelTx = new WalletModelTransaction(recipients); ptrModelTx->useV2 = hasShieldedOutput || !isTransparent; + // First prepare tx window->showHide(true); LoadingDialog *dialog = new LoadingDialog(window, tr("Preparing transaction")); dialog->execute(this, REQUEST_PREPARE_TX, std::move(ptrUnlockedContext)); openDialogWithOpaqueBackgroundFullScreen(dialog, window); + + // If all went well, ask if want to broadcast it + if (processingResult) { + if (sendFinalStep()) { + updateEntryLabels(ptrModelTx->getRecipients()); + } + setFocusOnLastEntry(); + } else if (!processingResultError->isEmpty()){ + inform(*processingResultError); + } + + // Process finished, can reset the tx model now. todo: this can get wrapped on a cached struct. + delete ptrModelTx; + ptrModelTx = nullptr; + if (processingResultError) { + processingResultError->clear(); + processingResultError = nullopt; + } + processingResult = false; } OperationResult SendWidget::prepareShielded(WalletModelTransaction* currentTransaction, bool fromTransparent) @@ -388,6 +408,7 @@ OperationResult SendWidget::prepareShielded(WalletModelTransaction* currentTrans OperationResult SendWidget::prepareTransparent(WalletModelTransaction* currentTransaction) { + if (!walletModel) return errorOut("Error, no wallet model loaded"); // prepare transaction for getting txFee earlier WalletModel::SendCoinsReturn prepareStatus; prepareStatus = walletModel->prepareTransaction(currentTransaction, coinControlDialog->coinControl, fDelegationsChecked); @@ -414,17 +435,17 @@ OperationResult SendWidget::prepareTransparent(WalletModelTransaction* currentTr return OperationResult(true); } -bool SendWidget::sendFinalStep(WalletModelTransaction& currentTransaction) +bool SendWidget::sendFinalStep() { showHideOp(true); - const bool fStakeDelegationVoided = currentTransaction.getTransaction()->fStakeDelegationVoided; + const bool fStakeDelegationVoided = ptrModelTx->getTransaction()->fStakeDelegationVoided; QString warningStr = QString(); if (fStakeDelegationVoided) warningStr = tr("WARNING:\nTransaction spends a cold-stake delegation, voiding it.\n" "These coins will no longer be cold-staked."); TxDetailDialog* dialog = new TxDetailDialog(window, true, warningStr); dialog->setDisplayUnit(walletModel->getOptionsModel()->getDisplayUnit()); - dialog->setData(walletModel, currentTransaction); + dialog->setData(walletModel, ptrModelTx); dialog->adjustSize(); openDialogWithOpaqueBackgroundY(dialog, window, 3, 5); @@ -453,48 +474,31 @@ bool SendWidget::sendFinalStep(WalletModelTransaction& currentTransaction) return true; } -void SendWidget::resetSendProcess(QString informError) -{ - ptrModelTx.reset(); - isProcessing = false; - - if (!informError.isEmpty()) { - inform(informError); - } -} - -void SendWidget::txBroadcasted() -{ - // broadcast the tx now and update labels. - if (sendFinalStep(*ptrModelTx)) { - updateEntryLabels(ptrModelTx->getRecipients()); - } - setFocusOnLastEntry(); - resetSendProcess(QString()); -} - void SendWidget::run(int type) { + assert(!processingResult); if (type == REQUEST_PREPARE_TX) { if (!isProcessing) { isProcessing = true; OperationResult result(false); if ((result = ptrModelTx->useV2 ? - prepareShielded(ptrModelTx.get(), isTransparent) : - prepareTransparent(ptrModelTx.get()) + prepareShielded(ptrModelTx, isTransparent) : + prepareTransparent(ptrModelTx) )) { - QMetaObject::invokeMethod(this, "txBroadcasted", Qt::QueuedConnection); + processingResult = true; } else { - // error, reset state - QMetaObject::invokeMethod(this, "resetSendProcess", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(result.getError()))); + processingResult = false; + processingResultError = QString::fromStdString(result.getError()); } + isProcessing = false; } } } void SendWidget::onError(QString error, int type) { - QMetaObject::invokeMethod(this, "resetSendProcess", Qt::QueuedConnection, Q_ARG(QString, error)); + isProcessing = false; + processingResultError = error; } QString SendWidget::recipientsToString(QList recipients) diff --git a/src/qt/pivx/send.h b/src/qt/pivx/send.h index a25c5ef35431..189fea6fba5f 100644 --- a/src/qt/pivx/send.h +++ b/src/qt/pivx/send.h @@ -77,8 +77,6 @@ private Q_SLOTS: void onDeleteClicked(); void onResetCustomOptions(bool fRefreshAmounts); void onResetSettings(); - void txBroadcasted(); - void resetSendProcess(QString informError); private: Ui::send *ui; @@ -95,8 +93,10 @@ private Q_SLOTS: CoinControlDialog *coinControlDialog = nullptr; // Cached tx - std::shared_ptr ptrModelTx; + WalletModelTransaction* ptrModelTx{nullptr}; std::atomic isProcessing{false}; + Optional processingResultError{nullopt}; + std::atomic processingResult{false}; ContactsDropdown *menuContacts = nullptr; TooltipMenu *menu = nullptr; @@ -109,7 +109,7 @@ private Q_SLOTS: SendMultiRow* createEntry(); OperationResult prepareShielded(WalletModelTransaction* tx, bool fromTransparent); OperationResult prepareTransparent(WalletModelTransaction* tx); - bool sendFinalStep(WalletModelTransaction& currentTransaction); + bool sendFinalStep(); void setFocusOnLastEntry(); void showHideCheckBoxDelegations(); void updateEntryLabels(QList recipients); diff --git a/src/qt/pivx/sendconfirmdialog.cpp b/src/qt/pivx/sendconfirmdialog.cpp index 2acc322d7053..ea90c77e40f9 100644 --- a/src/qt/pivx/sendconfirmdialog.cpp +++ b/src/qt/pivx/sendconfirmdialog.cpp @@ -129,17 +129,17 @@ QString formatAdressToShow(const QString& address) return addressToShow; } -void TxDetailDialog::setData(WalletModel *model, WalletModelTransaction &tx) +void TxDetailDialog::setData(WalletModel *model, WalletModelTransaction* _tx) { this->model = model; - this->tx = &tx; - CAmount txFee = tx.getTransactionFee(); - CAmount totalAmount = tx.getTotalTransactionAmount() + txFee; + this->tx = _tx; + CAmount txFee = tx->getTransactionFee(); + CAmount totalAmount = tx->getTotalTransactionAmount() + txFee; ui->textAmount->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, totalAmount, false, BitcoinUnits::separatorAlways) + " (Fee included)"); - int nRecipients = tx.getRecipients().size(); + int nRecipients = tx->getRecipients().size(); if (nRecipients == 1) { - const SendCoinsRecipient& recipient = tx.getRecipients().at(0); + const SendCoinsRecipient& recipient = tx->getRecipients().at(0); if (recipient.isP2CS) { ui->labelSend->setText(tr("Delegating to")); } @@ -158,7 +158,7 @@ void TxDetailDialog::setData(WalletModel *model, WalletModelTransaction &tx) ui->textSendLabel->setText(QString::number(nRecipients) + " recipients"); ui->textSend->setVisible(false); } - ui->textInputs->setText(QString::number(tx.getTransaction()->vin.size())); + ui->textInputs->setText(QString::number(tx->getTransaction()->vin.size())); ui->textFee->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, txFee, false, BitcoinUnits::separatorAlways)); } diff --git a/src/qt/pivx/sendconfirmdialog.h b/src/qt/pivx/sendconfirmdialog.h index c1f00419c8d7..01dab52fd5c6 100644 --- a/src/qt/pivx/sendconfirmdialog.h +++ b/src/qt/pivx/sendconfirmdialog.h @@ -31,7 +31,7 @@ class TxDetailDialog : public FocusedDialog bool isConfirm() { return this->confirm;} WalletModel::SendCoinsReturn getStatus() { return this->sendStatus;} - void setData(WalletModel *model, WalletModelTransaction& tx); + void setData(WalletModel *model, WalletModelTransaction* tx); void setData(WalletModel *model, const QModelIndex &index); void setDisplayUnit(int unit){this->nDisplayUnit = unit;}; @@ -49,7 +49,7 @@ public Q_SLOTS: bool confirm = false; WalletModel *model = nullptr; WalletModel::SendCoinsReturn sendStatus; - WalletModelTransaction *tx = nullptr; + WalletModelTransaction* tx; uint256 txHash; bool inputsLoaded = false; diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index a600d26312a2..eeddf6617465 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -406,16 +406,13 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact { LOCK2(cs_main, wallet->cs_wallet); - transaction->newPossibleKeyChange(wallet); + CReserveKey* keyChange = transaction->newPossibleKeyChange(wallet); CAmount nFeeRequired = 0; int nChangePosInOut = -1; std::string strFailReason; - CWalletTx* newTx = transaction->getTransaction(); - CReserveKey* keyChange = transaction->getPossibleKeyChange(); - bool fCreated = wallet->CreateTransaction(vecSend, - *newTx, + transaction->getTransaction(), *keyChange, nFeeRequired, nChangePosInOut, diff --git a/src/qt/walletmodeltransaction.cpp b/src/qt/walletmodeltransaction.cpp index 87e8edfdd16a..610c4ef0029b 100644 --- a/src/qt/walletmodeltransaction.cpp +++ b/src/qt/walletmodeltransaction.cpp @@ -60,9 +60,10 @@ CAmount WalletModelTransaction::getTotalTransactionAmount() return totalTransactionAmount; } -void WalletModelTransaction::newPossibleKeyChange(CWallet* wallet) +CReserveKey* WalletModelTransaction::newPossibleKeyChange(CWallet* wallet) { keyChange = new CReserveKey(wallet); + return keyChange; } CReserveKey* WalletModelTransaction::getPossibleKeyChange() diff --git a/src/qt/walletmodeltransaction.h b/src/qt/walletmodeltransaction.h index b8bb7fc1710b..e9141f14efac 100644 --- a/src/qt/walletmodeltransaction.h +++ b/src/qt/walletmodeltransaction.h @@ -32,7 +32,7 @@ class WalletModelTransaction CAmount getTotalTransactionAmount(); - void newPossibleKeyChange(CWallet* wallet); + CReserveKey* newPossibleKeyChange(CWallet* wallet); CReserveKey* getPossibleKeyChange(); void setTransaction(CWalletTx* tx); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 1191d3d9641e..951a1ebacf13 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -921,7 +921,7 @@ void SendMoney(const CTxDestination& address, CAmount nValue, CWalletTx& wtxNew) // Create and send the transaction CReserveKey reservekey(pwalletMain); CAmount nFeeRequired; - if (!pwalletMain->CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired, strError, nullptr, ALL_COINS, (CAmount)0)) { + if (!pwalletMain->CreateTransaction(scriptPubKey, nValue, &wtxNew, reservekey, nFeeRequired, strError, nullptr, ALL_COINS, (CAmount)0)) { if (nValue + nFeeRequired > pwalletMain->GetAvailableBalance()) strError = strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired)); LogPrintf("SendMoney() : %s\n", strError); @@ -1063,7 +1063,7 @@ UniValue CreateColdStakeDelegation(const UniValue& params, CWalletTx& wtxNew, CR // Delegate transparent coins CAmount nFeeRequired; CScript scriptPubKey = GetScriptForStakeDelegation(*stakeKey, ownerKey); - if (!pwalletMain->CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired, strError, nullptr, ALL_COINS, (CAmount)0, fUseDelegated)) { + if (!pwalletMain->CreateTransaction(scriptPubKey, nValue, &wtxNew, reservekey, nFeeRequired, strError, nullptr, ALL_COINS, (CAmount)0, fUseDelegated)) { if (nValue + nFeeRequired > currBalance) strError = strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired)); LogPrintf("%s : %s\n", __func__, strError); @@ -2059,7 +2059,7 @@ UniValue sendmany(const JSONRPCRequest& request) CAmount nFeeRequired = 0; std::string strFailReason; int nChangePosInOut = -1; - bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosInOut, strFailReason); + bool fCreated = pwalletMain->CreateTransaction(vecSend, &wtx, keyChange, nFeeRequired, nChangePosInOut, strFailReason); if (!fCreated) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); const CWallet::CommitResult& res = pwalletMain->CommitTransaction(wtx, keyChange, g_connman.get()); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 5366b5467d8c..05385177475a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2580,7 +2580,7 @@ bool CWallet::CreateBudgetFeeTX(CWalletTx& tx, const uint256& hash, CReserveKey& CCoinControl* coinControl = nullptr; int nChangePosInOut = -1; - bool success = CreateTransaction(vecSend, tx, keyChange, nFeeRet, nChangePosInOut, strFail, coinControl, ALL_COINS, true, (CAmount)0); + bool success = CreateTransaction(vecSend, &tx, keyChange, nFeeRet, nChangePosInOut, strFail, coinControl, ALL_COINS, true, (CAmount)0); if (!success) { LogPrintf("%s: Error - %s\n", __func__, strFail); return false; @@ -2611,7 +2611,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool ov CReserveKey reservekey(this); CWalletTx wtx; - if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosInOut, strFailReason, &coinControl, ALL_COINS, false)) + if (!CreateTransaction(vecSend, &wtx, reservekey, nFeeRet, nChangePosInOut, strFailReason, &coinControl, ALL_COINS, false)) return false; if (nChangePosInOut != -1) @@ -2633,7 +2633,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool ov } bool CWallet::CreateTransaction(const std::vector& vecSend, - CWalletTx& wtxNew, + CWalletTx* wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, @@ -2659,8 +2659,8 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, return false; } - wtxNew.fTimeReceivedIsTxTime = true; - wtxNew.BindWallet(this); + wtxNew->fTimeReceivedIsTxTime = true; + wtxNew->BindWallet(this); CMutableTransaction txNew; CScript scriptChange; @@ -2680,7 +2680,7 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, nChangePosInOut = nChangePosRequest; txNew.vin.clear(); txNew.vout.clear(); - wtxNew.fFromMe = true; + wtxNew->fFromMe = true; CAmount nTotalValue = nValue + nFeeRet; @@ -2728,7 +2728,7 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, for (std::pair pcoin : setCoins) { if(pcoin.first->vout[pcoin.second].scriptPubKey.IsPayToColdStaking()) - wtxNew.fStakeDelegationVoided = true; + wtxNew->fStakeDelegationVoided = true; //The coin age after the next block (depth+1) is used instead of the current, //reflecting an assumption the user would accept a bit more delay for //a chance at a free transaction. @@ -2850,7 +2850,7 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, } // Embed the constructed transaction data in wtxNew. - *static_cast(&wtxNew) = CTransaction(txNew); + *static_cast(wtxNew) = CTransaction(txNew); // Limit size if (nBytes >= MAX_STANDARD_TX_SIZE) { @@ -2890,7 +2890,7 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, return true; } -bool CWallet::CreateTransaction(CScript scriptPubKey, const CAmount& nValue, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl, AvailableCoinsType coin_type, CAmount nFeePay, bool fIncludeDelegated) +bool CWallet::CreateTransaction(CScript scriptPubKey, const CAmount& nValue, CWalletTx* wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl, AvailableCoinsType coin_type, CAmount nFeePay, bool fIncludeDelegated) { std::vector vecSend; vecSend.emplace_back(scriptPubKey, nValue, false); @@ -3771,7 +3771,7 @@ void CWallet::AutoCombineDust(CConnman* connman) // 10% safety margin to avoid "Insufficient funds" errors vecSend[0].nAmount = nTotalRewardsValue - (nTotalRewardsValue / 10); - if (!CreateTransaction(vecSend, wtx, keyChange, nFeeRet, nChangePosInOut, strErr, coinControl, ALL_COINS, true, false, CAmount(0))) { + if (!CreateTransaction(vecSend, &wtx, keyChange, nFeeRet, nChangePosInOut, strErr, coinControl, ALL_COINS, true, false, CAmount(0))) { LogPrintf("AutoCombineDust createtransaction failed, reason: %s\n", strErr); continue; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index d8419e79a9c6..84328b485945 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -640,7 +640,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface * @note passing nChangePosInOut as -1 will result in setting a random position */ bool CreateTransaction(const std::vector& vecSend, - CWalletTx& wtxNew, + CWalletTx* wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, @@ -650,7 +650,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool sign = true, CAmount nFeePay = 0, bool fIncludeDelegated = false); - bool CreateTransaction(CScript scriptPubKey, const CAmount& nValue, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl = NULL, AvailableCoinsType coin_type = ALL_COINS, CAmount nFeePay = 0, bool fIncludeDelegated = false); + bool CreateTransaction(CScript scriptPubKey, const CAmount& nValue, CWalletTx* wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl = NULL, AvailableCoinsType coin_type = ALL_COINS, CAmount nFeePay = 0, bool fIncludeDelegated = false); // enumeration for CommitResult (return status of CommitTransaction) enum CommitStatus From 3ee23f84830ac63e459b507a5a8fb1034f4c5427 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 12 Nov 2020 11:44:20 -0300 Subject: [PATCH 066/121] Move-only: Standard namespace with general CWDestination variant and encode/decode destination functions moved to their own file. --- CMakeLists.txt | 1 + src/Makefile.am | 2 ++ src/destination_io.cpp | 58 +++++++++++++++++++++++++++++++++++++++++ src/destination_io.h | 25 ++++++++++++++++++ src/script/ismine.h | 1 + src/script/standard.cpp | 51 ------------------------------------ src/script/standard.h | 15 ----------- src/wallet/wallet.h | 1 + 8 files changed, 88 insertions(+), 66 deletions(-) create mode 100644 src/destination_io.cpp create mode 100644 src/destination_io.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5851f8b910d1..3cd0335801ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -455,6 +455,7 @@ set(SAPLING_SOURCES ./src/bech32.cpp ./src/sapling/sapling_util.cpp ./src/sapling/key_io_sapling.cpp + ./src/destination_io.cpp ./src/sapling/sapling_core_write.cpp ./src/sapling/prf.cpp ./src/sapling/noteencryption.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 4b84cc44650c..6f6506a808d1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -287,6 +287,7 @@ BITCOIN_CORE_H = \ wallet/hdchain.h \ wallet/rpcwallet.h \ wallet/scriptpubkeyman.h \ + destination_io.h \ wallet/wallet.h \ wallet/walletdb.h \ zpivchain.h \ @@ -401,6 +402,7 @@ libbitcoin_wallet_a_SOURCES = \ wallet/rpczpiv.cpp \ wallet/hdchain.cpp \ wallet/scriptpubkeyman.cpp \ + destination_io.cpp \ wallet/wallet.cpp \ wallet/wallet_zerocoin.cpp \ wallet/walletdb.cpp \ diff --git a/src/destination_io.cpp b/src/destination_io.cpp new file mode 100644 index 000000000000..e5e1f53fb6f6 --- /dev/null +++ b/src/destination_io.cpp @@ -0,0 +1,58 @@ +// Copyright (c) 2020 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#include "destination_io.h" +#include "base58.h" +#include "sapling/key_io_sapling.h" + +namespace Standard { + + std::string EncodeDestination(const CWDestination &address, const CChainParams::Base58Type addrType) { + const CTxDestination *dest = boost::get(&address); + if (!dest) { + return KeyIO::EncodePaymentAddress(*boost::get(&address)); + } + return EncodeDestination(*dest, addrType); + }; + + CWDestination DecodeDestination(const std::string& strAddress) + { + bool isStaking = false; + return DecodeDestination(strAddress, isStaking); + } + + CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking) + { + bool isShielded = false; + return DecodeDestination(strAddress, isStaking, isShielded); + } + + // agregar isShielded + CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking, bool& isShielded) + { + CWDestination dest; + CTxDestination regDest = ::DecodeDestination(strAddress, isStaking); + if (!IsValidDestination(regDest)) { + const auto sapDest = KeyIO::DecodeSaplingPaymentAddress(strAddress); + if (sapDest) { + isShielded = true; + return *sapDest; + } + } + return regDest; + + } + + bool IsValidDestination(const CWDestination& address) + { + // Only regular base58 addresses and shielded addresses accepted here for now + const libzcash::SaplingPaymentAddress *dest1 = boost::get(&address); + if (dest1) return true; + + const CTxDestination *dest = boost::get(&address); + return dest && ::IsValidDestination(*dest); + } + +} // End Standard namespace + diff --git a/src/destination_io.h b/src/destination_io.h new file mode 100644 index 000000000000..5973f93e6f79 --- /dev/null +++ b/src/destination_io.h @@ -0,0 +1,25 @@ +// Copyright (c) 2020 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#ifndef DESTINATION_IO_H +#define DESTINATION_IO_H + +#include "script/standard.h" + +// Regular + shielded addresses variant. +typedef boost::variant CWDestination; + +namespace Standard { + + std::string EncodeDestination(const CWDestination &address, const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS); + + CWDestination DecodeDestination(const std::string& strAddress); + CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking); + CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking, bool& isShielded); + + bool IsValidDestination(const CWDestination& dest); + +} // End Standard namespace + +#endif //DESTINATION_IO_H diff --git a/src/script/ismine.h b/src/script/ismine.h index a81acf354514..403a7d510a4c 100644 --- a/src/script/ismine.h +++ b/src/script/ismine.h @@ -7,6 +7,7 @@ #ifndef BITCOIN_SCRIPT_ISMINE_H #define BITCOIN_SCRIPT_ISMINE_H +#include "destination_io.h" #include "key.h" #include "script/standard.h" #include diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 1a33439cea64..6b46e7812c34 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -6,9 +6,7 @@ #include "script/standard.h" -#include "base58.h" #include "pubkey.h" -#include "sapling/key_io_sapling.h" #include "script/script.h" typedef std::vector valtype; @@ -340,52 +338,3 @@ bool IsValidDestination(const CTxDestination& dest) { return dest.which() != 0; } -namespace Standard { - - std::string EncodeDestination(const CWDestination &address, const CChainParams::Base58Type addrType) { - const CTxDestination *dest = boost::get(&address); - if (!dest) { - return KeyIO::EncodePaymentAddress(*boost::get(&address)); - } - return EncodeDestination(*dest, addrType); - }; - - CWDestination DecodeDestination(const std::string& strAddress) - { - bool isStaking = false; - return DecodeDestination(strAddress, isStaking); - } - - CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking) - { - bool isShielded = false; - return DecodeDestination(strAddress, isStaking, isShielded); - } - - // agregar isShielded - CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking, bool& isShielded) - { - CWDestination dest; - CTxDestination regDest = ::DecodeDestination(strAddress, isStaking); - if (!IsValidDestination(regDest)) { - const auto sapDest = KeyIO::DecodeSaplingPaymentAddress(strAddress); - if (sapDest) { - isShielded = true; - return *sapDest; - } - } - return regDest; - - } - - bool IsValidDestination(const CWDestination& address) - { - // Only regular base58 addresses and shielded addresses accepted here for now - const libzcash::SaplingPaymentAddress *dest1 = boost::get(&address); - if (dest1) return true; - - const CTxDestination *dest = boost::get(&address); - return dest && ::IsValidDestination(*dest); - } - -} // End Standard namespace diff --git a/src/script/standard.h b/src/script/standard.h index 49d67c08ef14..a99470b606cf 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -71,9 +71,6 @@ class CNoDestination { */ typedef boost::variant CTxDestination; -// Regular + shielded addresses variant. -typedef boost::variant CWDestination; - /** Check whether a CTxDestination is a CNoDestination. */ bool IsValidDestination(const CTxDestination& dest); @@ -90,16 +87,4 @@ CScript GetScriptForMultisig(int nRequired, const std::vector& keys); CScript GetScriptForStakeDelegation(const CKeyID& stakingKey, const CKeyID& spendingKey); CScript GetScriptForOpReturn(const uint256& message); -namespace Standard { - -std::string EncodeDestination(const CWDestination &address, const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS); - -CWDestination DecodeDestination(const std::string& strAddress); -CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking); -CWDestination DecodeDestination(const std::string& strAddress, bool& isStaking, bool& isShielded); - -bool IsValidDestination(const CWDestination& dest); - -} // End Standard namespace - #endif // BITCOIN_SCRIPT_STANDARD_H diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 84328b485945..165585b39a66 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -14,6 +14,7 @@ #include "consensus/tx_verify.h" #include "consensus/validation.h" #include "crypter.h" +#include "destination_io.h" #include "kernel.h" #include "key.h" #include "keystore.h" From 89fcc2d767fc8ed8ffd4a78823d598b2217156ad Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 13 Nov 2020 21:33:19 -0300 Subject: [PATCH 067/121] wallet: GetKeyCreationTime for CWDestination method implemented. --- src/destination_io.cpp | 10 ++++++++++ src/destination_io.h | 4 ++++ src/wallet/wallet.cpp | 7 +++++++ src/wallet/wallet.h | 1 + 4 files changed, 22 insertions(+) diff --git a/src/destination_io.cpp b/src/destination_io.cpp index e5e1f53fb6f6..9e7c538864f3 100644 --- a/src/destination_io.cpp +++ b/src/destination_io.cpp @@ -54,5 +54,15 @@ namespace Standard { return dest && ::IsValidDestination(*dest); } + const libzcash::SaplingPaymentAddress* GetShieldedDestination(const CWDestination& dest) + { + return boost::get(&dest); + } + + const CTxDestination* GetTransparentDestination(const CWDestination& dest) + { + return boost::get(&dest); + } + } // End Standard namespace diff --git a/src/destination_io.h b/src/destination_io.h index 5973f93e6f79..938e693b57e6 100644 --- a/src/destination_io.h +++ b/src/destination_io.h @@ -20,6 +20,10 @@ namespace Standard { bool IsValidDestination(const CWDestination& dest); + // boost::get wrapper + const libzcash::SaplingPaymentAddress* GetShieldedDestination(const CWDestination& dest); + const CTxDestination * GetTransparentDestination(const CWDestination& dest); + } // End Standard namespace #endif //DESTINATION_IO_H diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 05385177475a..a3a0e38a9f0c 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -150,6 +150,13 @@ PairResult CWallet::getNewAddress(CTxDestination& ret, const std::string address return PairResult(true); } +int64_t CWallet::GetKeyCreationTime(const CWDestination& dest) +{ + auto shieldDest = Standard::GetShieldedDestination(dest); + auto transpDest = Standard::GetTransparentDestination(dest); + return shieldDest ? GetKeyCreationTime(*shieldDest) : transpDest ? GetKeyCreationTime(*transpDest) : 0; +} + int64_t CWallet::GetKeyCreationTime(CPubKey pubkey) { return mapKeyMetadata[pubkey.GetID()].nCreateTime; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 165585b39a66..c67fd8e9039c 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -509,6 +509,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface const CChainParams::Base58Type addrType = CChainParams::PUBKEY_ADDRESS); PairResult getNewAddress(CTxDestination& ret, std::string label); PairResult getNewStakingAddress(CTxDestination& ret, std::string label); + int64_t GetKeyCreationTime(const CWDestination& dest); int64_t GetKeyCreationTime(CPubKey pubkey); int64_t GetKeyCreationTime(const CTxDestination& address); int64_t GetKeyCreationTime(const libzcash::SaplingPaymentAddress& address); From 98652a6f588a91ce2d2898e85960509b3d6db7b7 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 13 Nov 2020 21:33:56 -0300 Subject: [PATCH 068/121] GUI: wallet's shielded address creation time connected. --- src/addressbook.cpp | 4 ++++ src/addressbook.h | 1 + src/qt/addresstablemodel.cpp | 11 ++++++----- src/qt/pivx/addressholder.cpp | 3 +++ src/qt/pivx/receivewidget.cpp | 2 +- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/addressbook.cpp b/src/addressbook.cpp index e8f2755cc36f..3ca67d9604eb 100644 --- a/src/addressbook.cpp +++ b/src/addressbook.cpp @@ -41,6 +41,10 @@ namespace AddressBook { return purpose == AddressBookPurpose::RECEIVE; } + bool CAddressBookData::isShieldedReceivePurpose() const { + return purpose == AddressBookPurpose::SHIELDED_RECEIVE; + } + bool CAddressBookData::isShielded() const { return IsShieldedPurpose(purpose); } diff --git a/src/addressbook.h b/src/addressbook.h index 42b1e549f413..d1c927bdc0cf 100644 --- a/src/addressbook.h +++ b/src/addressbook.h @@ -42,6 +42,7 @@ namespace AddressBook { bool isSendColdStakingPurpose() const; bool isSendPurpose() const; bool isReceivePurpose() const; + bool isShieldedReceivePurpose() const; bool isShielded() const; }; diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 2c60af9020cd..d1afb1d5ca5b 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -149,9 +149,8 @@ class AddressTablePriv bool fMine = IsMine(*wallet, dest); QString addressStr = QString::fromStdString(Standard::EncodeDestination(dest, addrType)); uint creationTime = 0; - if (addrBookData.isReceivePurpose()) { - const auto& address = *boost::get(&dest); - creationTime = static_cast(wallet->GetKeyCreationTime(address)); + if (addrBookData.isReceivePurpose() || addrBookData.isShieldedReceivePurpose()) { + creationTime = static_cast(wallet->GetKeyCreationTime(dest)); } AddressTableEntry::Type addressType = translateTransactionType( @@ -217,8 +216,10 @@ class AddressTablePriv uint creationTime = 0; std::string stdPurpose = purpose.toStdString(); - if (stdPurpose == AddressBook::AddressBookPurpose::RECEIVE) - creationTime = static_cast(wallet->GetKeyCreationTime(DecodeDestination(address.toStdString()))); + if (stdPurpose == AddressBook::AddressBookPurpose::RECEIVE || + stdPurpose == AddressBook::AddressBookPurpose::SHIELDED_RECEIVE) { + creationTime = static_cast(wallet->GetKeyCreationTime(Standard::DecodeDestination(address.toStdString()))); + } updatePurposeCachedCounted(stdPurpose, true); diff --git a/src/qt/pivx/addressholder.cpp b/src/qt/pivx/addressholder.cpp index 9bd168c4e480..71b08fc55ff3 100644 --- a/src/qt/pivx/addressholder.cpp +++ b/src/qt/pivx/addressholder.cpp @@ -8,6 +8,9 @@ void AddressHolder::init(QWidget* holder,const QModelIndex &index, bool isHovered, bool isSelected) const { MyAddressRow *row = static_cast(holder); QString address = index.data(Qt::DisplayRole).toString(); + if (index.data(AddressTableModel::TypeRole).toString() == AddressTableModel::ShieldedReceive) { + address = address.left(22) + "..." + address.right(22); + } QString label = index.sibling(index.row(), AddressTableModel::Label).data(Qt::DisplayRole).toString(); uint time = index.sibling(index.row(), AddressTableModel::Date).data(Qt::DisplayRole).toUInt(); QString date = (time == 0) ? "" : GUIUtil::dateTimeStr(QDateTime::fromTime_t(time)); diff --git a/src/qt/pivx/receivewidget.cpp b/src/qt/pivx/receivewidget.cpp index 07f417b297f5..c52a56351b45 100644 --- a/src/qt/pivx/receivewidget.cpp +++ b/src/qt/pivx/receivewidget.cpp @@ -163,7 +163,7 @@ void ReceiveWidget::refreshView(QString refreshAddress) QString addressToShow = latestAddress; int64_t time = walletModel->getKeyCreationTime(latestAddress.toStdString()); if (shieldedMode) { - addressToShow = addressToShow.left(20) + "..." + addressToShow.right(20); + addressToShow = addressToShow.left(20) + "..." + addressToShow.right(19); } ui->labelAddress->setText(addressToShow); From 610c83ed7885a928786386f02e065693d22902de Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 14 Nov 2020 00:42:40 -0300 Subject: [PATCH 069/121] GUI: receive widget, renamed public to transparent. --- src/qt/pivx/forms/receivewidget.ui | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qt/pivx/forms/receivewidget.ui b/src/qt/pivx/forms/receivewidget.ui index fed5bc931156..3bebe2f78baf 100644 --- a/src/qt/pivx/forms/receivewidget.ui +++ b/src/qt/pivx/forms/receivewidget.ui @@ -6,7 +6,7 @@ 0 0 - 629 + 819 629 @@ -154,7 +154,7 @@ Qt::NoFocus - Public + Transparent true @@ -198,10 +198,10 @@ - + - Accept public PIV or shielded PIV + Accept transparent or shielded PIV Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter From 8ad9dee56fed1148dcb1fbb9accb329ca29e22f5 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 14 Nov 2020 13:32:44 -0300 Subject: [PATCH 070/121] GUI: tx detail dialog, connecting shielded outputs. --- src/qt/pivx/sendconfirmdialog.cpp | 74 +++++++++++++++++++++---------- src/qt/pivx/sendconfirmdialog.h | 2 +- 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/src/qt/pivx/sendconfirmdialog.cpp b/src/qt/pivx/sendconfirmdialog.cpp index ea90c77e40f9..0fcf49d1a6b4 100644 --- a/src/qt/pivx/sendconfirmdialog.cpp +++ b/src/qt/pivx/sendconfirmdialog.cpp @@ -78,9 +78,9 @@ TxDetailDialog::TxDetailDialog(QWidget *parent, bool _isConfirmDialog, const QSt connect(ui->pushOutputs, &QPushButton::clicked, this, &TxDetailDialog::onOutputsClicked); } -void TxDetailDialog::setData(WalletModel *model, const QModelIndex &index) +void TxDetailDialog::setData(WalletModel *_model, const QModelIndex &index) { - this->model = model; + this->model = _model; TransactionRecord *rec = static_cast(index.internalPointer()); QDateTime date = index.data(TransactionTableModel::DateRole).toDateTime(); QString address = index.data(Qt::DisplayRole).toString(); @@ -88,20 +88,21 @@ void TxDetailDialog::setData(WalletModel *model, const QModelIndex &index) QString amountText = BitcoinUnits::formatWithUnit(nDisplayUnit, amount, true, BitcoinUnits::separatorAlways); ui->textAmount->setText(amountText); - const CWalletTx* tx = model->getTx(rec->hash); - if (tx) { + const CWalletTx* _tx = model->getTx(rec->hash); + if (_tx) { this->txHash = rec->hash; - QString hash = QString::fromStdString(tx->GetHash().GetHex()); + QString hash = QString::fromStdString(_tx->GetHash().GetHex()); ui->textId->setText(hash.left(20) + "..." + hash.right(20)); ui->textId->setTextInteractionFlags(Qt::TextSelectableByMouse); - if (tx->vout.size() == 1) { + if (_tx->vout.size() == 1) { ui->textSendLabel->setText((address.size() < 40) ? address : address.left(20) + "..." + address.right(20)); } else { - ui->textSendLabel->setText(QString::number(tx->vout.size()) + " recipients"); + ui->textSendLabel->setText(QString::number(_tx->vout.size() + + (_tx->sapData ? _tx->sapData->vShieldedOutput.size() : 0)) + " recipients"); } ui->textSend->setVisible(false); - ui->textInputs->setText(QString::number(tx->vin.size())); + ui->textInputs->setText(QString::number(_tx->vin.size())); ui->textConfirmations->setText(QString::number(rec->status.depth)); ui->textDate->setText(GUIUtil::dateTimeStrWithSeconds(date)); ui->textStatus->setText(QString::fromStdString(rec->statusToString())); @@ -129,9 +130,9 @@ QString formatAdressToShow(const QString& address) return addressToShow; } -void TxDetailDialog::setData(WalletModel *model, WalletModelTransaction* _tx) +void TxDetailDialog::setData(WalletModel *_model, WalletModelTransaction* _tx) { - this->model = model; + this->model = _model; this->tx = _tx; CAmount txFee = tx->getTransactionFee(); CAmount totalAmount = tx->getTotalTransactionAmount() + txFee; @@ -179,11 +180,11 @@ void TxDetailDialog::onInputsClicked() ui->gridInputs->setVisible(true); if (!inputsLoaded) { inputsLoaded = true; - const CWalletTx* tx = (this->tx) ? this->tx->getTransaction() : model->getTx(this->txHash); - if (tx) { - ui->gridInputs->setMinimumHeight(50 + (50 * tx->vin.size())); + const CWalletTx* walletTx = (this->tx) ? this->tx->getTransaction() : model->getTx(this->txHash); + if (walletTx) { + ui->gridInputs->setMinimumHeight(50 + (50 * walletTx->vin.size())); int i = 1; - for (const CTxIn &in : tx->vin) { + for (const CTxIn &in : walletTx->vin) { QString hash = QString::fromStdString(in.prevout.hash.GetHex()); QLabel *label_txid = new QLabel(hash.left(18) + "..." + hash.right(18)); QLabel *label_txidn = new QLabel(QString::number(in.prevout.n)); @@ -199,6 +200,16 @@ void TxDetailDialog::onInputsClicked() } } +void appendOutput(QGridLayout* layoutGrid, int gridPosition, QString labelRes, CAmount nValue, int nDisplayUnit) +{ + QLabel *label_address = new QLabel(labelRes); + QLabel *label_value = new QLabel(BitcoinUnits::formatWithUnit(nDisplayUnit, nValue, false, BitcoinUnits::separatorAlways)); + label_value->setAlignment(Qt::AlignCenter | Qt::AlignRight); + setCssProperty({label_address, label_value}, "text-body2-dialog"); + layoutGrid->addWidget(label_address, gridPosition, 0); + layoutGrid->addWidget(label_value, gridPosition, 0); +} + void TxDetailDialog::onOutputsClicked() { if (ui->outputsScrollArea->isVisible()) { @@ -207,14 +218,14 @@ void TxDetailDialog::onOutputsClicked() ui->outputsScrollArea->setVisible(true); if (!outputsLoaded) { outputsLoaded = true; - QGridLayout* layoutGrid = new QGridLayout(); + QGridLayout* layoutGrid = new QGridLayout(this); layoutGrid->setContentsMargins(0,0,12,0); ui->container_outputs_base->setLayout(layoutGrid); - const CWalletTx* tx = (this->tx) ? this->tx->getTransaction() : model->getTx(this->txHash); - if (tx) { + const CWalletTx* walletTx = (this->tx) ? this->tx->getTransaction() : model->getTx(this->txHash); + if (walletTx) { int i = 0; - for (const CTxOut &out : tx->vout) { + for (const CTxOut &out : walletTx->vout) { QString labelRes; CTxDestination dest; bool isCsAddress = out.scriptPubKey.IsPayToColdStaking(); @@ -225,14 +236,29 @@ void TxDetailDialog::onOutputsClicked() } else { labelRes = tr("Unknown"); } - QLabel *label_address = new QLabel(labelRes); - QLabel *label_value = new QLabel(BitcoinUnits::formatWithUnit(nDisplayUnit, out.nValue, false, BitcoinUnits::separatorAlways)); - label_value->setAlignment(Qt::AlignCenter | Qt::AlignRight); - setCssProperty({label_address, label_value}, "text-body2-dialog"); - layoutGrid->addWidget(label_address,i,0); - layoutGrid->addWidget(label_value,i,0); + appendOutput(layoutGrid, i, labelRes, out.nValue, nDisplayUnit); i++; } + + // shielded recipients + if (walletTx->sapData) { + for (int j = 0; j < (int) walletTx->sapData->vShieldedOutput.size(); ++j) { + const SaplingOutPoint op(walletTx->GetHash(), j); + if (walletTx->mapSaplingNoteData.find(op) == walletTx->mapSaplingNoteData.end()) { + continue; + } + // Obtain the noteData to get the cached amount value + SaplingNoteData noteData = walletTx->mapSaplingNoteData.at(op); + Optional opAddr = + pwalletMain->GetSaplingScriptPubKeyMan()->GetShieldedAddressFrom(*walletTx, op); + + QString labelRes = QString::fromStdString(Standard::EncodeDestination(*opAddr)); + labelRes = labelRes.left(18) + "..." + labelRes.right(18); + appendOutput(layoutGrid, i, labelRes, *noteData.amount, nDisplayUnit); + + i++; + } + } } } } diff --git a/src/qt/pivx/sendconfirmdialog.h b/src/qt/pivx/sendconfirmdialog.h index 01dab52fd5c6..f11d7e7b3825 100644 --- a/src/qt/pivx/sendconfirmdialog.h +++ b/src/qt/pivx/sendconfirmdialog.h @@ -49,7 +49,7 @@ public Q_SLOTS: bool confirm = false; WalletModel *model = nullptr; WalletModel::SendCoinsReturn sendStatus; - WalletModelTransaction* tx; + WalletModelTransaction* tx{nullptr}; uint256 txHash; bool inputsLoaded = false; From 7351217f391c678e20e48375e516226b5d258620 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 17 Nov 2020 02:29:48 -0300 Subject: [PATCH 071/121] transaction rows, adding missing records types. unshield coins and change coins between shielded addresses --- src/qt/pivx/txrow.cpp | 4 ++-- src/qt/pivx/txviewholder.cpp | 8 +++++--- src/qt/transactionrecord.cpp | 30 ++++++++++++++++++++++++++---- src/qt/transactionrecord.h | 4 +++- src/qt/transactiontablemodel.cpp | 8 +++++++- 5 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/qt/pivx/txrow.cpp b/src/qt/pivx/txrow.cpp index 3db5da8b9bba..78945657337c 100644 --- a/src/qt/pivx/txrow.cpp +++ b/src/qt/pivx/txrow.cpp @@ -98,6 +98,7 @@ void TxRow::setType(bool isLightTheme, int type, bool isConfirmed) css = "text-list-amount-send"; break; case TransactionRecord::SendToSelf: + case TransactionRecord::SendToSelfShieldToShieldChangeAddress: path = "://ic-transaction-mint"; css = "text-list-amount-send"; break; @@ -123,9 +124,8 @@ void TxRow::setType(bool isLightTheme, int type, bool isConfirmed) path = "://ic-transaction-cs-contract"; css = "text-list-amount-send"; break; - - // TODO: Complete me.. case TransactionRecord::SendToSelfShieldedAddress: + case TransactionRecord::SendToSelfShieldToTransparent: path = "://ic-transaction-mint"; css = "text-list-amount-unconfirmed"; cssAmountBottom = "text-list-amount-send-small"; diff --git a/src/qt/pivx/txviewholder.cpp b/src/qt/pivx/txviewholder.cpp index c8089c63e2e7..d0aafe595da5 100644 --- a/src/qt/pivx/txviewholder.cpp +++ b/src/qt/pivx/txviewholder.cpp @@ -28,7 +28,8 @@ void TxViewHolder::init(QWidget* holder, const QModelIndex &index, bool isHovere QModelIndex indexType = rIndex.sibling(rIndex.row(),TransactionTableModel::Type); QString label = indexType.data(Qt::DisplayRole).toString(); - txRow->showHideSecondAmount(type == TransactionRecord::SendToSelfShieldedAddress); + bool hasDoubleAmount = type == TransactionRecord::SendToSelfShieldedAddress || type == TransactionRecord::SendToSelfShieldToTransparent; + txRow->showHideSecondAmount(hasDoubleAmount); if (type != TransactionRecord::ZerocoinMint && type != TransactionRecord::ZerocoinSpend_Change_zPiv && @@ -51,10 +52,11 @@ void TxViewHolder::init(QWidget* holder, const QModelIndex &index, bool isHovere txRow->setDate(date); txRow->setLabel(label); QString amountText = BitcoinUnits::formatWithUnit(nDisplayUnit, amountTop, true, BitcoinUnits::separatorAlways); - if (type == TransactionRecord::SendToSelfShieldedAddress) { + if (hasDoubleAmount) { qint64 amountBottom = rIndex.data(TransactionTableModel::ShieldedCreditAmountRole).toLongLong(); QString amountBottomText = BitcoinUnits::formatWithUnit(nDisplayUnit, amountBottom, true, BitcoinUnits::separatorAlways); - txRow->setAmount(amountBottomText + " shielded", amountText + " fee"); + txRow->setAmount(amountBottomText + (type == TransactionRecord::SendToSelfShieldedAddress ? " shielded" : ""), + amountText + " fee"); } else { txRow->setAmount(amountText, ""); } diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 606ef8077cdf..a35fdcc2d3e4 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -247,10 +247,32 @@ bool TransactionRecord::decomposeSendToSelfTransaction(const CWalletTx& wtx, con sub.address = EncodeDestination(address); } } else { - // shielded send to self. - sub.type = TransactionRecord::SendToSelfShieldedAddress; - nChange += wtx.GetShieldedChange(); - sub.shieldedCredit = wtx.GetShieldedAvailableCredit(); + // we know that all of the inputs and outputs are mine and that have shielded data. + // Let's see if only have transparent inputs, so we know that this is a + // transparent -> shield transaction + if (wtx.sapData->vShieldedSpend.empty()) { + sub.type = TransactionRecord::SendToSelfShieldedAddress; + sub.shieldedCredit = wtx.GetShieldedAvailableCredit(); + nChange += wtx.GetShieldedChange(); + } else { + // we know that the inputs are shielded now, let's see if + // if we have transparent outputs. if we have then we are converting back coins, + // from shield to transparent + if (!wtx.vout.empty()) { + sub.type = TransactionRecord::SendToSelfShieldToTransparent; + // Label for payment to self + CTxDestination address; + if (ExtractDestination(wtx.vout[0].scriptPubKey, address)) { + sub.address = EncodeDestination(address); + } + // little hack to show the correct amount + sub.shieldedCredit = wtx.GetCredit(ISMINE_SPENDABLE_TRANSPARENT); + } else { + // we know that the outputs are only shield, this is purely a change address tx. + // show only the fee. + sub.type = TransactionRecord::SendToSelfShieldToShieldChangeAddress; + } + } } sub.debit = -(nDebit - nChange); diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h index 62ef81baa947..cf30455a2725 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -95,7 +95,9 @@ class TransactionRecord P2CSUnlockStaker, // Staker watching the owner spent the delegated utxo SendToShielded, // Shielded send RecvWithShieldedAddress, // Shielded receive - SendToSelfShieldedAddress // Shielded send to self + SendToSelfShieldedAddress, // Shielded send to self + SendToSelfShieldToTransparent, // Unshield coins to self + SendToSelfShieldToShieldChangeAddress // Changing coins from one shielded address to another inside the wallet. }; /** Number of confirmation recommended for accepting a transaction */ diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index e62262aa2b11..0909c8829ba8 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -467,7 +467,11 @@ QString TransactionTableModel::formatTxType(const TransactionRecord* wtx) const case TransactionRecord::SendToSelf: return tr("Payment to yourself"); case TransactionRecord::SendToSelfShieldedAddress: - return tr("Shielded send to yourself"); + return tr("Shielded coins to yourself"); + case TransactionRecord::SendToSelfShieldToTransparent: + return tr("Unshielded coins to yourself"); + case TransactionRecord::SendToSelfShieldToShieldChangeAddress: + return tr("Shielded change, transfer between own shielded addresses"); case TransactionRecord::StakeMint: return tr("%1 Stake").arg(CURRENCY_UNIT.c_str()); case TransactionRecord::StakeZPIV: @@ -567,6 +571,8 @@ QString TransactionTableModel::formatTxToAddress(const TransactionRecord* wtx, b return label.isEmpty() ? "" : label; } case TransactionRecord::SendToSelfShieldedAddress: + case TransactionRecord::SendToSelfShieldToTransparent: + case TransactionRecord::SendToSelfShieldToShieldChangeAddress: // Do not show the send to self address. todo: add addressbook for shielded addr return ""; default: { From addb08a64c3f55b0ada9dbcaad896697a3424744 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 18 Nov 2020 13:59:57 -0300 Subject: [PATCH 072/121] SSPKM: speeding up the address decryption process caching the shielded address on the note's data when is possible. --- src/sapling/saplingscriptpubkeyman.cpp | 7 +++++++ src/sapling/saplingscriptpubkeyman.h | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index f4c4ca1c0df4..8cc62cf31357 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -342,6 +342,7 @@ std::pair SaplingScriptPubKe SaplingNoteData nd; nd.ivk = ivk; nd.amount = result->value(); + nd.address = address; noteData.insert(std::make_pair(op, nd)); break; } @@ -505,6 +506,12 @@ std::set> SaplingScriptPubKeyMan::G Optional SaplingScriptPubKeyMan::GetShieldedAddressFrom(const CWalletTx& tx, const SaplingOutPoint& op) { + // Try first using the cached data if available + const auto& it = tx.mapSaplingNoteData.find(op); + if (it != tx.mapSaplingNoteData.end() && it->second.address) { + return it->second.address; + } + // Try to decrypt it using the note data ivk (if exists) auto noteAndAddress = tx.DecryptSaplingNote(op); if (noteAndAddress) { diff --git a/src/sapling/saplingscriptpubkeyman.h b/src/sapling/saplingscriptpubkeyman.h index 530a09bd4c6b..c2fcf3e5eed7 100644 --- a/src/sapling/saplingscriptpubkeyman.h +++ b/src/sapling/saplingscriptpubkeyman.h @@ -53,6 +53,12 @@ class SaplingNoteData */ Optional amount{nullopt}; + /** + * Cached shielded address + * It will be loaded the first time that the note is decrypted + */ + Optional address{nullopt}; + /** * Block height corresponding to the most current witness. * From 7c3e977cdd0670e01d42afe58863ab1152dfa002 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 18 Nov 2020 17:42:49 -0300 Subject: [PATCH 073/121] Model and GUI: CoinControlDialog updateView for notes connected. --- src/qt/coincontroldialog.cpp | 6 +++++- src/qt/coincontroldialog.h | 4 ++++ src/qt/pivx/send.cpp | 2 +- src/qt/walletmodel.cpp | 27 +++++++++++++++++++++++++++ src/qt/walletmodel.h | 2 ++ 5 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 25f3df0bb6c1..2c771ee8c10c 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -482,6 +482,10 @@ void CoinControlDialog::updateLabels() if (!model) return; + ui->labelTitle->setText(fSelectTransparent ? + "Select PIV Outputs to Spend" : + "Select Shielded PIV to Spend"); + // nPayAmount CAmount nPayAmount = 0; bool fDust = false; @@ -727,7 +731,7 @@ void CoinControlDialog::updateView() int nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); nSelectableInputs = 0; std::map> mapCoins; - model->listCoins(mapCoins); + model->listCoins(mapCoins, fSelectTransparent); for (const auto& coins : mapCoins) { CCoinControlWidgetItem* itemWalletAddress = new CCoinControlWidgetItem(); diff --git a/src/qt/coincontroldialog.h b/src/qt/coincontroldialog.h index 131644483136..28889d640b28 100644 --- a/src/qt/coincontroldialog.h +++ b/src/qt/coincontroldialog.h @@ -53,6 +53,7 @@ class CoinControlDialog : public QDialog void refreshDialog(); void clearPayAmounts(); void addPayAmount(const CAmount& amount); + void setSelectionType(bool isTransparent) { fSelectTransparent = isTransparent; } CCoinControl* coinControl; @@ -66,6 +67,9 @@ class CoinControlDialog : public QDialog QList payAmounts{}; unsigned int nSelectableInputs{0}; + // whether should show available utxo or notes. + bool fSelectTransparent{true}; + QMenu* contextMenu; QTreeWidgetItem* contextMenuItem; QAction* copyTransactionHashAction; diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index d21ce65713d2..cb470ac80856 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -611,8 +611,8 @@ void SendWidget::onChangeCustomFeeClicked() void SendWidget::onCoinControlClicked() { - // TODO: Implement unspent notes coin control if (walletModel->getBalance() > 0) { + coinControlDialog->setSelectionType(isTransparent); coinControlDialog->refreshDialog(); setCoinControlPayAmounts(); coinControlDialog->exec(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index eeddf6617465..3f68c35d8394 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -942,6 +942,33 @@ bool WalletModel::isSpent(const COutPoint& outpoint) const return wallet->IsSpent(outpoint.hash, outpoint.n); } +void WalletModel::listCoins(std::map>& mapCoins, bool fTransparent) const +{ + if (fTransparent) { + listCoins(mapCoins); + } else { + listAvailableNotes(mapCoins); + } +} + +void WalletModel::listAvailableNotes(std::map>& mapCoins) const +{ + std::vector notes; + Optional dummy = nullopt; + wallet->GetSaplingScriptPubKeyMan()->GetFilteredNotes(notes, dummy); + for (const auto& note : notes) { + ListCoinsKey key{QString::fromStdString(KeyIO::EncodePaymentAddress(note.address)), false, nullopt}; + ListCoinsValue value{ + note.op.GetHash(), + (int)note.op.n, + (CAmount)note.note.value(), + 0, + note.confirmations + }; + mapCoins[key].emplace_back(value); + } +} + // AvailableCoins + LockedCoins grouped by wallet address (put change in one group with wallet address) void WalletModel::listCoins(std::map>& mapCoins) const { diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 96f76b25fd1a..a551539a798a 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -303,7 +303,9 @@ class WalletModel : public QObject int nDepth; }; + void listCoins(std::map>& mapCoins, bool fSelectTransparent) const; void listCoins(std::map>& mapCoins) const; + void listAvailableNotes(std::map>& mapCoins) const; bool isLockedCoin(uint256 hash, unsigned int n) const; void lockCoin(COutPoint& output); From 57e0d16ace4175f904e075837593120ce14b5ff9 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 18 Nov 2020 17:49:47 -0300 Subject: [PATCH 074/121] transaction: adding a boolean to BaseOutPoint to know whether is from a transparent or a shielded outpoint. --- src/primitives/transaction.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 56ebbd4c5095..68b3ecb27c03 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -31,9 +31,11 @@ class BaseOutPoint public: uint256 hash; uint32_t n; + bool isTransparent{true}; BaseOutPoint() { SetNull(); } - BaseOutPoint(uint256 hashIn, uint32_t nIn) { hash = hashIn; n = nIn; } + BaseOutPoint(const uint256& hashIn, const uint32_t nIn, bool isTransparentIn = true) : + hash(hashIn), n(nIn), isTransparent(isTransparentIn) { } ADD_SERIALIZE_METHODS; @@ -75,7 +77,7 @@ class COutPoint : public BaseOutPoint { public: COutPoint() : BaseOutPoint() {}; - COutPoint(uint256 hashIn, uint32_t nIn) : BaseOutPoint(hashIn, nIn) {}; + COutPoint(const uint256& hashIn, const uint32_t nIn) : BaseOutPoint(hashIn, nIn, true) {}; std::string ToString() const; }; @@ -85,7 +87,7 @@ class SaplingOutPoint : public BaseOutPoint { public: SaplingOutPoint() : BaseOutPoint() {}; - SaplingOutPoint(uint256 hashIn, uint32_t nIn) : BaseOutPoint(hashIn, nIn) {}; + SaplingOutPoint(const uint256& hashIn, const uint32_t nIn) : BaseOutPoint(hashIn, nIn, false) {}; std::string ToString() const; }; From d1de1db9e661de0c4aa0f3c663f22b3403ac9655 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 18 Nov 2020 18:31:25 -0300 Subject: [PATCH 075/121] wallet: use parent class BaseOutPoint instead of COutPoint in coin control. --- src/coincontrol.h | 14 +++++------ src/qt/coincontroldialog.cpp | 49 +++++++++++++++++++++--------------- src/qt/walletmodel.cpp | 4 +-- src/qt/walletmodel.h | 2 +- src/wallet/wallet.cpp | 4 +-- 5 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/coincontrol.h b/src/coincontrol.h index 969379988da1..867dc06031aa 100644 --- a/src/coincontrol.h +++ b/src/coincontrol.h @@ -52,17 +52,17 @@ class CCoinControl return (!setSelected.empty()); } - bool IsSelected(const COutPoint& output) const + bool IsSelected(const BaseOutPoint& output) const { return (setSelected.count(output) > 0); } - void Select(const COutPoint& output) + void Select(const BaseOutPoint& output) { setSelected.insert(output); } - void UnSelect(const COutPoint& output) + void UnSelect(const BaseOutPoint& output) { setSelected.erase(output); } @@ -72,7 +72,7 @@ class CCoinControl setSelected.clear(); } - void ListSelected(std::vector& vOutpoints) const + void ListSelected(std::vector& vOutpoints) const { vOutpoints.assign(setSelected.begin(), setSelected.end()); } @@ -82,14 +82,14 @@ class CCoinControl return setSelected.size(); } - void SetSelection(std::set setSelected) + void SetSelection(std::set& _setSelected) { this->setSelected.clear(); - this->setSelected = setSelected; + this->setSelected = _setSelected; } private: - std::set setSelected; + std::set setSelected; }; #endif // BITCOIN_COINCONTROL_H diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 2c771ee8c10c..f15b62aa8a57 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -203,9 +203,9 @@ CoinControlDialog::~CoinControlDialog() delete coinControl; } -void CoinControlDialog::setModel(WalletModel* model) +void CoinControlDialog::setModel(WalletModel* _model) { - this->model = model; + this->model = _model; if (model && model->getOptionsModel() && model->getAddressTableModel()) { updateView(); @@ -234,6 +234,7 @@ void CoinControlDialog::buttonSelectAllClicked() // Toggle lock state void CoinControlDialog::buttonToggleLockClicked() { + if (!fSelectTransparent) return; // todo: implement locked notes QTreeWidgetItem* item; // Works in list-mode only if (ui->radioListMode->isChecked()) { @@ -276,8 +277,7 @@ void CoinControlDialog::showMenu(const QPoint& point) contextMenuItem = item; // disable some items (like Copy Transaction ID, lock, unlock) for tree roots in context menu - if (item->text(COLUMN_TXHASH).length() == 64) // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode) - { + if (item->text(COLUMN_TXHASH).length() == 64) { // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode) copyTransactionHashAction->setEnabled(true); if (model->isLockedCoin(uint256(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt())) { lockAction->setEnabled(false); @@ -286,8 +286,7 @@ void CoinControlDialog::showMenu(const QPoint& point) lockAction->setEnabled(true); unlockAction->setEnabled(false); } - } else // this means click on parent node in tree mode -> disable all - { + } else { // this means click on parent node in tree mode -> disable all copyTransactionHashAction->setEnabled(false); lockAction->setEnabled(false); unlockAction->setEnabled(false); @@ -331,6 +330,7 @@ void CoinControlDialog::copyTransactionHash() // context menu action: lock coin void CoinControlDialog::lockCoin() { + if (!fSelectTransparent) return; // todo: implement locked notes if (contextMenuItem->checkState(COLUMN_CHECKBOX) == Qt::Checked) contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); @@ -344,6 +344,7 @@ void CoinControlDialog::lockCoin() // context menu action: unlock coin void CoinControlDialog::unlockCoin() { + if (!fSelectTransparent) return; // todo: implement locked notes COutPoint outpt(uint256(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); model->unlockCoin(outpt); contextMenuItem->setDisabled(false); @@ -416,8 +417,7 @@ void CoinControlDialog::sortView(int column, Qt::SortOrder order) // treeview: clicked on header void CoinControlDialog::headerSectionClicked(int logicalIndex) { - if (logicalIndex == COLUMN_CHECKBOX) // click on most left column -> do nothing - { + if (logicalIndex == COLUMN_CHECKBOX) { // click on most left column -> do nothing ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder); } else { if (sortColumn == logicalIndex) @@ -448,10 +448,10 @@ void CoinControlDialog::radioListMode(bool checked) // checkbox clicked by user void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column) { - if (column == COLUMN_CHECKBOX && item->text(COLUMN_TXHASH).length() == 64) // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode) - { - COutPoint outpt(uint256(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt()); - + if (column == COLUMN_CHECKBOX && item->text(COLUMN_TXHASH).length() == 64) { // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode) + BaseOutPoint outpt(uint256(item->text(COLUMN_TXHASH).toStdString()), + item->text(COLUMN_VOUT_INDEX).toUInt(), + fSelectTransparent); if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked) coinControl->UnSelect(outpt); else if (item->isDisabled()) // locked (this happens if "check all" through parent node) @@ -460,7 +460,7 @@ void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column) coinControl->Select(outpt); // selection changed -> update labels - if (ui->treeWidget->isEnabled()){ // do not update on every click for (un)select all + if (ui->treeWidget->isEnabled()) { // do not update on every click for (un)select all updateLabels(); } } @@ -469,12 +469,16 @@ void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column) // shows count of locked unspent outputs void CoinControlDialog::updateLabelLocked() { - std::set vOutpts = model->listLockedCoins(); - if (!vOutpts.empty()) { - ui->labelLocked->setText(tr("(%1 locked)").arg(vOutpts.size())); - ui->labelLocked->setVisible(true); - } else - ui->labelLocked->setVisible(false); + if (fSelectTransparent) { + std::set vOutpts = model->listLockedCoins(); + if (!vOutpts.empty()) { + ui->labelLocked->setText(tr("(%1 locked)").arg(vOutpts.size())); + ui->labelLocked->setVisible(true); + } else + ui->labelLocked->setVisible(false); + } else { + // TODO: implement locked notes functionality inside the wallet.. + } } void CoinControlDialog::updateLabels() @@ -498,6 +502,11 @@ void CoinControlDialog::updateLabels() } } + // TODO: Connect labels calculation in the future.. + if (!fSelectTransparent) { + return; + } + CAmount nAmount = 0; CAmount nPayFee = 0; CAmount nAfterFee = 0; @@ -506,7 +515,7 @@ void CoinControlDialog::updateLabels() unsigned int nBytesInputs = 0; unsigned int nQuantity = 0; - std::vector vCoinControl; + std::vector vCoinControl; std::vector vOutputs; coinControl->ListSelected(vCoinControl); model->getOutputs(vCoinControl, vOutputs); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 3f68c35d8394..d42b43268013 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -905,10 +905,10 @@ std::string WalletModel::getLabelForAddress(const CTxDestination& address) } // returns a list of COutputs from COutPoints -void WalletModel::getOutputs(const std::vector& vOutpoints, std::vector& vOutputs) +void WalletModel::getOutputs(const std::vector& vOutpoints, std::vector& vOutputs) { LOCK2(cs_main, wallet->cs_wallet); - for (const COutPoint& outpoint : vOutpoints) { + for (const auto& outpoint : vOutpoints) { const auto* tx = wallet->GetWalletTx(outpoint.hash); if (!tx) continue; bool fConflicted; diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index a551539a798a..62c847287a7b 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -275,7 +275,7 @@ class WalletModel : public QObject bool isMine(const QString& addressStr); bool IsShieldedDestination(const CWDestination& address); bool isUsed(CTxDestination address); - void getOutputs(const std::vector& vOutpoints, std::vector& vOutputs); + void getOutputs(const std::vector& vOutpoints, std::vector& vOutputs); bool getMNCollateralCandidate(COutPoint& outPoint); bool isSpent(const COutPoint& outpoint) const; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a3a0e38a9f0c..50696f6865b4 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2537,10 +2537,10 @@ bool CWallet::SelectCoinsToSpend(const std::vector& vAvailableCoins, co std::set > setPresetCoins; CAmount nValueFromPresetInputs = 0; - std::vector vPresetInputs; + std::vector vPresetInputs; if (coinControl) coinControl->ListSelected(vPresetInputs); - for (const COutPoint& outpoint : vPresetInputs) { + for (const auto& outpoint : vPresetInputs) { std::map::const_iterator it = mapWallet.find(outpoint.hash); if (it != mapWallet.end()) { const CWalletTx* pcoin = &it->second; From b11db7f34c475f9f7bd25f287409fe354e666a99 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 19 Nov 2020 10:57:15 -0300 Subject: [PATCH 076/121] wrapper for the coin control outpoint so we can cache the output amount and not have to re request + look for it later on the process. --- src/coincontrol.h | 28 +++++++++++++++++++++------- src/qt/coincontroldialog.cpp | 5 +++-- src/qt/walletmodel.cpp | 7 ++++--- src/qt/walletmodel.h | 3 ++- src/wallet/wallet.cpp | 10 +++++----- 5 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/coincontrol.h b/src/coincontrol.h index 867dc06031aa..2bd839df6bee 100644 --- a/src/coincontrol.h +++ b/src/coincontrol.h @@ -11,6 +11,20 @@ #include "primitives/transaction.h" #include "script/standard.h" +class OutPointWrapper { +public: + BaseOutPoint outPoint; + CAmount value; + + bool operator<(const OutPointWrapper& obj2) const { + return this->outPoint < obj2.outPoint; + } + + bool operator==(const OutPointWrapper& obj2) const { + return this->outPoint == obj2.outPoint; + } +}; + /** Coin Control Features. */ class CCoinControl { @@ -54,17 +68,17 @@ class CCoinControl bool IsSelected(const BaseOutPoint& output) const { - return (setSelected.count(output) > 0); + return (setSelected.count(OutPointWrapper{output, 0}) > 0); } - void Select(const BaseOutPoint& output) + void Select(const BaseOutPoint& output, CAmount value = 0) { - setSelected.insert(output); + setSelected.insert(OutPointWrapper{output, value}); } void UnSelect(const BaseOutPoint& output) { - setSelected.erase(output); + setSelected.erase(OutPointWrapper{output, 0}); } void UnSelectAll() @@ -72,7 +86,7 @@ class CCoinControl setSelected.clear(); } - void ListSelected(std::vector& vOutpoints) const + void ListSelected(std::vector& vOutpoints) const { vOutpoints.assign(setSelected.begin(), setSelected.end()); } @@ -82,14 +96,14 @@ class CCoinControl return setSelected.size(); } - void SetSelection(std::set& _setSelected) + void SetSelection(std::set& _setSelected) { this->setSelected.clear(); this->setSelected = _setSelected; } private: - std::set setSelected; + std::set setSelected; }; #endif // BITCOIN_COINCONTROL_H diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index f15b62aa8a57..821ddaf8e186 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -452,12 +452,13 @@ void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column) BaseOutPoint outpt(uint256(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt(), fSelectTransparent); + CAmount value = item->text(COLUMN_AMOUNT).toLong(); if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked) coinControl->UnSelect(outpt); else if (item->isDisabled()) // locked (this happens if "check all" through parent node) item->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); else - coinControl->Select(outpt); + coinControl->Select(outpt, value); // selection changed -> update labels if (ui->treeWidget->isEnabled()) { // do not update on every click for (un)select all @@ -515,7 +516,7 @@ void CoinControlDialog::updateLabels() unsigned int nBytesInputs = 0; unsigned int nQuantity = 0; - std::vector vCoinControl; + std::vector vCoinControl; std::vector vOutputs; coinControl->ListSelected(vCoinControl); model->getOutputs(vCoinControl, vOutputs); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index d42b43268013..0895dcabcfd5 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -13,6 +13,7 @@ #include "transactiontablemodel.h" #include "base58.h" +#include "coincontrol.h" #include "db.h" #include "keystore.h" #include "sapling/key_io_sapling.h" @@ -905,16 +906,16 @@ std::string WalletModel::getLabelForAddress(const CTxDestination& address) } // returns a list of COutputs from COutPoints -void WalletModel::getOutputs(const std::vector& vOutpoints, std::vector& vOutputs) +void WalletModel::getOutputs(const std::vector& vOutpoints, std::vector& vOutputs) { LOCK2(cs_main, wallet->cs_wallet); for (const auto& outpoint : vOutpoints) { - const auto* tx = wallet->GetWalletTx(outpoint.hash); + const auto* tx = wallet->GetWalletTx(outpoint.outPoint.hash); if (!tx) continue; bool fConflicted; const int nDepth = tx->GetDepthAndMempool(fConflicted); if (nDepth < 0 || fConflicted) continue; - vOutputs.emplace_back(tx, outpoint.n, nDepth, true, true); + vOutputs.emplace_back(tx, outpoint.outPoint.n, nDepth, true, true); } } diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 62c847287a7b..029ff13c61f7 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -32,6 +32,7 @@ class WalletModelTransaction; class CCoinControl; class CKeyID; class COutPoint; +class OutPointWrapper; class COutput; class CPubKey; class CWallet; @@ -275,7 +276,7 @@ class WalletModel : public QObject bool isMine(const QString& addressStr); bool IsShieldedDestination(const CWDestination& address); bool isUsed(CTxDestination address); - void getOutputs(const std::vector& vOutpoints, std::vector& vOutputs); + void getOutputs(const std::vector& vOutpoints, std::vector& vOutputs); bool getMNCollateralCandidate(COutPoint& outPoint); bool isSpent(const COutPoint& outpoint) const; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 50696f6865b4..404473db83d4 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2537,18 +2537,18 @@ bool CWallet::SelectCoinsToSpend(const std::vector& vAvailableCoins, co std::set > setPresetCoins; CAmount nValueFromPresetInputs = 0; - std::vector vPresetInputs; + std::vector vPresetInputs; if (coinControl) coinControl->ListSelected(vPresetInputs); for (const auto& outpoint : vPresetInputs) { - std::map::const_iterator it = mapWallet.find(outpoint.hash); + std::map::const_iterator it = mapWallet.find(outpoint.outPoint.hash); if (it != mapWallet.end()) { const CWalletTx* pcoin = &it->second; // Clearly invalid input, fail - if (pcoin->vout.size() <= outpoint.n) + if (pcoin->vout.size() <= outpoint.outPoint.n) return false; - nValueFromPresetInputs += pcoin->vout[outpoint.n].nValue; - setPresetCoins.emplace(pcoin, outpoint.n); + nValueFromPresetInputs += pcoin->vout[outpoint.outPoint.n].nValue; + setPresetCoins.emplace(pcoin, outpoint.outPoint.n); } else return false; // TODO: Allow non-wallet inputs } From 0b7914290b63bd505ee69595f994e5feaa95d0cf Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 19 Nov 2020 11:57:50 -0300 Subject: [PATCH 077/121] GUI: cleaning a redundant loop over the selected coins in coin control in the wallet to retrieve the total balance. Amounts are now being cached in the coin control dialog. --- src/coincontrol.h | 16 +++++++++------- src/qt/coincontroldialog.cpp | 8 +++++--- src/qt/pivx/send.cpp | 8 +++++++- src/qt/walletmodel.h | 2 +- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/coincontrol.h b/src/coincontrol.h index 2bd839df6bee..c8d7249ff07c 100644 --- a/src/coincontrol.h +++ b/src/coincontrol.h @@ -10,6 +10,7 @@ #include "policy/feerate.h" #include "primitives/transaction.h" #include "script/standard.h" +#include class OutPointWrapper { public: @@ -96,14 +97,15 @@ class CCoinControl return setSelected.size(); } - void SetSelection(std::set& _setSelected) - { - this->setSelected.clear(); - this->setSelected = _setSelected; - } - private: - std::set setSelected; + + struct SimpleOutpointHash { + size_t operator() (const OutPointWrapper& obj) const { + return (UintToArith256(obj.outPoint.hash) + obj.outPoint.n).GetCheapHash(); + } + }; + + std::unordered_set setSelected; }; #endif // BITCOIN_COINCONTROL_H diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 821ddaf8e186..b2f42d2bdcd1 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -452,13 +452,15 @@ void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column) BaseOutPoint outpt(uint256(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt(), fSelectTransparent); - CAmount value = item->text(COLUMN_AMOUNT).toLong(); if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked) coinControl->UnSelect(outpt); else if (item->isDisabled()) // locked (this happens if "check all" through parent node) item->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); - else + else { + CAmount value = 0; + ParseFixedPoint(item->text(COLUMN_AMOUNT).toStdString(), 8, &value); coinControl->Select(outpt, value); + } // selection changed -> update labels if (ui->treeWidget->isEnabled()) { // do not update on every click for (un)select all @@ -771,7 +773,7 @@ void CoinControlDialog::updateView() CAmount nSum = 0; int nChildren = 0; - for (const WalletModel::ListCoinsValue& out: coins.second) { + for (const WalletModel::ListCoinsValue& out : coins.second) { ++nSelectableInputs; nSum += out.nValue; nChildren++; diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index cb470ac80856..42c1b40a3e68 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -144,7 +144,13 @@ void SendWidget::refreshAmounts() CAmount totalAmount = 0; if (coinControlDialog->coinControl->HasSelected()) { // Set remaining balance to the sum of the coinControl selected inputs - totalAmount = walletModel->getBalance(coinControlDialog->coinControl) - total; + std::vector coins; + coinControlDialog->coinControl->ListSelected(coins); + CAmount selectedBalance = 0; + for (const auto& coin : coins) { + selectedBalance += coin.value; + } + totalAmount = selectedBalance - total; ui->labelTitleTotalRemaining->setText(tr("Total remaining from the selected UTXO")); } else { // Wallet's unlocked balance. diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 029ff13c61f7..e73882e0fd5f 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -297,7 +297,7 @@ class WalletModel : public QObject class ListCoinsValue { public: - const uint256& txhash; + uint256 txhash; int outIndex; CAmount nValue; int64_t nTime; From a5a411eaf2b7305365d5f2a7e0508894b6e4bbeb Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 19 Nov 2020 12:01:33 -0300 Subject: [PATCH 078/121] GUI: send screen resetting coin control when the tx source type changes. --- src/qt/pivx/send.cpp | 10 ++++++++-- src/qt/pivx/send.h | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index 42c1b40a3e68..29a3ee9b1ad7 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -231,15 +231,20 @@ void SendWidget::onResetSettings() void SendWidget::onResetCustomOptions(bool fRefreshAmounts) { - coinControlDialog->coinControl->SetNull(); ui->btnChangeAddress->setActive(false); - ui->btnCoinControl->setActive(false); if (ui->checkBoxDelegations->isChecked()) ui->checkBoxDelegations->setChecked(false); + resetCoinControl(); if (fRefreshAmounts) { refreshAmounts(); } } +void SendWidget::resetCoinControl() +{ + coinControlDialog->coinControl->SetNull(); + ui->btnCoinControl->setActive(false); +} + void SendWidget::clearEntries() { int num = entries.length(); @@ -656,6 +661,7 @@ void SendWidget::onCheckBoxChanged() void SendWidget::onPIVSelected(bool _isTransparent) { isTransparent = _isTransparent; + resetCoinControl(); refreshAmounts(); updateStyle(coinIcon); } diff --git a/src/qt/pivx/send.h b/src/qt/pivx/send.h index 189fea6fba5f..b0d2a16a2442 100644 --- a/src/qt/pivx/send.h +++ b/src/qt/pivx/send.h @@ -115,6 +115,7 @@ private Q_SLOTS: void updateEntryLabels(QList recipients); void setCustomFeeSelected(bool isSelected, const CAmount& customFee = DEFAULT_TRANSACTION_FEE); void setCoinControlPayAmounts(); + void resetCoinControl(); }; #endif // SEND_H From a88918adf68f6b9e54fd737c16b9855b389e64c7 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 19 Nov 2020 19:55:08 -0300 Subject: [PATCH 079/121] GUI: tx detail/confirm dialog, initial work to be able to present from where the shielded transaction are taking funds. --- src/qt/pivx/sendconfirmdialog.cpp | 52 ++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/src/qt/pivx/sendconfirmdialog.cpp b/src/qt/pivx/sendconfirmdialog.cpp index 0fcf49d1a6b4..b01c162207d2 100644 --- a/src/qt/pivx/sendconfirmdialog.cpp +++ b/src/qt/pivx/sendconfirmdialog.cpp @@ -137,6 +137,16 @@ void TxDetailDialog::setData(WalletModel *_model, WalletModelTransaction* _tx) CAmount txFee = tx->getTransactionFee(); CAmount totalAmount = tx->getTotalTransactionAmount() + txFee; + // inputs label + CWalletTx* walletTx = tx->getTransaction(); + if (walletTx->sapData && walletTx->sapData->vShieldedSpend.empty()) { + ui->labelTitlePrevTx->setText(tr("Previous Transaction")); + ui->labelOutputIndex->setText(tr("Output Index")); + } else { + ui->labelTitlePrevTx->setText(tr("Note From Address")); + ui->labelOutputIndex->setText(tr("Note Amount")); + } + ui->textAmount->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, totalAmount, false, BitcoinUnits::separatorAlways) + " (Fee included)"); int nRecipients = tx->getRecipients().size(); if (nRecipients == 1) { @@ -172,6 +182,17 @@ void TxDetailDialog::accept() QDialog::accept(); } +void loadInputs(const QString& leftLabel, const QString& rightLabel, QGridLayout *gridLayoutInput, int pos) +{ + QLabel *label_txid = new QLabel(leftLabel); + QLabel *label_txidn = new QLabel(rightLabel); + label_txidn->setAlignment(Qt::AlignCenter | Qt::AlignRight); + setCssProperty({label_txid, label_txidn}, "text-body2-dialog"); + + gridLayoutInput->addWidget(label_txid, pos, 0); + gridLayoutInput->addWidget(label_txidn, pos, 1); +} + void TxDetailDialog::onInputsClicked() { if (ui->gridInputs->isVisible()) { @@ -182,18 +203,25 @@ void TxDetailDialog::onInputsClicked() inputsLoaded = true; const CWalletTx* walletTx = (this->tx) ? this->tx->getTransaction() : model->getTx(this->txHash); if (walletTx) { - ui->gridInputs->setMinimumHeight(50 + (50 * walletTx->vin.size())); - int i = 1; - for (const CTxIn &in : walletTx->vin) { - QString hash = QString::fromStdString(in.prevout.hash.GetHex()); - QLabel *label_txid = new QLabel(hash.left(18) + "..." + hash.right(18)); - QLabel *label_txidn = new QLabel(QString::number(in.prevout.n)); - label_txidn->setAlignment(Qt::AlignCenter | Qt::AlignRight); - setCssProperty({label_txid, label_txidn}, "text-body2-dialog"); - - ui->gridLayoutInput->addWidget(label_txid,i,0); - ui->gridLayoutInput->addWidget(label_txidn,i,1); - i++; + if (walletTx->sapData && walletTx->sapData->vShieldedSpend.empty()) { + // transparent inputs + ui->gridInputs->setMinimumHeight(50 + (50 * walletTx->vin.size())); + int i = 1; + for (const CTxIn& in : walletTx->vin) { + QString hash = QString::fromStdString(in.prevout.hash.GetHex()); + loadInputs(hash.left(18) + "..." + hash.right(18), + QString::number(in.prevout.n), + ui->gridLayoutInput, i); + i++; + } + } else { + // TODO: load shielded spends. + // note: the spends could be or not in the wallet, remember that this dialog is called + // from the dashboard as a tx detail dialog and from the send screen as a confirmation dialog. + // In case of the dashboard call, we need to know whether the tx is from me or not, + // if it's not from this wallet then we don't have any information about the inputs and should do nothing here. + // but well.. for now, just hide everything until gets implemented. + ui->gridInputs->setVisible(false); } } } From 25ca39ad56808be54da49a6d3733d80eccfabb56 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 19 Nov 2020 20:06:11 -0300 Subject: [PATCH 080/121] sapling operation: coin control selection process implemented and connected for transparent inputs. --- src/sapling/sapling_operation.cpp | 77 +++++++++++++++++++++++++------ src/sapling/sapling_operation.h | 4 ++ 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/src/sapling/sapling_operation.cpp b/src/sapling/sapling_operation.cpp index 8a90442e58ab..7be5c554c329 100644 --- a/src/sapling/sapling_operation.cpp +++ b/src/sapling/sapling_operation.cpp @@ -4,6 +4,7 @@ #include "sapling/sapling_operation.h" +#include "coincontrol.h" #include "net.h" // for g_connman #include "policy/policy.h" // for GetDustThreshold #include "sapling/key_io_sapling.h" @@ -64,23 +65,41 @@ TxValues calculateTarget(const std::vector& recipients, const OperationResult SaplingOperation::build() { + bool isFromtAddress = false; + bool isFromShielded = false; + + if (coinControl) { + // if coin control was selected it overrides any other defined configuration + std::vector coins; + coinControl->ListSelected(coins); + // first check that every selected input is from the same type, cannot be mixed for clear privacy reasons. + // error is thrown below if it happens, not here. + for (const auto& coin : coins) { + if (coin.outPoint.isTransparent) { + isFromtAddress = true; + } else { + isFromShielded = true; + } + } + } else { + // Regular flow + isFromtAddress = fromAddress.isFromTAddress(); + isFromShielded = fromAddress.isFromSapAddress(); - bool isFromtAddress = fromAddress.isFromTAddress(); - bool isFromShielded = fromAddress.isFromSapAddress(); - - if (!isFromtAddress && !isFromShielded) { - isFromtAddress = selectFromtaddrs; - isFromShielded = selectFromShield; - - // It needs to have a from. if (!isFromtAddress && !isFromShielded) { - return errorOut("From address parameter missing"); + isFromtAddress = selectFromtaddrs; + isFromShielded = selectFromShield; } + } - // Cannot be from both - if (isFromtAddress && isFromShielded) { - return errorOut("From address type cannot be shielded and transparent"); - } + // It needs to have a from. + if (!isFromtAddress && !isFromShielded) { + return errorOut("From address parameter missing"); + } + + // Cannot be from both + if (isFromtAddress && isFromShielded) { + return errorOut("From address type cannot be shielded and transparent"); } if (recipients.empty()) { @@ -228,6 +247,25 @@ void SaplingOperation::setFromAddress(const libzcash::SaplingPaymentAddress& _pa OperationResult SaplingOperation::loadUtxos(TxValues& txValues) { + // If the user has selected coins to spend then, directly load them. + // The spendability, depth and other checks should have been done on the user selection side, + // no need to do them again. + if (coinControl && coinControl->HasSelected()) { + std::vector vCoins; + coinControl->ListSelected(vCoins); + + std::vector selectedUTXOInputs; + CAmount nSelectedValue = 0; + for (const auto& outpoint : vCoins) { + const auto* tx = pwalletMain->GetWalletTx(outpoint.outPoint.hash); + if (!tx) continue; + nSelectedValue += tx->vout[outpoint.outPoint.n].nValue; + selectedUTXOInputs.emplace_back(tx, outpoint.outPoint.n, 0, true, true); + } + return loadUtxos(txValues, selectedUTXOInputs, nSelectedValue); + } + + // No coin control selected, let's find the utxo by our own. std::set destinations; if (fromAddress.isFromTAddress()) destinations.insert(fromAddress.fromTaddr); CWallet::AvailableCoinsFilter coinsFilter(false, @@ -279,7 +317,12 @@ OperationResult SaplingOperation::loadUtxos(TxValues& txValues) FormatMoney(selectedUTXOAmount), FormatMoney(dustThreshold - dustChange), FormatMoney(dustChange), FormatMoney(dustThreshold))); } - transInputs = selectedTInputs; + return loadUtxos(txValues, selectedTInputs, selectedUTXOAmount); +} + +OperationResult SaplingOperation::loadUtxos(TxValues& txValues, const std::vector& selectedUTXO, const CAmount selectedUTXOAmount) +{ + transInputs = selectedUTXO; txValues.transInTotal = selectedUTXOAmount; // update the transaction with these inputs @@ -287,12 +330,16 @@ OperationResult SaplingOperation::loadUtxos(TxValues& txValues) const auto& outPoint = t.tx->vout[t.i]; txBuilder.AddTransparentInput(COutPoint(t.tx->GetHash(), t.i), outPoint.scriptPubKey, outPoint.nValue); } - return OperationResult(true); } OperationResult SaplingOperation::loadUnspentNotes(TxValues& txValues, uint256& ovk) { + // if we already have selected the notes, let's directly set them. + if (coinControl && coinControl->HasSelected()) { + // todo: add notes selection. + } + shieldedInputs.clear(); pwalletMain->GetSaplingScriptPubKeyMan()->GetFilteredNotes(shieldedInputs, fromAddress.fromSapAddr, mindepth); diff --git a/src/sapling/sapling_operation.h b/src/sapling/sapling_operation.h index 83965430fc2c..6d4f41a2f5c6 100644 --- a/src/sapling/sapling_operation.h +++ b/src/sapling/sapling_operation.h @@ -15,6 +15,7 @@ #define CTXIN_SPEND_DUST_SIZE 148 #define CTXOUT_REGULAR_SIZE 34 +class CCoinControl; struct TxValues; struct ShieldedRecipient @@ -100,6 +101,7 @@ class SaplingOperation { SaplingOperation* setMinDepth(int _mindepth) { assert(_mindepth >= 0); mindepth = _mindepth; return this; } SaplingOperation* setTxBuilder(TransactionBuilder& builder) { txBuilder = builder; return this; } SaplingOperation* setTransparentKeyChange(CReserveKey* reserveKey) { tkeyChange = reserveKey; return this; } + SaplingOperation* setCoinControl(const CCoinControl* _coinControl) { coinControl = _coinControl; return this; } CTransaction getFinalTx() { return finalTx; } @@ -108,6 +110,7 @@ class SaplingOperation { // In case of no addressFrom filter selected, it will accept any utxo in the wallet as input. bool selectFromtaddrs{false}; bool selectFromShield{false}; + const CCoinControl* coinControl{nullptr}; std::vector recipients; std::vector transInputs; std::vector shieldedInputs; @@ -122,6 +125,7 @@ class SaplingOperation { CTransaction finalTx; OperationResult loadUtxos(TxValues& values); + OperationResult loadUtxos(TxValues& txValues, const std::vector& selectedUTXO, const CAmount selectedUTXOAmount); OperationResult loadUnspentNotes(TxValues& txValues, uint256& ovk); OperationResult checkTxValues(TxValues& txValues, bool isFromtAddress, bool isFromShielded); }; From 99811d2982e17aa78d74c23f3df6892ded88a156 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 19 Nov 2020 20:07:07 -0300 Subject: [PATCH 081/121] GUI: connected coin control for +v2 transactions. --- src/qt/pivx/send.cpp | 5 ++++- src/qt/walletmodel.cpp | 5 ++++- src/qt/walletmodel.h | 4 +++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index 29a3ee9b1ad7..78d39eaaadb6 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -410,7 +410,10 @@ void SendWidget::onSendClicked() OperationResult SendWidget::prepareShielded(WalletModelTransaction* currentTransaction, bool fromTransparent) { - auto result = walletModel->PrepareShieldedTransaction(currentTransaction, fromTransparent); + bool hasCoinsOrNotesSelected = coinControlDialog && coinControlDialog->coinControl && coinControlDialog->coinControl->HasSelected(); + auto result = walletModel->PrepareShieldedTransaction(currentTransaction, + fromTransparent, + hasCoinsOrNotesSelected ? coinControlDialog->coinControl : nullptr); if (!result) { return errorOut(result.m_error.toStdString()); } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 0895dcabcfd5..a80d72fd4d6c 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -513,7 +513,9 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction& tran return SendCoinsReturn(OK); } -OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction* modelTransaction, bool fromTransparent) +OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction* modelTransaction, + bool fromTransparent, + const CCoinControl* coinControl) { // Basic checks first @@ -548,6 +550,7 @@ OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction* ->setTransparentKeyChange(modelTransaction->getPossibleKeyChange()) ->setSelectTransparentCoins(fromTransparent) ->setSelectShieldedCoins(!fromTransparent) + ->setCoinControl(coinControl) ->build(); if (!operationResult) { diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index e73882e0fd5f..dc322b3b5de4 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -207,7 +207,9 @@ class WalletModel : public QObject SendCoinsReturn sendCoins(WalletModelTransaction& transaction); // Prepare shielded transaction. - OperationResult PrepareShieldedTransaction(WalletModelTransaction* modelTransaction, bool fromTransparent); + OperationResult PrepareShieldedTransaction(WalletModelTransaction* modelTransaction, + bool fromTransparent, + const CCoinControl* coinControl = nullptr); // Wallet encryption bool setWalletEncrypted(bool encrypted, const SecureString& passphrase); From 3e12d8933a6a399e2a8c1dc1be2d529b5b8e2de9 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 19 Nov 2020 20:15:19 -0300 Subject: [PATCH 082/121] SSPKM: Created a method to retrieve the notes from a list of SaplingOutPoints --- src/sapling/saplingscriptpubkeyman.cpp | 31 ++++++++++++++++++++++++++ src/sapling/saplingscriptpubkeyman.h | 4 ++++ 2 files changed, 35 insertions(+) diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index 8cc62cf31357..57e0e9498e96 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -374,6 +374,37 @@ std::vector SaplingScriptPubKeyMan::FindMySapli return ret; } +void SaplingScriptPubKeyMan::GetNotes(const std::vector& saplingOutpoints, + std::vector& saplingEntriesRet) +{ + for (const auto& outpoint : saplingOutpoints) { + const auto* wtx = wallet->GetWalletTx(outpoint.hash); + if (!wtx) throw std::runtime_error("No transaction available for hash " + outpoint.hash.GetHex()); + const auto& it = wtx->mapSaplingNoteData.find(outpoint); + if (it != wtx->mapSaplingNoteData.end()) { + + const SaplingOutPoint& op = it->first; + const SaplingNoteData& nd = it->second; + + const OutputDescription& outDesc = wtx->sapData->vShieldedOutput[op.n]; + auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt( + outDesc.encCiphertext, + nd.ivk, + outDesc.ephemeralKey, + outDesc.cmu); + assert(static_cast(maybe_pt)); + auto notePt = maybe_pt.get(); + + auto maybe_pa = nd.ivk.address(notePt.d); + assert(static_cast(maybe_pa)); + auto pa = maybe_pa.get(); + + auto note = notePt.note(nd.ivk).get(); + saplingEntriesRet.emplace_back(op, pa, note, notePt.memo(), wtx->GetDepthInMainChain()); + } + } +} + /** * Find notes in the wallet filtered by payment address, min depth and ability to spend. * These notes are decrypted and added to the output parameter vector, saplingEntries. diff --git a/src/sapling/saplingscriptpubkeyman.h b/src/sapling/saplingscriptpubkeyman.h index c2fcf3e5eed7..4c76fae2fcbf 100644 --- a/src/sapling/saplingscriptpubkeyman.h +++ b/src/sapling/saplingscriptpubkeyman.h @@ -242,6 +242,10 @@ class SaplingScriptPubKeyMan { //! Find all of the addresses in the given tx that have been sent to a SaplingPaymentAddress in this wallet. std::vector FindMySaplingAddresses(const CTransaction& tx) const; + //! Find notes for the outpoints + void GetNotes(const std::vector& saplingOutpoints, + std::vector& saplingEntriesRet); + /* Find notes filtered by payment address, min depth, ability to spend */ void GetFilteredNotes(std::vector& saplingEntries, Optional& address, From 3b7209649c65245b8cd8e915613d6df5508c83fe Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 20 Nov 2020 10:40:04 -0300 Subject: [PATCH 083/121] GUI: sendToSelf text "shielded" moved to "shielding" --- src/qt/transactiontablemodel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 0909c8829ba8..1b1871e36155 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -467,9 +467,9 @@ QString TransactionTableModel::formatTxType(const TransactionRecord* wtx) const case TransactionRecord::SendToSelf: return tr("Payment to yourself"); case TransactionRecord::SendToSelfShieldedAddress: - return tr("Shielded coins to yourself"); + return tr("Shielding coins to yourself"); case TransactionRecord::SendToSelfShieldToTransparent: - return tr("Unshielded coins to yourself"); + return tr("Unshielding coins to yourself"); case TransactionRecord::SendToSelfShieldToShieldChangeAddress: return tr("Shielded change, transfer between own shielded addresses"); case TransactionRecord::StakeMint: From 9e8cd0ce31289c05437f783528628a3186a57882 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 20 Nov 2020 18:28:06 -0300 Subject: [PATCH 084/121] Sapling operation: notes coin control selection implemented. --- src/sapling/sapling_operation.cpp | 53 ++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/src/sapling/sapling_operation.cpp b/src/sapling/sapling_operation.cpp index 7be5c554c329..6edba0c1ba3b 100644 --- a/src/sapling/sapling_operation.cpp +++ b/src/sapling/sapling_operation.cpp @@ -335,26 +335,42 @@ OperationResult SaplingOperation::loadUtxos(TxValues& txValues, const std::vecto OperationResult SaplingOperation::loadUnspentNotes(TxValues& txValues, uint256& ovk) { + shieldedInputs.clear(); // if we already have selected the notes, let's directly set them. - if (coinControl && coinControl->HasSelected()) { - // todo: add notes selection. - } + bool hasCoinControl = coinControl && coinControl->HasSelected(); + if (hasCoinControl) { + std::vector vCoins; + coinControl->ListSelected(vCoins); - shieldedInputs.clear(); - pwalletMain->GetSaplingScriptPubKeyMan()->GetFilteredNotes(shieldedInputs, fromAddress.fromSapAddr, mindepth); - - for (const auto& entry : shieldedInputs) { - std::string data(entry.memo.begin(), entry.memo.end()); - LogPrint(BCLog::SAPLING,"%s: found unspent Sapling note (txid=%s, vShieldedSpend=%d, amount=%s, memo=%s)\n", - __func__ , - entry.op.hash.ToString().substr(0, 10), - entry.op.n, - FormatMoney(entry.note.value()), - HexStr(data).substr(0, 10)); - } + // Converting outpoint wrapper to sapling outpoints + std::vector vSaplingOutpoints; + for (const auto& outpoint : vCoins) { + vSaplingOutpoints.emplace_back(outpoint.outPoint.hash, outpoint.outPoint.n); + } + + pwalletMain->GetSaplingScriptPubKeyMan()->GetNotes(vSaplingOutpoints, shieldedInputs); - if (shieldedInputs.empty()) { - return errorOut("Insufficient funds, no available notes to spend"); + if (shieldedInputs.empty()) { + return errorOut("Insufficient funds, no available notes to spend"); + } + } else { + // If we don't have coinControl then let's find the notes + pwalletMain->GetSaplingScriptPubKeyMan()->GetFilteredNotes(shieldedInputs, fromAddress.fromSapAddr, mindepth); + + for (const auto& entry : shieldedInputs) { + std::string data(entry.memo.begin(), entry.memo.end()); + LogPrint(BCLog::SAPLING, + "%s: found unspent Sapling note (txid=%s, vShieldedSpend=%d, amount=%s, memo=%s)\n", + __func__, + entry.op.hash.ToString().substr(0, 10), + entry.op.n, + FormatMoney(entry.note.value()), + HexStr(data).substr(0, 10)); + } + + if (shieldedInputs.empty()) { + return errorOut("Insufficient funds, no available notes to spend"); + } } // sort in descending order, so big notes appear first @@ -387,7 +403,8 @@ OperationResult SaplingOperation::loadUnspentNotes(TxValues& txValues, uint256& ops.emplace_back(t.op); notes.emplace_back(t.note); txValues.shieldedInTotal += t.note.value(); - if (txValues.shieldedInTotal >= txValues.target) { + if (!hasCoinControl && txValues.shieldedInTotal >= txValues.target) { + // coin control selection by pass this check, uses all the selected notes. // Select another note if there is change less than the dust threshold. dustChange = txValues.shieldedInTotal - txValues.target; if (dustChange == 0 || dustChange >= dustThreshold) { From c7b4f9d994b76bec0410bd780cd70e5c1972ea7e Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 21 Nov 2020 12:21:17 -0300 Subject: [PATCH 085/121] GUI: send screen, implemented a control to shield all of the available transparent coins. --- src/qt/guiutil.cpp | 5 ++++ src/qt/guiutil.h | 2 ++ src/qt/pivx/forms/send.ui | 10 +++++++ src/qt/pivx/send.cpp | 58 +++++++++++++++++++++++++++++++++------ src/qt/pivx/send.h | 2 ++ src/qt/walletmodel.cpp | 4 +-- 6 files changed, 71 insertions(+), 10 deletions(-) diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index e300a34496fe..1c2d8171ffed 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -126,6 +126,11 @@ QString formatBalance(CAmount amount, int nDisplayUnit, bool isZpiv) return (amount == 0) ? ("0.00 " + BitcoinUnits::name(nDisplayUnit, isZpiv)) : BitcoinUnits::floorHtmlWithUnit(nDisplayUnit, amount, false, BitcoinUnits::separatorAlways, true, isZpiv); } +QString formatBalanceWithoutHtml(CAmount amount, int nDisplayUnit, bool isZpiv) +{ + return (amount == 0) ? ("0.00 " + BitcoinUnits::name(nDisplayUnit, isZpiv)) : BitcoinUnits::floorWithUnit(nDisplayUnit, amount, false, BitcoinUnits::separatorAlways, true, isZpiv); +} + void setupAddressWidget(QValidatedLineEdit* widget, QWidget* parent) { parent->setFocusProxy(widget); diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 7f66bc7594db..9d3f81a8307f 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -59,6 +59,8 @@ CAmount parseValue(const QString& text, int displayUnit, bool* valid_out = 0); // Format an amount QString formatBalance(CAmount amount, int nDisplayUnit = 0, bool isZpiv = false); +QString formatBalanceWithoutHtml(CAmount amount, int nDisplayUnit = 0, bool isZpiv = false); + // Set up widgets for address and amounts void setupAddressWidget(QValidatedLineEdit* widget, QWidget* parent); diff --git a/src/qt/pivx/forms/send.ui b/src/qt/pivx/forms/send.ui index a2e42bee47e1..e4f7763c6d15 100644 --- a/src/qt/pivx/forms/send.ui +++ b/src/qt/pivx/forms/send.ui @@ -761,6 +761,16 @@ padding-top:2px; + + + + + 0 + 50 + + + + diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index 78d39eaaadb6..32dda4f2895f 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -76,10 +76,15 @@ SendWidget::SendWidget(PIVXGUI* parent) : ui->btnUri->setTitleClassAndText("btn-title-grey", tr("Open URI")); ui->btnUri->setSubTitleClassAndText("text-subtitle", tr("Parse a payment request")); + // Shield coins + ui->btnShieldCoins->setTitleClassAndText("btn-title-grey", tr("Shield Coins")); + ui->btnShieldCoins->setSubTitleClassAndText("text-subtitle", tr("Convert all transparent coins into shielded coins")); + connect(ui->pushButtonFee, &QPushButton::clicked, this, &SendWidget::onChangeCustomFeeClicked); connect(ui->btnCoinControl, &OptionButton::clicked, this, &SendWidget::onCoinControlClicked); connect(ui->btnChangeAddress, &OptionButton::clicked, this, &SendWidget::onChangeAddressClicked); connect(ui->btnUri, &OptionButton::clicked, this, &SendWidget::onOpenUriClicked); + connect(ui->btnShieldCoins, &OptionButton::clicked, this, &SendWidget::onShieldCoinsClicked); connect(ui->pushButtonReset, &QPushButton::clicked, [this](){ onResetCustomOptions(true); }); connect(ui->checkBoxDelegations, &QCheckBox::stateChanged, this, &SendWidget::onCheckBoxChanged); @@ -367,6 +372,11 @@ void SendWidget::onSendClicked() return; } + ProcessSend(recipients, hasShieldedOutput); +} + +void SendWidget::ProcessSend(const QList& recipients, bool hasShieldedOutput) +{ auto ptrUnlockedContext = MakeUnique(walletModel->requestUnlock()); if (!ptrUnlockedContext->isValid()) { // Unlock wallet was cancelled @@ -411,13 +421,9 @@ void SendWidget::onSendClicked() OperationResult SendWidget::prepareShielded(WalletModelTransaction* currentTransaction, bool fromTransparent) { bool hasCoinsOrNotesSelected = coinControlDialog && coinControlDialog->coinControl && coinControlDialog->coinControl->HasSelected(); - auto result = walletModel->PrepareShieldedTransaction(currentTransaction, - fromTransparent, - hasCoinsOrNotesSelected ? coinControlDialog->coinControl : nullptr); - if (!result) { - return errorOut(result.m_error.toStdString()); - } - return OperationResult(true); + return walletModel->PrepareShieldedTransaction(currentTransaction, + fromTransparent, + hasCoinsOrNotesSelected ? coinControlDialog->coinControl : nullptr); } OperationResult SendWidget::prepareTransparent(WalletModelTransaction* currentTransaction) @@ -502,7 +508,7 @@ void SendWidget::run(int type) processingResult = true; } else { processingResult = false; - processingResultError = QString::fromStdString(result.getError()); + processingResultError = tr(result.getError().c_str()); } isProcessing = false; } @@ -637,6 +643,42 @@ void SendWidget::onCoinControlClicked() } } +void SendWidget::onShieldCoinsClicked() +{ + auto balances = walletModel->GetWalletBalances(); + CAmount availableBalance = balances.balance - balances.shielded_balance; + if (walletModel && availableBalance > 0) { + + if (!ask(tr("Shield Coins"), + tr("You are just about to anonymize all of your balance!\nAvailable %1\n\n" + "Meaning that you will be able to perform completely\nanonymous transactions" + "\n\nDo you want to continue?\n").arg(GUIUtil::formatBalanceWithoutHtml(availableBalance, nDisplayUnit, false)))) { + return; + } + + // First get a new address + QString strAddress; + auto res = walletModel->getNewShieldedAddress(strAddress, ""); + // Check for generation errors + if (!res.result) { + inform(tr("Error generating address to shield PIVs")); + return; + } + + // load recipient and process sending.. + QList recipients; + CAmount saplingTxFee = 10000 * 100; // DEFAULT_SAPLING_FEE. + SendCoinsRecipient recipient; + recipient.address = strAddress; + recipient.amount = availableBalance - saplingTxFee; + recipient.isShieldedAddr = true; + recipients.append(recipient); + ProcessSend(recipients, true); + } else { + inform(tr("You don't have any transparent PIVs to shield.")); + } +} + void SendWidget::setCoinControlPayAmounts() { if (!coinControlDialog) return; diff --git a/src/qt/pivx/send.h b/src/qt/pivx/send.h index b0d2a16a2442..c1ca5ce006c6 100644 --- a/src/qt/pivx/send.h +++ b/src/qt/pivx/send.h @@ -53,6 +53,7 @@ public Q_SLOTS: void onChangeCustomFeeClicked(); void onCoinControlClicked(); void onOpenUriClicked(); + void onShieldCoinsClicked(); void onValueChanged(); void refreshAmounts(); void changeTheme(bool isLightTheme, QString &theme) override; @@ -107,6 +108,7 @@ private Q_SLOTS: void resizeMenu(); QString recipientsToString(QList recipients); SendMultiRow* createEntry(); + void ProcessSend(const QList& recipients, bool hasShieldedOutput); OperationResult prepareShielded(WalletModelTransaction* tx, bool fromTransparent); OperationResult prepareTransparent(WalletModelTransaction* tx); bool sendFinalStep(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index a80d72fd4d6c..9e9ec1fb0ca1 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -514,8 +514,8 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction& tran } OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction* modelTransaction, - bool fromTransparent, - const CCoinControl* coinControl) + bool fromTransparent, + const CCoinControl* coinControl) { // Basic checks first From e1ca452919f5daadee272ba3989e8e079f147268 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 23 Nov 2020 10:43:11 -0300 Subject: [PATCH 086/121] SSPKM and GUI: unifying SaplingOutPoint GetCredit & GetDebit into GetOutPointValue. --- src/qt/transactionrecord.cpp | 5 ++--- src/sapling/saplingscriptpubkeyman.cpp | 7 +------ src/sapling/saplingscriptpubkeyman.h | 6 ++---- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index a35fdcc2d3e4..365da410f393 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -8,7 +8,6 @@ #include "base58.h" #include "sapling/key_io_sapling.h" -#include "timedata.h" #include "wallet/wallet.h" #include "zpivchain.h" @@ -221,7 +220,7 @@ bool TransactionRecord::decomposeCreditTransaction(const CWallet* wallet, const sub.address = (opAddr) ? KeyIO::EncodePaymentAddress(*opAddr) : ""; sub.type = TransactionRecord::RecvWithShieldedAddress; - sub.credit = sspkm->GetCredit(wtx, out); + sub.credit = sspkm->GetOutPointValue(wtx, out); sub.idx = i; parts.append(sub); } @@ -304,7 +303,7 @@ bool TransactionRecord::decomposeShieldedDebitTransaction(const CWallet* wallet, sub.involvesWatchAddress = involvesWatchAddress; sub.type = TransactionRecord::SendToShielded; sub.address = KeyIO::EncodePaymentAddress(*opAddr); - CAmount nValue = sspkm->GetDebit(wtx, out); + CAmount nValue = sspkm->GetOutPointValue(wtx, out); /* Add fee to first output */ if (!feeAdded) { // future, move from the hardcoded fee. nValue += COIN; diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index 57e0e9498e96..384737c941f1 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -614,12 +614,7 @@ isminetype SaplingScriptPubKeyMan::IsMine(const CWalletTx& wtx, const SaplingOut return pa ? ::IsMine(*wallet, *pa) : ISMINE_NO; } -CAmount SaplingScriptPubKeyMan::GetDebit(const CWalletTx& tx, const SaplingOutPoint& op) -{ - return TryToRecoverAndSetAmount(tx, op); -} - -CAmount SaplingScriptPubKeyMan::GetCredit(const CWalletTx& tx, const SaplingOutPoint& op) +CAmount SaplingScriptPubKeyMan::GetOutPointValue(const CWalletTx& tx, const SaplingOutPoint& op) { const auto& it = tx.mapSaplingNoteData.find(op); if (it == tx.mapSaplingNoteData.end()) { diff --git a/src/sapling/saplingscriptpubkeyman.h b/src/sapling/saplingscriptpubkeyman.h index 4c76fae2fcbf..b7b35fec3eba 100644 --- a/src/sapling/saplingscriptpubkeyman.h +++ b/src/sapling/saplingscriptpubkeyman.h @@ -287,10 +287,8 @@ class SaplingScriptPubKeyMan { //! Return true if the wallet can decrypt & spend the shielded output. isminetype IsMine(const CWalletTx& wtx, const SaplingOutPoint& op); - //! Return the shielded debit of an specific output description - CAmount GetDebit(const CWalletTx& tx, const SaplingOutPoint& op); - //! Return the shielded credit of an specific output description - CAmount GetCredit(const CWalletTx& tx, const SaplingOutPoint& op); + //! Return the shielded value of an specific output + CAmount GetOutPointValue(const CWalletTx& tx, const SaplingOutPoint& op); //! Return the shielded credit of the tx CAmount GetCredit(const CWalletTx& tx, const isminefilter& filter, const bool fUnspent = false); //! Return the shielded debit of the tx. From 19605731b4d83f77f18313ccb47b8a72ef593f25 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 23 Nov 2020 10:52:46 -0300 Subject: [PATCH 087/121] RPC getbalance: include shielded watch-only balance. --- src/wallet/rpcwallet.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 951a1ebacf13..dc27d07488c0 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1909,10 +1909,15 @@ UniValue getbalance(const JSONRPCRequest& request) LOCK2(cs_main, pwalletMain->cs_wallet); const int paramsSize = request.params.size(); - const int nMinDepth = (paramsSize > 0 ? request.params[0].get_int() : 0); - isminefilter filter = ISMINE_SPENDABLE | (paramsSize > 1 && request.params[1].get_bool() ? ISMINE_WATCH_ONLY : ISMINE_NO); - filter |= (paramsSize <= 2 || request.params[2].get_bool() ? ISMINE_SPENDABLE_DELEGATED : ISMINE_NO); - filter |= (paramsSize <= 3 || request.params[3].get_bool() ? ISMINE_SPENDABLE_SHIELDED : ISMINE_NO); + const int nMinDepth = paramsSize > 0 ? request.params[0].get_int() : 0; + bool fIncludeWatchOnly = paramsSize > 1 && request.params[1].get_bool(); + bool fIncludeDelegated = paramsSize <= 2 || request.params[2].get_bool(); + bool fIncludeShielded = paramsSize <= 3 || request.params[3].get_bool(); + + isminefilter filter = ISMINE_SPENDABLE | (fIncludeWatchOnly ? + (fIncludeShielded ? ISMINE_WATCH_ONLY_SHIELDED : ISMINE_WATCH_ONLY) : ISMINE_NO); + filter |= fIncludeDelegated ? ISMINE_SPENDABLE_DELEGATED : ISMINE_NO; + filter |= fIncludeShielded ? ISMINE_SPENDABLE_SHIELDED : ISMINE_NO; return ValueFromAmount(pwalletMain->GetAvailableBalance(filter, true, nMinDepth)); } From dfccdc74eea673751a645e9e9f036c75a955d0bf Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 23 Nov 2020 11:04:52 -0300 Subject: [PATCH 088/121] GUI: Move default address creation to addressTableModel::getAddressToShow instead of doing in on the receive widget. --- src/qt/addresstablemodel.cpp | 8 ++++++-- src/qt/pivx/receivewidget.cpp | 20 ++++---------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index d1afb1d5ca5b..3cfb420f3454 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -621,13 +621,17 @@ QString AddressTableModel::getAddressToShow(bool isShielded) const } } + // For some reason we don't have any address in our address book, let's create one + PairResult res(false); QString addressStr; if (!isShielded) { - // For some reason we don't have any address in our address book, let's create one Destination newAddress; - if (walletModel->getNewAddress(newAddress, "Default").result) { + res = walletModel->getNewAddress(newAddress, "Default"); + if (res.result) { addressStr = QString::fromStdString(newAddress.ToString()); } + } else { + res = walletModel->getNewShieldedAddress(addressStr, "default shielded"); } return addressStr; } diff --git a/src/qt/pivx/receivewidget.cpp b/src/qt/pivx/receivewidget.cpp index c52a56351b45..60c6404eaf42 100644 --- a/src/qt/pivx/receivewidget.cpp +++ b/src/qt/pivx/receivewidget.cpp @@ -141,23 +141,11 @@ void ReceiveWidget::refreshView(QString refreshAddress) try { QString latestAddress = (refreshAddress.isEmpty()) ? this->addressTableModel->getAddressToShow(shieldedMode) : refreshAddress; - if (latestAddress.isEmpty()) { // new default address - PairResult r(false); - if (!shieldedMode) { - Destination newAddress; - r = walletModel->getNewAddress(newAddress, "Default"); - latestAddress = QString::fromStdString(newAddress.ToString()); - } else { - // shielded - r = walletModel->getNewShieldedAddress(latestAddress, "default shielded"); - } - + if (latestAddress.isEmpty()) { // Check for generation errors - if (!r.result) { - ui->labelQrImg->setText(tr("No available address, try unlocking the wallet")); - inform(tr("Error generating address")); - return; - } + ui->labelQrImg->setText(tr("No available address, try unlocking the wallet")); + inform(tr("Error generating address")); + return; } QString addressToShow = latestAddress; From f7c7c0e29c383978542ec8666462045a5e9e0f0e Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 23 Nov 2020 11:09:03 -0300 Subject: [PATCH 089/121] WalletModel: simpler getKeyCreationTime. --- src/qt/walletmodel.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 9e9ec1fb0ca1..3c6aaee9cc87 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -826,13 +826,7 @@ int64_t WalletModel::getKeyCreationTime(const CTxDestination& address) int64_t WalletModel::getKeyCreationTime(const std::string& address) { - CWDestination dest = Standard::DecodeDestination(address); - const CTxDestination* add = boost::get(&dest); - if (add && IsValidDestination(*add)) { - return getKeyCreationTime(*add); - } else { - return getKeyCreationTime(*boost::get(&dest)); - } + return pwalletMain->GetKeyCreationTime(Standard::DecodeDestination(address)); } int64_t WalletModel::getKeyCreationTime(const libzcash::SaplingPaymentAddress& address) From 694e31986d4b26d2cc8c10f82d9f2f5f54cb0aa5 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 23 Nov 2020 11:11:42 -0300 Subject: [PATCH 090/121] GUI: send widget, do not update the entry labels if the transaction wasn't confirmed by the user. --- src/qt/pivx/send.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index 32dda4f2895f..8f1ee9d2e86d 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -491,7 +491,7 @@ bool SendWidget::sendFinalStep() } dialog->deleteLater(); - return true; + return false; } void SendWidget::run(int type) From 5100b2505179e546e4f193a680298903e67e915c Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 24 Nov 2020 01:05:34 -0300 Subject: [PATCH 091/121] GUI: fixing grid inputs row alignment. Issue more visible when there are lot of inputs selected, the grid is being resized to contain all of them and as them are appearing at the center vertically, the top section isn't being used, leaving a large empty blank space and making the bottom of the grid to overlap with the view below. --- src/qt/pivx/forms/sendconfirmdialog.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qt/pivx/forms/sendconfirmdialog.ui b/src/qt/pivx/forms/sendconfirmdialog.ui index 6fec2ed23e78..0f33e33f3f1e 100644 --- a/src/qt/pivx/forms/sendconfirmdialog.ui +++ b/src/qt/pivx/forms/sendconfirmdialog.ui @@ -219,7 +219,7 @@ background:transparent; 0 0 586 - 644 + 631 @@ -596,7 +596,7 @@ background:transparent; - + From 374da0b89df6097e363b8bf90003da6a64c99c7a Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 24 Nov 2020 01:07:06 -0300 Subject: [PATCH 092/121] Send confirmation dialog: connected the shielded transaction fee plus minor correction to the shielded address presentation format. --- src/qt/pivx/sendconfirmdialog.cpp | 2 +- src/qt/walletmodel.cpp | 2 ++ src/sapling/sapling_operation.h | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/qt/pivx/sendconfirmdialog.cpp b/src/qt/pivx/sendconfirmdialog.cpp index b01c162207d2..2069c33bf6f1 100644 --- a/src/qt/pivx/sendconfirmdialog.cpp +++ b/src/qt/pivx/sendconfirmdialog.cpp @@ -123,7 +123,7 @@ QString formatAdressToShow(const QString& address) { QString addressToShow; if (address.size() > 60) { - addressToShow = address.left(60) + "\n" + address.mid(60); + addressToShow = address.left(57) + "\n" + address.mid(57); } else { addressToShow = address; } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 3c6aaee9cc87..503d6d21fddb 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -551,6 +551,7 @@ OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction* ->setSelectTransparentCoins(fromTransparent) ->setSelectShieldedCoins(!fromTransparent) ->setCoinControl(coinControl) + ->setMinDepth(fromTransparent ? 1 : 5) ->build(); if (!operationResult) { @@ -559,6 +560,7 @@ OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction* // load the transaction and key change (if needed) modelTransaction->setTransaction(new CWalletTx(wallet, operation.getFinalTx())); + modelTransaction->setTransactionFee(operation.getFee()); // in the future, fee will be dynamically calculated. return operationResult; } diff --git a/src/sapling/sapling_operation.h b/src/sapling/sapling_operation.h index 6d4f41a2f5c6..d72890369c43 100644 --- a/src/sapling/sapling_operation.h +++ b/src/sapling/sapling_operation.h @@ -103,6 +103,7 @@ class SaplingOperation { SaplingOperation* setTransparentKeyChange(CReserveKey* reserveKey) { tkeyChange = reserveKey; return this; } SaplingOperation* setCoinControl(const CCoinControl* _coinControl) { coinControl = _coinControl; return this; } + CAmount getFee() { return fee; } CTransaction getFinalTx() { return finalTx; } private: From 2a5b2655dc5301477c70705f502f929972af3d67 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 24 Nov 2020 01:40:14 -0300 Subject: [PATCH 093/121] GUI: Dashboard tx filter by shielded transaction included + shielded recv and send addresses filter included. --- src/qt/pivx/qtutils.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/qt/pivx/qtutils.cpp b/src/qt/pivx/qtutils.cpp index e670abd05bfc..ef2c525e955e 100644 --- a/src/qt/pivx/qtutils.cpp +++ b/src/qt/pivx/qtutils.cpp @@ -139,6 +139,8 @@ void setFilterAddressBook(QComboBox* filter, SortEdit* lineEdit) filter->addItem(QObject::tr("Delegator"), AddressTableModel::Delegator); filter->addItem(QObject::tr("Delegable"), AddressTableModel::Delegable); filter->addItem(QObject::tr("Staking Contacts"), AddressTableModel::ColdStakingSend); + filter->addItem(QObject::tr("Shielded Recv"), AddressTableModel::ShieldedReceive); + filter->addItem(QObject::tr("Shielded Contact"), AddressTableModel::ShieldedSend); } void setSortTx(QComboBox* filter, SortEdit* lineEdit) @@ -157,10 +159,12 @@ void setSortTxTypeFilter(QComboBox* filter, SortEdit* lineEditType) filter->addItem(QObject::tr("All"), TransactionFilterProxy::ALL_TYPES); filter->addItem(QObject::tr("Received"), TransactionFilterProxy::TYPE(TransactionRecord::RecvWithAddress) | TransactionFilterProxy::TYPE(TransactionRecord::RecvFromOther)); filter->addItem(QObject::tr("Sent"), TransactionFilterProxy::TYPE(TransactionRecord::SendToAddress) | TransactionFilterProxy::TYPE(TransactionRecord::SendToOther)); + filter->addItem(QObject::tr("Shielded"), TransactionFilterProxy::TYPE(TransactionRecord::RecvWithShieldedAddress | TransactionRecord::SendToShielded)); filter->addItem(QObject::tr("Mined"), TransactionFilterProxy::TYPE(TransactionRecord::Generated)); filter->addItem(QObject::tr("Minted"), TransactionFilterProxy::TYPE(TransactionRecord::StakeMint)); filter->addItem(QObject::tr("MN reward"), TransactionFilterProxy::TYPE(TransactionRecord::MNReward)); - filter->addItem(QObject::tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf)); + filter->addItem(QObject::tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf | + TransactionRecord::SendToSelfShieldedAddress | TransactionRecord::SendToSelfShieldToShieldChangeAddress | TransactionRecord::SendToSelfShieldToTransparent)); filter->addItem(QObject::tr("Cold stakes"), TransactionFilterProxy::TYPE(TransactionRecord::StakeDelegated)); filter->addItem(QObject::tr("Hot stakes"), TransactionFilterProxy::TYPE(TransactionRecord::StakeHot)); filter->addItem(QObject::tr("Delegated"), TransactionFilterProxy::TYPE(TransactionRecord::P2CSDelegationSent) | TransactionFilterProxy::TYPE(TransactionRecord::P2CSDelegationSentOwner)); From e3f1536337aff22283f22c585eef00d8ffb23f27 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 24 Nov 2020 02:16:22 -0300 Subject: [PATCH 094/121] GUI: transaction confirmation dialog showing the "sending to" shielded addresses outputs. --- src/qt/pivx/sendconfirmdialog.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/qt/pivx/sendconfirmdialog.cpp b/src/qt/pivx/sendconfirmdialog.cpp index 2069c33bf6f1..4f0e6e45187f 100644 --- a/src/qt/pivx/sendconfirmdialog.cpp +++ b/src/qt/pivx/sendconfirmdialog.cpp @@ -250,10 +250,23 @@ void TxDetailDialog::onOutputsClicked() layoutGrid->setContentsMargins(0,0,12,0); ui->container_outputs_base->setLayout(layoutGrid); - const CWalletTx* walletTx = (this->tx) ? this->tx->getTransaction() : model->getTx(this->txHash); - if (walletTx) { + // If the there is a model tx, then this is a confirmation dialog + if (tx) { + const QList& recipients = tx->getRecipients(); + for (int i = 0; i < recipients.size(); ++i) { + const auto& recipient = recipients[i]; + int charsSize = recipient.isShieldedAddr ? 18 : 16; + QString labelRes = recipient.address.left(charsSize) + "..." + recipient.address.right(charsSize); + appendOutput(layoutGrid, i, labelRes, recipient.amount, nDisplayUnit); + } + } else { + // Tx detail dialog + const CWalletTx* walletTx = model->getTx(this->txHash); + if (!walletTx) return; + + // transparent recipients int i = 0; - for (const CTxOut &out : walletTx->vout) { + for (const CTxOut& out : walletTx->vout) { QString labelRes; CTxDestination dest; bool isCsAddress = out.scriptPubKey.IsPayToColdStaking(); @@ -272,6 +285,7 @@ void TxDetailDialog::onOutputsClicked() if (walletTx->sapData) { for (int j = 0; j < (int) walletTx->sapData->vShieldedOutput.size(); ++j) { const SaplingOutPoint op(walletTx->GetHash(), j); + // TODO: This only works for txs that are stored, not for when this is a confirmation dialog.. if (walletTx->mapSaplingNoteData.find(op) == walletTx->mapSaplingNoteData.end()) { continue; } @@ -287,6 +301,7 @@ void TxDetailDialog::onOutputsClicked() i++; } } + } } } From 381803e1b2213f1b78501a49d6087332d86455f1 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Tue, 24 Nov 2020 21:43:41 -0300 Subject: [PATCH 095/121] GUI: fixed coin control notes selection OutPoint hash issue --- src/qt/walletmodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 503d6d21fddb..da56753c3656 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -959,7 +959,7 @@ void WalletModel::listAvailableNotes(std::map Date: Wed, 25 Nov 2020 00:26:50 -0300 Subject: [PATCH 096/121] sapling operation: inform user about notes with less than the min depth instead of simple return a "no available notes". --- src/sapling/sapling_operation.cpp | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/sapling/sapling_operation.cpp b/src/sapling/sapling_operation.cpp index 6edba0c1ba3b..0d413e654fa8 100644 --- a/src/sapling/sapling_operation.cpp +++ b/src/sapling/sapling_operation.cpp @@ -344,6 +344,7 @@ OperationResult SaplingOperation::loadUnspentNotes(TxValues& txValues, uint256& // Converting outpoint wrapper to sapling outpoints std::vector vSaplingOutpoints; + vSaplingOutpoints.reserve(vCoins.size()); for (const auto& outpoint : vCoins) { vSaplingOutpoints.emplace_back(outpoint.outPoint.hash, outpoint.outPoint.n); } @@ -356,20 +357,13 @@ OperationResult SaplingOperation::loadUnspentNotes(TxValues& txValues, uint256& } else { // If we don't have coinControl then let's find the notes pwalletMain->GetSaplingScriptPubKeyMan()->GetFilteredNotes(shieldedInputs, fromAddress.fromSapAddr, mindepth); - - for (const auto& entry : shieldedInputs) { - std::string data(entry.memo.begin(), entry.memo.end()); - LogPrint(BCLog::SAPLING, - "%s: found unspent Sapling note (txid=%s, vShieldedSpend=%d, amount=%s, memo=%s)\n", - __func__, - entry.op.hash.ToString().substr(0, 10), - entry.op.n, - FormatMoney(entry.note.value()), - HexStr(data).substr(0, 10)); - } - if (shieldedInputs.empty()) { - return errorOut("Insufficient funds, no available notes to spend"); + // Just to notify the user properly, check if the wallet has notes with less than the min depth + std::vector _shieldedInputs; + pwalletMain->GetSaplingScriptPubKeyMan()->GetFilteredNotes(_shieldedInputs, fromAddress.fromSapAddr, 0); + return errorOut(_shieldedInputs.empty() ? + "Insufficient funds, no available notes to spend" : + "Insufficient funds, shielded PIV need at least 5 confirmations"); } } From 1a1938ee487ddb5549a69cc64ca59b312a3ea3dc Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 26 Nov 2020 13:11:05 -0300 Subject: [PATCH 097/121] SSPKM: add function to return the address of an specific shielded input. --- src/sapling/saplingscriptpubkeyman.cpp | 14 ++++++++++++++ src/sapling/saplingscriptpubkeyman.h | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index 384737c941f1..02506bee9243 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -497,6 +497,20 @@ void SaplingScriptPubKeyMan::GetFilteredNotes( } } +Optional + SaplingScriptPubKeyMan::GetAddressFromInputIfPossible(const CWalletTx* wtx, int index) +{ + if (!wtx->sapData || wtx->sapData->vShieldedSpend.empty()) return nullopt; + + SpendDescription spendDesc = wtx->sapData->vShieldedSpend.at(index); + if (!IsSaplingNullifierFromMe(spendDesc.nullifier)) return nullopt; + + // Knowing that the spent note is from us, we can get the address from + const SaplingOutPoint& outPoint = mapSaplingNullifiersToNotes.at(spendDesc.nullifier); + const CWalletTx& txPrev = wallet->mapWallet.at(outPoint.hash); + return txPrev.mapSaplingNoteData.at(outPoint).address; +} + bool SaplingScriptPubKeyMan::IsSaplingNullifierFromMe(const uint256& nullifier) const { LOCK(wallet->cs_wallet); diff --git a/src/sapling/saplingscriptpubkeyman.h b/src/sapling/saplingscriptpubkeyman.h index b7b35fec3eba..9cd02a365003 100644 --- a/src/sapling/saplingscriptpubkeyman.h +++ b/src/sapling/saplingscriptpubkeyman.h @@ -263,6 +263,10 @@ class SaplingScriptPubKeyMan { bool requireSpendingKey=true, bool ignoreLocked=true); + + //! Return the address from where the shielded spend is taking the funds from (if possible) + Optional GetAddressFromInputIfPossible(const CWalletTx* wtx, int index); + //! Whether the nullifier is from this wallet bool IsSaplingNullifierFromMe(const uint256& nullifier) const; From 26ac1ecf42e27fe0bc2bb4a1208ed4c8f5710c9c Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 26 Nov 2020 13:43:56 -0300 Subject: [PATCH 098/121] GUI: confirmation and tx dialog showing shielded inputs properly. --- src/qt/pivx/forms/sendconfirmdialog.ui | 6 ++--- src/qt/pivx/sendconfirmdialog.cpp | 36 ++++++++++++++++++-------- src/qt/walletmodel.cpp | 6 +++++ src/qt/walletmodel.h | 3 +++ 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/qt/pivx/forms/sendconfirmdialog.ui b/src/qt/pivx/forms/sendconfirmdialog.ui index 0f33e33f3f1e..aa0c0abe9054 100644 --- a/src/qt/pivx/forms/sendconfirmdialog.ui +++ b/src/qt/pivx/forms/sendconfirmdialog.ui @@ -18,7 +18,7 @@ - 574 + 580 500 @@ -219,7 +219,7 @@ background:transparent; 0 0 586 - 631 + 581 @@ -607,7 +607,7 @@ background:transparent; 0 - 90 + 40 diff --git a/src/qt/pivx/sendconfirmdialog.cpp b/src/qt/pivx/sendconfirmdialog.cpp index 4f0e6e45187f..35fdfaa0394a 100644 --- a/src/qt/pivx/sendconfirmdialog.cpp +++ b/src/qt/pivx/sendconfirmdialog.cpp @@ -102,7 +102,8 @@ void TxDetailDialog::setData(WalletModel *_model, const QModelIndex &index) } ui->textSend->setVisible(false); - ui->textInputs->setText(QString::number(_tx->vin.size())); + int inputsSize = (_tx->sapData && !_tx->sapData->vShieldedSpend.empty()) ? _tx->sapData->vShieldedSpend.size() : _tx->vin.size(); + ui->textInputs->setText(QString::number(inputsSize)); ui->textConfirmations->setText(QString::number(rec->status.depth)); ui->textDate->setText(GUIUtil::dateTimeStrWithSeconds(date)); ui->textStatus->setText(QString::fromStdString(rec->statusToString())); @@ -144,7 +145,7 @@ void TxDetailDialog::setData(WalletModel *_model, WalletModelTransaction* _tx) ui->labelOutputIndex->setText(tr("Output Index")); } else { ui->labelTitlePrevTx->setText(tr("Note From Address")); - ui->labelOutputIndex->setText(tr("Note Amount")); + ui->labelOutputIndex->setText(tr("Index")); } ui->textAmount->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, totalAmount, false, BitcoinUnits::separatorAlways) + " (Fee included)"); @@ -169,7 +170,9 @@ void TxDetailDialog::setData(WalletModel *_model, WalletModelTransaction* _tx) ui->textSendLabel->setText(QString::number(nRecipients) + " recipients"); ui->textSend->setVisible(false); } - ui->textInputs->setText(QString::number(tx->getTransaction()->vin.size())); + + int inputsSize = (walletTx->sapData && !walletTx->sapData->vShieldedSpend.empty()) ? walletTx->sapData->vShieldedSpend.size() : walletTx->vin.size(); + ui->textInputs->setText(QString::number(inputsSize)); ui->textFee->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, txFee, false, BitcoinUnits::separatorAlways)); } @@ -198,7 +201,7 @@ void TxDetailDialog::onInputsClicked() if (ui->gridInputs->isVisible()) { ui->gridInputs->setVisible(false); } else { - ui->gridInputs->setVisible(true); + bool showGrid = true; if (!inputsLoaded) { inputsLoaded = true; const CWalletTx* walletTx = (this->tx) ? this->tx->getTransaction() : model->getTx(this->txHash); @@ -215,16 +218,27 @@ void TxDetailDialog::onInputsClicked() i++; } } else { - // TODO: load shielded spends. - // note: the spends could be or not in the wallet, remember that this dialog is called - // from the dashboard as a tx detail dialog and from the send screen as a confirmation dialog. - // In case of the dashboard call, we need to know whether the tx is from me or not, - // if it's not from this wallet then we don't have any information about the inputs and should do nothing here. - // but well.. for now, just hide everything until gets implemented. - ui->gridInputs->setVisible(false); + bool fInfoAvailable = false; + for (int i = 0; i < (int) walletTx->sapData->vShieldedSpend.size(); ++i) { + Optional opAddr = model->getShieldedAddressFromSpendDesc(walletTx, i); + if (opAddr) { + QString addr = *opAddr; + loadInputs(addr.left(16) + "..." + addr.right(16), + QString::number(i), + ui->gridLayoutInput, i); + fInfoAvailable = true; + } + } + + if (!fInfoAvailable) { + // note: the spends are not from the wallet, let's not show anything here + showGrid = false; + } + } } } + ui->gridInputs->setVisible(showGrid); } } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index da56753c3656..a616fe841c4e 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -1081,3 +1081,9 @@ bool WalletModel::isUsed(CTxDestination address) { return wallet->IsUsed(address); } + +Optional WalletModel::getShieldedAddressFromSpendDesc(const CWalletTx* wtx, int index) +{ + Optional opAddr = wallet->GetSaplingScriptPubKeyMan()->GetAddressFromInputIfPossible(wtx, index); + return opAddr ? Optional(QString::fromStdString(KeyIO::EncodePaymentAddress(*opAddr))) : nullopt; +} diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index dc322b3b5de4..ee35abeb71da 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -182,6 +182,9 @@ class WalletModel : public QObject // plus return isShielded = true if the parsed address is a valid shielded address. bool validateAddress(const QString& address, bool fStaking, bool& isShielded); + // Return the address from where the shielded spend is taking the funds from (if possible) + Optional getShieldedAddressFromSpendDesc(const CWalletTx* wtx, int index); + // Return status record for SendCoins, contains error id + information struct SendCoinsReturn { SendCoinsReturn(StatusCode status = OK) : status(status) {} From 7e063ced4b72d528cc2598b331387f2742eb2595 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 26 Nov 2020 01:57:07 +0100 Subject: [PATCH 099/121] [DB] Add functions to database the common sapling OVK with given seed --- src/wallet/walletdb.cpp | 17 +++++++++++++++-- src/wallet/walletdb.h | 4 ++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 5ebc25bf1f2d..88555b9e3ef7 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -149,6 +149,17 @@ bool CWalletDB::WriteCryptedSaplingZKey( return true; } +bool CWalletDB::WriteSaplingCommonOVK(const uint256& ovk) +{ + nWalletDBUpdateCounter++; + return Write(std::string("commonovk"), ovk); +} + +bool CWalletDB::ReadSaplingCommonOVK(uint256& ovkRet) +{ + return Read(std::string("commonovk"), ovkRet); +} + bool CWalletDB::WriteWitnessCacheSize(int64_t nWitnessCacheSize) { nWalletDBUpdateCounter++; @@ -650,14 +661,16 @@ bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CW ssKey >> ivk; libzcash::SaplingExtendedSpendingKey key; ssValue >> key; - if (!pwallet->LoadSaplingZKey(key)) { strErr = "Error reading wallet database: LoadSaplingZKey failed"; return false; } - //add checks for integrity wss.nZKeys++; + } else if (strType == "commonovk") { + uint256 ovk; + ssValue >> ovk; + // !TODO: cache ovk value in the wallet } else if (strType == "csapzkey") { libzcash::SaplingIncomingViewingKey ivk; ssKey >> ivk; diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index de9b30c15a31..ddded9147904 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -164,6 +164,10 @@ class CWalletDB : public CDB const std::vector& vchCryptedSecret, const CKeyMetadata &keyMeta); + /// Common output viewing key, used when shielding transparent funds + bool WriteSaplingCommonOVK(const uint256& ovk); + bool ReadSaplingCommonOVK(uint256& ovkRet); + bool WriteWitnessCacheSize(int64_t nWitnessCacheSize); /// Write destination data key,value tuple to database From 31b549a667f8ca2d00dee7d397a2e9e0d5755118 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 26 Nov 2020 02:51:56 +0100 Subject: [PATCH 100/121] [Wallet] Save sapling common OVK inside the key manager --- src/sapling/sapling_operation.cpp | 9 ++--- src/sapling/saplingscriptpubkeyman.cpp | 40 +++++++++++++++++-- src/sapling/saplingscriptpubkeyman.h | 4 ++ src/test/librust/sapling_rpc_wallet_tests.cpp | 2 +- src/wallet/rpcwallet.cpp | 6 ++- src/wallet/walletdb.cpp | 2 +- 6 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/sapling/sapling_operation.cpp b/src/sapling/sapling_operation.cpp index 0d413e654fa8..c79752008d98 100644 --- a/src/sapling/sapling_operation.cpp +++ b/src/sapling/sapling_operation.cpp @@ -123,11 +123,10 @@ OperationResult SaplingOperation::build() return result; } } else { - // Sending from a t-address, which we don't have an ovk for. Instead, - // generate a common one from the HD seed. This ensures the data is - // recoverable, while keeping it logically separate from the ZIP 32 - // Sapling key hierarchy, which the user might not be using. - ovk = pwalletMain->GetSaplingScriptPubKeyMan()->getCommonOVKFromSeed(); + // Get the common OVK for recovering t->shield outputs. + // If not already databased, a new one will be generated from the HD seed. + // It is safe to do it here, as the wallet is unlocked. + ovk = pwalletMain->GetSaplingScriptPubKeyMan()->getCommonOVK(); } // Add outputs diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index 02506bee9243..43279a2fbaaf 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -580,7 +580,17 @@ Optional ovks; - ovks.emplace(getCommonOVKFromSeed()); + // Get the common OVK for recovering t->shield outputs. + // If not already databased, a new one will be generated from the HD seed (this throws an error if the + // wallet is currently locked). As the ovk is created when the wallet is unlocked for sending a t->shield + // tx for the first time, a failure to decode can happen only if this note was sent (from a t-addr) + // using this wallet.dat on another computer (and never sent t->shield txes from this computer). + try { + ovks.emplace(getCommonOVK()); + } catch (...) { + LogPrintf("WARNING: No CommonOVK found. Some notes might not be correctly recovered. " + "Unlock the wallet and call 'viewshieldedtransaction %s' to fix.\n", tx.GetHash().ToString()); + } if (!tx.sapData->vShieldedSpend.empty()) { const auto& it = mapSaplingNullifiersToNotes.find(tx.sapData->vShieldedSpend[0].nullifier); if (it != mapSaplingNullifiersToNotes.end()) { @@ -1164,16 +1174,38 @@ void SaplingScriptPubKeyMan::SetHDChain(CHDChain& chain, bool memonly) throw std::runtime_error(std::string(__func__) + ": Not found sapling seed in wallet"); } +uint256 SaplingScriptPubKeyMan::getCommonOVK() +{ + // If already loaded, return it + if (commonOVK) return *commonOVK; + + // Else, look for it in the database + uint256 ovk; + if (CWalletDB(wallet->strWalletFile).ReadSaplingCommonOVK(ovk)) { + commonOVK = std::move(ovk); + return *commonOVK; + } + + // Otherwise create one. This throws if the wallet is encrypted. + // So we should always call this after unlocking the wallet during a spend + // from a transparent address, or when changing/setting the HD seed. + commonOVK = getCommonOVKFromSeed(); + if (!CWalletDB(wallet->strWalletFile).WriteSaplingCommonOVK(*commonOVK)) { + throw std::runtime_error("Unable to write sapling Common OVK to database"); + } + return *commonOVK; +} + + uint256 SaplingScriptPubKeyMan::getCommonOVKFromSeed() { // Sending from a t-address, which we don't have an ovk for. Instead, // generate a common one from the HD seed. This ensures the data is // recoverable, while keeping it logically separate from the ZIP 32 // Sapling key hierarchy, which the user might not be using. - const CKeyID seedID = GetHDChain().GetID(); CKey key; - if (!wallet->GetKey(seedID, key)) { - throw std::runtime_error("Shielded spend, HD seed not found"); + if (!wallet->GetKey(GetHDChain().GetID(), key)) { + throw std::runtime_error("HD seed not found, wallet must be un-locked"); } HDSeed seed{key.GetPrivKey()}; return ovkForShieldingFromTaddr(seed); diff --git a/src/sapling/saplingscriptpubkeyman.h b/src/sapling/saplingscriptpubkeyman.h index 9cd02a365003..ecd4c834bfa3 100644 --- a/src/sapling/saplingscriptpubkeyman.h +++ b/src/sapling/saplingscriptpubkeyman.h @@ -180,6 +180,8 @@ class SaplingScriptPubKeyMan { void SetHDChain(CHDChain& chain, bool memonly); const CHDChain& GetHDChain() const { return hdChain; } + uint256 getCommonOVK(); + void setCommonOVK(const uint256& ovk) { commonOVK = ovk; } uint256 getCommonOVKFromSeed(); /* Encrypt Sapling keys */ @@ -376,6 +378,8 @@ class SaplingScriptPubKeyMan { CWallet* wallet{nullptr}; /* the HD chain data model (external/internal chain counters) */ CHDChain hdChain; + /* cached common OVK for sapling spends from t addresses */ + Optional commonOVK; /** diff --git a/src/test/librust/sapling_rpc_wallet_tests.cpp b/src/test/librust/sapling_rpc_wallet_tests.cpp index 2489e47f1a9b..f3e6548a7a79 100644 --- a/src/test/librust/sapling_rpc_wallet_tests.cpp +++ b/src/test/librust/sapling_rpc_wallet_tests.cpp @@ -466,7 +466,7 @@ BOOST_AUTO_TEST_CASE(rpc_shieldedsendmany_taddr_to_sapling) BOOST_CHECK(libzcash::AttemptSaplingOutDecryption( tx.sapData->vShieldedOutput[0].outCiphertext, - pwalletMain->GetSaplingScriptPubKeyMan()->getCommonOVKFromSeed(), + pwalletMain->GetSaplingScriptPubKeyMan()->getCommonOVK(), tx.sapData->vShieldedOutput[0].cv, tx.sapData->vShieldedOutput[0].cmu, tx.sapData->vShieldedOutput[0].ephemeralKey)); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index dc27d07488c0..3a9859e88a35 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1360,8 +1360,10 @@ UniValue viewshieldedtransaction(const JSONRPCRequest& request) // Collect OutgoingViewingKeys for recovering output information std::set ovks; - // Generate the common ovk for recovering t->shield outputs. - ovks.insert(sspkm->getCommonOVKFromSeed()); + // Get the common OVK for recovering t->shield outputs. + // If not already databased, a new one will be generated from the HD seed. + // It is safe to do it here, as the wallet is unlocked. + ovks.insert(sspkm->getCommonOVK()); // Sapling spends for (size_t i = 0; i < wtx.sapData->vShieldedSpend.size(); ++i) { diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 88555b9e3ef7..16b1d9b4ed85 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -670,7 +670,7 @@ bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CW } else if (strType == "commonovk") { uint256 ovk; ssValue >> ovk; - // !TODO: cache ovk value in the wallet + pwallet->GetSaplingScriptPubKeyMan()->setCommonOVK(ovk); } else if (strType == "csapzkey") { libzcash::SaplingIncomingViewingKey ivk; ssKey >> ivk; From 71550c17cb4dfce133b6c3f6f83290d0f67cff51 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 26 Nov 2020 03:14:59 +0100 Subject: [PATCH 101/121] [Refactor] Don't expose getCommonOVKFromSeed --- src/sapling/saplingscriptpubkeyman.cpp | 3 +-- src/sapling/saplingscriptpubkeyman.h | 7 ++++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index 43279a2fbaaf..46263e3f14db 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -1196,8 +1196,7 @@ uint256 SaplingScriptPubKeyMan::getCommonOVK() return *commonOVK; } - -uint256 SaplingScriptPubKeyMan::getCommonOVKFromSeed() +uint256 SaplingScriptPubKeyMan::getCommonOVKFromSeed() const { // Sending from a t-address, which we don't have an ovk for. Instead, // generate a common one from the HD seed. This ensures the data is diff --git a/src/sapling/saplingscriptpubkeyman.h b/src/sapling/saplingscriptpubkeyman.h index ecd4c834bfa3..8705913ac3d5 100644 --- a/src/sapling/saplingscriptpubkeyman.h +++ b/src/sapling/saplingscriptpubkeyman.h @@ -180,9 +180,13 @@ class SaplingScriptPubKeyMan { void SetHDChain(CHDChain& chain, bool memonly); const CHDChain& GetHDChain() const { return hdChain; } + /* Get cached sapling commonOVK + * If nullopt, read it from the database, and save it. + * If not found in the database, create a new one from the HD seed (throw + * if the wallet is locked), write it to database, and save it. + */ uint256 getCommonOVK(); void setCommonOVK(const uint256& ovk) { commonOVK = ovk; } - uint256 getCommonOVKFromSeed(); /* Encrypt Sapling keys */ bool EncryptSaplingKeys(CKeyingMaterial& vMasterKeyIn); @@ -380,6 +384,7 @@ class SaplingScriptPubKeyMan { CHDChain hdChain; /* cached common OVK for sapling spends from t addresses */ Optional commonOVK; + uint256 getCommonOVKFromSeed() const; /** From eddacb96cb134874ad209ae733f209a96d15bc19 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 26 Nov 2020 04:14:09 +0100 Subject: [PATCH 102/121] [Wallet] Update commonOVK when setting the HD seed --- src/sapling/saplingscriptpubkeyman.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index 46263e3f14db..d28523d5b037 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -1156,6 +1156,12 @@ void SaplingScriptPubKeyMan::SetHDSeed(const CKeyID& keyID, bool force, bool mem } SetHDChain(newHdChain, memonly); + + // Update the commonOVK to recover t->shield notes + commonOVK = getCommonOVKFromSeed(); + if (!memonly && !CWalletDB(wallet->strWalletFile).WriteSaplingCommonOVK(*commonOVK)) { + throw std::runtime_error(std::string(__func__) + ": writing sapling commonOVK failed"); + } } void SaplingScriptPubKeyMan::SetHDChain(CHDChain& chain, bool memonly) From 5b4d1040692665a69d1db3c4bcc6e885f20e2fae Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 26 Nov 2020 07:09:04 +0100 Subject: [PATCH 103/121] [BUG] Fix selection of correct ovk from spent note --- src/sapling/saplingscriptpubkeyman.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index d28523d5b037..690207d6e5b3 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -592,19 +592,19 @@ OptionalvShieldedSpend.empty()) { - const auto& it = mapSaplingNullifiersToNotes.find(tx.sapData->vShieldedSpend[0].nullifier); + const auto& spend = tx.sapData->vShieldedSpend[0]; + const auto& it = mapSaplingNullifiersToNotes.find(spend.nullifier); if (it != mapSaplingNullifiersToNotes.end()) { const SaplingOutPoint& prevOut = it->second; const CWalletTx* txPrev = wallet->GetWalletTx(prevOut.hash); if (!txPrev) return nullopt; + const auto& itPrev = txPrev->mapSaplingNoteData.find(prevOut); if (itPrev != txPrev->mapSaplingNoteData.end()) { const SaplingNoteData& noteData = itPrev->second; - libzcash::SaplingExtendedSpendingKey extsk; libzcash::SaplingExtendedFullViewingKey extfvk; - if (wallet->GetSaplingFullViewingKey(noteData.ivk, extfvk) && - wallet->GetSaplingSpendingKey(extfvk, extsk)) { - ovks.emplace(extsk.expsk.ovk); + if (wallet->GetSaplingFullViewingKey(noteData.ivk, extfvk)) { + ovks.emplace(extfvk.fvk.ovk); } } } From cd40162ddfdc07416d1ab73aed8003ce2b2127c3 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 26 Nov 2020 07:13:45 +0100 Subject: [PATCH 104/121] [Wallet] Add recovering from all inputs ovk in SSPKM::TryToRecoverNote --- src/sapling/saplingscriptpubkeyman.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index 690207d6e5b3..2c207c26fce7 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -578,7 +578,6 @@ Optional ovks; // Get the common OVK for recovering t->shield outputs. // If not already databased, a new one will be generated from the HD seed (this throws an error if the @@ -591,14 +590,12 @@ OptionalvShieldedSpend.empty()) { - const auto& spend = tx.sapData->vShieldedSpend[0]; + for (const auto& spend : tx.sapData->vShieldedSpend) { const auto& it = mapSaplingNullifiersToNotes.find(spend.nullifier); if (it != mapSaplingNullifiersToNotes.end()) { const SaplingOutPoint& prevOut = it->second; const CWalletTx* txPrev = wallet->GetWalletTx(prevOut.hash); - if (!txPrev) return nullopt; - + if (!txPrev) continue; const auto& itPrev = txPrev->mapSaplingNoteData.find(prevOut); if (itPrev != txPrev->mapSaplingNoteData.end()) { const SaplingNoteData& noteData = itPrev->second; From a59f0b6d3b664226a17fed9fb50f294cccbe2fdf Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 26 Nov 2020 12:54:35 +0100 Subject: [PATCH 105/121] [Refactor] Don't try to recover with both commonOVK and spends OVK A transaction cannot have mixed inputs. --- src/sapling/saplingscriptpubkeyman.cpp | 43 ++++++++++++++------------ 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index 2c207c26fce7..20dce1db59b9 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -577,31 +577,36 @@ Optional> SaplingScriptPubKeyMan::TryToRecoverNote(const CWalletTx& tx, const SaplingOutPoint& op) { - // Try to recover it with the ovks. + const uint256& txId = tx.GetHash(); + assert(txId == op.hash); + // Try to recover it with the ovks (either the common one, if t->shield tx, or the ones from the spends) std::set ovks; // Get the common OVK for recovering t->shield outputs. // If not already databased, a new one will be generated from the HD seed (this throws an error if the // wallet is currently locked). As the ovk is created when the wallet is unlocked for sending a t->shield // tx for the first time, a failure to decode can happen only if this note was sent (from a t-addr) // using this wallet.dat on another computer (and never sent t->shield txes from this computer). - try { - ovks.emplace(getCommonOVK()); - } catch (...) { - LogPrintf("WARNING: No CommonOVK found. Some notes might not be correctly recovered. " - "Unlock the wallet and call 'viewshieldedtransaction %s' to fix.\n", tx.GetHash().ToString()); - } - for (const auto& spend : tx.sapData->vShieldedSpend) { - const auto& it = mapSaplingNullifiersToNotes.find(spend.nullifier); - if (it != mapSaplingNullifiersToNotes.end()) { - const SaplingOutPoint& prevOut = it->second; - const CWalletTx* txPrev = wallet->GetWalletTx(prevOut.hash); - if (!txPrev) continue; - const auto& itPrev = txPrev->mapSaplingNoteData.find(prevOut); - if (itPrev != txPrev->mapSaplingNoteData.end()) { - const SaplingNoteData& noteData = itPrev->second; - libzcash::SaplingExtendedFullViewingKey extfvk; - if (wallet->GetSaplingFullViewingKey(noteData.ivk, extfvk)) { - ovks.emplace(extfvk.fvk.ovk); + if (tx.vin.size() > 0) { + try { + ovks.emplace(getCommonOVK()); + } catch (...) { + LogPrintf("WARNING: No CommonOVK found. Some notes might not be correctly recovered. " + "Unlock the wallet and call 'viewshieldedtransaction %s' to fix.\n", txId.ToString()); + } + } else { + for (const auto& spend : tx.sapData->vShieldedSpend) { + const auto& it = mapSaplingNullifiersToNotes.find(spend.nullifier); + if (it != mapSaplingNullifiersToNotes.end()) { + const SaplingOutPoint& prevOut = it->second; + const CWalletTx* txPrev = wallet->GetWalletTx(prevOut.hash); + if (!txPrev) continue; + const auto& itPrev = txPrev->mapSaplingNoteData.find(prevOut); + if (itPrev != txPrev->mapSaplingNoteData.end()) { + const SaplingNoteData& noteData = itPrev->second; + libzcash::SaplingExtendedFullViewingKey extfvk; + if (wallet->GetSaplingFullViewingKey(noteData.ivk, extfvk)) { + ovks.emplace(extfvk.fvk.ovk); + } } } } From e349ef4615958dff41710edfc948805f12629a3e Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 26 Nov 2020 11:00:51 +0100 Subject: [PATCH 106/121] Refactor: cache SaplingNoteData for externally 'sent' notes too Thus only decrypt a note when it first gets added to the wallet --- src/sapling/saplingscriptpubkeyman.cpp | 65 ++++++++++++------- src/sapling/saplingscriptpubkeyman.h | 5 +- src/test/librust/sapling_wallet_tests.cpp | 3 +- src/wallet/rpcwallet.cpp | 58 ++++++++++------- .../test/wallet_shielded_balances_tests.cpp | 9 ++- src/wallet/wallet.cpp | 39 +++++++++-- src/wallet/wallet.h | 2 + 7 files changed, 121 insertions(+), 60 deletions(-) diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index 20dce1db59b9..fe76e3bb1163 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -53,23 +53,24 @@ void SaplingScriptPubKeyMan::UpdateSaplingNullifierNoteMapWithTx(CWalletTx& wtx) SaplingOutPoint op = item.first; SaplingNoteData nd = item.second; - if (nd.witnesses.empty()) { + if (nd.witnesses.empty() || !nd.IsMyNote()) { // If there are no witnesses, erase the nullifier and associated mapping. if (item.second.nullifier) { mapSaplingNullifiersToNotes.erase(item.second.nullifier.get()); } item.second.nullifier = boost::none; } else { + const libzcash::SaplingIncomingViewingKey& ivk = *(nd.ivk); uint64_t position = nd.witnesses.front().position(); - auto extfvk = wallet->mapSaplingFullViewingKeys.at(nd.ivk); + auto extfvk = wallet->mapSaplingFullViewingKeys.at(ivk); OutputDescription output = wtx.sapData->vShieldedOutput[op.n]; - auto optPlaintext = libzcash::SaplingNotePlaintext::decrypt(output.encCiphertext, nd.ivk, output.ephemeralKey, output.cmu); + auto optPlaintext = libzcash::SaplingNotePlaintext::decrypt(output.encCiphertext, ivk, output.ephemeralKey, output.cmu); if (!optPlaintext) { // An item in mapSaplingNoteData must have already been successfully decrypted, // otherwise the item would not exist in the first place. assert(false); } - auto optNote = optPlaintext.get().note(nd.ivk); + auto optNote = optPlaintext.get().note(ivk); if (!optNote) { assert(false); } @@ -386,20 +387,24 @@ void SaplingScriptPubKeyMan::GetNotes(const std::vector& saplin const SaplingOutPoint& op = it->first; const SaplingNoteData& nd = it->second; + // skip sent notes + if (!nd.IsMyNote()) continue; + const libzcash::SaplingIncomingViewingKey& ivk = *(nd.ivk); + const OutputDescription& outDesc = wtx->sapData->vShieldedOutput[op.n]; auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt( outDesc.encCiphertext, - nd.ivk, + ivk, outDesc.ephemeralKey, outDesc.cmu); assert(static_cast(maybe_pt)); auto notePt = maybe_pt.get(); - auto maybe_pa = nd.ivk.address(notePt.d); + auto maybe_pa = ivk.address(notePt.d); assert(static_cast(maybe_pa)); auto pa = maybe_pa.get(); - auto note = notePt.note(nd.ivk).get(); + auto note = notePt.note(ivk).get(); saplingEntriesRet.emplace_back(op, pa, note, notePt.memo(), wtx->GetDepthInMainChain()); } } @@ -460,15 +465,19 @@ void SaplingScriptPubKeyMan::GetFilteredNotes( const SaplingOutPoint& op = it.first; const SaplingNoteData& nd = it.second; + // Skip sent notes + if (!nd.IsMyNote()) continue; + const libzcash::SaplingIncomingViewingKey& ivk = *(nd.ivk); + auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt( wtx.sapData->vShieldedOutput[op.n].encCiphertext, - nd.ivk, + ivk, wtx.sapData->vShieldedOutput[op.n].ephemeralKey, wtx.sapData->vShieldedOutput[op.n].cmu); assert(static_cast(maybe_pt)); auto notePt = maybe_pt.get(); - auto maybe_pa = nd.ivk.address(notePt.d); + auto maybe_pa = ivk.address(notePt.d); assert(static_cast(maybe_pa)); auto pa = maybe_pa.get(); @@ -491,7 +500,7 @@ void SaplingScriptPubKeyMan::GetFilteredNotes( // continue; //} - auto note = notePt.note(nd.ivk).get(); + auto note = notePt.note(ivk).get(); saplingEntries.emplace_back(op, pa, note, notePt.memo(), wtx.GetDepthInMainChain()); } } @@ -536,9 +545,13 @@ std::set> SaplingScriptPubKeyMan::G } for (const auto& txPair : wallet->mapWallet) { for (const auto & noteDataPair : txPair.second.mapSaplingNoteData) { - auto & noteData = noteDataPair.second; - auto & nullifier = noteData.nullifier; - auto & ivk = noteData.ivk; + const auto& noteData = noteDataPair.second; + + // Skip sent notes + if (!noteData.IsMyNote()) continue; + const libzcash::SaplingIncomingViewingKey& ivk = *(noteData.ivk); + + const auto& nullifier = noteData.nullifier; if (nullifier && ivkMap.count(ivk)) { for (const auto & addr : ivkMap[ivk]) { nullifierSet.insert(std::make_pair(addr, nullifier.get())); @@ -586,7 +599,7 @@ Optionalshield // tx for the first time, a failure to decode can happen only if this note was sent (from a t-addr) // using this wallet.dat on another computer (and never sent t->shield txes from this computer). - if (tx.vin.size() > 0) { + if (!tx.vin.empty()) { try { ovks.emplace(getCommonOVK()); } catch (...) { @@ -603,8 +616,9 @@ OptionalmapSaplingNoteData.find(prevOut); if (itPrev != txPrev->mapSaplingNoteData.end()) { const SaplingNoteData& noteData = itPrev->second; + if (!noteData.IsMyNote()) continue; libzcash::SaplingExtendedFullViewingKey extfvk; - if (wallet->GetSaplingFullViewingKey(noteData.ivk, extfvk)) { + if (wallet->GetSaplingFullViewingKey(*(noteData.ivk), extfvk)) { ovks.emplace(extfvk.fvk.ovk); } } @@ -616,22 +630,24 @@ Optionalfirst; - nCredit = note.value(); - // if it's not set, then set it. - wallet->mapWallet[tx.GetHash()].mapSaplingNoteData[op].amount = nCredit; + noteValue = note.value(); } else { // if cannot be decrypted, use RecoverSaplingNote. auto optNotePlainAndAddress = TryToRecoverNote(tx, op); if (optNotePlainAndAddress) { - nCredit += optNotePlainAndAddress->first.value(); + noteValue = optNotePlainAndAddress->first.value(); + } else { + return 0; } } - return nCredit; + // if it's not set, then set it. + wallet->mapWallet[tx.GetHash()].mapSaplingNoteData[op].amount = noteValue; + return noteValue; } isminetype SaplingScriptPubKeyMan::IsMine(const CWalletTx& wtx, const SaplingOutPoint& op) @@ -643,11 +659,10 @@ isminetype SaplingScriptPubKeyMan::IsMine(const CWalletTx& wtx, const SaplingOut CAmount SaplingScriptPubKeyMan::GetOutPointValue(const CWalletTx& tx, const SaplingOutPoint& op) { const auto& it = tx.mapSaplingNoteData.find(op); - if (it == tx.mapSaplingNoteData.end()) { - return 0; + if (it != tx.mapSaplingNoteData.end() && it->second.amount) { + return *(it->second.amount); } - SaplingNoteData noteData = it->second; - return (noteData.amount) ? *noteData.amount : TryToRecoverAndSetAmount(tx, op); + return TryToRecoverAndSetAmount(tx, op); } CAmount SaplingScriptPubKeyMan::GetCredit(const CWalletTx& tx, const isminefilter& filter, const bool fUnspent) diff --git a/src/sapling/saplingscriptpubkeyman.h b/src/sapling/saplingscriptpubkeyman.h index 8705913ac3d5..e080f4b57dfd 100644 --- a/src/sapling/saplingscriptpubkeyman.h +++ b/src/sapling/saplingscriptpubkeyman.h @@ -45,8 +45,11 @@ class SaplingNoteData SaplingNoteData(const libzcash::SaplingIncomingViewingKey& _ivk) : ivk {_ivk}, nullifier() { } SaplingNoteData(const libzcash::SaplingIncomingViewingKey& _ivk, const uint256& n) : ivk {_ivk}, nullifier(n) { } + /* witnesses/ivk: only for own (received) outputs */ std::list witnesses; - libzcash::SaplingIncomingViewingKey ivk; + Optional ivk {nullopt}; + inline bool IsMyNote() const { return ivk != nullopt; } + /** * Cached note amount. * It will be loaded the first time that the note is decrypted. diff --git a/src/test/librust/sapling_wallet_tests.cpp b/src/test/librust/sapling_wallet_tests.cpp index 9bb3ae909edf..2e5941fe1f47 100644 --- a/src/test/librust/sapling_wallet_tests.cpp +++ b/src/test/librust/sapling_wallet_tests.cpp @@ -117,7 +117,8 @@ BOOST_AUTO_TEST_CASE(SetSaplingNoteAddrsInCWalletTx) { BOOST_CHECK(noteData == wtx.mapSaplingNoteData); // Test individual fields in case equality operator is defined/changed. - BOOST_CHECK(ivk == wtx.mapSaplingNoteData[op].ivk); + BOOST_CHECK(wtx.mapSaplingNoteData[op].IsMyNote()); + BOOST_CHECK(ivk == *(wtx.mapSaplingNoteData[op].ivk)); BOOST_CHECK(nullifier == wtx.mapSaplingNoteData[op].nullifier); BOOST_CHECK(nd.witnessHeight == wtx.mapSaplingNoteData[op].witnessHeight); BOOST_CHECK(witness == wtx.mapSaplingNoteData[op].witnesses.front()); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 3a9859e88a35..da1044691a81 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1323,6 +1323,10 @@ UniValue viewshieldedtransaction(const JSONRPCRequest& request) + HelpExampleRpc("viewshieldedtransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") ); + if (!pwalletMain->HasSaplingSPKM()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Sapling wallet not initialized."); + } + EnsureWalletIsUnlocked(); LOCK2(cs_main, pwalletMain->cs_wallet); @@ -1374,48 +1378,54 @@ UniValue viewshieldedtransaction(const JSONRPCRequest& request) if (res == sspkm->mapSaplingNullifiersToNotes.end()) { continue; } - auto op = res->second; - auto wtxPrev = pwalletMain->mapWallet.at(op.hash); - - auto decrypted = wtxPrev.DecryptSaplingNote(op).get(); - auto notePt = decrypted.first; - auto pa = decrypted.second; - - // Store the OutgoingViewingKey for recovering outputs - libzcash::SaplingExtendedFullViewingKey extfvk; - assert(pwalletMain->GetSaplingFullViewingKey(wtxPrev.mapSaplingNoteData.at(op).ivk, extfvk)); - ovks.insert(extfvk.fvk.ovk); + const auto& op = res->second; + std::string addrStr = "unknown"; + UniValue amountStr = UniValue("unknown"); + CAmount amount = 0; + auto wtxPrevIt = pwalletMain->mapWallet.find(op.hash); + if (wtxPrevIt != pwalletMain->mapWallet.end()) { + const auto ndIt = wtxPrevIt->second.mapSaplingNoteData.find(op); + if (ndIt != wtxPrevIt->second.mapSaplingNoteData.end()) { + // get cached address and amount + if (ndIt->second.address) { + addrStr = KeyIO::EncodePaymentAddress(*(ndIt->second.address)); + } + if (ndIt->second.amount) { + amount = *(ndIt->second.amount); + amountStr = ValueFromAmount(amount); + } + } + } UniValue entry_(UniValue::VOBJ); entry_.pushKV("spend", (int)i); entry_.pushKV("txidPrev", op.hash.GetHex()); entry_.pushKV("outputPrev", (int)op.n); - entry_.pushKV("address", KeyIO::EncodePaymentAddress(pa)); - entry_.pushKV("value", ValueFromAmount(notePt.value())); - entry_.pushKV("valueSat", notePt.value()); + entry_.pushKV("address", addrStr); + entry_.pushKV("value", amountStr); + entry_.pushKV("valueSat", amount); spends.push_back(entry_); } // Sapling outputs for (uint32_t i = 0; i < wtx.sapData->vShieldedOutput.size(); ++i) { auto op = SaplingOutPoint(hash, i); - + if (!wtx.mapSaplingNoteData.count(op)) continue; libzcash::SaplingNotePlaintext notePt; libzcash::SaplingPaymentAddress pa; - bool isOutgoing; - auto decrypted = wtx.DecryptSaplingNote(op); - if (decrypted) { + const bool isOutgoing = !wtx.mapSaplingNoteData.at(op).IsMyNote(); + if (!isOutgoing) { + auto decrypted = wtx.DecryptSaplingNote(op); + assert(decrypted); notePt = decrypted->first; pa = decrypted->second; - isOutgoing = false; } else { - // Try recovering the output - auto recovered = wtx.RecoverSaplingNote(op, ovks); + // Try recovering with the inputs ovk + auto recovered = sspkm->TryToRecoverNote(wtx, op); if (recovered) { notePt = recovered->first; pa = recovered->second; - isOutgoing = true; } else { // Unreadable continue; @@ -3944,7 +3954,9 @@ UniValue getsaplingnotescount(const JSONRPCRequest& request) int count = 0; for (const auto& wtx : pwalletMain->mapWallet) { if (wtx.second.GetDepthInMainChain() >= nMinDepth) { - count += wtx.second.mapSaplingNoteData.size(); + for (const auto& nd : wtx.second.mapSaplingNoteData) { + if (nd.second.IsMyNote()) count++; + } } } return count; diff --git a/src/wallet/test/wallet_shielded_balances_tests.cpp b/src/wallet/test/wallet_shielded_balances_tests.cpp index dfa9c51ecc2c..490e492e5da3 100644 --- a/src/wallet/test/wallet_shielded_balances_tests.cpp +++ b/src/wallet/test/wallet_shielded_balances_tests.cpp @@ -32,6 +32,7 @@ CWalletTx& SetWalletNotesData(CWallet* wallet, CWalletTx& wtx) { Optional saplingNoteData{nullopt}; wallet->FindNotesDataAndAddMissingIVKToKeystore(wtx, saplingNoteData); + assert(static_cast(saplingNoteData)); wtx.SetSaplingNoteData(*saplingNoteData); BOOST_CHECK(wallet->AddToWallet(wtx)); // Updated tx @@ -88,15 +89,17 @@ struct SaplingSpendValues { SaplingSpendValues UpdateWalletInternalNotesData(CWalletTx& wtx, SaplingOutPoint& sapPoint, CWallet& wallet) { // Get note - SaplingNoteData nd = wtx.mapSaplingNoteData[sapPoint]; + SaplingNoteData nd = wtx.mapSaplingNoteData.at(sapPoint); + assert(nd.IsMyNote()); + const auto& ivk = *(nd.ivk); auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt( wtx.sapData->vShieldedOutput[sapPoint.n].encCiphertext, - nd.ivk, + ivk, wtx.sapData->vShieldedOutput[sapPoint.n].ephemeralKey, wtx.sapData->vShieldedOutput[sapPoint.n].cmu); assert(static_cast(maybe_pt)); boost::optional notePlainText = maybe_pt.get(); - libzcash::SaplingNote note = notePlainText->note(nd.ivk).get(); + libzcash::SaplingNote note = notePlainText->note(ivk).get(); // Append note to the tree auto commitment = note.cmu().get(); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 404473db83d4..334709c3aa4f 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1037,6 +1037,25 @@ bool CWallet::FindNotesDataAndAddMissingIVKToKeystore(const CTransaction& tx, Op return true; } +void CWallet::AddExternalNotesDataToTx(CWalletTx& wtx) const +{ + if (HasSaplingSPKM() && wtx.IsShieldedTx()) { + const uint256& txId = wtx.GetHash(); + // Add the external outputs. + SaplingOutPoint op {txId, 0}; + for (unsigned int i = 0; i < wtx.sapData->vShieldedOutput.size(); i++) { + op.n = i; + if (wtx.mapSaplingNoteData.count(op)) continue; // internal output + auto recovered = GetSaplingScriptPubKeyMan()->TryToRecoverNote(wtx, op); + if (recovered) { + // Always true for 'IsFromMe' transactions + wtx.mapSaplingNoteData[op].address = recovered->second; + wtx.mapSaplingNoteData[op].amount = recovered->first.value(); + } + } + } +} + /** * Add a transaction to the wallet, or update it. * pblock is optional, but should be provided if the transaction is known to be in a block. @@ -1071,7 +1090,8 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const uint256& bl } } - if (fExisted || IsMine(tx) || IsFromMe(tx) || (saplingNoteData && !saplingNoteData->empty())) { + bool isFromMe = IsFromMe(tx); + if (fExisted || IsMine(tx) || isFromMe || (saplingNoteData && !saplingNoteData->empty())) { /* Check if any keys in the wallet keypool that were supposed to be unused * have appeared in a new transaction. If so, remove those keys from the keypool. @@ -1085,9 +1105,13 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const uint256& bl } CWalletTx wtx(this, tx); + if (wtx.IsShieldedTx()) { + if (saplingNoteData && !saplingNoteData->empty()) { + wtx.SetSaplingNoteData(*saplingNoteData); + } - if (saplingNoteData && !saplingNoteData->empty()) { - wtx.SetSaplingNoteData(*saplingNoteData); + // Add external notes info if we are sending + if (isFromMe) AddExternalNotesDataToTx(wtx); } // Get merkle branch if transaction was found in a block @@ -4629,8 +4653,9 @@ Optional> CWalletTx::DecryptSaplingNote(SaplingOutPoint op) const { - // Check whether we can decrypt this SaplingOutPoint - if (this->mapSaplingNoteData.count(op) == 0) { + // Check whether we can decrypt this SaplingOutPoint with the ivk + auto it = this->mapSaplingNoteData.find(op); + if (it == this->mapSaplingNoteData.end() || !it->second.IsMyNote()) { return nullopt; } @@ -4639,13 +4664,13 @@ Optional(maybe_pt)); auto notePt = maybe_pt.get(); - auto maybe_pa = nd.ivk.address(notePt.d); + auto maybe_pa = nd.ivk->address(notePt.d); assert(static_cast(maybe_pa)); auto pa = maybe_pa.get(); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index c67fd8e9039c..223c792fecd5 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -518,6 +518,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface // Search for notes and addresses from this wallet in the tx, and add the addresses --> IVK mapping to the keystore if missing. bool FindNotesDataAndAddMissingIVKToKeystore(const CTransaction& tx, Optional& saplingNoteData); + // Decrypt sapling output notes with the inputs ovk and updates saplingNoteDataMap + void AddExternalNotesDataToTx(CWalletTx& wtx) const; //! Generates new Sapling key libzcash::SaplingPaymentAddress GenerateNewSaplingZKey(std::string label = ""); From 2c561710e48bee23597ab5f1fa31084cf829a699 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 27 Nov 2020 18:07:45 +0100 Subject: [PATCH 107/121] Wallet: Add the memo to SaplingNoteData --- src/sapling/saplingscriptpubkeyman.cpp | 1 + src/sapling/saplingscriptpubkeyman.h | 10 ++++++++-- src/wallet/wallet.cpp | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index fe76e3bb1163..fd1fa7bbb1db 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -344,6 +344,7 @@ std::pair SaplingScriptPubKe nd.ivk = ivk; nd.amount = result->value(); nd.address = address; + nd.memo = result->memo(); noteData.insert(std::make_pair(op, nd)); break; } diff --git a/src/sapling/saplingscriptpubkeyman.h b/src/sapling/saplingscriptpubkeyman.h index e080f4b57dfd..9d40592723a5 100644 --- a/src/sapling/saplingscriptpubkeyman.h +++ b/src/sapling/saplingscriptpubkeyman.h @@ -52,16 +52,22 @@ class SaplingNoteData /** * Cached note amount. - * It will be loaded the first time that the note is decrypted. + * It will be loaded the first time that the note is decrypted (when the tx is added to the wallet). */ Optional amount{nullopt}; /** * Cached shielded address - * It will be loaded the first time that the note is decrypted + * It will be loaded the first time that the note is decrypted (when the tx is added to the wallet) */ Optional address{nullopt}; + /** + * Cached note memo (only for non-empty memo) + * It will be loaded the first time that the note is decrypted (when the tx is added to the wallet) + */ + Optional> memo; + /** * Block height corresponding to the most current witness. * diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 334709c3aa4f..a4cbf4ffafa3 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1051,6 +1051,7 @@ void CWallet::AddExternalNotesDataToTx(CWalletTx& wtx) const // Always true for 'IsFromMe' transactions wtx.mapSaplingNoteData[op].address = recovered->second; wtx.mapSaplingNoteData[op].amount = recovered->first.value(); + wtx.mapSaplingNoteData[op].memo = recovered->first.memo(); } } } From 6235acb41fd68dea46e1d58fadff32579d4a85d2 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 27 Nov 2020 18:44:29 +0100 Subject: [PATCH 108/121] Refactor: remove extra decrypt operations Just rely on mapSaplingNoteData --- src/qt/pivx/sendconfirmdialog.cpp | 4 +- src/qt/transactionrecord.cpp | 4 +- src/sapling/saplingscriptpubkeyman.cpp | 114 ++++++++----------------- src/sapling/saplingscriptpubkeyman.h | 9 +- src/wallet/rpcwallet.cpp | 46 +++++----- 5 files changed, 64 insertions(+), 113 deletions(-) diff --git a/src/qt/pivx/sendconfirmdialog.cpp b/src/qt/pivx/sendconfirmdialog.cpp index 35fdfaa0394a..a8f41186d161 100644 --- a/src/qt/pivx/sendconfirmdialog.cpp +++ b/src/qt/pivx/sendconfirmdialog.cpp @@ -306,9 +306,9 @@ void TxDetailDialog::onOutputsClicked() // Obtain the noteData to get the cached amount value SaplingNoteData noteData = walletTx->mapSaplingNoteData.at(op); Optional opAddr = - pwalletMain->GetSaplingScriptPubKeyMan()->GetShieldedAddressFrom(*walletTx, op); + pwalletMain->GetSaplingScriptPubKeyMan()->GetOutPointAddress(*walletTx, op); - QString labelRes = QString::fromStdString(Standard::EncodeDestination(*opAddr)); + QString labelRes = opAddr ? QString::fromStdString(Standard::EncodeDestination(*opAddr)) : ""; labelRes = labelRes.left(18) + "..." + labelRes.right(18); appendOutput(layoutGrid, i, labelRes, *noteData.amount, nDisplayUnit); diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 365da410f393..d384d9aba5b8 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -211,7 +211,7 @@ bool TransactionRecord::decomposeCreditTransaction(const CWallet* wallet, const auto sspkm = wallet->GetSaplingScriptPubKeyMan(); for (int i = 0; i < (int) wtx.sapData->vShieldedOutput.size(); ++i) { SaplingOutPoint out(sub.hash, i); - auto opAddr = sspkm->GetShieldedAddressFrom(wtx, out); + auto opAddr = sspkm->GetOutPointAddress(wtx, out); if (opAddr) { // skip it if change if (sspkm->IsNoteSaplingChange(out, *opAddr)) { @@ -294,7 +294,7 @@ bool TransactionRecord::decomposeShieldedDebitTransaction(const CWallet* wallet, bool feeAdded = false; for (int i = 0; i < (int) wtx.sapData->vShieldedOutput.size(); ++i) { SaplingOutPoint out(sub.hash, i); - auto opAddr = sspkm->GetShieldedAddressFrom(wtx, out); + auto opAddr = sspkm->GetOutPointAddress(wtx, out); // skip change if (!opAddr || sspkm->IsNoteSaplingChange(out, *opAddr)) { continue; diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index fd1fa7bbb1db..b1ac11140a12 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -563,27 +563,20 @@ std::set> SaplingScriptPubKeyMan::G return nullifierSet; } -Optional SaplingScriptPubKeyMan::GetShieldedAddressFrom(const CWalletTx& tx, const SaplingOutPoint& op) +Optional SaplingScriptPubKeyMan::GetOutPointAddress(const CWalletTx& tx, const SaplingOutPoint& op) { - // Try first using the cached data if available - const auto& it = tx.mapSaplingNoteData.find(op); - if (it != tx.mapSaplingNoteData.end() && it->second.address) { - return it->second.address; - } - - // Try to decrypt it using the note data ivk (if exists) - auto noteAndAddress = tx.DecryptSaplingNote(op); - if (noteAndAddress) { - return Optional(noteAndAddress->second); + if (!tx.mapSaplingNoteData.count(op)) { + return nullopt; } + return tx.mapSaplingNoteData.at(op).address; +} - // Try to recover it with the ovks - Optional optAddRet = nullopt; - auto optNotePlainAndAddress = TryToRecoverNote(tx, op); - if (optNotePlainAndAddress) { - optAddRet = optNotePlainAndAddress->second; +CAmount SaplingScriptPubKeyMan::GetOutPointValue(const CWalletTx& tx, const SaplingOutPoint& op) +{ + if (!tx.mapSaplingNoteData.count(op)) { + return 0; } - return optAddRet; + return tx.mapSaplingNoteData.at(op).amount ? *(tx.mapSaplingNoteData.at(op).amount) : 0; } Optionalfirst; - noteValue = note.value(); - } else { - // if cannot be decrypted, use RecoverSaplingNote. - auto optNotePlainAndAddress = TryToRecoverNote(tx, op); - if (optNotePlainAndAddress) { - noteValue = optNotePlainAndAddress->first.value(); - } else { - return 0; - } - } - // if it's not set, then set it. - wallet->mapWallet[tx.GetHash()].mapSaplingNoteData[op].amount = noteValue; - return noteValue; -} - isminetype SaplingScriptPubKeyMan::IsMine(const CWalletTx& wtx, const SaplingOutPoint& op) { - Optional pa = GetShieldedAddressFrom(wtx, op); - return pa ? ::IsMine(*wallet, *pa) : ISMINE_NO; -} - -CAmount SaplingScriptPubKeyMan::GetOutPointValue(const CWalletTx& tx, const SaplingOutPoint& op) -{ - const auto& it = tx.mapSaplingNoteData.find(op); - if (it != tx.mapSaplingNoteData.end() && it->second.amount) { - return *(it->second.amount); + auto ndIt = wtx.mapSaplingNoteData.find(op); + if (ndIt != wtx.mapSaplingNoteData.end() && ndIt->second.IsMyNote()) { + const auto addr = ndIt->second.address; + return (static_cast(addr) && HaveSpendingKeyForPaymentAddress(*addr)) ? + ISMINE_SPENDABLE_SHIELDED : ISMINE_WATCH_ONLY_SHIELDED; } - return TryToRecoverAndSetAmount(tx, op); + return ISMINE_NO; } CAmount SaplingScriptPubKeyMan::GetCredit(const CWalletTx& tx, const isminefilter& filter, const bool fUnspent) @@ -684,8 +651,7 @@ CAmount SaplingScriptPubKeyMan::GetCredit(const CWalletTx& tx, const isminefilte } // todo: check if we can spend this note or not. (if not, then it's a watch only) - // Check whether the note value was already cached or needs to be loaded - nCredit += noteData.amount ? *noteData.amount : TryToRecoverAndSetAmount(tx, op); + nCredit += noteData.amount ? *noteData.amount : 0; } return nCredit; } @@ -696,21 +662,16 @@ CAmount SaplingScriptPubKeyMan::GetDebit(const CTransaction& tx, const isminefil for (const SpendDescription& spend : tx.sapData->vShieldedSpend) { const auto &it = mapSaplingNullifiersToNotes.find(spend.nullifier); if (it != mapSaplingNullifiersToNotes.end()) { - // If we have the sapling output means that this input is mine. - SaplingOutPoint op = it->second; - const auto& itTx = wallet->mapWallet.find(op.hash); - if (itTx == wallet->mapWallet.end()) { - continue; - } - - // Now try to decrypt the note (it should never fail if it reach to this point, mapSaplingNullifiersToNotes is loaded after the note decryption) - Optional> decryptedNote = itTx->second.DecryptSaplingNote(op); - - // todo: Add watch only check. - CAmount value = decryptedNote->first.value(); - nDebit += value; + // If we have the spend nullifier, it means that this input is ours. + // The transaction (and decrypted note data) has been added to the wallet. + const SaplingOutPoint& op = it->second; + assert(wallet->mapWallet.count(op.hash)); + const auto& wtx = wallet->mapWallet.at(op.hash); + assert(wtx.mapSaplingNoteData.count(op)); + const auto& nd = wtx.mapSaplingNoteData.at(op); + assert(nd.IsMyNote()); // todo: Add watch only check. + assert(static_cast(nd.amount)); + nDebit += *(nd.amount); if (!Params().GetConsensus().MoneyRange(nDebit)) throw std::runtime_error("SaplingScriptPubKeyMan::GetDebit() : value out of range"); } @@ -725,19 +686,16 @@ CAmount SaplingScriptPubKeyMan::GetShieldedChange(const CWalletTx& wtx) } const uint256& txHash = wtx.GetHash(); CAmount nChange = 0; - SaplingOutPoint sapOutPoint{txHash, 0}; + SaplingOutPoint op{txHash, 0}; for (uint32_t pos = 0; pos < (uint32_t) wtx.sapData->vShieldedOutput.size(); ++pos) { - sapOutPoint.n = pos; - const auto noteAndAddress = wtx.DecryptSaplingNote(sapOutPoint); - if (noteAndAddress) { - const libzcash::SaplingNotePlaintext& notePlaintext = noteAndAddress->first; - const libzcash::SaplingPaymentAddress& pa = noteAndAddress->second; - - if (IsNoteSaplingChange(sapOutPoint, pa)) { - nChange += notePlaintext.value(); - if (!Params().GetConsensus().MoneyRange(nChange)) - throw std::runtime_error("GetShieldedChange() : value out of range"); - } + op.n = pos; + if (!wtx.mapSaplingNoteData.count(op)) continue; + const auto& nd = wtx.mapSaplingNoteData.at(op); + if (!nd.IsMyNote() || !static_cast(nd.address) || !static_cast(nd.amount)) continue; + if (IsNoteSaplingChange(op, *(nd.address))) { + nChange += *(nd.amount); + if (!Params().GetConsensus().MoneyRange(nChange)) + throw std::runtime_error("GetShieldedChange() : value out of range"); } } return nChange; diff --git a/src/sapling/saplingscriptpubkeyman.h b/src/sapling/saplingscriptpubkeyman.h index 9d40592723a5..1c88006ccb4a 100644 --- a/src/sapling/saplingscriptpubkeyman.h +++ b/src/sapling/saplingscriptpubkeyman.h @@ -294,11 +294,6 @@ class SaplingScriptPubKeyMan { std::set> GetNullifiersForAddresses(const std::set & addresses); bool IsNoteSaplingChange(const std::set>& nullifierSet, const libzcash::PaymentAddress& address, const SaplingOutPoint& entry); - //! Decrypt the encrypted note and return the address if possible - Optional GetShieldedAddressFrom(const CWalletTx& tx, const SaplingOutPoint& op); - - //! Try to decrypt the note and load the amount into the always available SaplingNoteData - CAmount TryToRecoverAndSetAmount(const CWalletTx& tx, const SaplingOutPoint& op); //! Try to recover the note using the wallet's ovks (mostly used when the outpoint is a debit) Optional GetOutPointAddress(const CWalletTx& tx, const SaplingOutPoint& op); + //! Return the shielded value of a specific outpoint of wallet transaction CAmount GetOutPointValue(const CWalletTx& tx, const SaplingOutPoint& op); //! Return the shielded credit of the tx CAmount GetCredit(const CWalletTx& tx, const isminefilter& filter, const bool fUnspent = false); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index da1044691a81..942426896a87 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1347,7 +1347,7 @@ UniValue viewshieldedtransaction(const JSONRPCRequest& request) UniValue spends(UniValue::VARR); UniValue outputs(UniValue::VARR); - auto addMemo = [](UniValue &entry, std::array &memo) { + auto addMemo = [](UniValue& entry, const std::array& memo) { auto end = FindFirstNonZero(memo.rbegin(), memo.rend()); entry.pushKV("memo", HexStr(memo.begin(), end.base())); // If the leading byte is 0xF4 or lower, the memo field should be interpreted as a @@ -1411,35 +1411,31 @@ UniValue viewshieldedtransaction(const JSONRPCRequest& request) for (uint32_t i = 0; i < wtx.sapData->vShieldedOutput.size(); ++i) { auto op = SaplingOutPoint(hash, i); if (!wtx.mapSaplingNoteData.count(op)) continue; - libzcash::SaplingNotePlaintext notePt; - libzcash::SaplingPaymentAddress pa; - - const bool isOutgoing = !wtx.mapSaplingNoteData.at(op).IsMyNote(); - if (!isOutgoing) { - auto decrypted = wtx.DecryptSaplingNote(op); - assert(decrypted); - notePt = decrypted->first; - pa = decrypted->second; - } else { - // Try recovering with the inputs ovk - auto recovered = sspkm->TryToRecoverNote(wtx, op); - if (recovered) { - notePt = recovered->first; - pa = recovered->second; - } else { - // Unreadable - continue; - } + const auto& nd = wtx.mapSaplingNoteData.at(op); + + const bool isOutgoing = !nd.IsMyNote(); + std::string addrStr = "unknown"; + UniValue amountStr = UniValue("unknown"); + CAmount amount = 0; + if (nd.address) { + addrStr = KeyIO::EncodePaymentAddress(*(nd.address)); + } + if (nd.amount) { + amount = *(nd.amount); + amountStr = ValueFromAmount(amount); } - auto memo = notePt.memo(); UniValue entry_(UniValue::VOBJ); entry_.pushKV("output", (int)op.n); entry_.pushKV("outgoing", isOutgoing); - entry_.pushKV("address", KeyIO::EncodePaymentAddress(pa)); - entry_.pushKV("value", ValueFromAmount(notePt.value())); - entry_.pushKV("valueSat", notePt.value()); - addMemo(entry_, memo); + entry_.pushKV("address", addrStr); + entry_.pushKV("value", amountStr); + entry_.pushKV("valueSat", amount); + + if (nd.memo) { + addMemo(entry_, *(nd.memo)); + } + outputs.push_back(entry_); } From c171cb22e55e1d7d8a0751b93488b35c7b2fb5ea Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 27 Nov 2020 21:16:46 +0100 Subject: [PATCH 109/121] DB: include new cache data of SaplingNoteData in the serialization --- src/sapling/saplingscriptpubkeyman.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/sapling/saplingscriptpubkeyman.h b/src/sapling/saplingscriptpubkeyman.h index 1c88006ccb4a..efcd9fe6767c 100644 --- a/src/sapling/saplingscriptpubkeyman.h +++ b/src/sapling/saplingscriptpubkeyman.h @@ -106,10 +106,18 @@ class SaplingNoteData READWRITE(nullifier); READWRITE(witnesses); READWRITE(witnessHeight); + READWRITE(amount); + READWRITE(address); + READWRITE(memo); } friend bool operator==(const SaplingNoteData& a, const SaplingNoteData& b) { - return (a.ivk == b.ivk && a.nullifier == b.nullifier && a.witnessHeight == b.witnessHeight); + return (a.ivk == b.ivk && + a.nullifier == b.nullifier && + a.witnessHeight == b.witnessHeight && + a.amount == b.amount && + a.address == b.address && + a.memo == b.memo); } friend bool operator!=(const SaplingNoteData& a, const SaplingNoteData& b) { From c26c65ddc03611ca119546cd5bbe0c272b29f8ca Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sat, 28 Nov 2020 02:33:59 +0100 Subject: [PATCH 110/121] GUI: Add SPORK_20 check in sendCoins --- src/qt/pivx/send.cpp | 11 +++++++++-- src/qt/walletmodel.cpp | 11 ++++++++--- src/qt/walletmodel.h | 1 + 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index 8f1ee9d2e86d..a83f328590bb 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -377,6 +377,13 @@ void SendWidget::onSendClicked() void SendWidget::ProcessSend(const QList& recipients, bool hasShieldedOutput) { + // First check SPORK_20 (before unlock) + bool isShieldedTx = hasShieldedOutput || !isTransparent; + if (isShieldedTx && walletModel->isSaplingInMaintenance()) { + inform(tr("Sapling Protocol temporarily in maintenance. Shielded transactions disabled (SPORK 20)")); + return; + } + auto ptrUnlockedContext = MakeUnique(walletModel->requestUnlock()); if (!ptrUnlockedContext->isValid()) { // Unlock wallet was cancelled @@ -390,9 +397,9 @@ void SendWidget::ProcessSend(const QList& recipients, bool h return; } ptrModelTx = new WalletModelTransaction(recipients); - ptrModelTx->useV2 = hasShieldedOutput || !isTransparent; + ptrModelTx->useV2 = isShieldedTx; - // First prepare tx + // Prepare tx window->showHide(true); LoadingDialog *dialog = new LoadingDialog(window, tr("Preparing transaction")); dialog->execute(this, REQUEST_PREPARE_TX, std::move(ptrUnlockedContext)); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index a616fe841c4e..46f258652c72 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -71,6 +71,11 @@ bool WalletModel::isColdStakingNetworkelyEnabled() const return !sporkManager.IsSporkActive(SPORK_19_COLDSTAKING_MAINTENANCE); } +bool WalletModel::isSaplingInMaintenance() const +{ + return sporkManager.IsSporkActive(SPORK_20_SAPLING_MAINTENANCE); +} + bool WalletModel::isStakingStatusActive() const { return wallet && wallet->pStakerStatus && wallet->pStakerStatus->IsActive(); @@ -457,15 +462,15 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction& tran bool fColdStakingActive = isColdStakingNetworkelyEnabled(); bool fSaplingActive = Params().GetConsensus().NetworkUpgradeActive(cachedNumBlocks, Consensus::UPGRADE_V5_DUMMY); - // Double check tx before do anything + // Double check the tx before doing anything + CWalletTx* newTx = transaction.getTransaction(); CValidationState state; - if (!CheckTransaction(*transaction.getTransaction(), true, true, state, true, fColdStakingActive, fSaplingActive)) { + if (!CheckTransaction(*newTx, true, true, state, true, fColdStakingActive, fSaplingActive)) { return TransactionCheckFailed; } { LOCK2(cs_main, wallet->cs_wallet); - CWalletTx* newTx = transaction.getTransaction(); QList recipients = transaction.getRecipients(); // Store PaymentRequests in wtx.vOrderForm in wallet. diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index ee35abeb71da..97cbdda08bb4 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -150,6 +150,7 @@ class WalletModel : public QObject bool isRegTestNetwork() const; /** Whether cold staking is enabled or disabled in the network **/ bool isColdStakingNetworkelyEnabled() const; + bool isSaplingInMaintenance() const; CAmount getMinColdStakingAmount() const; /* current staking status from the miner thread **/ bool isStakingStatusActive() const; From 6b458aeede38569729479b5ed47e582e5203ab82 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sat, 28 Nov 2020 03:57:56 +0100 Subject: [PATCH 111/121] BUG: fix correct fee/amount in decomposeShieldedDebitTransaction --- src/qt/transactionrecord.cpp | 15 ++++++++------- src/qt/transactionrecord.h | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index d384d9aba5b8..692dba226eea 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -281,7 +281,7 @@ bool TransactionRecord::decomposeSendToSelfTransaction(const CWalletTx& wtx, con return true; } -bool TransactionRecord::decomposeShieldedDebitTransaction(const CWallet* wallet, const CWalletTx& wtx, +bool TransactionRecord::decomposeShieldedDebitTransaction(const CWallet* wallet, const CWalletTx& wtx, CAmount nTxFee, bool involvesWatchAddress, QList& parts) { // Return early if there are no outputs. @@ -291,7 +291,6 @@ bool TransactionRecord::decomposeShieldedDebitTransaction(const CWallet* wallet, TransactionRecord sub(wtx.GetHash(), wtx.GetTxTime(), wtx.GetTotalSize()); auto sspkm = wallet->GetSaplingScriptPubKeyMan(); - bool feeAdded = false; for (int i = 0; i < (int) wtx.sapData->vShieldedOutput.size(); ++i) { SaplingOutPoint out(sub.hash, i); auto opAddr = sspkm->GetOutPointAddress(wtx, out); @@ -305,9 +304,9 @@ bool TransactionRecord::decomposeShieldedDebitTransaction(const CWallet* wallet, sub.address = KeyIO::EncodePaymentAddress(*opAddr); CAmount nValue = sspkm->GetOutPointValue(wtx, out); /* Add fee to first output */ - if (!feeAdded) { // future, move from the hardcoded fee. - nValue += COIN; - feeAdded = true; + if (nTxFee > 0) { + nValue += nTxFee; + nTxFee = 0; } sub.debit = -nValue; parts.append(sub); @@ -327,7 +326,9 @@ bool TransactionRecord::decomposeDebitTransaction(const CWallet* wallet, const C return false; } - CAmount nTxFee = nDebit - wtx.GetValueOut(); + // GetValueOut is the sum of transparent outs and negative sapValueBalance (shielded outs minus shielded spends). + // Therefore to get the sum of the whole outputs of the tx, must re-add the shielded inputs spent to it + CAmount nTxFee = nDebit - (wtx.GetValueOut() + wtx.GetDebit(ISMINE_SPENDABLE_SHIELDED | ISMINE_WATCH_ONLY_SHIELDED)); unsigned int txSize = wtx.GetTotalSize(); const uint256& txHash = wtx.GetHash(); const int64_t txTime = wtx.GetTxTime(); @@ -377,7 +378,7 @@ bool TransactionRecord::decomposeDebitTransaction(const CWallet* wallet, const C } // Decompose shielded debit - return decomposeShieldedDebitTransaction(wallet, wtx, involvesWatchAddress, parts); + return decomposeShieldedDebitTransaction(wallet, wtx, nTxFee, involvesWatchAddress, parts); } // Check whether all the shielded inputs and outputs are from and send to this wallet diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h index cf30455a2725..13887d9f3926 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -145,7 +145,7 @@ class TransactionRecord const CAmount& nDebit, bool involvesWatchAddress, QList& parts); - static bool decomposeShieldedDebitTransaction(const CWallet* wallet, const CWalletTx& wtx, + static bool decomposeShieldedDebitTransaction(const CWallet* wallet, const CWalletTx& wtx, CAmount nTxFee, bool involvesWatchAddress, QList& parts); static std::string getValueOrReturnEmpty(const std::map& mapValue, const std::string& key); From d2faa048b6166351b2b202828f78c5300fe08f7c Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sat, 28 Nov 2020 13:58:01 +0100 Subject: [PATCH 112/121] BUG: fix SSPKM::GetCredit counting externally sent notes Thus making the GUI display a balance higher than it should. --- src/sapling/saplingscriptpubkeyman.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index b1ac11140a12..4f4975cc4427 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -644,6 +644,9 @@ CAmount SaplingScriptPubKeyMan::GetCredit(const CWalletTx& tx, const isminefilte // Obtain the noteData and check if the nullifier has being spent or not SaplingNoteData noteData = tx.mapSaplingNoteData.at(op); + // Skip externally sent notes + if (!noteData.IsMyNote()) continue; + // The nullifier could be null if the wallet was locked when the noteData was created. if (noteData.nullifier && (fUnspent && IsSaplingSpent(*noteData.nullifier))) { From 43c1e4b882abc462f33ab4b0089aa9ed74f322d1 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sat, 28 Nov 2020 14:32:50 +0100 Subject: [PATCH 113/121] GUI: connect labels calculation in coin control for shielded outputs --- src/qt/coincontroldialog.cpp | 81 ++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index b2f42d2bdcd1..53e08f8ac807 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -505,11 +505,6 @@ void CoinControlDialog::updateLabels() } } - // TODO: Connect labels calculation in the future.. - if (!fSelectTransparent) { - return; - } - CAmount nAmount = 0; CAmount nPayFee = 0; CAmount nAfterFee = 0; @@ -523,25 +518,36 @@ void CoinControlDialog::updateLabels() coinControl->ListSelected(vCoinControl); model->getOutputs(vCoinControl, vOutputs); - for (const COutput& out : vOutputs) { - // unselect already spent, very unlikely scenario, this could happen - // when selected are spent elsewhere, like rpc or another computer - uint256 txhash = out.tx->GetHash(); - COutPoint outpt(txhash, out.i); - if (model->isSpent(outpt)) { - coinControl->UnSelect(outpt); - continue; - } + if (fSelectTransparent) { + for (const COutput& out : vOutputs) { + // unselect already spent, very unlikely scenario, this could happen + // when selected are spent elsewhere, like rpc or another computer + uint256 txhash = out.tx->GetHash(); + COutPoint outpt(txhash, out.i); + if (model->isSpent(outpt)) { + coinControl->UnSelect(outpt); + continue; + } - // Quantity - nQuantity++; - // Amount - nAmount += out.tx->vout[out.i].nValue; - // Bytes - nBytesInputs += 148; - // Additional byte for P2CS - if (out.tx->vout[out.i].scriptPubKey.IsPayToColdStaking()) - nBytesInputs++; + // Quantity + nQuantity++; + // Amount + nAmount += out.tx->vout[out.i].nValue; + // Bytes + nBytesInputs += CTXIN_SPEND_DUST_SIZE; + // Additional byte for P2CS + if (out.tx->vout[out.i].scriptPubKey.IsPayToColdStaking()) + nBytesInputs++; + } + } else { + for (const OutPointWrapper& out : vCoinControl) { + // Quantity + nQuantity++; + // Amount + nAmount += out.value; + // Bytes + nBytesInputs += SPENDDESCRIPTION_SIZE; + } } // update SelectAll button state @@ -550,33 +556,38 @@ void CoinControlDialog::updateLabels() updatePushButtonSelectAll(coinControl->QuantitySelected() * 2 > nSelectableInputs); // calculation - const int P2PKH_OUT_SIZE = 34; const int P2CS_OUT_SIZE = 61; if (nQuantity > 0) { // Bytes: nBytesInputs + (num_of_outputs * bytes_per_output) - nBytes = nBytesInputs + std::max(1, payAmounts.size()) * (forDelegation ? P2CS_OUT_SIZE : P2PKH_OUT_SIZE); // always assume +1 (p2pkh) output for change here - nBytes += P2PKH_OUT_SIZE; + if (fSelectTransparent) { + nBytes = nBytesInputs + std::max(1, payAmounts.size()) * ( + forDelegation ? P2CS_OUT_SIZE : CTXOUT_REGULAR_SIZE); + nBytes += CTXOUT_REGULAR_SIZE; + } else { + nBytes = nBytesInputs + std::max(1, payAmounts.size()) * SPENDDESCRIPTION_SIZE; + nBytes += SPENDDESCRIPTION_SIZE; + } + // nVersion, nLockTime and vin/vout len sizes nBytes += 10; - // Fee - nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, mempool); + // Fee (default K fixed for shielded fee for now) + nPayFee = GetMinRelayFee(nBytes, false) * (fSelectTransparent ? 1 : DEFAULT_SHIELDEDTXFEE_K); if (nPayAmount > 0) { nChange = nAmount - nPayFee - nPayAmount; // Never create dust outputs; if we would, just add the dust to the fee. - if (nChange > 0 && nChange < CENT) { - CTxOut txout(nChange, (CScript)std::vector(24, 0)); - if (IsDust(txout, ::minRelayTxFee)) { - nPayFee += nChange; - nChange = 0; - } + CAmount dustThreshold = fSelectTransparent ? GetDustThreshold(minRelayTxFee) : + GetShieldedDustThreshold(minRelayTxFee); + if (nChange > 0 && nChange < dustThreshold) { + nPayFee += nChange; + nChange = 0; } if (nChange == 0) - nBytes -= P2PKH_OUT_SIZE; + nBytes -= (fSelectTransparent ? CTXOUT_REGULAR_SIZE : SPENDDESCRIPTION_SIZE); } // after fee From 9737bcf152eb877652d327359a5b1d4e5e4ead01 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sat, 28 Nov 2020 18:07:35 +0100 Subject: [PATCH 114/121] Refactor: don't save empty memos in SaplingNoteData --- src/sapling/saplingscriptpubkeyman.cpp | 6 +++++- src/wallet/rpcwallet.cpp | 14 +++++++++----- src/wallet/wallet.cpp | 6 +++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index 4f4975cc4427..d90c59097481 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -344,7 +344,11 @@ std::pair SaplingScriptPubKe nd.ivk = ivk; nd.amount = result->value(); nd.address = address; - nd.memo = result->memo(); + const auto& memo = result->memo(); + // don't save empty memo (starting with 0xF6) + if (memo[0] < 0xF6) { + nd.memo = memo; + } noteData.insert(std::make_pair(op, nd)); break; } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 942426896a87..20fee6978cec 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1347,7 +1347,14 @@ UniValue viewshieldedtransaction(const JSONRPCRequest& request) UniValue spends(UniValue::VARR); UniValue outputs(UniValue::VARR); - auto addMemo = [](UniValue& entry, const std::array& memo) { + auto addMemo = [](UniValue& entry, const Optional> optMemo) { + // empty memo + if (!static_cast(optMemo)) { + const std::array memo {0xF6}; + entry.pushKV("memo", HexStr(memo.begin(), memo.end())); + return; + } + const auto& memo = *optMemo; auto end = FindFirstNonZero(memo.rbegin(), memo.rend()); entry.pushKV("memo", HexStr(memo.begin(), end.base())); // If the leading byte is 0xF4 or lower, the memo field should be interpreted as a @@ -1431,10 +1438,7 @@ UniValue viewshieldedtransaction(const JSONRPCRequest& request) entry_.pushKV("address", addrStr); entry_.pushKV("value", amountStr); entry_.pushKV("valueSat", amount); - - if (nd.memo) { - addMemo(entry_, *(nd.memo)); - } + addMemo(entry_, nd.memo); outputs.push_back(entry_); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a4cbf4ffafa3..c1be33fb5786 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1051,7 +1051,11 @@ void CWallet::AddExternalNotesDataToTx(CWalletTx& wtx) const // Always true for 'IsFromMe' transactions wtx.mapSaplingNoteData[op].address = recovered->second; wtx.mapSaplingNoteData[op].amount = recovered->first.value(); - wtx.mapSaplingNoteData[op].memo = recovered->first.memo(); + const auto& memo = recovered->first.memo(); + // don't save empty memo (starting with 0xF6) + if (memo[0] < 0xF6) { + wtx.mapSaplingNoteData[op].memo = memo; + } } } } From 033ea0c4b316a67d728409f3daac92d68e1ea0a6 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sat, 28 Nov 2020 18:30:45 +0100 Subject: [PATCH 115/121] GUI: CoinControlDialog::updateLabels -Loop over OutPointWrapper directly --- src/coincontrol.h | 9 ++-- src/qt/coincontroldialog.cpp | 87 +++++++++++++------------------ src/qt/coincontroldialog.h | 5 +- src/qt/pivx/coldstakingwidget.cpp | 2 +- src/qt/pivx/send.cpp | 3 +- 5 files changed, 48 insertions(+), 58 deletions(-) diff --git a/src/coincontrol.h b/src/coincontrol.h index c8d7249ff07c..895a9a89f1d4 100644 --- a/src/coincontrol.h +++ b/src/coincontrol.h @@ -16,6 +16,7 @@ class OutPointWrapper { public: BaseOutPoint outPoint; CAmount value; + bool isP2CS; bool operator<(const OutPointWrapper& obj2) const { return this->outPoint < obj2.outPoint; @@ -69,17 +70,17 @@ class CCoinControl bool IsSelected(const BaseOutPoint& output) const { - return (setSelected.count(OutPointWrapper{output, 0}) > 0); + return (setSelected.count(OutPointWrapper{output, 0, false}) > 0); } - void Select(const BaseOutPoint& output, CAmount value = 0) + void Select(const BaseOutPoint& output, CAmount value = 0, bool isP2CS = false) { - setSelected.insert(OutPointWrapper{output, value}); + setSelected.insert(OutPointWrapper{output, value, isP2CS}); } void UnSelect(const BaseOutPoint& output) { - setSelected.erase(OutPointWrapper{output, 0}); + setSelected.erase(OutPointWrapper{output, 0, false}); } void UnSelectAll() diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 53e08f8ac807..5181cab3686a 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -459,7 +459,8 @@ void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column) else { CAmount value = 0; ParseFixedPoint(item->text(COLUMN_AMOUNT).toStdString(), 8, &value); - coinControl->Select(outpt, value); + bool isP2CS = item->data(COLUMN_CHECKBOX, Qt::UserRole) == QString("Delegated"); + coinControl->Select(outpt, value, isP2CS); } // selection changed -> update labels @@ -493,13 +494,13 @@ void CoinControlDialog::updateLabels() "Select PIV Outputs to Spend" : "Select Shielded PIV to Spend"); - // nPayAmount + // nPayAmount (!todo fix dust) CAmount nPayAmount = 0; bool fDust = false; - for (const CAmount& amount : payAmounts) { - nPayAmount += amount; - if (amount > 0) { - CTxOut txout(amount, (CScript)std::vector(24, 0)); + for (const auto& amount : payAmounts) { + nPayAmount += amount.first; + if (amount.first > 0) { + CTxOut txout(amount.first, (CScript)std::vector(24, 0)); if (IsDust(txout, ::minRelayTxFee)) fDust = true; } @@ -514,40 +515,16 @@ void CoinControlDialog::updateLabels() unsigned int nQuantity = 0; std::vector vCoinControl; - std::vector vOutputs; coinControl->ListSelected(vCoinControl); - model->getOutputs(vCoinControl, vOutputs); - if (fSelectTransparent) { - for (const COutput& out : vOutputs) { - // unselect already spent, very unlikely scenario, this could happen - // when selected are spent elsewhere, like rpc or another computer - uint256 txhash = out.tx->GetHash(); - COutPoint outpt(txhash, out.i); - if (model->isSpent(outpt)) { - coinControl->UnSelect(outpt); - continue; - } - - // Quantity - nQuantity++; - // Amount - nAmount += out.tx->vout[out.i].nValue; - // Bytes - nBytesInputs += CTXIN_SPEND_DUST_SIZE; - // Additional byte for P2CS - if (out.tx->vout[out.i].scriptPubKey.IsPayToColdStaking()) - nBytesInputs++; - } - } else { - for (const OutPointWrapper& out : vCoinControl) { - // Quantity - nQuantity++; - // Amount - nAmount += out.value; - // Bytes - nBytesInputs += SPENDDESCRIPTION_SIZE; - } + for (const OutPointWrapper& out : vCoinControl) { + // Quantity + nQuantity++; + // Amount + nAmount += out.value; + // Bytes + nBytesInputs += (fSelectTransparent ? (CTXIN_SPEND_DUST_SIZE + (out.isP2CS ? 1 : 0)) + : SPENDDESCRIPTION_SIZE); } // update SelectAll button state @@ -558,22 +535,32 @@ void CoinControlDialog::updateLabels() // calculation const int P2CS_OUT_SIZE = 61; if (nQuantity > 0) { - // Bytes: nBytesInputs + (num_of_outputs * bytes_per_output) + bool isShieldedTx = !fSelectTransparent; + // Bytes: nBytesInputs + (sum of nBytesOutputs) // always assume +1 (p2pkh) output for change here - if (fSelectTransparent) { - nBytes = nBytesInputs + std::max(1, payAmounts.size()) * ( - forDelegation ? P2CS_OUT_SIZE : CTXOUT_REGULAR_SIZE); - nBytes += CTXOUT_REGULAR_SIZE; - } else { - nBytes = nBytesInputs + std::max(1, payAmounts.size()) * SPENDDESCRIPTION_SIZE; - nBytes += SPENDDESCRIPTION_SIZE; + nBytes = nBytesInputs + (fSelectTransparent ? CTXOUT_REGULAR_SIZE : OUTPUTDESCRIPTION_SIZE); + for (const auto& a : payAmounts) { + bool shieldedOut = a.second; + isShieldedTx |= shieldedOut; + nBytes += (shieldedOut ? OUTPUTDESCRIPTION_SIZE + : (forDelegation ? P2CS_OUT_SIZE : CTXOUT_REGULAR_SIZE)); + } + + // Shielded txes must include binding sig and valueBalance + if (isShieldedTx) { + nBytes += (BINDINGSIG_SIZE + 8); + // (plus at least 2 bytes for shielded in/outs len sizes) + nBytes += 2; } - // nVersion, nLockTime and vin/vout len sizes + // !TODO: ExtraPayload size for special txes. For now 1 byte for nullopt. + nBytes += 1; + + // nVersion, nType, nLockTime and vin/vout len sizes nBytes += 10; // Fee (default K fixed for shielded fee for now) - nPayFee = GetMinRelayFee(nBytes, false) * (fSelectTransparent ? 1 : DEFAULT_SHIELDEDTXFEE_K); + nPayFee = GetMinRelayFee(nBytes, false) * (isShieldedTx ? DEFAULT_SHIELDEDTXFEE_K : 1); if (nPayAmount > 0) { nChange = nAmount - nPayFee - nPayAmount; @@ -836,9 +823,9 @@ void CoinControlDialog::clearPayAmounts() payAmounts.clear(); } -void CoinControlDialog::addPayAmount(const CAmount& amount) +void CoinControlDialog::addPayAmount(const CAmount& amount, bool isShieldedRecipient) { - payAmounts.push_back(amount); + payAmounts.emplace_back(amount, isShieldedRecipient); } void CoinControlDialog::updatePushButtonSelectAll(bool checked) diff --git a/src/qt/coincontroldialog.h b/src/qt/coincontroldialog.h index 28889d640b28..041553261b2a 100644 --- a/src/qt/coincontroldialog.h +++ b/src/qt/coincontroldialog.h @@ -52,7 +52,7 @@ class CoinControlDialog : public QDialog void updateView(); void refreshDialog(); void clearPayAmounts(); - void addPayAmount(const CAmount& amount); + void addPayAmount(const CAmount& amount, bool isShieldedRecipient); void setSelectionType(bool isTransparent) { fSelectTransparent = isTransparent; } CCoinControl* coinControl; @@ -64,7 +64,8 @@ class CoinControlDialog : public QDialog int sortColumn; Qt::SortOrder sortOrder; bool forDelegation; - QList payAmounts{}; + // pair (recipient amount, ishielded recipient) + std::vector> payAmounts{}; unsigned int nSelectableInputs{0}; // whether should show available utxo or notes. diff --git a/src/qt/pivx/coldstakingwidget.cpp b/src/qt/pivx/coldstakingwidget.cpp index cd5ac7d7e9ed..41c6056d46de 100644 --- a/src/qt/pivx/coldstakingwidget.cpp +++ b/src/qt/pivx/coldstakingwidget.cpp @@ -547,7 +547,7 @@ void ColdStakingWidget::setCoinControlPayAmounts() { if (!coinControlDialog) return; coinControlDialog->clearPayAmounts(); - coinControlDialog->addPayAmount(sendMultiRow->getAmountValue()); + coinControlDialog->addPayAmount(sendMultiRow->getAmountValue(), false); } void ColdStakingWidget::onColdStakeClicked() diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index a83f328590bb..91fa18b9eef3 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -692,7 +692,8 @@ void SendWidget::setCoinControlPayAmounts() coinControlDialog->clearPayAmounts(); QMutableListIterator it(entries); while (it.hasNext()) { - coinControlDialog->addPayAmount(it.next()->getAmountValue()); + const auto& entry = it.next(); + coinControlDialog->addPayAmount(entry->getAmountValue(), entry->getValue().isShieldedAddr); } } From d5e97d46df3c39faef99e4ad312a06028c1053a9 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sat, 28 Nov 2020 18:33:49 +0100 Subject: [PATCH 116/121] Cleanup: remove unused walletModel::getOutputs --- src/qt/walletmodel.cpp | 14 -------------- src/qt/walletmodel.h | 1 - 2 files changed, 15 deletions(-) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 46f258652c72..ffd6f386af25 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -909,20 +909,6 @@ std::string WalletModel::getLabelForAddress(const CTxDestination& address) return label; } -// returns a list of COutputs from COutPoints -void WalletModel::getOutputs(const std::vector& vOutpoints, std::vector& vOutputs) -{ - LOCK2(cs_main, wallet->cs_wallet); - for (const auto& outpoint : vOutpoints) { - const auto* tx = wallet->GetWalletTx(outpoint.outPoint.hash); - if (!tx) continue; - bool fConflicted; - const int nDepth = tx->GetDepthAndMempool(fConflicted); - if (nDepth < 0 || fConflicted) continue; - vOutputs.emplace_back(tx, outpoint.outPoint.n, nDepth, true, true); - } -} - // returns a COutPoint of 10000 PIV if found bool WalletModel::getMNCollateralCandidate(COutPoint& outPoint) { diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 97cbdda08bb4..7ae551184abf 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -282,7 +282,6 @@ class WalletModel : public QObject bool isMine(const QString& addressStr); bool IsShieldedDestination(const CWDestination& address); bool isUsed(CTxDestination address); - void getOutputs(const std::vector& vOutpoints, std::vector& vOutputs); bool getMNCollateralCandidate(COutPoint& outPoint); bool isSpent(const COutPoint& outpoint) const; From 8c946eb76d040dae5a6c9591b3aa95c275f0f838 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 28 Nov 2020 20:38:16 -0300 Subject: [PATCH 117/121] GUI: send screen, hide custom change address for shielded transactions --- src/qt/pivx/send.cpp | 9 ++++++++- src/qt/pivx/send.h | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index 91fa18b9eef3..aae173ac6ad3 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -9,7 +9,6 @@ #include "qt/pivx/sendchangeaddressdialog.h" #include "qt/pivx/optionbutton.h" #include "qt/pivx/sendconfirmdialog.h" -#include "qt/pivx/myaddressrow.h" #include "qt/pivx/guitransactionsutils.h" #include "qt/pivx/loadingdialog.h" #include "clientmodel.h" @@ -250,6 +249,13 @@ void SendWidget::resetCoinControl() ui->btnCoinControl->setActive(false); } +void SendWidget::resetChangeAddress() +{ + coinControlDialog->coinControl->destChange = CNoDestination(); + ui->btnChangeAddress->setActive(false); + ui->btnChangeAddress->setVisible(isTransparent); +} + void SendWidget::clearEntries() { int num = entries.length(); @@ -714,6 +720,7 @@ void SendWidget::onCheckBoxChanged() void SendWidget::onPIVSelected(bool _isTransparent) { isTransparent = _isTransparent; + resetChangeAddress(); resetCoinControl(); refreshAmounts(); updateStyle(coinIcon); diff --git a/src/qt/pivx/send.h b/src/qt/pivx/send.h index c1ca5ce006c6..6690413ace0c 100644 --- a/src/qt/pivx/send.h +++ b/src/qt/pivx/send.h @@ -118,6 +118,7 @@ private Q_SLOTS: void setCustomFeeSelected(bool isSelected, const CAmount& customFee = DEFAULT_TRANSACTION_FEE); void setCoinControlPayAmounts(); void resetCoinControl(); + void resetChangeAddress(); }; #endif // SEND_H From fb12325786feb4dc8adeff31dfaab4567fd296e8 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 28 Nov 2020 21:14:15 -0300 Subject: [PATCH 118/121] GUI: dashboard, better shielded records filters. --- src/qt/pivx/qtutils.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/qt/pivx/qtutils.cpp b/src/qt/pivx/qtutils.cpp index ef2c525e955e..05ac9631e07a 100644 --- a/src/qt/pivx/qtutils.cpp +++ b/src/qt/pivx/qtutils.cpp @@ -157,14 +157,27 @@ void setSortTxTypeFilter(QComboBox* filter, SortEdit* lineEditType) { initComboBox(filter, lineEditType); filter->addItem(QObject::tr("All"), TransactionFilterProxy::ALL_TYPES); - filter->addItem(QObject::tr("Received"), TransactionFilterProxy::TYPE(TransactionRecord::RecvWithAddress) | TransactionFilterProxy::TYPE(TransactionRecord::RecvFromOther)); - filter->addItem(QObject::tr("Sent"), TransactionFilterProxy::TYPE(TransactionRecord::SendToAddress) | TransactionFilterProxy::TYPE(TransactionRecord::SendToOther)); - filter->addItem(QObject::tr("Shielded"), TransactionFilterProxy::TYPE(TransactionRecord::RecvWithShieldedAddress | TransactionRecord::SendToShielded)); + filter->addItem(QObject::tr("Received"), + TransactionFilterProxy::TYPE(TransactionRecord::RecvWithAddress) | + TransactionFilterProxy::TYPE(TransactionRecord::RecvFromOther) | + TransactionFilterProxy::TYPE(TransactionRecord::RecvWithShieldedAddress)); + filter->addItem(QObject::tr("Sent"), + TransactionFilterProxy::TYPE(TransactionRecord::SendToAddress) | + TransactionFilterProxy::TYPE(TransactionRecord::SendToOther) | + TransactionFilterProxy::TYPE(TransactionRecord::SendToShielded)); + filter->addItem(QObject::tr("Shield"), + TransactionFilterProxy::TYPE(TransactionRecord::RecvWithShieldedAddress) | + TransactionFilterProxy::TYPE(TransactionRecord::SendToShielded) | + TransactionFilterProxy::TYPE(TransactionRecord::SendToSelfShieldToShieldChangeAddress) | + TransactionFilterProxy::TYPE(TransactionRecord::SendToSelfShieldToTransparent) | + TransactionFilterProxy::TYPE(TransactionRecord::SendToSelfShieldedAddress)); filter->addItem(QObject::tr("Mined"), TransactionFilterProxy::TYPE(TransactionRecord::Generated)); filter->addItem(QObject::tr("Minted"), TransactionFilterProxy::TYPE(TransactionRecord::StakeMint)); filter->addItem(QObject::tr("MN reward"), TransactionFilterProxy::TYPE(TransactionRecord::MNReward)); - filter->addItem(QObject::tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf | - TransactionRecord::SendToSelfShieldedAddress | TransactionRecord::SendToSelfShieldToShieldChangeAddress | TransactionRecord::SendToSelfShieldToTransparent)); + filter->addItem(QObject::tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf) | + TransactionFilterProxy::TYPE(TransactionRecord::SendToSelfShieldedAddress) | + TransactionFilterProxy::TYPE(TransactionRecord::SendToSelfShieldToShieldChangeAddress) | + TransactionFilterProxy::TYPE(TransactionRecord::SendToSelfShieldToTransparent)); filter->addItem(QObject::tr("Cold stakes"), TransactionFilterProxy::TYPE(TransactionRecord::StakeDelegated)); filter->addItem(QObject::tr("Hot stakes"), TransactionFilterProxy::TYPE(TransactionRecord::StakeHot)); filter->addItem(QObject::tr("Delegated"), TransactionFilterProxy::TYPE(TransactionRecord::P2CSDelegationSent) | TransactionFilterProxy::TYPE(TransactionRecord::P2CSDelegationSentOwner)); From 48c4f860dc541c9d08c42de88e11d0436b935de1 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 28 Nov 2020 21:27:34 -0300 Subject: [PATCH 119/121] GUI: transaction detail/confirm dialog, fixing inputs grid for shielded txs. --- src/qt/pivx/sendconfirmdialog.cpp | 25 ++++++++++++++++--------- src/qt/pivx/sendconfirmdialog.h | 2 ++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/qt/pivx/sendconfirmdialog.cpp b/src/qt/pivx/sendconfirmdialog.cpp index a8f41186d161..1a8bcb8ca536 100644 --- a/src/qt/pivx/sendconfirmdialog.cpp +++ b/src/qt/pivx/sendconfirmdialog.cpp @@ -78,6 +78,17 @@ TxDetailDialog::TxDetailDialog(QWidget *parent, bool _isConfirmDialog, const QSt connect(ui->pushOutputs, &QPushButton::clicked, this, &TxDetailDialog::onOutputsClicked); } +void TxDetailDialog::setInputsType(const CWalletTx* _tx) +{ + if (_tx->sapData && _tx->sapData->vShieldedSpend.empty()) { + ui->labelTitlePrevTx->setText(tr("Previous Transaction")); + ui->labelOutputIndex->setText(tr("Output Index")); + } else { + ui->labelTitlePrevTx->setText(tr("Note From Address")); + ui->labelOutputIndex->setText(tr("Index")); + } +} + void TxDetailDialog::setData(WalletModel *_model, const QModelIndex &index) { this->model = _model; @@ -102,6 +113,7 @@ void TxDetailDialog::setData(WalletModel *_model, const QModelIndex &index) } ui->textSend->setVisible(false); + setInputsType(_tx); int inputsSize = (_tx->sapData && !_tx->sapData->vShieldedSpend.empty()) ? _tx->sapData->vShieldedSpend.size() : _tx->vin.size(); ui->textInputs->setText(QString::number(inputsSize)); ui->textConfirmations->setText(QString::number(rec->status.depth)); @@ -140,13 +152,7 @@ void TxDetailDialog::setData(WalletModel *_model, WalletModelTransaction* _tx) // inputs label CWalletTx* walletTx = tx->getTransaction(); - if (walletTx->sapData && walletTx->sapData->vShieldedSpend.empty()) { - ui->labelTitlePrevTx->setText(tr("Previous Transaction")); - ui->labelOutputIndex->setText(tr("Output Index")); - } else { - ui->labelTitlePrevTx->setText(tr("Note From Address")); - ui->labelOutputIndex->setText(tr("Index")); - } + setInputsType(walletTx); ui->textAmount->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, totalAmount, false, BitcoinUnits::separatorAlways) + " (Fee included)"); int nRecipients = tx->getRecipients().size(); @@ -218,14 +224,15 @@ void TxDetailDialog::onInputsClicked() i++; } } else { + ui->gridInputs->setMinimumHeight(50 + (50 * walletTx->sapData->vShieldedSpend.size())); bool fInfoAvailable = false; for (int i = 0; i < (int) walletTx->sapData->vShieldedSpend.size(); ++i) { Optional opAddr = model->getShieldedAddressFromSpendDesc(walletTx, i); if (opAddr) { QString addr = *opAddr; - loadInputs(addr.left(16) + "..." + addr.right(16), + loadInputs(addr.left(18) + "..." + addr.right(18), QString::number(i), - ui->gridLayoutInput, i); + ui->gridLayoutInput, i + 1); fInfoAvailable = true; } } diff --git a/src/qt/pivx/sendconfirmdialog.h b/src/qt/pivx/sendconfirmdialog.h index f11d7e7b3825..9dda3eebae7e 100644 --- a/src/qt/pivx/sendconfirmdialog.h +++ b/src/qt/pivx/sendconfirmdialog.h @@ -54,6 +54,8 @@ public Q_SLOTS: bool inputsLoaded = false; bool outputsLoaded = false; + + void setInputsType(const CWalletTx* _tx); }; #endif // SENDCONFIRMDIALOG_H From 0e0d3ad1869b0da26a78124becbdbcbd4780b407 Mon Sep 17 00:00:00 2001 From: furszy Date: Sun, 29 Nov 2020 00:38:18 -0300 Subject: [PATCH 120/121] GUI: transaction record, fixing shielded credit for shield to self transactions plus setting the address receiving the shielded funds. --- src/qt/pivx/sendconfirmdialog.cpp | 4 +++- src/qt/transactionrecord.cpp | 12 +++++++++--- src/qt/transactionrecord.h | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/qt/pivx/sendconfirmdialog.cpp b/src/qt/pivx/sendconfirmdialog.cpp index 1a8bcb8ca536..86eca628db67 100644 --- a/src/qt/pivx/sendconfirmdialog.cpp +++ b/src/qt/pivx/sendconfirmdialog.cpp @@ -105,7 +105,9 @@ void TxDetailDialog::setData(WalletModel *_model, const QModelIndex &index) QString hash = QString::fromStdString(_tx->GetHash().GetHex()); ui->textId->setText(hash.left(20) + "..." + hash.right(20)); ui->textId->setTextInteractionFlags(Qt::TextSelectableByMouse); - if (_tx->vout.size() == 1) { + // future: subdivide shielded and transparent by type and + // do not show send xxx recipients for txes with a single output + change (show the address directly). + if (_tx->vout.size() == 1 || (_tx->sapData && _tx->sapData->vShieldedOutput.size() == 1)) { ui->textSendLabel->setText((address.size() < 40) ? address : address.left(20) + "..." + address.right(20)); } else { ui->textSendLabel->setText(QString::number(_tx->vout.size() + diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 692dba226eea..064964602270 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -232,7 +232,7 @@ bool TransactionRecord::decomposeCreditTransaction(const CWallet* wallet, const bool TransactionRecord::decomposeSendToSelfTransaction(const CWalletTx& wtx, const CAmount& nCredit, const CAmount& nDebit, bool involvesWatchAddress, - QList& parts) + QList& parts, const CWallet* wallet) { // Payment to self tx is presented as a single record. TransactionRecord sub(wtx.GetHash(), wtx.GetTxTime(), wtx.GetTotalSize()); @@ -251,8 +251,14 @@ bool TransactionRecord::decomposeSendToSelfTransaction(const CWalletTx& wtx, con // transparent -> shield transaction if (wtx.sapData->vShieldedSpend.empty()) { sub.type = TransactionRecord::SendToSelfShieldedAddress; - sub.shieldedCredit = wtx.GetShieldedAvailableCredit(); + sub.shieldedCredit = wtx.GetCredit(ISMINE_SPENDABLE_SHIELDED); nChange += wtx.GetShieldedChange(); + + SaplingOutPoint out(sub.hash, 0); + auto opAddr = wallet->GetSaplingScriptPubKeyMan()->GetOutPointAddress(wtx, out); + if (opAddr) { + sub.address = KeyIO::EncodePaymentAddress(*opAddr); + } } else { // we know that the inputs are shielded now, let's see if // if we have transparent outputs. if we have then we are converting back coins, @@ -474,7 +480,7 @@ QList TransactionRecord::decomposeTransaction(const CWallet* // Check if this tx is purely a payment to self. if (fAllFromMe && fAllToMe && allShieldedOutToMe && allShieldedSpendsFromMe) { // Single record for sendToSelf. - if (decomposeSendToSelfTransaction(wtx, nCredit, nDebit, involvesWatchAddress, parts)) { + if (decomposeSendToSelfTransaction(wtx, nCredit, nDebit, involvesWatchAddress, parts, wallet)) { return parts; } } diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h index 13887d9f3926..aaa135292f14 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -139,7 +139,7 @@ class TransactionRecord static bool decomposeSendToSelfTransaction(const CWalletTx& wtx, const CAmount& nCredit, const CAmount& nDebit, bool involvesWatchAddress, - QList& parts); + QList& parts, const CWallet* wallet); static bool decomposeDebitTransaction(const CWallet* wallet, const CWalletTx& wtx, const CAmount& nDebit, bool involvesWatchAddress, From 64d3175b025fb93d983ebf6e94f31b6f69e144c0 Mon Sep 17 00:00:00 2001 From: furszy Date: Sun, 29 Nov 2020 12:57:25 -0300 Subject: [PATCH 121/121] GUI: send screen, simple texts changes. --- src/qt/pivx/forms/send.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qt/pivx/forms/send.ui b/src/qt/pivx/forms/send.ui index e4f7763c6d15..92f7c805cacf 100644 --- a/src/qt/pivx/forms/send.ui +++ b/src/qt/pivx/forms/send.ui @@ -81,7 +81,7 @@ - Transfer coins publicly or fully private + Transfer coins publicly or privately @@ -202,7 +202,7 @@ padding-top:2px; - Select which coins do you want to spend + Select which coins to spend Qt::AlignCenter