From bc0a1b7dc86bef69680837afb1b4ffa8fca1060b Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 19 Apr 2025 09:52:03 +0000 Subject: [PATCH 01/13] refactor: remove unused `CWalletTx::GetWallet()` Introduced in dash#3155, it is now unused. --- src/wallet/transaction.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/wallet/transaction.h b/src/wallet/transaction.h index 885af840408de..0cb09d835d1da 100644 --- a/src/wallet/transaction.h +++ b/src/wallet/transaction.h @@ -273,11 +273,6 @@ class CWalletTx m_is_cache_empty = true; } - const CWallet* GetWallet() const - { - return pwallet; - } - //! filter decides which addresses will count towards the debit CAmount GetDebit(const isminefilter& filter) const; CAmount GetCredit(const isminefilter& filter) const; From ed12dab7a230eb7c4078271aaefbc7983136c994 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 19 Apr 2025 17:12:04 +0000 Subject: [PATCH 02/13] move-only: section CoinJoin-specific code to separate source file Functions are in order of declaration in `wallet/wallet.h`. Review with `git log -p -n1 --color-moved=dimmed_zebra` --- src/Makefile.am | 2 + src/coinjoin/client.cpp | 1 + src/wallet/coinjoin.cpp | 578 ++++++++++++++++++++++++++++++++++++++++ src/wallet/coinjoin.h | 18 ++ src/wallet/receive.cpp | 12 - src/wallet/wallet.cpp | 553 -------------------------------------- src/wallet/wallet.h | 9 - 7 files changed, 599 insertions(+), 574 deletions(-) create mode 100644 src/wallet/coinjoin.cpp create mode 100644 src/wallet/coinjoin.h diff --git a/src/Makefile.am b/src/Makefile.am index 1f82cbf9f8bcb..3d76eb85286aa 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -392,6 +392,7 @@ BITCOIN_CORE_H = \ wallet/bip39.h \ wallet/bip39_english.h \ wallet/coincontrol.h \ + wallet/coinjoin.h \ wallet/coinselection.h \ wallet/context.h \ wallet/crypter.h \ @@ -591,6 +592,7 @@ libbitcoin_wallet_a_SOURCES = \ coinjoin/interfaces.cpp \ coinjoin/util.cpp \ wallet/bip39.cpp \ + wallet/coinjoin.cpp \ wallet/coincontrol.cpp \ wallet/context.cpp \ wallet/crypter.cpp \ diff --git a/src/coinjoin/client.cpp b/src/coinjoin/client.cpp index 26b1e7cb70f53..280eae0a9785f 100644 --- a/src/coinjoin/client.cpp +++ b/src/coinjoin/client.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include diff --git a/src/wallet/coinjoin.cpp b/src/wallet/coinjoin.cpp new file mode 100644 index 0000000000000..c0070ccfd5536 --- /dev/null +++ b/src/wallet/coinjoin.cpp @@ -0,0 +1,578 @@ +// Copyright (c) 2009-2021 The Bitcoin Core developers +// Copyright (c) 2014-2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include +#include +#include + +void CWallet::InitCJSaltFromDb() +{ + assert(nCoinJoinSalt.IsNull()); + + WalletBatch batch(GetDatabase()); + if (!batch.ReadCoinJoinSalt(nCoinJoinSalt) && batch.ReadCoinJoinSalt(nCoinJoinSalt, true)) { + // Migrate salt stored with legacy key + batch.WriteCoinJoinSalt(nCoinJoinSalt); + } +} + +const uint256& CWallet::GetCoinJoinSalt() +{ + if (nCoinJoinSalt.IsNull()) { + InitCJSaltFromDb(); + } + return nCoinJoinSalt; +} + +bool CWallet::SetCoinJoinSalt(const uint256& cj_salt) +{ + WalletBatch batch(GetDatabase()); + // Only store new salt in CWallet if database write is successful + if (batch.WriteCoinJoinSalt(cj_salt)) { + nCoinJoinSalt = cj_salt; + return true; + } + return false; +} + +bool CWallet::SelectTxDSInsByDenomination(int nDenom, CAmount nValueMax, std::vector& vecTxDSInRet) +{ + LOCK(cs_wallet); + + CAmount nValueTotal{0}; + + std::set setRecentTxIds; + std::vector vCoins; + + vecTxDSInRet.clear(); + + if (!CoinJoin::IsValidDenomination(nDenom)) { + return false; + } + CAmount nDenomAmount = CoinJoin::DenominationToAmount(nDenom); + + CCoinControl coin_control; + coin_control.nCoinType = CoinType::ONLY_READY_TO_MIX; + AvailableCoins(vCoins, &coin_control); + WalletCJLogPrint(this, "CWallet::%s -- vCoins.size(): %d\n", __func__, vCoins.size()); + + Shuffle(vCoins.rbegin(), vCoins.rend(), FastRandomContext()); + + for (const auto& out : vCoins) { + uint256 txHash = out.tx->GetHash(); + CAmount nValue = out.tx->tx->vout[out.i].nValue; + if (setRecentTxIds.find(txHash) != setRecentTxIds.end()) continue; // no duplicate txids + if (nValueTotal + nValue > nValueMax) continue; + if (nValue != nDenomAmount) continue; + + CTxIn txin = CTxIn(txHash, out.i); + CScript scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; + int nRounds = GetRealOutpointCoinJoinRounds(txin.prevout); + + nValueTotal += nValue; + vecTxDSInRet.emplace_back(CTxDSIn(txin, scriptPubKey, nRounds)); + setRecentTxIds.emplace(txHash); + WalletCJLogPrint(this, "CWallet::%s -- hash: %s, nValue: %d.%08d\n", + __func__, txHash.ToString(), nValue / COIN, nValue % COIN); + } + + WalletCJLogPrint(this, "CWallet::%s -- setRecentTxIds.size(): %d\n", __func__, setRecentTxIds.size()); + + return nValueTotal > 0; +} + +struct CompareByPriority +{ + bool operator()(const COutput& t1, + const COutput& t2) const + { + return CoinJoin::CalculateAmountPriority(t1.GetInputCoin().effective_value) > CoinJoin::CalculateAmountPriority(t2.GetInputCoin().effective_value); + } +}; + +bool CWallet::SelectDenominatedAmounts(CAmount nValueMax, std::set& setAmountsRet) const +{ + LOCK(cs_wallet); + + CAmount nValueTotal{0}; + setAmountsRet.clear(); + + std::vector vCoins; + CCoinControl coin_control; + coin_control.nCoinType = CoinType::ONLY_READY_TO_MIX; + AvailableCoins(vCoins, &coin_control); + // larger denoms first + std::sort(vCoins.rbegin(), vCoins.rend(), CompareByPriority()); + + for (const auto& out : vCoins) { + CAmount nValue = out.tx->tx->vout[out.i].nValue; + if (nValueTotal + nValue <= nValueMax) { + nValueTotal += nValue; + setAmountsRet.emplace(nValue); + } + } + + return nValueTotal >= CoinJoin::GetSmallestDenomination(); +} + +std::vector CWallet::SelectCoinsGroupedByAddresses(bool fSkipDenominated, bool fAnonymizable, bool fSkipUnconfirmed, int nMaxOupointsPerAddress) const +{ + LOCK(cs_wallet); + + isminefilter filter = ISMINE_SPENDABLE; + + // Try using the cache for already confirmed mixable inputs. + // This should only be used if nMaxOupointsPerAddress was NOT specified. + if(nMaxOupointsPerAddress == -1 && fAnonymizable && fSkipUnconfirmed) { + if(fSkipDenominated && fAnonymizableTallyCachedNonDenom) { + LogPrint(BCLog::SELECTCOINS, "SelectCoinsGroupedByAddresses - using cache for non-denom inputs %d\n", vecAnonymizableTallyCachedNonDenom.size()); + return vecAnonymizableTallyCachedNonDenom; + } + if(!fSkipDenominated && fAnonymizableTallyCached) { + LogPrint(BCLog::SELECTCOINS, "SelectCoinsGroupedByAddresses - using cache for all inputs %d\n", vecAnonymizableTallyCached.size()); + return vecAnonymizableTallyCached; + } + } + + CAmount nSmallestDenom = CoinJoin::GetSmallestDenomination(); + + // Tally + std::map mapTally; + std::set setWalletTxesCounted; + for (const auto& outpoint : setWalletUTXO) { + + if (!setWalletTxesCounted.emplace(outpoint.hash).second) continue; + + std::map::const_iterator it = mapWallet.find(outpoint.hash); + if (it == mapWallet.end()) continue; + + const CWalletTx& wtx = (*it).second; + + if(wtx.IsCoinBase() && wtx.GetBlocksToMaturity() > 0) continue; + if(fSkipUnconfirmed && !wtx.IsTrusted()) continue; + if (wtx.GetDepthInMainChain() < 0) continue; + + for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { + CTxDestination txdest; + if (!ExtractDestination(wtx.tx->vout[i].scriptPubKey, txdest)) continue; + + isminefilter mine = IsMine(txdest); + if(!(mine & filter)) continue; + + auto itTallyItem = mapTally.find(txdest); + if (nMaxOupointsPerAddress != -1 && itTallyItem != mapTally.end() && int64_t(itTallyItem->second.vecInputCoins.size()) >= nMaxOupointsPerAddress) continue; + + if(IsSpent(outpoint.hash, i) || IsLockedCoin(outpoint.hash, i)) continue; + + if(fSkipDenominated && CoinJoin::IsDenominatedAmount(wtx.tx->vout[i].nValue)) continue; + + if(fAnonymizable) { + // ignore collaterals + if(CoinJoin::IsCollateralAmount(wtx.tx->vout[i].nValue)) continue; + if (fMasternodeMode && dmn_types::IsCollateralAmount(wtx.tx->vout[i].nValue)) continue; + // ignore outputs that are 10 times smaller then the smallest denomination + // otherwise they will just lead to higher fee / lower priority + if(wtx.tx->vout[i].nValue <= nSmallestDenom/10) continue; + // ignore mixed + if (IsFullyMixed(COutPoint(outpoint.hash, i))) continue; + } + + if (itTallyItem == mapTally.end()) { + itTallyItem = mapTally.emplace(txdest, CompactTallyItem()).first; + itTallyItem->second.txdest = txdest; + } + itTallyItem->second.nAmount += wtx.tx->vout[i].nValue; + itTallyItem->second.vecInputCoins.emplace_back(wtx.tx, i); + } + } + + // construct resulting vector + // NOTE: vecTallyRet is "sorted" by txdest (i.e. address), just like mapTally + std::vector vecTallyRet; + for (const auto& item : mapTally) { + if(fAnonymizable && item.second.nAmount < nSmallestDenom) continue; + vecTallyRet.push_back(item.second); + } + + // Cache already confirmed mixable entries for later use. + // This should only be used if nMaxOupointsPerAddress was NOT specified. + if(nMaxOupointsPerAddress == -1 && fAnonymizable && fSkipUnconfirmed) { + if(fSkipDenominated) { + vecAnonymizableTallyCachedNonDenom = vecTallyRet; + fAnonymizableTallyCachedNonDenom = true; + } else { + vecAnonymizableTallyCached = vecTallyRet; + fAnonymizableTallyCached = true; + } + } + + // debug + if (LogAcceptDebug(BCLog::SELECTCOINS)) { + std::string strMessage = "SelectCoinsGroupedByAddresses - vecTallyRet:\n"; + for (const auto& item : vecTallyRet) + strMessage += strprintf(" %s %f\n", EncodeDestination(item.txdest), float(item.nAmount)/COIN); + LogPrint(BCLog::SELECTCOINS, "%s", strMessage); /* Continued */ + } + + return vecTallyRet; +} + +bool CWallet::HasCollateralInputs(bool fOnlyConfirmed) const +{ + LOCK(cs_wallet); + + std::vector vCoins; + CCoinControl coin_control; + coin_control.m_include_unsafe_inputs = !fOnlyConfirmed; + coin_control.nCoinType = CoinType::ONLY_COINJOIN_COLLATERAL; + AvailableCoins(vCoins, &coin_control); + + return !vCoins.empty(); +} + +int CWallet::CountInputsWithAmount(CAmount nInputAmount) const +{ + CAmount nTotal = 0; + + LOCK(cs_wallet); + + for (const auto& outpoint : setWalletUTXO) { + const auto it = mapWallet.find(outpoint.hash); + if (it == mapWallet.end()) continue; + if (it->second.tx->vout[outpoint.n].nValue != nInputAmount) continue; + if (it->second.GetDepthInMainChain() < 0) continue; + + nTotal++; + } + + return nTotal; +} + +// Recursively determine the rounds of a given input (How deep is the CoinJoin chain for a given input) +int CWallet::GetRealOutpointCoinJoinRounds(const COutPoint& outpoint, int nRounds) const +{ + LOCK(cs_wallet); + + const int nRoundsMax = MAX_COINJOIN_ROUNDS + CCoinJoinClientOptions::GetRandomRounds(); + + if (nRounds >= nRoundsMax) { + // there can only be nRoundsMax rounds max + return nRoundsMax - 1; + } + + auto pair = mapOutpointRoundsCache.emplace(outpoint, -10); + auto nRoundsRef = &pair.first->second; + if (!pair.second) { + // we already processed it, just return what we have + return *nRoundsRef; + } + + // TODO wtx should refer to a CWalletTx object, not a pointer, based on surrounding code + const CWalletTx* wtx = GetWalletTx(outpoint.hash); + + if (wtx == nullptr || wtx->tx == nullptr) { + // no such tx in this wallet + *nRoundsRef = -1; + WalletCJLogPrint(this, "%s FAILED %-70s %3d\n", __func__, outpoint.ToStringShort(), -1); + return *nRoundsRef; + } + + // bounds check + if (outpoint.n >= wtx->tx->vout.size()) { + // should never actually hit this + *nRoundsRef = -4; + WalletCJLogPrint(this, "%s FAILED %-70s %3d\n", __func__, outpoint.ToStringShort(), -4); + return *nRoundsRef; + } + + auto txOutRef = &wtx->tx->vout[outpoint.n]; + + if (CoinJoin::IsCollateralAmount(txOutRef->nValue)) { + *nRoundsRef = -3; + WalletCJLogPrint(this, "%s UPDATED %-70s %3d\n", __func__, outpoint.ToStringShort(), *nRoundsRef); + return *nRoundsRef; + } + + // make sure the final output is non-denominate + if (!CoinJoin::IsDenominatedAmount(txOutRef->nValue)) { //NOT DENOM + *nRoundsRef = -2; + WalletCJLogPrint(this, "%s UPDATED %-70s %3d\n", __func__, outpoint.ToStringShort(), *nRoundsRef); + return *nRoundsRef; + } + + for (const auto& out : wtx->tx->vout) { + if (!CoinJoin::IsDenominatedAmount(out.nValue)) { + // this one is denominated but there is another non-denominated output found in the same tx + *nRoundsRef = 0; + WalletCJLogPrint(this, "%s UPDATED %-70s %3d\n", __func__, outpoint.ToStringShort(), *nRoundsRef); + return *nRoundsRef; + } + } + + // make sure we spent all of it with 0 fee, reset to 0 rounds otherwise + if (wtx->GetDebit(ISMINE_SPENDABLE) != wtx->GetCredit(ISMINE_SPENDABLE)) { + *nRoundsRef = 0; + WalletCJLogPrint(this, "%s UPDATED %-70s %3d\n", __func__, outpoint.ToStringShort(), *nRoundsRef); + return *nRoundsRef; + } + + int nShortest = -10; // an initial value, should be no way to get this by calculations + bool fDenomFound = false; + // only denoms here so let's look up + for (const auto& txinNext : wtx->tx->vin) { + if (IsMine(txinNext)) { + int n = GetRealOutpointCoinJoinRounds(txinNext.prevout, nRounds + 1); + // denom found, find the shortest chain or initially assign nShortest with the first found value + if(n >= 0 && (n < nShortest || nShortest == -10)) { + nShortest = n; + fDenomFound = true; + } + } + } + *nRoundsRef = fDenomFound + ? (nShortest >= nRoundsMax - 1 ? nRoundsMax : nShortest + 1) // good, we a +1 to the shortest one but only nRoundsMax rounds max allowed + : 0; // too bad, we are the fist one in that chain + WalletCJLogPrint(this, "%s UPDATED %-70s %3d\n", __func__, outpoint.ToStringShort(), *nRoundsRef); + return *nRoundsRef; +} + +// respect current settings +int CWallet::GetCappedOutpointCoinJoinRounds(const COutPoint& outpoint) const +{ + LOCK(cs_wallet); + int realCoinJoinRounds = GetRealOutpointCoinJoinRounds(outpoint); + return realCoinJoinRounds > CCoinJoinClientOptions::GetRounds() ? CCoinJoinClientOptions::GetRounds() : realCoinJoinRounds; +} + +void CWallet::ClearCoinJoinRoundsCache() +{ + LOCK(cs_wallet); + mapOutpointRoundsCache.clear(); + MarkDirty(); + // Notify UI + NotifyTransactionChanged(uint256::ONE, CT_UPDATED); +} + +bool CWallet::IsDenominated(const COutPoint& outpoint) const +{ + LOCK(cs_wallet); + + const auto it = mapWallet.find(outpoint.hash); + if (it == mapWallet.end()) { + return false; + } + + if (outpoint.n >= it->second.tx->vout.size()) { + return false; + } + + return CoinJoin::IsDenominatedAmount(it->second.tx->vout[outpoint.n].nValue); +} + +bool CWallet::IsFullyMixed(const COutPoint& outpoint) const +{ + int nRounds = GetRealOutpointCoinJoinRounds(outpoint); + // Mix again if we don't have N rounds yet + if (nRounds < CCoinJoinClientOptions::GetRounds()) return false; + + // Try to mix a "random" number of rounds more than minimum. + // If we have already mixed N + MaxOffset rounds, don't mix again. + // Otherwise, we should mix again 50% of the time, this results in an exponential decay + // N rounds 50% N+1 25% N+2 12.5%... until we reach N + GetRandomRounds() rounds where we stop. + if (nRounds < CCoinJoinClientOptions::GetRounds() + CCoinJoinClientOptions::GetRandomRounds()) { + CDataStream ss(SER_GETHASH, PROTOCOL_VERSION); + ss << outpoint << nCoinJoinSalt; + uint256 nHash; + CSHA256().Write((const unsigned char*)ss.data(), ss.size()).Finalize(nHash.begin()); + if (ReadLE64(nHash.begin()) % 2 == 0) { + return false; + } + } + + return true; +} + +void CWallet::RecalculateMixedCredit(const uint256 hash) +{ + AssertLockHeld(cs_wallet); + if (auto it = mapWallet.find(hash); it != mapWallet.end()) { + // Recalculate all credits for this tx + it->second.MarkDirty(); + } + fAnonymizableTallyCached = false; + fAnonymizableTallyCachedNonDenom = false; +} + +CAmount CWallet::GetBalanceAnonymized(const CCoinControl& coinControl) const +{ + if (!CCoinJoinClientOptions::IsEnabled()) return 0; + + CAmount anonymized_amount{0}; + LOCK(cs_wallet); + for (auto pcoin : GetSpendableTXs()) { + anonymized_amount += pcoin->GetAnonymizedCredit(coinControl); + } + return anonymized_amount; +} + +CAmount CWallet::GetAnonymizableBalance(bool fSkipDenominated, bool fSkipUnconfirmed) const +{ + if (!CCoinJoinClientOptions::IsEnabled()) return 0; + + std::vector vecTally = SelectCoinsGroupedByAddresses(fSkipDenominated, true, fSkipUnconfirmed); + if (vecTally.empty()) return 0; + + CAmount nTotal = 0; + + const CAmount nSmallestDenom = CoinJoin::GetSmallestDenomination(); + const CAmount nMixingCollateral = CoinJoin::GetCollateralAmount(); + for (const auto& item : vecTally) { + bool fIsDenominated = CoinJoin::IsDenominatedAmount(item.nAmount); + if(fSkipDenominated && fIsDenominated) continue; + // assume that the fee to create denoms should be mixing collateral at max + if(item.nAmount >= nSmallestDenom + (fIsDenominated ? 0 : nMixingCollateral)) + nTotal += item.nAmount; + } + + return nTotal; +} + +// Note: calculated including unconfirmed, +// that's ok as long as we use it for informational purposes only +float CWallet::GetAverageAnonymizedRounds() const +{ + if (!CCoinJoinClientOptions::IsEnabled()) return 0; + + int nTotal = 0; + int nCount = 0; + + LOCK(cs_wallet); + for (const auto& outpoint : setWalletUTXO) { + if(!IsDenominated(outpoint)) continue; + + nTotal += GetCappedOutpointCoinJoinRounds(outpoint); + nCount++; + } + + if(nCount == 0) return 0; + + return (float)nTotal/nCount; +} + +// Note: calculated including unconfirmed, +// that's ok as long as we use it for informational purposes only +CAmount CWallet::GetNormalizedAnonymizedBalance() const +{ + if (!CCoinJoinClientOptions::IsEnabled()) return 0; + + CAmount nTotal = 0; + + LOCK(cs_wallet); + for (const auto& outpoint : setWalletUTXO) { + const auto it = mapWallet.find(outpoint.hash); + if (it == mapWallet.end()) continue; + + CAmount nValue = it->second.tx->vout[outpoint.n].nValue; + if (!CoinJoin::IsDenominatedAmount(nValue)) continue; + if (it->second.GetDepthInMainChain() < 0) continue; + + int nRounds = GetCappedOutpointCoinJoinRounds(outpoint); + nTotal += nValue * nRounds / CCoinJoinClientOptions::GetRounds(); + } + + return nTotal; +} + +CAmount CWalletTx::GetAnonymizedCredit(const CCoinControl& coinControl) const +{ + if (!pwallet) + return 0; + + AssertLockHeld(pwallet->cs_wallet); + + // Exclude coinbase and conflicted txes + if (IsCoinBase() || GetDepthInMainChain() < 0) + return 0; + + CAmount nCredit = 0; + uint256 hashTx = GetHash(); + for (unsigned int i = 0; i < tx->vout.size(); i++) + { + const CTxOut &txout = tx->vout[i]; + const COutPoint outpoint = COutPoint(hashTx, i); + + if (coinControl.HasSelected() && !coinControl.IsSelected(outpoint)) { + continue; + } + + if (pwallet->IsSpent(hashTx, i) || !CoinJoin::IsDenominatedAmount(txout.nValue)) continue; + + if (pwallet->IsFullyMixed(outpoint)) { + nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE); + if (!MoneyRange(nCredit)) + throw std::runtime_error(std::string(__func__) + ": value out of range"); + } + } + + return nCredit; +} + +CWalletTx::CoinJoinCredits CWalletTx::GetAvailableCoinJoinCredits() const +{ + CWalletTx::CoinJoinCredits ret; + if (pwallet == nullptr) + return ret; + + AssertLockHeld(pwallet->cs_wallet); + + // Must wait until coinbase is safely deep enough in the chain before valuing it + if (IsCoinBase() && GetBlocksToMaturity() > 0) + return ret; + + int nDepth = GetDepthInMainChain(); + if (nDepth < 0) return ret; + + ret.is_unconfirmed = IsTrusted() && nDepth == 0; + + if (m_amounts[ANON_CREDIT].m_cached[ISMINE_SPENDABLE]) { + if (ret.is_unconfirmed && m_amounts[DENOM_UCREDIT].m_cached[ISMINE_SPENDABLE]) { + return {m_amounts[ANON_CREDIT].m_value[ISMINE_SPENDABLE], m_amounts[DENOM_UCREDIT].m_value[ISMINE_SPENDABLE], ret.is_unconfirmed}; + } else if (!ret.is_unconfirmed && m_amounts[DENOM_CREDIT].m_cached[ISMINE_SPENDABLE]) { + return {m_amounts[ANON_CREDIT].m_value[ISMINE_SPENDABLE], m_amounts[DENOM_CREDIT].m_value[ISMINE_SPENDABLE], ret.is_unconfirmed}; + } + } + + uint256 hashTx = GetHash(); + for (unsigned int i = 0; i < tx->vout.size(); i++) { + const CTxOut &txout = tx->vout[i]; + const COutPoint outpoint = COutPoint(hashTx, i); + + if (pwallet->IsSpent(hashTx, i) || !CoinJoin::IsDenominatedAmount(txout.nValue)) continue; + const CAmount credit = pwallet->GetCredit(txout, ISMINE_SPENDABLE); + + if (pwallet->IsFullyMixed(outpoint)) { + ret.m_anonymized += credit; + if (!MoneyRange(ret.m_anonymized)) + throw std::runtime_error(std::string(__func__) + ": value out of range"); + } + + ret.m_denominated += credit; + if (!MoneyRange(ret.m_denominated)) + throw std::runtime_error(std::string(__func__) + ": value out of range"); + } + + m_amounts[ANON_CREDIT].Set(ISMINE_SPENDABLE, ret.m_anonymized); + if (ret.is_unconfirmed) { + m_amounts[DENOM_UCREDIT].Set(ISMINE_SPENDABLE, ret.m_denominated); + } else { + m_amounts[DENOM_CREDIT].Set(ISMINE_SPENDABLE, ret.m_denominated); + } + return ret; +} diff --git a/src/wallet/coinjoin.h b/src/wallet/coinjoin.h new file mode 100644 index 0000000000000..f8d10dae65b36 --- /dev/null +++ b/src/wallet/coinjoin.h @@ -0,0 +1,18 @@ +// Copyright (c) 2009-2021 The Bitcoin Core developers +// Copyright (c) 2014-2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_WALLET_COINJOIN_H +#define BITCOIN_WALLET_COINJOIN_H + +// Use a macro instead of a function for conditional logging to prevent +// evaluating arguments when logging for the category is not enabled. +#define WalletCJLogPrint(wallet, ...) \ + do { \ + if (LogAcceptDebug(BCLog::COINJOIN)) { \ + wallet->WalletLogPrintf(__VA_ARGS__); \ + } \ + } while (0) + +#endif // BITCOIN_WALLET_COINJOIN_H diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp index d2631466bd448..5f61a24df5006 100644 --- a/src/wallet/receive.cpp +++ b/src/wallet/receive.cpp @@ -313,18 +313,6 @@ bool CWalletTx::IsTrusted() const return pwallet->IsTrusted(*this, trusted_parents); } -CAmount CWallet::GetBalanceAnonymized(const CCoinControl& coinControl) const -{ - if (!CCoinJoinClientOptions::IsEnabled()) return 0; - - CAmount anonymized_amount{0}; - LOCK(cs_wallet); - for (auto pcoin : GetSpendableTXs()) { - anonymized_amount += pcoin->GetAnonymizedCredit(coinControl); - } - return anonymized_amount; -} - CWallet::Balance CWallet::GetBalance(const int min_depth, const bool avoid_reuse, const bool fAddLocked) const { Balance ret; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index b5c9b46dd2954..53d3bb151b497 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1379,150 +1379,6 @@ CAmount CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const return 0; } -// Recursively determine the rounds of a given input (How deep is the CoinJoin chain for a given input) -int CWallet::GetRealOutpointCoinJoinRounds(const COutPoint& outpoint, int nRounds) const -{ - LOCK(cs_wallet); - - const int nRoundsMax = MAX_COINJOIN_ROUNDS + CCoinJoinClientOptions::GetRandomRounds(); - - if (nRounds >= nRoundsMax) { - // there can only be nRoundsMax rounds max - return nRoundsMax - 1; - } - - auto pair = mapOutpointRoundsCache.emplace(outpoint, -10); - auto nRoundsRef = &pair.first->second; - if (!pair.second) { - // we already processed it, just return what we have - return *nRoundsRef; - } - - // TODO wtx should refer to a CWalletTx object, not a pointer, based on surrounding code - const CWalletTx* wtx = GetWalletTx(outpoint.hash); - - if (wtx == nullptr || wtx->tx == nullptr) { - // no such tx in this wallet - *nRoundsRef = -1; - WalletCJLogPrint(this, "%s FAILED %-70s %3d\n", __func__, outpoint.ToStringShort(), -1); - return *nRoundsRef; - } - - // bounds check - if (outpoint.n >= wtx->tx->vout.size()) { - // should never actually hit this - *nRoundsRef = -4; - WalletCJLogPrint(this, "%s FAILED %-70s %3d\n", __func__, outpoint.ToStringShort(), -4); - return *nRoundsRef; - } - - auto txOutRef = &wtx->tx->vout[outpoint.n]; - - if (CoinJoin::IsCollateralAmount(txOutRef->nValue)) { - *nRoundsRef = -3; - WalletCJLogPrint(this, "%s UPDATED %-70s %3d\n", __func__, outpoint.ToStringShort(), *nRoundsRef); - return *nRoundsRef; - } - - // make sure the final output is non-denominate - if (!CoinJoin::IsDenominatedAmount(txOutRef->nValue)) { //NOT DENOM - *nRoundsRef = -2; - WalletCJLogPrint(this, "%s UPDATED %-70s %3d\n", __func__, outpoint.ToStringShort(), *nRoundsRef); - return *nRoundsRef; - } - - for (const auto& out : wtx->tx->vout) { - if (!CoinJoin::IsDenominatedAmount(out.nValue)) { - // this one is denominated but there is another non-denominated output found in the same tx - *nRoundsRef = 0; - WalletCJLogPrint(this, "%s UPDATED %-70s %3d\n", __func__, outpoint.ToStringShort(), *nRoundsRef); - return *nRoundsRef; - } - } - - // make sure we spent all of it with 0 fee, reset to 0 rounds otherwise - if (wtx->GetDebit(ISMINE_SPENDABLE) != wtx->GetCredit(ISMINE_SPENDABLE)) { - *nRoundsRef = 0; - WalletCJLogPrint(this, "%s UPDATED %-70s %3d\n", __func__, outpoint.ToStringShort(), *nRoundsRef); - return *nRoundsRef; - } - - int nShortest = -10; // an initial value, should be no way to get this by calculations - bool fDenomFound = false; - // only denoms here so let's look up - for (const auto& txinNext : wtx->tx->vin) { - if (IsMine(txinNext)) { - int n = GetRealOutpointCoinJoinRounds(txinNext.prevout, nRounds + 1); - // denom found, find the shortest chain or initially assign nShortest with the first found value - if(n >= 0 && (n < nShortest || nShortest == -10)) { - nShortest = n; - fDenomFound = true; - } - } - } - *nRoundsRef = fDenomFound - ? (nShortest >= nRoundsMax - 1 ? nRoundsMax : nShortest + 1) // good, we a +1 to the shortest one but only nRoundsMax rounds max allowed - : 0; // too bad, we are the fist one in that chain - WalletCJLogPrint(this, "%s UPDATED %-70s %3d\n", __func__, outpoint.ToStringShort(), *nRoundsRef); - return *nRoundsRef; -} - -// respect current settings -int CWallet::GetCappedOutpointCoinJoinRounds(const COutPoint& outpoint) const -{ - LOCK(cs_wallet); - int realCoinJoinRounds = GetRealOutpointCoinJoinRounds(outpoint); - return realCoinJoinRounds > CCoinJoinClientOptions::GetRounds() ? CCoinJoinClientOptions::GetRounds() : realCoinJoinRounds; -} - -void CWallet::ClearCoinJoinRoundsCache() -{ - LOCK(cs_wallet); - mapOutpointRoundsCache.clear(); - MarkDirty(); - // Notify UI - NotifyTransactionChanged(uint256::ONE, CT_UPDATED); -} - -bool CWallet::IsDenominated(const COutPoint& outpoint) const -{ - LOCK(cs_wallet); - - const auto it = mapWallet.find(outpoint.hash); - if (it == mapWallet.end()) { - return false; - } - - if (outpoint.n >= it->second.tx->vout.size()) { - return false; - } - - return CoinJoin::IsDenominatedAmount(it->second.tx->vout[outpoint.n].nValue); -} - -bool CWallet::IsFullyMixed(const COutPoint& outpoint) const -{ - int nRounds = GetRealOutpointCoinJoinRounds(outpoint); - // Mix again if we don't have N rounds yet - if (nRounds < CCoinJoinClientOptions::GetRounds()) return false; - - // Try to mix a "random" number of rounds more than minimum. - // If we have already mixed N + MaxOffset rounds, don't mix again. - // Otherwise, we should mix again 50% of the time, this results in an exponential decay - // N rounds 50% N+1 25% N+2 12.5%... until we reach N + GetRandomRounds() rounds where we stop. - if (nRounds < CCoinJoinClientOptions::GetRounds() + CCoinJoinClientOptions::GetRandomRounds()) { - CDataStream ss(SER_GETHASH, PROTOCOL_VERSION); - ss << outpoint << nCoinJoinSalt; - uint256 nHash; - CSHA256().Write((const unsigned char*)ss.data(), ss.size()).Finalize(nHash.begin()); - if (ReadLE64(nHash.begin()) % 2 == 0) { - return false; - } - } - - return true; -} - isminetype CWallet::IsMine(const CTxOut& txout) const { AssertLockHeld(cs_wallet); @@ -1994,93 +1850,6 @@ std::set CWalletTx::GetConflicts() const return result; } -CAmount CWalletTx::GetAnonymizedCredit(const CCoinControl& coinControl) const -{ - if (!pwallet) - return 0; - - AssertLockHeld(pwallet->cs_wallet); - - // Exclude coinbase and conflicted txes - if (IsCoinBase() || GetDepthInMainChain() < 0) - return 0; - - CAmount nCredit = 0; - uint256 hashTx = GetHash(); - for (unsigned int i = 0; i < tx->vout.size(); i++) - { - const CTxOut &txout = tx->vout[i]; - const COutPoint outpoint = COutPoint(hashTx, i); - - if (coinControl.HasSelected() && !coinControl.IsSelected(outpoint)) { - continue; - } - - if (pwallet->IsSpent(hashTx, i) || !CoinJoin::IsDenominatedAmount(txout.nValue)) continue; - - if (pwallet->IsFullyMixed(outpoint)) { - nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE); - if (!MoneyRange(nCredit)) - throw std::runtime_error(std::string(__func__) + ": value out of range"); - } - } - - return nCredit; -} - -CWalletTx::CoinJoinCredits CWalletTx::GetAvailableCoinJoinCredits() const -{ - CWalletTx::CoinJoinCredits ret; - if (pwallet == nullptr) - return ret; - - AssertLockHeld(pwallet->cs_wallet); - - // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsCoinBase() && GetBlocksToMaturity() > 0) - return ret; - - int nDepth = GetDepthInMainChain(); - if (nDepth < 0) return ret; - - ret.is_unconfirmed = IsTrusted() && nDepth == 0; - - if (m_amounts[ANON_CREDIT].m_cached[ISMINE_SPENDABLE]) { - if (ret.is_unconfirmed && m_amounts[DENOM_UCREDIT].m_cached[ISMINE_SPENDABLE]) { - return {m_amounts[ANON_CREDIT].m_value[ISMINE_SPENDABLE], m_amounts[DENOM_UCREDIT].m_value[ISMINE_SPENDABLE], ret.is_unconfirmed}; - } else if (!ret.is_unconfirmed && m_amounts[DENOM_CREDIT].m_cached[ISMINE_SPENDABLE]) { - return {m_amounts[ANON_CREDIT].m_value[ISMINE_SPENDABLE], m_amounts[DENOM_CREDIT].m_value[ISMINE_SPENDABLE], ret.is_unconfirmed}; - } - } - - uint256 hashTx = GetHash(); - for (unsigned int i = 0; i < tx->vout.size(); i++) { - const CTxOut &txout = tx->vout[i]; - const COutPoint outpoint = COutPoint(hashTx, i); - - if (pwallet->IsSpent(hashTx, i) || !CoinJoin::IsDenominatedAmount(txout.nValue)) continue; - const CAmount credit = pwallet->GetCredit(txout, ISMINE_SPENDABLE); - - if (pwallet->IsFullyMixed(outpoint)) { - ret.m_anonymized += credit; - if (!MoneyRange(ret.m_anonymized)) - throw std::runtime_error(std::string(__func__) + ": value out of range"); - } - - ret.m_denominated += credit; - if (!MoneyRange(ret.m_denominated)) - throw std::runtime_error(std::string(__func__) + ": value out of range"); - } - - m_amounts[ANON_CREDIT].Set(ISMINE_SPENDABLE, ret.m_anonymized); - if (ret.is_unconfirmed) { - m_amounts[DENOM_UCREDIT].Set(ISMINE_SPENDABLE, ret.m_denominated); - } else { - m_amounts[DENOM_CREDIT].Set(ISMINE_SPENDABLE, ret.m_denominated); - } - return ret; -} - // Rebroadcast transactions from the wallet. We do this on a random timer // to slightly obfuscate which transactions come from our wallet. // @@ -2162,113 +1931,6 @@ std::unordered_set CWallet::GetSpendableTXs() return ret; } -CAmount CWallet::GetAnonymizableBalance(bool fSkipDenominated, bool fSkipUnconfirmed) const -{ - if (!CCoinJoinClientOptions::IsEnabled()) return 0; - - std::vector vecTally = SelectCoinsGroupedByAddresses(fSkipDenominated, true, fSkipUnconfirmed); - if (vecTally.empty()) return 0; - - CAmount nTotal = 0; - - const CAmount nSmallestDenom = CoinJoin::GetSmallestDenomination(); - const CAmount nMixingCollateral = CoinJoin::GetCollateralAmount(); - for (const auto& item : vecTally) { - bool fIsDenominated = CoinJoin::IsDenominatedAmount(item.nAmount); - if(fSkipDenominated && fIsDenominated) continue; - // assume that the fee to create denoms should be mixing collateral at max - if(item.nAmount >= nSmallestDenom + (fIsDenominated ? 0 : nMixingCollateral)) - nTotal += item.nAmount; - } - - return nTotal; -} - -// Note: calculated including unconfirmed, -// that's ok as long as we use it for informational purposes only -float CWallet::GetAverageAnonymizedRounds() const -{ - if (!CCoinJoinClientOptions::IsEnabled()) return 0; - - int nTotal = 0; - int nCount = 0; - - LOCK(cs_wallet); - for (const auto& outpoint : setWalletUTXO) { - if(!IsDenominated(outpoint)) continue; - - nTotal += GetCappedOutpointCoinJoinRounds(outpoint); - nCount++; - } - - if(nCount == 0) return 0; - - return (float)nTotal/nCount; -} - -// Note: calculated including unconfirmed, -// that's ok as long as we use it for informational purposes only -CAmount CWallet::GetNormalizedAnonymizedBalance() const -{ - if (!CCoinJoinClientOptions::IsEnabled()) return 0; - - CAmount nTotal = 0; - - LOCK(cs_wallet); - for (const auto& outpoint : setWalletUTXO) { - const auto it = mapWallet.find(outpoint.hash); - if (it == mapWallet.end()) continue; - - CAmount nValue = it->second.tx->vout[outpoint.n].nValue; - if (!CoinJoin::IsDenominatedAmount(nValue)) continue; - if (it->second.GetDepthInMainChain() < 0) continue; - - int nRounds = GetCappedOutpointCoinJoinRounds(outpoint); - nTotal += nValue * nRounds / CCoinJoinClientOptions::GetRounds(); - } - - return nTotal; -} - -const uint256& CWallet::GetCoinJoinSalt() -{ - if (nCoinJoinSalt.IsNull()) { - InitCJSaltFromDb(); - } - return nCoinJoinSalt; -} - -void CWallet::InitCJSaltFromDb() -{ - assert(nCoinJoinSalt.IsNull()); - - WalletBatch batch(GetDatabase()); - if (!batch.ReadCoinJoinSalt(nCoinJoinSalt) && batch.ReadCoinJoinSalt(nCoinJoinSalt, true)) { - // Migrate salt stored with legacy key - batch.WriteCoinJoinSalt(nCoinJoinSalt); - } -} - -bool CWallet::SetCoinJoinSalt(const uint256& cj_salt) -{ - WalletBatch batch(GetDatabase()); - // Only store new salt in CWallet if database write is successful - if (batch.WriteCoinJoinSalt(cj_salt)) { - nCoinJoinSalt = cj_salt; - return true; - } - return false; -} - -struct CompareByPriority -{ - bool operator()(const COutput& t1, - const COutput& t2) const - { - return CoinJoin::CalculateAmountPriority(t1.GetInputCoin().effective_value) > CoinJoin::CalculateAmountPriority(t2.GetInputCoin().effective_value); - } -}; - bool CWallet::SignTransaction(CMutableTransaction& tx) const { AssertLockHeld(cs_wallet); @@ -2379,210 +2041,6 @@ bool CWallet::SignSpecialTxPayload(const uint256& hash, const CKeyID& keyid, std return false; } -bool CWallet::SelectTxDSInsByDenomination(int nDenom, CAmount nValueMax, std::vector& vecTxDSInRet) -{ - LOCK(cs_wallet); - - CAmount nValueTotal{0}; - - std::set setRecentTxIds; - std::vector vCoins; - - vecTxDSInRet.clear(); - - if (!CoinJoin::IsValidDenomination(nDenom)) { - return false; - } - CAmount nDenomAmount = CoinJoin::DenominationToAmount(nDenom); - - CCoinControl coin_control; - coin_control.nCoinType = CoinType::ONLY_READY_TO_MIX; - AvailableCoins(vCoins, &coin_control); - WalletCJLogPrint(this, "CWallet::%s -- vCoins.size(): %d\n", __func__, vCoins.size()); - - Shuffle(vCoins.rbegin(), vCoins.rend(), FastRandomContext()); - - for (const auto& out : vCoins) { - uint256 txHash = out.tx->GetHash(); - CAmount nValue = out.tx->tx->vout[out.i].nValue; - if (setRecentTxIds.find(txHash) != setRecentTxIds.end()) continue; // no duplicate txids - if (nValueTotal + nValue > nValueMax) continue; - if (nValue != nDenomAmount) continue; - - CTxIn txin = CTxIn(txHash, out.i); - CScript scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; - int nRounds = GetRealOutpointCoinJoinRounds(txin.prevout); - - nValueTotal += nValue; - vecTxDSInRet.emplace_back(CTxDSIn(txin, scriptPubKey, nRounds)); - setRecentTxIds.emplace(txHash); - WalletCJLogPrint(this, "CWallet::%s -- hash: %s, nValue: %d.%08d\n", - __func__, txHash.ToString(), nValue / COIN, nValue % COIN); - } - - WalletCJLogPrint(this, "CWallet::%s -- setRecentTxIds.size(): %d\n", __func__, setRecentTxIds.size()); - - return nValueTotal > 0; -} - -std::vector CWallet::SelectCoinsGroupedByAddresses(bool fSkipDenominated, bool fAnonymizable, bool fSkipUnconfirmed, int nMaxOupointsPerAddress) const -{ - LOCK(cs_wallet); - - isminefilter filter = ISMINE_SPENDABLE; - - // Try using the cache for already confirmed mixable inputs. - // This should only be used if nMaxOupointsPerAddress was NOT specified. - if(nMaxOupointsPerAddress == -1 && fAnonymizable && fSkipUnconfirmed) { - if(fSkipDenominated && fAnonymizableTallyCachedNonDenom) { - LogPrint(BCLog::SELECTCOINS, "SelectCoinsGroupedByAddresses - using cache for non-denom inputs %d\n", vecAnonymizableTallyCachedNonDenom.size()); - return vecAnonymizableTallyCachedNonDenom; - } - if(!fSkipDenominated && fAnonymizableTallyCached) { - LogPrint(BCLog::SELECTCOINS, "SelectCoinsGroupedByAddresses - using cache for all inputs %d\n", vecAnonymizableTallyCached.size()); - return vecAnonymizableTallyCached; - } - } - - CAmount nSmallestDenom = CoinJoin::GetSmallestDenomination(); - - // Tally - std::map mapTally; - std::set setWalletTxesCounted; - for (const auto& outpoint : setWalletUTXO) { - - if (!setWalletTxesCounted.emplace(outpoint.hash).second) continue; - - std::map::const_iterator it = mapWallet.find(outpoint.hash); - if (it == mapWallet.end()) continue; - - const CWalletTx& wtx = (*it).second; - - if(wtx.IsCoinBase() && wtx.GetBlocksToMaturity() > 0) continue; - if(fSkipUnconfirmed && !wtx.IsTrusted()) continue; - if (wtx.GetDepthInMainChain() < 0) continue; - - for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { - CTxDestination txdest; - if (!ExtractDestination(wtx.tx->vout[i].scriptPubKey, txdest)) continue; - - isminefilter mine = IsMine(txdest); - if(!(mine & filter)) continue; - - auto itTallyItem = mapTally.find(txdest); - if (nMaxOupointsPerAddress != -1 && itTallyItem != mapTally.end() && int64_t(itTallyItem->second.vecInputCoins.size()) >= nMaxOupointsPerAddress) continue; - - if(IsSpent(outpoint.hash, i) || IsLockedCoin(outpoint.hash, i)) continue; - - if(fSkipDenominated && CoinJoin::IsDenominatedAmount(wtx.tx->vout[i].nValue)) continue; - - if(fAnonymizable) { - // ignore collaterals - if(CoinJoin::IsCollateralAmount(wtx.tx->vout[i].nValue)) continue; - if (fMasternodeMode && dmn_types::IsCollateralAmount(wtx.tx->vout[i].nValue)) continue; - // ignore outputs that are 10 times smaller then the smallest denomination - // otherwise they will just lead to higher fee / lower priority - if(wtx.tx->vout[i].nValue <= nSmallestDenom/10) continue; - // ignore mixed - if (IsFullyMixed(COutPoint(outpoint.hash, i))) continue; - } - - if (itTallyItem == mapTally.end()) { - itTallyItem = mapTally.emplace(txdest, CompactTallyItem()).first; - itTallyItem->second.txdest = txdest; - } - itTallyItem->second.nAmount += wtx.tx->vout[i].nValue; - itTallyItem->second.vecInputCoins.emplace_back(wtx.tx, i); - } - } - - // construct resulting vector - // NOTE: vecTallyRet is "sorted" by txdest (i.e. address), just like mapTally - std::vector vecTallyRet; - for (const auto& item : mapTally) { - if(fAnonymizable && item.second.nAmount < nSmallestDenom) continue; - vecTallyRet.push_back(item.second); - } - - // Cache already confirmed mixable entries for later use. - // This should only be used if nMaxOupointsPerAddress was NOT specified. - if(nMaxOupointsPerAddress == -1 && fAnonymizable && fSkipUnconfirmed) { - if(fSkipDenominated) { - vecAnonymizableTallyCachedNonDenom = vecTallyRet; - fAnonymizableTallyCachedNonDenom = true; - } else { - vecAnonymizableTallyCached = vecTallyRet; - fAnonymizableTallyCached = true; - } - } - - // debug - if (LogAcceptDebug(BCLog::SELECTCOINS)) { - std::string strMessage = "SelectCoinsGroupedByAddresses - vecTallyRet:\n"; - for (const auto& item : vecTallyRet) - strMessage += strprintf(" %s %f\n", EncodeDestination(item.txdest), float(item.nAmount)/COIN); - LogPrint(BCLog::SELECTCOINS, "%s", strMessage); /* Continued */ - } - - return vecTallyRet; -} - -bool CWallet::SelectDenominatedAmounts(CAmount nValueMax, std::set& setAmountsRet) const -{ - LOCK(cs_wallet); - - CAmount nValueTotal{0}; - setAmountsRet.clear(); - - std::vector vCoins; - CCoinControl coin_control; - coin_control.nCoinType = CoinType::ONLY_READY_TO_MIX; - AvailableCoins(vCoins, &coin_control); - // larger denoms first - std::sort(vCoins.rbegin(), vCoins.rend(), CompareByPriority()); - - for (const auto& out : vCoins) { - CAmount nValue = out.tx->tx->vout[out.i].nValue; - if (nValueTotal + nValue <= nValueMax) { - nValueTotal += nValue; - setAmountsRet.emplace(nValue); - } - } - - return nValueTotal >= CoinJoin::GetSmallestDenomination(); -} - -int CWallet::CountInputsWithAmount(CAmount nInputAmount) const -{ - CAmount nTotal = 0; - - LOCK(cs_wallet); - - for (const auto& outpoint : setWalletUTXO) { - const auto it = mapWallet.find(outpoint.hash); - if (it == mapWallet.end()) continue; - if (it->second.tx->vout[outpoint.n].nValue != nInputAmount) continue; - if (it->second.GetDepthInMainChain() < 0) continue; - - nTotal++; - } - - return nTotal; -} - -bool CWallet::HasCollateralInputs(bool fOnlyConfirmed) const -{ - LOCK(cs_wallet); - - std::vector vCoins; - CCoinControl coin_control; - coin_control.m_include_unsafe_inputs = !fOnlyConfirmed; - coin_control.nCoinType = CoinType::ONLY_COINJOIN_COLLATERAL; - AvailableCoins(vCoins, &coin_control); - - return !vCoins.empty(); -} - bool CWallet::GetBudgetSystemCollateralTX(CTransactionRef& tx, uint256 hash, CAmount amount, const COutPoint& outpoint) { CScript scriptChange; @@ -2982,17 +2440,6 @@ void ReserveDestination::ReturnDestination() address = CNoDestination(); } -void CWallet::RecalculateMixedCredit(const uint256 hash) -{ - AssertLockHeld(cs_wallet); - if (auto it = mapWallet.find(hash); it != mapWallet.end()) { - // Recalculate all credits for this tx - it->second.MarkDirty(); - } - fAnonymizableTallyCached = false; - fAnonymizableTallyCachedNonDenom = false; -} - bool CWallet::LockCoin(const COutPoint& output, WalletBatch* batch) { AssertLockHeld(cs_wallet); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f4eb31a6dc7aa..1ff835bcb0ca5 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -156,15 +156,6 @@ static const std::map WALLET_FLAG_MAP{ extern const std::map WALLET_FLAG_CAVEATS; -// Use a macro instead of a function for conditional logging to prevent -// evaluating arguments when logging for the category is not enabled. -#define WalletCJLogPrint(wallet, ...) \ - do { \ - if (LogAcceptDebug(BCLog::COINJOIN)) { \ - wallet->WalletLogPrintf(__VA_ARGS__); \ - } \ - } while (0) - /** A wrapper to reserve an address from a wallet * * ReserveDestination is used to reserve an address. It is passed around From 460c1876386ed4b3c54deb1c874d83d65cb5a064 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 22 Apr 2025 18:31:43 +0000 Subject: [PATCH 03/13] refactor: move some CoinJoin-specific logic out of CWalletTx and CWallet --- src/wallet/coinjoin.cpp | 71 ++++++++++++++++++--------------------- src/wallet/coinjoin.h | 23 +++++++++++++ src/wallet/interfaces.cpp | 3 +- src/wallet/receive.cpp | 3 +- src/wallet/transaction.h | 16 --------- src/wallet/wallet.h | 7 ++-- 6 files changed, 63 insertions(+), 60 deletions(-) diff --git a/src/wallet/coinjoin.cpp b/src/wallet/coinjoin.cpp index c0070ccfd5536..6e8e07f450a22 100644 --- a/src/wallet/coinjoin.cpp +++ b/src/wallet/coinjoin.cpp @@ -410,14 +410,14 @@ void CWallet::RecalculateMixedCredit(const uint256 hash) fAnonymizableTallyCachedNonDenom = false; } -CAmount CWallet::GetBalanceAnonymized(const CCoinControl& coinControl) const +CAmount GetBalanceAnonymized(const CWallet& wallet, const CCoinControl& coinControl) { if (!CCoinJoinClientOptions::IsEnabled()) return 0; CAmount anonymized_amount{0}; - LOCK(cs_wallet); - for (auto pcoin : GetSpendableTXs()) { - anonymized_amount += pcoin->GetAnonymizedCredit(coinControl); + LOCK(wallet.cs_wallet); + for (auto pcoin : wallet.GetSpendableTXs()) { + anonymized_amount += CachedTxGetAnonymizedCredit(wallet, *pcoin, coinControl); } return anonymized_amount; } @@ -490,32 +490,29 @@ CAmount CWallet::GetNormalizedAnonymizedBalance() const return nTotal; } -CAmount CWalletTx::GetAnonymizedCredit(const CCoinControl& coinControl) const +CAmount CachedTxGetAnonymizedCredit(const CWallet& wallet, const CWalletTx& wtx, const CCoinControl& coinControl) { - if (!pwallet) - return 0; - - AssertLockHeld(pwallet->cs_wallet); + AssertLockHeld(wallet.cs_wallet); // Exclude coinbase and conflicted txes - if (IsCoinBase() || GetDepthInMainChain() < 0) + if (wtx.IsCoinBase() || wtx.GetDepthInMainChain() < 0) return 0; CAmount nCredit = 0; - uint256 hashTx = GetHash(); - for (unsigned int i = 0; i < tx->vout.size(); i++) + uint256 hashTx = wtx.GetHash(); + for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { - const CTxOut &txout = tx->vout[i]; + const CTxOut &txout = wtx.tx->vout[i]; const COutPoint outpoint = COutPoint(hashTx, i); if (coinControl.HasSelected() && !coinControl.IsSelected(outpoint)) { continue; } - if (pwallet->IsSpent(hashTx, i) || !CoinJoin::IsDenominatedAmount(txout.nValue)) continue; + if (wallet.IsSpent(hashTx, i) || !CoinJoin::IsDenominatedAmount(txout.nValue)) continue; - if (pwallet->IsFullyMixed(outpoint)) { - nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE); + if (wallet.IsFullyMixed(outpoint)) { + nCredit += wallet.GetCredit(txout, ISMINE_SPENDABLE); if (!MoneyRange(nCredit)) throw std::runtime_error(std::string(__func__) + ": value out of range"); } @@ -524,40 +521,38 @@ CAmount CWalletTx::GetAnonymizedCredit(const CCoinControl& coinControl) const return nCredit; } -CWalletTx::CoinJoinCredits CWalletTx::GetAvailableCoinJoinCredits() const +CoinJoinCredits CachedTxGetAvailableCoinJoinCredits(const CWallet& wallet, const CWalletTx& wtx) { - CWalletTx::CoinJoinCredits ret; - if (pwallet == nullptr) - return ret; + CoinJoinCredits ret; - AssertLockHeld(pwallet->cs_wallet); + AssertLockHeld(wallet.cs_wallet); // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsCoinBase() && GetBlocksToMaturity() > 0) + if (wtx.IsCoinBase() && wtx.GetBlocksToMaturity() > 0) return ret; - int nDepth = GetDepthInMainChain(); + int nDepth = wtx.GetDepthInMainChain(); if (nDepth < 0) return ret; - ret.is_unconfirmed = IsTrusted() && nDepth == 0; + ret.is_unconfirmed = wtx.IsTrusted() && nDepth == 0; - if (m_amounts[ANON_CREDIT].m_cached[ISMINE_SPENDABLE]) { - if (ret.is_unconfirmed && m_amounts[DENOM_UCREDIT].m_cached[ISMINE_SPENDABLE]) { - return {m_amounts[ANON_CREDIT].m_value[ISMINE_SPENDABLE], m_amounts[DENOM_UCREDIT].m_value[ISMINE_SPENDABLE], ret.is_unconfirmed}; - } else if (!ret.is_unconfirmed && m_amounts[DENOM_CREDIT].m_cached[ISMINE_SPENDABLE]) { - return {m_amounts[ANON_CREDIT].m_value[ISMINE_SPENDABLE], m_amounts[DENOM_CREDIT].m_value[ISMINE_SPENDABLE], ret.is_unconfirmed}; + if (wtx.m_amounts[CWalletTx::ANON_CREDIT].m_cached[ISMINE_SPENDABLE]) { + if (ret.is_unconfirmed && wtx.m_amounts[CWalletTx::DENOM_UCREDIT].m_cached[ISMINE_SPENDABLE]) { + return {wtx.m_amounts[CWalletTx::ANON_CREDIT].m_value[ISMINE_SPENDABLE], wtx.m_amounts[CWalletTx::DENOM_UCREDIT].m_value[ISMINE_SPENDABLE], ret.is_unconfirmed}; + } else if (!ret.is_unconfirmed && wtx.m_amounts[CWalletTx::DENOM_CREDIT].m_cached[ISMINE_SPENDABLE]) { + return {wtx.m_amounts[CWalletTx::ANON_CREDIT].m_value[ISMINE_SPENDABLE], wtx.m_amounts[CWalletTx::DENOM_CREDIT].m_value[ISMINE_SPENDABLE], ret.is_unconfirmed}; } } - uint256 hashTx = GetHash(); - for (unsigned int i = 0; i < tx->vout.size(); i++) { - const CTxOut &txout = tx->vout[i]; + uint256 hashTx = wtx.GetHash(); + for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { + const CTxOut &txout = wtx.tx->vout[i]; const COutPoint outpoint = COutPoint(hashTx, i); - if (pwallet->IsSpent(hashTx, i) || !CoinJoin::IsDenominatedAmount(txout.nValue)) continue; - const CAmount credit = pwallet->GetCredit(txout, ISMINE_SPENDABLE); + if (wallet.IsSpent(hashTx, i) || !CoinJoin::IsDenominatedAmount(txout.nValue)) continue; + const CAmount credit = wallet.GetCredit(txout, ISMINE_SPENDABLE); - if (pwallet->IsFullyMixed(outpoint)) { + if (wallet.IsFullyMixed(outpoint)) { ret.m_anonymized += credit; if (!MoneyRange(ret.m_anonymized)) throw std::runtime_error(std::string(__func__) + ": value out of range"); @@ -568,11 +563,11 @@ CWalletTx::CoinJoinCredits CWalletTx::GetAvailableCoinJoinCredits() const throw std::runtime_error(std::string(__func__) + ": value out of range"); } - m_amounts[ANON_CREDIT].Set(ISMINE_SPENDABLE, ret.m_anonymized); + wtx.m_amounts[CWalletTx::ANON_CREDIT].Set(ISMINE_SPENDABLE, ret.m_anonymized); if (ret.is_unconfirmed) { - m_amounts[DENOM_UCREDIT].Set(ISMINE_SPENDABLE, ret.m_denominated); + wtx.m_amounts[CWalletTx::DENOM_UCREDIT].Set(ISMINE_SPENDABLE, ret.m_denominated); } else { - m_amounts[DENOM_CREDIT].Set(ISMINE_SPENDABLE, ret.m_denominated); + wtx.m_amounts[CWalletTx::DENOM_CREDIT].Set(ISMINE_SPENDABLE, ret.m_denominated); } return ret; } diff --git a/src/wallet/coinjoin.h b/src/wallet/coinjoin.h index f8d10dae65b36..ae2f785d6eae8 100644 --- a/src/wallet/coinjoin.h +++ b/src/wallet/coinjoin.h @@ -6,6 +6,14 @@ #ifndef BITCOIN_WALLET_COINJOIN_H #define BITCOIN_WALLET_COINJOIN_H +#include +#include +#include + +class CCoinControl; +class CWallet; +class CWalletTx; + // Use a macro instead of a function for conditional logging to prevent // evaluating arguments when logging for the category is not enabled. #define WalletCJLogPrint(wallet, ...) \ @@ -15,4 +23,19 @@ } \ } while (0) +CAmount GetBalanceAnonymized(const CWallet& wallet, const CCoinControl& coinControl); + +CAmount CachedTxGetAnonymizedCredit(const CWallet& wallet, const CWalletTx& wtx, const CCoinControl& coinControl) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); + +struct CoinJoinCredits +{ + CAmount m_anonymized{0}; + CAmount m_denominated{0}; + bool is_unconfirmed{false}; +}; + +CoinJoinCredits CachedTxGetAvailableCoinJoinCredits(const CWallet& wallet, const CWalletTx& wtx) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); + #endif // BITCOIN_WALLET_COINJOIN_H diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index c48ef9af4b268..fbc2a6abc5aa9 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -424,7 +425,7 @@ class WalletImpl : public Wallet CAmount getAvailableBalance(const CCoinControl& coin_control) override { if (coin_control.IsUsingCoinJoin()) { - return m_wallet->GetBalanceAnonymized(coin_control); + return GetBalanceAnonymized(*m_wallet, coin_control); } else { return m_wallet->GetAvailableBalance(&coin_control); } diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp index 5f61a24df5006..e67b731afc4b3 100644 --- a/src/wallet/receive.cpp +++ b/src/wallet/receive.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -339,7 +340,7 @@ CWallet::Balance CWallet::GetBalance(const int min_depth, const bool avoid_reuse ret.m_mine_immature += wtx.GetImmatureCredit(); ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit(); if (cj_enabled) { - const auto balance_anonymized = wtx.GetAvailableCoinJoinCredits(); + const auto balance_anonymized = CachedTxGetAvailableCoinJoinCredits(*this, wtx); ret.m_anonymized += balance_anonymized.m_anonymized; if (balance_anonymized.is_unconfirmed) { ret.m_denominated_untrusted_pending += balance_anonymized.m_denominated; diff --git a/src/wallet/transaction.h b/src/wallet/transaction.h index 0cb09d835d1da..7d6b57694c5af 100644 --- a/src/wallet/transaction.h +++ b/src/wallet/transaction.h @@ -285,22 +285,6 @@ class CWalletTx CAmount GetImmatureWatchOnlyCredit(const bool fUseCache = true) const; CAmount GetChange() const; - struct CoinJoinCredits - { - CAmount m_anonymized{0}; - CAmount m_denominated{0}; - bool is_unconfirmed{false}; - }; - - // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct - // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The - // annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid - // having to resolve the issue of member access into incomplete type CWallet. - /* Requires cs_wallet lock. */ - CAmount GetAnonymizedCredit(const CCoinControl& coinControl) const NO_THREAD_SAFETY_ANALYSIS; - /* Requires cs_wallet lock. */ - CoinJoinCredits GetAvailableCoinJoinCredits() const NO_THREAD_SAFETY_ANALYSIS; - /** Get the marginal bytes if spending the specified output from this transaction */ int GetSpendSize(unsigned int out, bool use_max_sig = false) const { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 1ff835bcb0ca5..e391c97d40e07 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -356,9 +356,6 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati /** Internal database handle. */ std::unique_ptr const m_database; - // A helper function which loops through wallet UTXOs - std::unordered_set GetSpendableTXs() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - /** * The following is used to keep track of how far behind the wallet is * from the chain sync, and to allow clients to block on us being caught up. @@ -474,6 +471,9 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati void UpdateProgress(const std::string& title, int nProgress) override; + /* A helper function which loops through wallet UTXOs */ + std::unordered_set GetSpendableTXs() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + /** Map from txid to CWalletTx for all transactions this wallet is * interested in, including received and sent transactions. */ std::map mapWallet GUARDED_BY(cs_wallet); @@ -666,7 +666,6 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati CAmount m_denominated_untrusted_pending{0}; }; Balance GetBalance(const int min_depth = 0, const bool avoid_reuse = true, const bool fAddLocked = false) const; - CAmount GetBalanceAnonymized(const CCoinControl& coinControl) const; CAmount GetAnonymizableBalance(bool fSkipDenominated = false, bool fSkipUnconfirmed = true) const; float GetAverageAnonymizedRounds() const; From e03fdff753040a89e79ad1737789959343c3f4af Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Wed, 30 Apr 2025 10:59:52 +0000 Subject: [PATCH 04/13] refactor: move ISLock and ChainLock wtx check to CWallet Co-authored-by: UdjinM6 --- src/wallet/interfaces.cpp | 7 +++++-- src/wallet/receive.cpp | 6 +++--- src/wallet/rpcwallet.cpp | 20 ++++++++++++-------- src/wallet/transaction.h | 25 ++++++++++++++++++------- src/wallet/wallet.cpp | 37 +++++++++++++++++++------------------ src/wallet/wallet.h | 3 +++ 6 files changed, 60 insertions(+), 38 deletions(-) diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index fbc2a6abc5aa9..29c1e90d47926 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -94,7 +94,10 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) //! Construct wallet tx status struct. WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { + AssertLockHeld(wallet.cs_wallet); + WalletTxStatus result; result.block_height = wtx.m_confirm.block_height > 0 ? wtx.m_confirm.block_height : std::numeric_limits::max(); result.blocks_to_maturity = wtx.GetBlocksToMaturity(); @@ -106,8 +109,8 @@ WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx) result.is_abandoned = wtx.isAbandoned(); result.is_coinbase = wtx.IsCoinBase(); result.is_in_main_chain = wtx.IsInMainChain(); - result.is_chainlocked = wtx.IsChainLocked(); - result.is_islocked = wtx.IsLockedByInstantSend(); + result.is_chainlocked = wallet.IsTxChainLocked(wtx); + result.is_islocked = wallet.IsTxLockedByInstantSend(wtx); return result; } diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp index e67b731afc4b3..4eba12235947c 100644 --- a/src/wallet/receive.cpp +++ b/src/wallet/receive.cpp @@ -282,7 +282,7 @@ bool CWallet::IsTrusted(const CWalletTx& wtx, std::set& trusted_parents int nDepth = wtx.GetDepthInMainChain(); if (nDepth >= 1) return true; if (nDepth < 0) return false; - if (wtx.IsLockedByInstantSend()) return true; + if (IsTxLockedByInstantSend(wtx)) return true; // using wtx's cached debit if (!m_spend_zero_conf_change || !wtx.IsFromMe(ISMINE_ALL)) return false; @@ -329,7 +329,7 @@ CWallet::Balance CWallet::GetBalance(const int min_depth, const bool avoid_reuse const int tx_depth{wtx.GetDepthInMainChain()}; const CAmount tx_credit_mine{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)}; const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)}; - if (is_trusted && ((tx_depth >= min_depth) || (fAddLocked && wtx.IsLockedByInstantSend()))) { + if (is_trusted && ((tx_depth >= min_depth) || (fAddLocked && IsTxLockedByInstantSend(wtx)))) { ret.m_mine_trusted += tx_credit_mine; ret.m_watchonly_trusted += tx_credit_watchonly; } @@ -371,7 +371,7 @@ std::map CWallet::GetAddressBalances() const continue; int nDepth = wtx.GetDepthInMainChain(); - if ((nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1)) && !wtx.IsLockedByInstantSend()) + if ((nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1)) && !IsTxLockedByInstantSend(wtx)) continue; for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 71722108dda37..c7b6d2169bbf7 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -58,13 +58,17 @@ bool HaveKey(const SigningProvider& wallet, const CKey& key) return wallet.HaveKey(key.GetPubKey().GetID()) || wallet.HaveKey(key2.GetPubKey().GetID()); } -static void WalletTxToJSON(interfaces::Chain& chain, const CWalletTx& wtx, UniValue& entry) +static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue& entry) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { + AssertLockHeld(wallet.cs_wallet); + + interfaces::Chain& chain = wallet.chain(); int confirms = wtx.GetDepthInMainChain(); bool fLocked = chain.isInstantSendLockedTx(wtx.GetHash()); bool chainlock = false; if (confirms > 0) { - chainlock = wtx.IsChainLocked(); + chainlock = wallet.IsTxChainLocked(wtx); } entry.pushKV("confirmations", confirms); entry.pushKV("instantlock", fLocked || chainlock); @@ -586,7 +590,7 @@ static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool b || !wallet.chain().checkFinalTx(*wtx.tx)) { continue; } - if (depth < min_depth && !(fAddLocked && wtx.IsLockedByInstantSend())) continue; + if (depth < min_depth && !(fAddLocked && wallet.IsTxLockedByInstantSend(wtx))) continue; for (const CTxOut& txout : wtx.tx->vout) { CTxDestination address; @@ -993,7 +997,7 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons const CWalletTx& wtx = pairWtx.second; int nDepth = wtx.GetDepthInMainChain(); - if ((nDepth < nMinDepth) && !(fAddLocked && wtx.IsLockedByInstantSend())) + if ((nDepth < nMinDepth) && !(fAddLocked && wallet.IsTxLockedByInstantSend(wtx))) continue; // Coinbase with less than 1 confirmation is no longer in the main chain @@ -1245,14 +1249,14 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM entry.pushKV("vout", s.vout); entry.pushKV("fee", ValueFromAmount(-nFee)); if (fLong) - WalletTxToJSON(wallet.chain(), wtx, entry); + WalletTxToJSON(wallet, wtx, entry); entry.pushKV("abandoned", wtx.isAbandoned()); ret.push_back(entry); } } // Received - if (listReceived.size() > 0 && ((wtx.GetDepthInMainChain() >= nMinDepth) || wtx.IsLockedByInstantSend())) + if (listReceived.size() > 0 && ((wtx.GetDepthInMainChain() >= nMinDepth) || wallet.IsTxLockedByInstantSend(wtx))) { for (const COutputEntry& r : listReceived) { @@ -1292,7 +1296,7 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM } entry.pushKV("vout", r.vout); if (fLong) - WalletTxToJSON(wallet.chain(), wtx, entry); + WalletTxToJSON(wallet, wtx, entry); ret.push_back(entry); } } @@ -1664,7 +1668,7 @@ static RPCHelpMan gettransaction() if (wtx.IsFromMe(filter)) entry.pushKV("fee", ValueFromAmount(nFee)); - WalletTxToJSON(pwallet->chain(), wtx, entry); + WalletTxToJSON(*pwallet, wtx, entry); UniValue details(UniValue::VARR); ListTransactions(*pwallet, wtx, 0, false, details, filter, nullptr /* filter_label */); diff --git a/src/wallet/transaction.h b/src/wallet/transaction.h index 7d6b57694c5af..fde7e2d2a21e2 100644 --- a/src/wallet/transaction.h +++ b/src/wallet/transaction.h @@ -77,9 +77,6 @@ class CWalletTx */ static constexpr const uint256& ABANDON_HASH = uint256::ONE; - mutable bool fIsChainlocked{false}; - mutable bool fIsInstantSendLocked{false}; - public: /** * Key/value map with information about the transaction. @@ -142,6 +139,8 @@ class CWalletTx mutable bool m_is_cache_empty{true}; mutable bool fChangeCached; mutable bool fInMempool; + mutable bool fIsChainlocked; + mutable bool fIsInstantSendLocked; mutable CAmount nChangeCached; CWalletTx(const CWallet* wallet, CTransactionRef arg) @@ -161,6 +160,8 @@ class CWalletTx fFromMe = false; fChangeCached = false; fInMempool = false; + fIsChainlocked = false; + fIsInstantSendLocked = false; nChangeCached = 0; nOrderPos = -1; m_confirm = Confirmation{}; @@ -307,10 +308,22 @@ class CWalletTx int64_t GetTxTime() const; - bool CanBeResent() const; + // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct + // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation + // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to + // resolve the issue of member access into incomplete type CWallet. Note + // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" + // in place. + bool CanBeResent() const NO_THREAD_SAFETY_ANALYSIS; /** Pass this transaction to node for mempool insertion and relay to peers if flag set to true */ - bool SubmitMemoryPoolAndRelay(bilingual_str& err_string, bool relay); + // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct + // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation + // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to + // resolve the issue of member access into incomplete type CWallet. Note + // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" + // in place. + bool SubmitMemoryPoolAndRelay(bilingual_str& err_string, bool relay) NO_THREAD_SAFETY_ANALYSIS; // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation @@ -334,8 +347,6 @@ class CWalletTx // in place. int GetDepthInMainChain() const NO_THREAD_SAFETY_ANALYSIS; bool IsInMainChain() const { return GetDepthInMainChain() > 0; } - bool IsLockedByInstantSend() const; - bool IsChainLocked() const NO_THREAD_SAFETY_ANALYSIS; /** * @return number of blocks to maturity for this transaction: diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 53d3bb151b497..205deb556381f 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1130,7 +1130,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) auto it = mapWallet.find(hashTx); assert(it != mapWallet.end()); const CWalletTx& origtx = it->second; - if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool() || origtx.IsLockedByInstantSend()) { + if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool() || IsTxLockedByInstantSend(origtx)) { return false; } @@ -1788,7 +1788,7 @@ void CWallet::ReacceptWalletTransactions() int nDepth = wtx.GetDepthInMainChain(); - if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.IsLockedByInstantSend() && !wtx.isAbandoned())) { + if (!wtx.IsCoinBase() && (nDepth == 0 && !IsTxLockedByInstantSend(wtx) && !wtx.isAbandoned())) { mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); } } @@ -1803,6 +1803,7 @@ void CWallet::ReacceptWalletTransactions() bool CWalletTx::CanBeResent() const { + AssertLockHeld(pwallet->cs_wallet); return // Can't relay if wallet is not broadcasting pwallet->GetBroadcastTransactions() && @@ -1814,11 +1815,12 @@ bool CWalletTx::CanBeResent() const // Don't try to submit conflicted or confirmed transactions. GetDepthInMainChain() == 0 && // Don't try to submit transactions locked via InstantSend. - !IsLockedByInstantSend(); + !pwallet->IsTxLockedByInstantSend(*this); } bool CWalletTx::SubmitMemoryPoolAndRelay(bilingual_str& err_string, bool relay) { + AssertLockHeld(pwallet->cs_wallet); if (!CanBeResent()) return false; // Submit transaction to mempool for relay @@ -3459,28 +3461,27 @@ int CWalletTx::GetDepthInMainChain() const return (pwallet->GetLastBlockHeight() - m_confirm.block_height + 1) * (isConflicted() ? -1 : 1); } -bool CWalletTx::IsLockedByInstantSend() const +bool CWallet::IsTxLockedByInstantSend(const CWalletTx& wtx) const { - if (fIsChainlocked) { - fIsInstantSendLocked = false; - } else if (!fIsInstantSendLocked) { - fIsInstantSendLocked = pwallet->chain().isInstantSendLockedTx(GetHash()); + AssertLockHeld(cs_wallet); + if (wtx.fIsChainlocked) { + wtx.fIsInstantSendLocked = false; + } else if (!wtx.fIsInstantSendLocked) { + wtx.fIsInstantSendLocked = chain().isInstantSendLockedTx(wtx.GetHash()); } - return fIsInstantSendLocked; + return wtx.fIsInstantSendLocked; } -bool CWalletTx::IsChainLocked() const +bool CWallet::IsTxChainLocked(const CWalletTx& wtx) const { - if (!fIsChainlocked) { - assert(pwallet != nullptr); - AssertLockHeld(pwallet->cs_wallet); - bool active; - int height; - if (pwallet->chain().findBlock(m_confirm.hashBlock, FoundBlock().inActiveChain(active).height(height)) && active) { - fIsChainlocked = pwallet->chain().hasChainLock(height, m_confirm.hashBlock); + AssertLockHeld(cs_wallet); + if (!wtx.fIsChainlocked) { + bool active; int height; + if (chain().findBlock(wtx.m_confirm.hashBlock, FoundBlock().inActiveChain(active).height(height)) && active) { + wtx.fIsChainlocked = chain().hasChainLock(height, wtx.m_confirm.hashBlock); } } - return fIsChainlocked; + return wtx.fIsChainlocked; } int CWalletTx::GetBlocksToMaturity() const diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index e391c97d40e07..3e3c7e3c82065 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -509,6 +509,9 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati const CWalletTx* GetWalletTx(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool IsTrusted(const CWalletTx& wtx, std::set& trusted_parents) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool IsTxLockedByInstantSend(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool IsTxChainLocked(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + //! check whether we support the named feature bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return IsFeatureSupported(nWalletVersion, wf); } From 258cdc2d63cccbb4896971b65d2a7687d939d59e Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 19 Apr 2025 19:33:42 +0000 Subject: [PATCH 05/13] refactor: move and rename `GetBudgetSystemCollateralTX()` to `spend.cpp` Need to move last `CreateTransaction` usage away from `wallet.cpp` so that circular dependencies can be resolved in an upcoming commit. `GetBudgetSystemCollateralTX()` is not a getter, it *generates* a transaction, renaming to `GenBudgetSystemCollateralTx()`. --- src/rpc/governance.cpp | 2 +- src/wallet/spend.cpp | 25 +++++++++++++++++++++++++ src/wallet/wallet.cpp | 25 ------------------------- src/wallet/wallet.h | 2 +- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/rpc/governance.cpp b/src/rpc/governance.cpp index 87178ae853268..82f7d7d5cfa55 100644 --- a/src/rpc/governance.cpp +++ b/src/rpc/governance.cpp @@ -218,7 +218,7 @@ static RPCHelpMan gobject_prepare() CTransactionRef tx; - if (!wallet->GetBudgetSystemCollateralTX(tx, govobj.GetHash(), govobj.GetMinCollateralFee(), outpoint)) { + if (!wallet->GenBudgetSystemCollateralTx(tx, govobj.GetHash(), govobj.GetMinCollateralFee(), outpoint)) { std::string err = "Error making collateral transaction for governance object. Please check your wallet balance and make sure your wallet is unlocked."; if (!request.params[5].isNull() && !request.params[6].isNull()) { err += "Please verify your specified output is valid and is enough for the combined proposal fee and transaction fee."; diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 92f9b2405a14d..8e03ffe68b82f 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -1059,3 +1059,28 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC return true; } + +bool CWallet::GenBudgetSystemCollateralTx(CTransactionRef& tx, uint256 hash, CAmount amount, const COutPoint& outpoint) +{ + CScript scriptChange; + scriptChange << OP_RETURN << ToByteVector(hash); + + CAmount nFeeRet = 0; + int nChangePosRet = -1; + bilingual_str error; + std::vector< CRecipient > vecSend; + vecSend.push_back((CRecipient){scriptChange, amount, false}); + + CCoinControl coinControl; + if (!outpoint.IsNull()) { + coinControl.Select(outpoint); + } + FeeCalculation fee_calc_out; + bool success = CreateTransaction(vecSend, tx, nFeeRet, nChangePosRet, error, coinControl, fee_calc_out); + if(!success){ + WalletLogPrintf("CWallet::GenBudgetSystemCollateralTx -- Error: %s\n", error.original); + return false; + } + + return true; +} diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 205deb556381f..970445f3b2efd 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2043,31 +2043,6 @@ bool CWallet::SignSpecialTxPayload(const uint256& hash, const CKeyID& keyid, std return false; } -bool CWallet::GetBudgetSystemCollateralTX(CTransactionRef& tx, uint256 hash, CAmount amount, const COutPoint& outpoint) -{ - CScript scriptChange; - scriptChange << OP_RETURN << ToByteVector(hash); - - CAmount nFeeRet = 0; - int nChangePosRet = -1; - bilingual_str error; - std::vector< CRecipient > vecSend; - vecSend.push_back((CRecipient){scriptChange, amount, false}); - - CCoinControl coinControl; - if (!outpoint.IsNull()) { - coinControl.Select(outpoint); - } - FeeCalculation fee_calc_out; - bool success = CreateTransaction(vecSend, tx, nFeeRet, nChangePosRet, error, coinControl, fee_calc_out); - if(!success){ - WalletLogPrintf("CWallet::GetBudgetSystemCollateralTX -- Error: %s\n", error.original); - return false; - } - - return true; -} - void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector> orderForm) { LOCK(cs_wallet); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 3e3c7e3c82065..cd45bfbb77717 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -674,7 +674,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati float GetAverageAnonymizedRounds() const; CAmount GetNormalizedAnonymizedBalance() const; - bool GetBudgetSystemCollateralTX(CTransactionRef& tx, uint256 hash, CAmount amount, const COutPoint& outpoint=COutPoint()/*defaults null*/); + bool GenBudgetSystemCollateralTx(CTransactionRef& tx, uint256 hash, CAmount amount, const COutPoint& outpoint=COutPoint()/*defaults null*/); CAmount GetAvailableBalance(const CCoinControl* coinControl = nullptr) const; /** From 9a5dc6241018bc32f135869b30db0e9c3e80fb7b Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 22 Apr 2025 19:29:28 +0000 Subject: [PATCH 06/13] partial bitcoin#22100: Clean up new wallet spend, receive files added bitcoin#21207 Includes changes that primarily affect `wallet/receive.cpp` --- src/bench/coin_selection.cpp | 4 +- src/bench/wallet_balance.cpp | 5 +- src/coinjoin/client.cpp | 3 +- src/rpc/coinjoin.cpp | 5 +- src/wallet/coinjoin.cpp | 27 +-- src/wallet/interfaces.cpp | 30 ++- src/wallet/receive.cpp | 236 ++++++++++++------------ src/wallet/receive.h | 47 +++++ src/wallet/rpcwallet.cpp | 54 +++--- src/wallet/spend.cpp | 25 ++- src/wallet/spend.h | 14 +- src/wallet/test/coinselector_tests.cpp | 4 +- src/wallet/test/psbt_wallet_tests.cpp | 4 +- src/wallet/test/wallet_tests.cpp | 15 +- src/wallet/transaction.h | 86 +-------- src/wallet/wallet.cpp | 83 ++++----- src/wallet/wallet.h | 63 ++++--- src/wallet/walletdb.cpp | 2 +- test/lint/lint-circular-dependencies.py | 1 + 19 files changed, 350 insertions(+), 358 deletions(-) diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index 6b917c38b47ad..8722e77df456f 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -17,7 +17,7 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector(&wallet, MakeTransactionRef(std::move(tx)))); + wtxs.push_back(std::make_unique(MakeTransactionRef(std::move(tx)))); } // Simple benchmark for wallet coin selection. Note that it maybe be necessary @@ -44,7 +44,7 @@ static void CoinSelection(benchmark::Bench& bench) // Create coins std::vector coins; for (const auto& wtx : wtxs) { - coins.emplace_back(wtx.get(), 0 /* iIn */, 6 * 24 /* nDepthIn */, true /* spendable */, true /* solvable */, true /* safe */); + coins.emplace_back(wallet, *wtx, 0 /* iIn */, 6 * 24 /* nDepthIn */, true /* spendable */, true /* solvable */, true /* safe */); } const CoinEligibilityFilter filter_standard(1, 6, 0); const CoinSelectionParams coin_selection_params(/* change_output_size= */ 34, diff --git a/src/bench/wallet_balance.cpp b/src/bench/wallet_balance.cpp index a4e384699abd5..c49df3085b81d 100644 --- a/src/bench/wallet_balance.cpp +++ b/src/bench/wallet_balance.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -35,11 +36,11 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b } SyncWithValidationInterfaceQueue(); - auto bal = wallet.GetBalance(); // Cache + auto bal = GetBalance(wallet); // Cache bench.minEpochIterations(epoch_iters).run([&] { if (set_dirty) wallet.MarkDirty(); - bal = wallet.GetBalance(); + bal = GetBalance(wallet); if (add_mine) assert(bal.m_mine_trusted > 0); }); } diff --git a/src/coinjoin/client.cpp b/src/coinjoin/client.cpp index 280eae0a9785f..8f7eafc01761b 100644 --- a/src/coinjoin/client.cpp +++ b/src/coinjoin/client.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -825,7 +826,7 @@ bool CCoinJoinClientSession::DoAutomaticDenominating(ChainstateManager& chainman return false; } - const auto bal = m_wallet->GetBalance(); + const auto bal = GetBalance(*m_wallet); // check if there is anything left to do CAmount nBalanceAnonymized = bal.m_anonymized; diff --git a/src/rpc/coinjoin.cpp b/src/rpc/coinjoin.cpp index 70c73bcfa79b4..f1cd576c20479 100644 --- a/src/rpc/coinjoin.cpp +++ b/src/rpc/coinjoin.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -278,7 +279,7 @@ static RPCHelpMan coinjoinsalt_generate() } } - const auto wallet_balance{wallet->GetBalance()}; + const auto wallet_balance{GetBalance(*wallet)}; const bool has_balance{(wallet_balance.m_anonymized + wallet_balance.m_denominated_trusted + wallet_balance.m_denominated_untrusted_pending) > 0}; @@ -380,7 +381,7 @@ static RPCHelpMan coinjoinsalt_set() } } - const auto wallet_balance{wallet->GetBalance()}; + const auto wallet_balance{GetBalance(*wallet)}; const bool has_balance{(wallet_balance.m_anonymized + wallet_balance.m_denominated_trusted + wallet_balance.m_denominated_untrusted_pending) > 0}; diff --git a/src/wallet/coinjoin.cpp b/src/wallet/coinjoin.cpp index 6e8e07f450a22..cdb2a3c3db6ca 100644 --- a/src/wallet/coinjoin.cpp +++ b/src/wallet/coinjoin.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -155,9 +156,9 @@ std::vector CWallet::SelectCoinsGroupedByAddresses(bool fSkipD const CWalletTx& wtx = (*it).second; - if(wtx.IsCoinBase() && wtx.GetBlocksToMaturity() > 0) continue; - if(fSkipUnconfirmed && !wtx.IsTrusted()) continue; - if (wtx.GetDepthInMainChain() < 0) continue; + if (wtx.IsCoinBase() && GetTxBlocksToMaturity(wtx) > 0) continue; + if (fSkipUnconfirmed && !CachedTxIsTrusted(*this, wtx)) continue; + if (GetTxDepthInMainChain(wtx) < 0) continue; for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { CTxDestination txdest; @@ -247,7 +248,7 @@ int CWallet::CountInputsWithAmount(CAmount nInputAmount) const const auto it = mapWallet.find(outpoint.hash); if (it == mapWallet.end()) continue; if (it->second.tx->vout[outpoint.n].nValue != nInputAmount) continue; - if (it->second.GetDepthInMainChain() < 0) continue; + if (GetTxDepthInMainChain(it->second) < 0) continue; nTotal++; } @@ -317,7 +318,7 @@ int CWallet::GetRealOutpointCoinJoinRounds(const COutPoint& outpoint, int nRound } // make sure we spent all of it with 0 fee, reset to 0 rounds otherwise - if (wtx->GetDebit(ISMINE_SPENDABLE) != wtx->GetCredit(ISMINE_SPENDABLE)) { + if (CachedTxGetDebit(*this, *wtx, ISMINE_SPENDABLE) != CachedTxGetCredit(*this, *wtx, ISMINE_SPENDABLE)) { *nRoundsRef = 0; WalletCJLogPrint(this, "%s UPDATED %-70s %3d\n", __func__, outpoint.ToStringShort(), *nRoundsRef); return *nRoundsRef; @@ -327,7 +328,7 @@ int CWallet::GetRealOutpointCoinJoinRounds(const COutPoint& outpoint, int nRound bool fDenomFound = false; // only denoms here so let's look up for (const auto& txinNext : wtx->tx->vin) { - if (IsMine(txinNext)) { + if (InputIsMine(*this, txinNext)) { int n = GetRealOutpointCoinJoinRounds(txinNext.prevout, nRounds + 1); // denom found, find the shortest chain or initially assign nShortest with the first found value if(n >= 0 && (n < nShortest || nShortest == -10)) { @@ -481,7 +482,7 @@ CAmount CWallet::GetNormalizedAnonymizedBalance() const CAmount nValue = it->second.tx->vout[outpoint.n].nValue; if (!CoinJoin::IsDenominatedAmount(nValue)) continue; - if (it->second.GetDepthInMainChain() < 0) continue; + if (GetTxDepthInMainChain(it->second) < 0) continue; int nRounds = GetCappedOutpointCoinJoinRounds(outpoint); nTotal += nValue * nRounds / CCoinJoinClientOptions::GetRounds(); @@ -495,7 +496,7 @@ CAmount CachedTxGetAnonymizedCredit(const CWallet& wallet, const CWalletTx& wtx, AssertLockHeld(wallet.cs_wallet); // Exclude coinbase and conflicted txes - if (wtx.IsCoinBase() || wtx.GetDepthInMainChain() < 0) + if (wtx.IsCoinBase() || wallet.GetTxBlocksToMaturity(wtx) < 0) return 0; CAmount nCredit = 0; @@ -512,7 +513,7 @@ CAmount CachedTxGetAnonymizedCredit(const CWallet& wallet, const CWalletTx& wtx, if (wallet.IsSpent(hashTx, i) || !CoinJoin::IsDenominatedAmount(txout.nValue)) continue; if (wallet.IsFullyMixed(outpoint)) { - nCredit += wallet.GetCredit(txout, ISMINE_SPENDABLE); + nCredit += OutputGetCredit(wallet, txout, ISMINE_SPENDABLE); if (!MoneyRange(nCredit)) throw std::runtime_error(std::string(__func__) + ": value out of range"); } @@ -528,13 +529,13 @@ CoinJoinCredits CachedTxGetAvailableCoinJoinCredits(const CWallet& wallet, const AssertLockHeld(wallet.cs_wallet); // Must wait until coinbase is safely deep enough in the chain before valuing it - if (wtx.IsCoinBase() && wtx.GetBlocksToMaturity() > 0) + if (wtx.IsCoinBase() && wallet.GetTxBlocksToMaturity(wtx) > 0) return ret; - int nDepth = wtx.GetDepthInMainChain(); + int nDepth = wallet.GetTxBlocksToMaturity(wtx); if (nDepth < 0) return ret; - ret.is_unconfirmed = wtx.IsTrusted() && nDepth == 0; + ret.is_unconfirmed = CachedTxIsTrusted(wallet, wtx) && nDepth == 0; if (wtx.m_amounts[CWalletTx::ANON_CREDIT].m_cached[ISMINE_SPENDABLE]) { if (ret.is_unconfirmed && wtx.m_amounts[CWalletTx::DENOM_UCREDIT].m_cached[ISMINE_SPENDABLE]) { @@ -550,7 +551,7 @@ CoinJoinCredits CachedTxGetAvailableCoinJoinCredits(const CWallet& wallet, const const COutPoint outpoint = COutPoint(hashTx, i); if (wallet.IsSpent(hashTx, i) || !CoinJoin::IsDenominatedAmount(txout.nValue)) continue; - const CAmount credit = wallet.GetCredit(txout, ISMINE_SPENDABLE); + const CAmount credit = OutputGetCredit(wallet, txout, ISMINE_SPENDABLE); if (wallet.IsFullyMixed(outpoint)) { ret.m_anonymized += credit; diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 29c1e90d47926..7eab40593bdf1 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -59,7 +60,7 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) result.tx = wtx.tx; result.txin_is_mine.reserve(wtx.tx->vin.size()); for (const auto& txin : wtx.tx->vin) { - result.txin_is_mine.emplace_back(wallet.IsMine(txin)); + result.txin_is_mine.emplace_back(InputIsMine(wallet, txin)); if (!fInputDenomFound && result.txin_is_mine.back() && wallet.IsDenominated(txin.prevout)) { fInputDenomFound = true; } @@ -77,9 +78,9 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) fOutputDenomFound = true; } } - result.credit = wtx.GetCredit(ISMINE_ALL); - result.debit = wtx.GetDebit(ISMINE_ALL); - result.change = wtx.GetChange(); + result.credit = CachedTxGetCredit(wallet, wtx, ISMINE_ALL); + result.debit = CachedTxGetDebit(wallet, wtx, ISMINE_ALL); + result.change = CachedTxGetChange(wallet, wtx); result.time = wtx.GetTxTime(); result.value_map = wtx.mapValue; result.is_coinbase = wtx.IsCoinBase(); @@ -100,15 +101,15 @@ WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx) WalletTxStatus result; result.block_height = wtx.m_confirm.block_height > 0 ? wtx.m_confirm.block_height : std::numeric_limits::max(); - result.blocks_to_maturity = wtx.GetBlocksToMaturity(); - result.depth_in_main_chain = wtx.GetDepthInMainChain(); + result.blocks_to_maturity = wallet.GetTxBlocksToMaturity(wtx); + result.depth_in_main_chain = wallet.GetTxDepthInMainChain(wtx); result.time_received = wtx.nTimeReceived; result.lock_time = wtx.tx->nLockTime; result.is_final = wallet.chain().checkFinalTx(*wtx.tx); - result.is_trusted = wtx.IsTrusted(); + result.is_trusted = CachedTxIsTrusted(wallet, wtx); result.is_abandoned = wtx.isAbandoned(); result.is_coinbase = wtx.IsCoinBase(); - result.is_in_main_chain = wtx.IsInMainChain(); + result.is_in_main_chain = wallet.IsTxInMainChain(wtx); result.is_chainlocked = wallet.IsTxChainLocked(wtx); result.is_islocked = wallet.IsTxLockedByInstantSend(wtx); return result; @@ -383,7 +384,7 @@ class WalletImpl : public Wallet } WalletBalances getBalances() override { - const auto bal = m_wallet->GetBalance(); + const auto bal = GetBalance(*m_wallet); WalletBalances result; result.balance = bal.m_mine_trusted; result.unconfirmed_balance = bal.m_mine_untrusted_pending; @@ -409,10 +410,7 @@ class WalletImpl : public Wallet balances = getBalances(); return true; } - CAmount getBalance() override - { - return m_wallet->GetBalance().m_mine_trusted; - } + CAmount getBalance() override { return GetBalance(*m_wallet).m_mine_trusted; } CAmount getAnonymizableBalance(bool fSkipDenominated, bool fSkipUnconfirmed) override { return m_wallet->GetAnonymizableBalance(fSkipDenominated, fSkipUnconfirmed); @@ -436,7 +434,7 @@ class WalletImpl : public Wallet isminetype txinIsMine(const CTxIn& txin) override { LOCK(m_wallet->cs_wallet); - return m_wallet->IsMine(txin); + return InputIsMine(*m_wallet, txin); } isminetype txoutIsMine(const CTxOut& txout) override { @@ -451,7 +449,7 @@ class WalletImpl : public Wallet CAmount getCredit(const CTxOut& txout, isminefilter filter) override { LOCK(m_wallet->cs_wallet); - return m_wallet->GetCredit(txout, filter); + return OutputGetCredit(*m_wallet, txout, filter); } CoinsList listCoins() override { @@ -475,7 +473,7 @@ class WalletImpl : public Wallet result.emplace_back(); auto it = m_wallet->mapWallet.find(output.hash); if (it != m_wallet->mapWallet.end()) { - int depth = it->second.GetDepthInMainChain(); + int depth = m_wallet->GetTxDepthInMainChain(it->second); if (depth >= 0) { result.back() = MakeWalletTxOut(*m_wallet, it->second, output.n, depth); } diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp index 4eba12235947c..334a37147ba08 100644 --- a/src/wallet/receive.cpp +++ b/src/wallet/receive.cpp @@ -9,27 +9,27 @@ #include #include -isminetype CWallet::IsMine(const CTxIn &txin) const +isminetype InputIsMine(const CWallet& wallet, const CTxIn &txin) { - AssertLockHeld(cs_wallet); - std::map::const_iterator mi = mapWallet.find(txin.prevout.hash); - if (mi != mapWallet.end()) + AssertLockHeld(wallet.cs_wallet); + std::map::const_iterator mi = wallet.mapWallet.find(txin.prevout.hash); + if (mi != wallet.mapWallet.end()) { const CWalletTx& prev = (*mi).second; if (txin.prevout.n < prev.tx->vout.size()) - return IsMine(prev.tx->vout[txin.prevout.n]); + return wallet.IsMine(prev.tx->vout[txin.prevout.n]); } return ISMINE_NO; } -bool CWallet::IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const +bool AllInputsMine(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter) { - LOCK(cs_wallet); + LOCK(wallet.cs_wallet); for (const CTxIn& txin : tx.vin) { - auto mi = mapWallet.find(txin.prevout.hash); - if (mi == mapWallet.end()) + auto mi = wallet.mapWallet.find(txin.prevout.hash); + if (mi == wallet.mapWallet.end()) return false; // any unknown inputs can't be from us const CWalletTx& prev = (*mi).second; @@ -37,33 +37,33 @@ bool CWallet::IsAllFromMe(const CTransaction& tx, const isminefilter& filter) co if (txin.prevout.n >= prev.tx->vout.size()) return false; // invalid input! - if (!(IsMine(prev.tx->vout[txin.prevout.n]) & filter)) + if (!(wallet.IsMine(prev.tx->vout[txin.prevout.n]) & filter)) return false; } return true; } -CAmount CWallet::GetCredit(const CTxOut& txout, const isminefilter& filter) const +CAmount OutputGetCredit(const CWallet& wallet, const CTxOut& txout, const isminefilter& filter) { if (!MoneyRange(txout.nValue)) throw std::runtime_error(std::string(__func__) + ": value out of range"); - LOCK(cs_wallet); - return ((IsMine(txout) & filter) ? txout.nValue : 0); + LOCK(wallet.cs_wallet); + return ((wallet.IsMine(txout) & filter) ? txout.nValue : 0); } -CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) const +CAmount TxGetCredit(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter) { CAmount nCredit = 0; for (const CTxOut& txout : tx.vout) { - nCredit += GetCredit(txout, filter); + nCredit += OutputGetCredit(wallet, txout, filter); if (!MoneyRange(nCredit)) throw std::runtime_error(std::string(__func__) + ": value out of range"); } return nCredit; } -bool CWallet::IsChange(const CScript& script) const +bool ScriptIsChange(const CWallet& wallet, const CScript& script) { // TODO: fix handling of 'change' outputs. The assumption is that any // payment to a script that is ours, but is not in the address book @@ -72,180 +72,177 @@ bool CWallet::IsChange(const CScript& script) const // a better way of identifying which outputs are 'the send' and which are // 'the change' will need to be implemented (maybe extend CWalletTx to remember // which output, if any, was change). - AssertLockHeld(cs_wallet); - if (IsMine(script)) + AssertLockHeld(wallet.cs_wallet); + if (wallet.IsMine(script)) { CTxDestination address; if (!ExtractDestination(script, address)) return true; - if (!FindAddressBookEntry(address)) { + if (!wallet.FindAddressBookEntry(address)) { return true; } } return false; } -bool CWallet::IsChange(const CTxOut& txout) const +bool OutputIsChange(const CWallet& wallet, const CTxOut& txout) { - return IsChange(txout.scriptPubKey); + return ScriptIsChange(wallet, txout.scriptPubKey); } -CAmount CWallet::GetChange(const CTxOut& txout) const +CAmount OutputGetChange(const CWallet& wallet, const CTxOut& txout) { - AssertLockHeld(cs_wallet); + AssertLockHeld(wallet.cs_wallet); if (!MoneyRange(txout.nValue)) throw std::runtime_error(std::string(__func__) + ": value out of range"); - return (IsChange(txout) ? txout.nValue : 0); + return (OutputIsChange(wallet, txout) ? txout.nValue : 0); } -CAmount CWallet::GetChange(const CTransaction& tx) const +CAmount TxGetChange(const CWallet& wallet, const CTransaction& tx) { - LOCK(cs_wallet); + LOCK(wallet.cs_wallet); CAmount nChange = 0; for (const CTxOut& txout : tx.vout) { - nChange += GetChange(txout); + nChange += OutputGetChange(wallet, txout); if (!MoneyRange(nChange)) throw std::runtime_error(std::string(__func__) + ": value out of range"); } return nChange; } -CAmount CWalletTx::GetCachableAmount(AmountType type, const isminefilter& filter, bool recalculate) const +static CAmount GetCachableAmount(const CWallet& wallet, const CWalletTx& wtx, CWalletTx::AmountType type, const isminefilter& filter, bool recalculate = false) { - auto& amount = m_amounts[type]; + auto& amount = wtx.m_amounts[type]; if (recalculate || !amount.m_cached[filter]) { - amount.Set(filter, type == DEBIT ? pwallet->GetDebit(*tx, filter) : pwallet->GetCredit(*tx, filter)); - m_is_cache_empty = false; + amount.Set(filter, type == CWalletTx::DEBIT ? wallet.GetDebit(*wtx.tx, filter) : TxGetCredit(wallet, *wtx.tx, filter)); + wtx.m_is_cache_empty = false; } return amount.m_value[filter]; } -CAmount CWalletTx::GetCredit(const isminefilter& filter) const +CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) { // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsImmatureCoinBase()) + if (wallet.IsTxImmatureCoinBase(wtx)) return 0; CAmount credit = 0; if (filter & ISMINE_SPENDABLE) { // GetBalance can assume transactions in mapWallet won't change - credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE); + credit += GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, ISMINE_SPENDABLE); } if (filter & ISMINE_WATCH_ONLY) { - credit += GetCachableAmount(CREDIT, ISMINE_WATCH_ONLY); + credit += GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, ISMINE_WATCH_ONLY); } return credit; } -CAmount CWalletTx::GetDebit(const isminefilter& filter) const +CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) { - if (tx->vin.empty()) + if (wtx.tx->vin.empty()) return 0; CAmount debit = 0; if (filter & ISMINE_SPENDABLE) { - debit += GetCachableAmount(DEBIT, ISMINE_SPENDABLE); + debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, ISMINE_SPENDABLE); } if (filter & ISMINE_WATCH_ONLY) { - debit += GetCachableAmount(DEBIT, ISMINE_WATCH_ONLY); + debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, ISMINE_WATCH_ONLY); } return debit; } -CAmount CWalletTx::GetChange() const +CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx) { - if (fChangeCached) - return nChangeCached; - nChangeCached = pwallet->GetChange(*tx); - fChangeCached = true; - return nChangeCached; + if (wtx.fChangeCached) + return wtx.nChangeCached; + wtx.nChangeCached = TxGetChange(wallet, *wtx.tx); + wtx.fChangeCached = true; + return wtx.nChangeCached; } -CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const +CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache) { - if (IsImmatureCoinBase() && IsInMainChain()) { - return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache); + if (wallet.IsTxImmatureCoinBase(wtx) && wallet.IsTxInMainChain(wtx)) { + return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache); } return 0; } -CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const +CAmount CachedTxGetImmatureWatchOnlyCredit(const CWallet& wallet, const CWalletTx& wtx, const bool fUseCache) { - if (IsImmatureCoinBase() && IsInMainChain()) { - return GetCachableAmount(IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache); + if (wallet.IsTxImmatureCoinBase(wtx) && wallet.IsTxInMainChain(wtx)) { + return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache); } return 0; } -CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const +CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache, const isminefilter& filter) { - if (pwallet == nullptr) - return 0; - // Avoid caching ismine for NO or ALL cases (could remove this check and simplify in the future). bool allow_cache = (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL; // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsImmatureCoinBase()) + if (wallet.IsTxImmatureCoinBase(wtx)) return 0; - if (fUseCache && allow_cache && m_amounts[AVAILABLE_CREDIT].m_cached[filter]) { - return m_amounts[AVAILABLE_CREDIT].m_value[filter]; + if (fUseCache && allow_cache && wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].m_cached[filter]) { + return wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].m_value[filter]; } - bool allow_used_addresses = (filter & ISMINE_USED) || !pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); + bool allow_used_addresses = (filter & ISMINE_USED) || !wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); CAmount nCredit = 0; - uint256 hashTx = GetHash(); - for (unsigned int i = 0; i < tx->vout.size(); i++) + uint256 hashTx = wtx.GetHash(); + for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { - if (!pwallet->IsSpent(hashTx, i) && (allow_used_addresses || !pwallet->IsSpentKey(hashTx, i))) - { - const CTxOut &txout = tx->vout[i]; - nCredit += pwallet->GetCredit(txout, filter); + if (!wallet.IsSpent(hashTx, i) && (allow_used_addresses || !wallet.IsSpentKey(hashTx, i))) { + const CTxOut &txout = wtx.tx->vout[i]; + nCredit += OutputGetCredit(wallet, txout, filter); if (!MoneyRange(nCredit)) throw std::runtime_error(std::string(__func__) + ": value out of range"); } } if (allow_cache) { - m_amounts[AVAILABLE_CREDIT].Set(filter, nCredit); - m_is_cache_empty = false; + wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].Set(filter, nCredit); + wtx.m_is_cache_empty = false; } return nCredit; } -void CWalletTx::GetAmounts(std::list& listReceived, - std::list& listSent, CAmount& nFee, const isminefilter& filter) const +void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx, + std::list& listReceived, + std::list& listSent, CAmount& nFee, const isminefilter& filter) { nFee = 0; listReceived.clear(); listSent.clear(); // Compute fee: - CAmount nDebit = GetDebit(filter); + CAmount nDebit = CachedTxGetDebit(wallet, wtx, filter); if (nDebit > 0) // debit>0 means we signed/sent this transaction { - CAmount nValueOut = tx->GetValueOut(); + CAmount nValueOut = wtx.tx->GetValueOut(); nFee = nDebit - nValueOut; } - LOCK(pwallet->cs_wallet); + LOCK(wallet.cs_wallet); // Sent/received. - for (unsigned int i = 0; i < tx->vout.size(); ++i) + for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i) { - const CTxOut& txout = tx->vout[i]; - isminetype fIsMine = pwallet->IsMine(txout); + const CTxOut& txout = wtx.tx->vout[i]; + isminetype fIsMine = wallet.IsMine(txout); // Only need to handle txouts if AT LEAST one of these is true: // 1) they debit from us (sent) // 2) the output is to us (received) if (nDebit > 0) { // Don't report 'change' txouts - if (pwallet->IsChange(txout)) + if (OutputIsChange(wallet, txout)) continue; } else if (!(fIsMine & filter)) @@ -256,8 +253,8 @@ void CWalletTx::GetAmounts(std::list& listReceived, if (!ExtractDestination(txout.scriptPubKey, address) && !txout.scriptPubKey.IsUnspendable()) { - pwallet->WalletLogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", - this->GetHash().ToString()); + wallet.WalletLogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", + wtx.GetHash().ToString()); address = CNoDestination(); } @@ -274,17 +271,22 @@ void CWalletTx::GetAmounts(std::list& listReceived, } -bool CWallet::IsTrusted(const CWalletTx& wtx, std::set& trusted_parents) const +bool CachedTxIsFromMe(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) +{ + return (CachedTxGetDebit(wallet, wtx, filter) > 0); +} + +bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx, std::set& trusted_parents) { - AssertLockHeld(cs_wallet); + AssertLockHeld(wallet.cs_wallet); // Quick answer in most cases - if (!chain().checkFinalTx(*wtx.tx)) return false; - int nDepth = wtx.GetDepthInMainChain(); + if (!wallet.chain().checkFinalTx(*wtx.tx)) return false; + int nDepth = wallet.GetTxDepthInMainChain(wtx); if (nDepth >= 1) return true; if (nDepth < 0) return false; - if (IsTxLockedByInstantSend(wtx)) return true; + if (wallet.IsTxLockedByInstantSend(wtx)) return true; // using wtx's cached debit - if (!m_spend_zero_conf_change || !wtx.IsFromMe(ISMINE_ALL)) return false; + if (!wallet.m_spend_zero_conf_change || !CachedTxIsFromMe(wallet, wtx, ISMINE_ALL)) return false; // Don't trust unconfirmed transactions from us unless they are in the mempool. if (!wtx.InMempool()) return false; @@ -293,43 +295,43 @@ bool CWallet::IsTrusted(const CWalletTx& wtx, std::set& trusted_parents for (const CTxIn& txin : wtx.tx->vin) { // Transactions not sent by us: not trusted - const CWalletTx* parent = GetWalletTx(txin.prevout.hash); + const CWalletTx* parent = wallet.GetWalletTx(txin.prevout.hash); if (parent == nullptr) return false; const CTxOut& parentOut = parent->tx->vout[txin.prevout.n]; // Check that this specific input being spent is trusted - if (IsMine(parentOut) != ISMINE_SPENDABLE) return false; + if (wallet.IsMine(parentOut) != ISMINE_SPENDABLE) return false; // If we've already trusted this parent, continue if (trusted_parents.count(parent->GetHash())) continue; // Recurse to check that the parent is also trusted - if (!IsTrusted(*parent, trusted_parents)) return false; + if (!CachedTxIsTrusted(wallet, *parent, trusted_parents)) return false; trusted_parents.insert(parent->GetHash()); } return true; } -bool CWalletTx::IsTrusted() const +bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx) { std::set trusted_parents; - LOCK(pwallet->cs_wallet); - return pwallet->IsTrusted(*this, trusted_parents); + LOCK(wallet.cs_wallet); + return CachedTxIsTrusted(wallet, wtx, trusted_parents); } -CWallet::Balance CWallet::GetBalance(const int min_depth, const bool avoid_reuse, const bool fAddLocked) const +Balance GetBalance(const CWallet& wallet, const int min_depth, bool avoid_reuse, bool fAddLocked) { Balance ret; isminefilter reuse_filter = avoid_reuse ? ISMINE_NO : ISMINE_USED; const bool cj_enabled = CCoinJoinClientOptions::IsEnabled(); { - LOCK(cs_wallet); + LOCK(wallet.cs_wallet); std::set trusted_parents; - for (const auto* pcoin : GetSpendableTXs()) { + for (const auto* pcoin : wallet.GetSpendableTXs()) { const auto& wtx{*pcoin}; - const bool is_trusted{IsTrusted(wtx, trusted_parents)}; - const int tx_depth{wtx.GetDepthInMainChain()}; - const CAmount tx_credit_mine{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)}; - const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)}; - if (is_trusted && ((tx_depth >= min_depth) || (fAddLocked && IsTxLockedByInstantSend(wtx)))) { + const bool is_trusted{CachedTxIsTrusted(wallet, wtx, trusted_parents)}; + const int tx_depth{wallet.GetTxDepthInMainChain(wtx)}; + const CAmount tx_credit_mine{CachedTxGetAvailableCredit(wallet, wtx, /* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)}; + const CAmount tx_credit_watchonly{CachedTxGetAvailableCredit(wallet, wtx, /* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)}; + if (is_trusted && ((tx_depth >= min_depth) || (fAddLocked && wallet.IsTxLockedByInstantSend(wtx)))) { ret.m_mine_trusted += tx_credit_mine; ret.m_watchonly_trusted += tx_credit_watchonly; } @@ -337,10 +339,10 @@ CWallet::Balance CWallet::GetBalance(const int min_depth, const bool avoid_reuse ret.m_mine_untrusted_pending += tx_credit_mine; ret.m_watchonly_untrusted_pending += tx_credit_watchonly; } - ret.m_mine_immature += wtx.GetImmatureCredit(); - ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit(); + ret.m_mine_immature += CachedTxGetImmatureCredit(wallet, wtx); + ret.m_watchonly_immature += CachedTxGetImmatureWatchOnlyCredit(wallet, wtx); if (cj_enabled) { - const auto balance_anonymized = CachedTxGetAvailableCoinJoinCredits(*this, wtx); + const auto balance_anonymized = CachedTxGetAvailableCoinJoinCredits(wallet, wtx); ret.m_anonymized += balance_anonymized.m_anonymized; if (balance_anonymized.is_unconfirmed) { ret.m_denominated_untrusted_pending += balance_anonymized.m_denominated; @@ -353,36 +355,36 @@ CWallet::Balance CWallet::GetBalance(const int min_depth, const bool avoid_reuse return ret; } -std::map CWallet::GetAddressBalances() const +std::map GetAddressBalances(const CWallet& wallet) { std::map balances; { - LOCK(cs_wallet); + LOCK(wallet.cs_wallet); std::set trusted_parents; - for (const auto& walletEntry : mapWallet) + for (const auto& walletEntry : wallet.mapWallet) { const CWalletTx& wtx = walletEntry.second; - if (!IsTrusted(wtx, trusted_parents)) + if (!CachedTxIsTrusted(wallet, wtx, trusted_parents)) continue; - if (wtx.IsImmatureCoinBase()) + if (wallet.IsTxImmatureCoinBase(wtx)) continue; - int nDepth = wtx.GetDepthInMainChain(); - if ((nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1)) && !IsTxLockedByInstantSend(wtx)) + int nDepth = wallet.GetTxDepthInMainChain(wtx); + if (nDepth < (CachedTxIsFromMe(wallet, wtx, ISMINE_ALL) ? 0 : 1) && !wallet.IsTxLockedByInstantSend(wtx)) continue; for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { CTxDestination addr; - if (!IsMine(wtx.tx->vout[i])) + if (!wallet.IsMine(wtx.tx->vout[i])) continue; if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr)) continue; - CAmount n = IsSpent(walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue; + CAmount n = wallet.IsSpent(walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue; balances[addr] += n; } } @@ -391,13 +393,13 @@ std::map CWallet::GetAddressBalances() const return balances; } -std::set< std::set > CWallet::GetAddressGroupings() const +std::set< std::set > GetAddressGroupings(const CWallet& wallet) { - AssertLockHeld(cs_wallet); + AssertLockHeld(wallet.cs_wallet); std::set< std::set > groupings; std::set grouping; - for (const auto& walletEntry : mapWallet) + for (const auto& walletEntry : wallet.mapWallet) { const CWalletTx& wtx = walletEntry.second; @@ -408,9 +410,9 @@ std::set< std::set > CWallet::GetAddressGroupings() const for (const CTxIn& txin : wtx.tx->vin) { CTxDestination address; - if(!IsMine(txin)) /* If this input isn't mine, ignore it */ + if(!InputIsMine(wallet, txin)) /* If this input isn't mine, ignore it */ continue; - if(!ExtractDestination(mapWallet.at(txin.prevout.hash).tx->vout[txin.prevout.n].scriptPubKey, address)) + if(!ExtractDestination(wallet.mapWallet.at(txin.prevout.hash).tx->vout[txin.prevout.n].scriptPubKey, address)) continue; grouping.insert(address); any_mine = true; @@ -420,7 +422,7 @@ std::set< std::set > CWallet::GetAddressGroupings() const if (any_mine) { for (const CTxOut& txout : wtx.tx->vout) - if (IsChange(txout)) + if (OutputIsChange(wallet, txout)) { CTxDestination txoutAddr; if(!ExtractDestination(txout.scriptPubKey, txoutAddr)) @@ -437,7 +439,7 @@ std::set< std::set > CWallet::GetAddressGroupings() const // group lone addrs by themselves for (const auto& txout : wtx.tx->vout) - if (IsMine(txout)) + if (wallet.IsMine(txout)) { CTxDestination address; if(!ExtractDestination(txout.scriptPubKey, address)) diff --git a/src/wallet/receive.h b/src/wallet/receive.h index 9396a93357cb0..ac3f1a49bb57a 100644 --- a/src/wallet/receive.h +++ b/src/wallet/receive.h @@ -10,11 +10,58 @@ #include #include +isminetype InputIsMine(const CWallet& wallet, const CTxIn& txin) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); + +/** Returns whether all of the inputs match the filter */ +bool AllInputsMine(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter); + +CAmount OutputGetCredit(const CWallet& wallet, const CTxOut& txout, const isminefilter& filter); +CAmount TxGetCredit(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter); + +bool ScriptIsChange(const CWallet& wallet, const CScript& script) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +bool OutputIsChange(const CWallet& wallet, const CTxOut& txout) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +CAmount OutputGetChange(const CWallet& wallet, const CTxOut& txout) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +CAmount TxGetChange(const CWallet& wallet, const CTransaction& tx); + +CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter); +//! filter decides which addresses will count towards the debit +CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter); +CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx); +CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache = true); +CAmount CachedTxGetImmatureWatchOnlyCredit(const CWallet& wallet, const CWalletTx& wtx, const bool fUseCache = true); +// TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct +// annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The +// annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid +// having to resolve the issue of member access into incomplete type CWallet. +CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache = true, const isminefilter& filter = ISMINE_SPENDABLE) NO_THREAD_SAFETY_ANALYSIS; struct COutputEntry { CTxDestination destination; CAmount amount; int vout; }; +void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx, + std::list& listReceived, + std::list& listSent, + CAmount& nFee, const isminefilter& filter); +bool CachedTxIsFromMe(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter); +bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx, std::set& trusted_parents) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx); + +struct Balance { + CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more + CAmount m_mine_untrusted_pending{0}; //!< Untrusted, but in mempool (pending) + CAmount m_mine_immature{0}; //!< Immature coinbases in the main chain + CAmount m_watchonly_trusted{0}; + CAmount m_watchonly_untrusted_pending{0}; + CAmount m_watchonly_immature{0}; + CAmount m_anonymized{0}; + CAmount m_denominated_trusted{0}; + CAmount m_denominated_untrusted_pending{0}; +}; +Balance GetBalance(const CWallet& wallet, int min_depth = 0, bool avoid_reuse = true, const bool fAddLocked = false); + +std::map GetAddressBalances(const CWallet& wallet); +std::set> GetAddressGroupings(const CWallet& wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); #endif // BITCOIN_WALLET_RECEIVE_H diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index c7b6d2169bbf7..504f2e0ece1a8 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -64,7 +65,7 @@ static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue AssertLockHeld(wallet.cs_wallet); interfaces::Chain& chain = wallet.chain(); - int confirms = wtx.GetDepthInMainChain(); + int confirms = wallet.GetTxDepthInMainChain(wtx); bool fLocked = chain.isInstantSendLockedTx(wtx.GetHash()); bool chainlock = false; if (confirms > 0) { @@ -87,12 +88,12 @@ static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue CHECK_NONFATAL(chain.findBlock(wtx.m_confirm.hashBlock, FoundBlock().time(block_time))); entry.pushKV("blocktime", block_time); } else { - entry.pushKV("trusted", wtx.IsTrusted()); + entry.pushKV("trusted", CachedTxIsTrusted(wallet, wtx)); } uint256 hash = wtx.GetHash(); entry.pushKV("txid", hash.GetHex()); UniValue conflicts(UniValue::VARR); - for (const uint256& conflict : wtx.GetConflicts()) + for (const uint256& conflict : wallet.GetTxConflicts(wtx)) conflicts.push_back(conflict.GetHex()); entry.pushKV("walletconflicts", conflicts); entry.pushKV("time", wtx.GetTxTime()); @@ -474,8 +475,8 @@ static RPCHelpMan listaddressgroupings() LOCK(pwallet->cs_wallet); UniValue jsonGroupings(UniValue::VARR); - std::map balances = pwallet->GetAddressBalances(); - for (const std::set& grouping : pwallet->GetAddressGroupings()) { + std::map balances = GetAddressBalances(*pwallet); + for (const std::set& grouping : GetAddressGroupings(*pwallet)) { UniValue jsonGrouping(UniValue::VARR); for (const CTxDestination& address : grouping) { @@ -531,7 +532,7 @@ static RPCHelpMan listaddressbalances() throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); UniValue jsonBalances(UniValue::VOBJ); - std::map balances = pwallet->GetAddressBalances(); + std::map balances = GetAddressBalances(*pwallet); for (auto& balance : balances) if (balance.second >= nMinAmount) jsonBalances.pushKV(EncodeDestination(balance.first), ValueFromAmount(balance.second)); @@ -583,10 +584,10 @@ static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool b CAmount amount = 0; for (const std::pair& wtx_pair : wallet.mapWallet) { const CWalletTx& wtx = wtx_pair.second; - int depth{wtx.GetDepthInMainChain()}; + int depth{wallet.GetTxDepthInMainChain(wtx)}; if (// Coinbase with less than 1 confirmation is no longer in the main chain (wtx.IsCoinBase() && (depth < 1 || !include_coinbase)) - || (wtx.IsImmatureCoinBase() && !include_immature_coinbase) + || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase) || !wallet.chain().checkFinalTx(*wtx.tx)) { continue; } @@ -742,7 +743,7 @@ static RPCHelpMan getbalance() bool include_watchonly = ParseIncludeWatchonly(request.params[3], *pwallet); bool avoid_reuse = GetAvoidReuseFlag(*pwallet, request.params[4]); - const auto bal = pwallet->GetBalance(min_depth, avoid_reuse, fAddLocked); + const auto bal = GetBalance(*pwallet, min_depth, avoid_reuse, fAddLocked); return ValueFromAmount(bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : 0)); }, @@ -767,7 +768,7 @@ static RPCHelpMan getunconfirmedbalance() LOCK(pwallet->cs_wallet); - return ValueFromAmount(pwallet->GetBalance().m_mine_untrusted_pending); + return ValueFromAmount(GetBalance(*pwallet).m_mine_untrusted_pending); }, }; } @@ -996,13 +997,13 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons for (const std::pair& pairWtx : wallet.mapWallet) { const CWalletTx& wtx = pairWtx.second; - int nDepth = wtx.GetDepthInMainChain(); + int nDepth = wallet.GetTxDepthInMainChain(wtx); if ((nDepth < nMinDepth) && !(fAddLocked && wallet.IsTxLockedByInstantSend(wtx))) continue; // Coinbase with less than 1 confirmation is no longer in the main chain if ((wtx.IsCoinBase() && (nDepth < 1 || !include_coinbase)) - || (wtx.IsImmatureCoinBase() && !include_immature_coinbase) + || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase) || !wallet.chain().checkFinalTx(*wtx.tx)) { continue; } @@ -1225,9 +1226,9 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM std::list listReceived; std::list listSent; - wtx.GetAmounts(listReceived, listSent, nFee, filter_ismine); + CachedTxGetAmounts(wallet, wtx, listReceived, listSent, nFee, filter_ismine); - bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY); + bool involvesWatchonly = CachedTxIsFromMe(wallet, wtx, ISMINE_WATCH_ONLY); // Sent if (!filter_label) @@ -1256,8 +1257,7 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM } // Received - if (listReceived.size() > 0 && ((wtx.GetDepthInMainChain() >= nMinDepth) || wallet.IsTxLockedByInstantSend(wtx))) - { + if (listReceived.size() > 0 && ((wallet.GetTxDepthInMainChain(wtx) >= nMinDepth) || wallet.IsTxLockedByInstantSend(wtx))) { for (const COutputEntry& r : listReceived) { std::string label; @@ -1275,9 +1275,9 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM MaybePushAddress(entry, r.destination); if (wtx.IsCoinBase()) { - if (wtx.GetDepthInMainChain() < 1) + if (wallet.GetTxDepthInMainChain(wtx) < 1) entry.pushKV("category", "orphan"); - else if (wtx.IsImmatureCoinBase()) + else if (wallet.IsTxImmatureCoinBase(wtx)) entry.pushKV("category", "immature"); else entry.pushKV("category", "generate"); @@ -1539,7 +1539,7 @@ static RPCHelpMan listsinceblock() for (const std::pair& pairWtx : wallet.mapWallet) { const CWalletTx& tx = pairWtx.second; - if (depth == -1 || abs(tx.GetDepthInMainChain()) < depth) { + if (depth == -1 || abs(wallet.GetTxDepthInMainChain(tx)) < depth) { ListTransactions(wallet, tx, 0, true, transactions, filter, nullptr /* filter_label */); } } @@ -1659,13 +1659,13 @@ static RPCHelpMan gettransaction() } const CWalletTx& wtx = it->second; - CAmount nCredit = wtx.GetCredit(filter); - CAmount nDebit = wtx.GetDebit(filter); + CAmount nCredit = CachedTxGetCredit(*pwallet, wtx, filter); + CAmount nDebit = CachedTxGetDebit(*pwallet, wtx, filter); CAmount nNet = nCredit - nDebit; - CAmount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit : 0); + CAmount nFee = (CachedTxIsFromMe(*pwallet, wtx, filter) ? wtx.tx->GetValueOut() - nDebit : 0); entry.pushKV("amount", ValueFromAmount(nNet - nFee)); - if (wtx.IsFromMe(filter)) + if (CachedTxIsFromMe(*pwallet, wtx, filter)) entry.pushKV("fee", ValueFromAmount(nFee)); WalletTxToJSON(*pwallet, wtx, entry); @@ -2425,7 +2425,7 @@ static RPCHelpMan getbalances() LOCK(wallet.cs_wallet); - const auto bal = wallet.GetBalance(); + const auto bal = GetBalance(wallet); UniValue balances{UniValue::VOBJ}; { UniValue balances_mine{UniValue::VOBJ}; @@ -2435,7 +2435,7 @@ static RPCHelpMan getbalances() if (wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) { // If the AVOID_REUSE flag is set, bal has been set to just the un-reused address balance. Get // the total balance, and then subtract bal to get the reused address balance. - const auto full_bal = wallet.GetBalance(0, false); + const auto full_bal = GetBalance(wallet, 0, false); balances_mine.pushKV("used", ValueFromAmount(full_bal.m_mine_trusted + full_bal.m_mine_untrusted_pending - bal.m_mine_trusted - bal.m_mine_untrusted_pending)); } balances_mine.pushKV("coinjoin", ValueFromAmount(bal.m_anonymized)); @@ -2518,7 +2518,7 @@ static RPCHelpMan getwalletinfo() bool fHDEnabled = spk_man && spk_man->GetHDChain(hdChainCurrent); UniValue obj(UniValue::VOBJ); - const auto bal = pwallet->GetBalance(); + const auto bal = GetBalance(*pwallet); obj.pushKV("walletname", pwallet->GetName()); obj.pushKV("walletversion", pwallet->GetVersion()); obj.pushKV("format", pwallet->GetDatabase().Format()); @@ -3930,7 +3930,7 @@ RPCHelpMan getaddressinfo() UniValue detail = DescribeWalletAddress(*pwallet, dest); ret.pushKVs(detail); - ret.pushKV("ischange", pwallet->IsChange(scriptPubKey)); + ret.pushKV("ischange", ScriptIsChange(*pwallet, scriptPubKey)); ScriptPubKeyMan* spk_man = pwallet->GetScriptPubKeyMan(scriptPubKey); if (spk_man) { diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 8e03ffe68b82f..0fe21fd4d4563 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -23,6 +23,11 @@ using interfaces::FoundBlock; static constexpr size_t OUTPUT_GROUP_MAX_ENTRIES{100}; +int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig) +{ + return CalculateMaximumSignedInputSize(wtx.tx->vout[out], &wallet, use_max_sig); +} + std::string COutput::ToString() const { return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); @@ -87,17 +92,17 @@ void CWallet::AvailableCoins(std::vector &vCoins, const CCoinControl* c if (!chain().checkFinalTx(*wtx.tx)) continue; - if (wtx.IsImmatureCoinBase()) + if (IsTxImmatureCoinBase(wtx)) continue; - int nDepth = wtx.GetDepthInMainChain(); + int nDepth = GetTxDepthInMainChain(wtx); // We should not consider coins which aren't at least in our mempool // It's possible for these to be conflicted via ancestors which we may never be able to detect if (nDepth == 0 && !wtx.InMempool()) continue; - bool safeTx = IsTrusted(wtx, trusted_parents); + bool safeTx = CachedTxIsTrusted(*this, wtx, trusted_parents); if (only_safe && !safeTx) { continue; @@ -173,7 +178,7 @@ void CWallet::AvailableCoins(std::vector &vCoins, const CCoinControl* c bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false; bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); - vCoins.push_back(COutput(&wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly))); + vCoins.push_back(COutput(*this, wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly))); // Checks the sum amount of all UTXO's. if (nMinimumSumAmount != MAX_MONEY) { @@ -212,7 +217,7 @@ const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int out AssertLockHeld(cs_wallet); const CTransaction* ptx = &tx; int n = output; - while (IsChange(ptx->vout[n]) && ptx->vin.size() > 0) { + while (OutputIsChange(*this, ptx->vout[n]) && ptx->vin.size() > 0) { const COutPoint& prevout = ptx->vin[0].prevout; auto it = mapWallet.find(prevout.hash); if (it == mapWallet.end() || it->second.tx->vout.size() <= prevout.n || @@ -248,14 +253,14 @@ std::map> CWallet::ListCoins() const for (const COutPoint& output : setLockedCoins) { auto it = mapWallet.find(output.hash); if (it != mapWallet.end()) { - int depth = it->second.GetDepthInMainChain(); + int depth = GetTxDepthInMainChain(it->second); if (depth >= 0 && output.n < it->second.tx->vout.size() && IsMine(it->second.tx->vout[output.n]) == is_mine_filter ) { CTxDestination address; if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) { result[address].emplace_back( - &it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */); + *this, it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */); } } } @@ -287,7 +292,7 @@ std::vector CWallet::GroupOutputs(const std::vector& outpu // Make an OutputGroup containing just this output OutputGroup group{coin_sel_params}; - group.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only); + group.Insert(input_coin, output.nDepth, CachedTxIsFromMe(*this, *output.tx, ISMINE_ALL), ancestors, descendants, positive_only); // Check the OutputGroup's eligibility. Only add the eligible ones. if (positive_only && group.GetSelectionAmount() <= 0) continue; @@ -333,7 +338,7 @@ std::vector CWallet::GroupOutputs(const std::vector& outpu } // Add the input_coin to group - group->Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only); + group->Insert(input_coin, output.nDepth, CachedTxIsFromMe(*this, *output.tx, ISMINE_ALL), ancestors, descendants, positive_only); } // Now we go through the entire map and pull out the OutputGroups @@ -460,7 +465,7 @@ bool CWallet::SelectCoins(const std::vector& vAvailableCoins, const CAm if (!IsFullyMixed(outpoint)) continue; } // Just to calculate the marginal byte size - CInputCoin coin(wtx.tx, outpoint.n, wtx.GetSpendSize(outpoint.n, false)); + CInputCoin coin(wtx.tx, outpoint.n, GetTxSpendSize(*this, wtx, outpoint.n, false)); nValueFromPresetInputs += coin.txout.nValue; if (coin.m_input_bytes <= 0) { return false; // Not solvable, can't estimate size for fee diff --git a/src/wallet/spend.h b/src/wallet/spend.h index 03f9a7c2b5023..829323c8977b6 100644 --- a/src/wallet/spend.h +++ b/src/wallet/spend.h @@ -9,6 +9,9 @@ #include #include +/** Get the marginal bytes if spending the specified output from this transaction */ +int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig = false); + class COutput { public: @@ -43,13 +46,13 @@ class COutput */ bool fSafe; - COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn, bool use_max_sig_in = false) + COutput(const CWallet& wallet, const CWalletTx& wtx, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn, bool use_max_sig_in = false) { - tx = txIn; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; nInputBytes = -1; use_max_sig = use_max_sig_in; + tx = &wtx; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; nInputBytes = -1; use_max_sig = use_max_sig_in; // If known and signable by the given wallet, compute nInputBytes // Failure will keep this value -1 - if (fSpendable && tx) { - nInputBytes = tx->GetSpendSize(i, use_max_sig); + if (fSpendable) { + nInputBytes = GetTxSpendSize(wallet, wtx, i, use_max_sig); } } @@ -61,4 +64,7 @@ class COutput } }; +// Get the marginal bytes of spending the specified output +int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false); + #endif // BITCOIN_WALLET_SPEND_H diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index 893303f57342e..ed0b111d0d825 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -75,7 +75,7 @@ static void add_coin(std::vector& coins, CWallet& wallet, const CAmount uint256 txid = tx.GetHash(); LOCK(wallet.cs_wallet); - auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(&wallet, MakeTransactionRef(std::move(tx)))); + auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)))); assert(ret.second); CWalletTx& wtx = (*ret.first).second; if (fIsFromMe) @@ -83,7 +83,7 @@ static void add_coin(std::vector& coins, CWallet& wallet, const CAmount wtx.m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, 1); wtx.m_is_cache_empty = false; } - COutput output(&wtx, nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */); + COutput output(wallet, wtx, nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */); coins.push_back(output); } diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp index a2456f3aa78e0..ae968df875f5b 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -32,12 +32,12 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test) CDataStream s_prev_tx1(ParseHex("0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000"), SER_NETWORK, PROTOCOL_VERSION); CTransactionRef prev_tx1; s_prev_tx1 >> prev_tx1; - m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx1->GetHash()), std::forward_as_tuple(&m_wallet, prev_tx1)); + m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx1->GetHash()), std::forward_as_tuple(prev_tx1)); CDataStream s_prev_tx2(ParseHex("0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000"), SER_NETWORK, PROTOCOL_VERSION); CTransactionRef prev_tx2; s_prev_tx2 >> prev_tx2; - m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx2->GetHash()), std::forward_as_tuple(&m_wallet, prev_tx2)); + m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx2->GetHash()), std::forward_as_tuple(prev_tx2)); // Import descriptors for keys and scripts import_descriptor(m_wallet, "sh(multi(2,xprv9s21ZrQH143K2LE7W4Xf3jATf9jECxSb7wj91ZnmY4qEJrS66Qru9RFqq8xbkgT32ya6HqYJweFdJUEDf5Q6JFV7jMiUws7kQfe6Tv4RbfN/0h/0h/0h,xprv9s21ZrQH143K2LE7W4Xf3jATf9jECxSb7wj91ZnmY4qEJrS66Qru9RFqq8xbkgT32ya6HqYJweFdJUEDf5Q6JFV7jMiUws7kQfe6Tv4RbfN/0h/0h/1h))"); diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index a7f80eac97a48..d41b88ab40c74 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -126,7 +127,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_CHECK(result.last_failed_block.IsNull()); BOOST_CHECK(result.last_scanned_block.IsNull()); BOOST_CHECK(!result.last_scanned_height); - BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0); + BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 0); } // Verify ScanForWalletTransactions picks up transactions in both the old @@ -146,7 +147,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_CHECK(result.last_failed_block.IsNull()); BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); - BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 1000 * COIN); + BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 1000 * COIN); } // Prune the older block file. @@ -175,7 +176,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_CHECK_EQUAL(result.last_failed_block, oldTip->GetBlockHash()); BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); - BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 500 * COIN); + BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 500 * COIN); } // Prune the remaining block file. @@ -202,7 +203,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash()); BOOST_CHECK(result.last_scanned_block.IsNull()); BOOST_CHECK(!result.last_scanned_height); - BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0); + BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 0); } } @@ -349,7 +350,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) { CWallet wallet(m_node.chain.get(), m_node.coinjoin_loader.get(), "", CreateDummyWalletDatabase()); - CWalletTx wtx(&wallet, m_coinbase_txns.back()); + CWalletTx wtx(m_coinbase_txns.back()); LOCK(wallet.cs_wallet); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -362,13 +363,13 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) // Call GetImmatureCredit() once before adding the key to the wallet to // cache the current immature credit amount, which is 0. - BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 0); + BOOST_CHECK_EQUAL(CachedTxGetImmatureCredit(wallet, wtx), 0); // Invalidate the cached value, add the key, and make sure a new immature // credit amount is calculated. wtx.MarkDirty(); AddKey(wallet, coinbaseKey); - BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 500*COIN); + BOOST_CHECK_EQUAL(CachedTxGetImmatureCredit(wallet, wtx), 500*COIN); } static int64_t AddTx(ChainstateManager& chainman, CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime) diff --git a/src/wallet/transaction.h b/src/wallet/transaction.h index fde7e2d2a21e2..fa63e2447960e 100644 --- a/src/wallet/transaction.h +++ b/src/wallet/transaction.h @@ -18,13 +18,9 @@ #include class CCoinControl; -struct bilingual_str; -struct COutputEntry; typedef std::map mapValue_t; -//Get the marginal bytes of spending the specified output -int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false); static inline void ReadOrderPos(int64_t& nOrderPos, mapValue_t& mapValue) { @@ -70,8 +66,6 @@ class CMerkleTx class CWalletTx { private: - const CWallet* const pwallet; - /** Constant used in hashBlock to indicate tx has been abandoned, only used at * serialization/deserialization to avoid ambiguity with conflicted. */ @@ -128,7 +122,6 @@ class CWalletTx // memory only enum AmountType { DEBIT, CREDIT, IMMATURE_CREDIT, AVAILABLE_CREDIT, ANON_CREDIT, DENOM_UCREDIT, DENOM_CREDIT, AMOUNTTYPE_ENUM_ELEMENTS }; - CAmount GetCachableAmount(AmountType type, const isminefilter& filter, bool recalculate = false) const; mutable CachableAmount m_amounts[AMOUNTTYPE_ENUM_ELEMENTS]; /** * This flag is true if all m_amounts caches are empty. This is particularly @@ -143,9 +136,8 @@ class CWalletTx mutable bool fIsInstantSendLocked; mutable CAmount nChangeCached; - CWalletTx(const CWallet* wallet, CTransactionRef arg) - : pwallet(wallet), - tx(std::move(arg)) + CWalletTx(CTransactionRef arg) + : tx(std::move(arg)) { Init(); } @@ -274,86 +266,13 @@ class CWalletTx m_is_cache_empty = true; } - //! filter decides which addresses will count towards the debit - CAmount GetDebit(const isminefilter& filter) const; - CAmount GetCredit(const isminefilter& filter) const; - CAmount GetImmatureCredit(bool fUseCache = true) const; - // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct - // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The - // annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid - // having to resolve the issue of member access into incomplete type CWallet. - CAmount GetAvailableCredit(bool fUseCache = true, const isminefilter& filter = ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS; - CAmount GetImmatureWatchOnlyCredit(const bool fUseCache = true) const; - CAmount GetChange() const; - - /** Get the marginal bytes if spending the specified output from this transaction */ - int GetSpendSize(unsigned int out, bool use_max_sig = false) const - { - return CalculateMaximumSignedInputSize(tx->vout[out], pwallet, use_max_sig); - } - - void GetAmounts(std::list& listReceived, - std::list& listSent, CAmount& nFee, const isminefilter& filter) const; - - bool IsFromMe(const isminefilter& filter) const - { - return (GetDebit(filter) > 0); - } - /** True if only scriptSigs are different */ bool IsEquivalentTo(const CWalletTx& tx) const; bool InMempool() const; - bool IsTrusted() const; int64_t GetTxTime() const; - // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct - // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation - // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to - // resolve the issue of member access into incomplete type CWallet. Note - // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" - // in place. - bool CanBeResent() const NO_THREAD_SAFETY_ANALYSIS; - - /** Pass this transaction to node for mempool insertion and relay to peers if flag set to true */ - // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct - // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation - // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to - // resolve the issue of member access into incomplete type CWallet. Note - // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" - // in place. - bool SubmitMemoryPoolAndRelay(bilingual_str& err_string, bool relay) NO_THREAD_SAFETY_ANALYSIS; - - // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct - // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation - // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to - // resolve the issue of member access into incomplete type CWallet. Note - // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" - // in place. - std::set GetConflicts() const NO_THREAD_SAFETY_ANALYSIS; - - /** - * Return depth of transaction in blockchain: - * <0 : conflicts with a transaction this deep in the blockchain - * 0 : in memory pool, waiting to be included in a block - * >=1 : this many blocks deep in the main chain - */ - // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct - // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation - // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to - // resolve the issue of member access into incomplete type CWallet. Note - // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" - // in place. - int GetDepthInMainChain() const NO_THREAD_SAFETY_ANALYSIS; - bool IsInMainChain() const { return GetDepthInMainChain() > 0; } - - /** - * @return number of blocks to maturity for this transaction: - * 0 : is not a coinbase transaction, or is a mature coinbase transaction - * >0 : is a coinbase transaction which matures in this many blocks - */ - int GetBlocksToMaturity() const; bool isAbandoned() const { return m_confirm.status == CWalletTx::ABANDONED; } void setAbandoned() { @@ -371,7 +290,6 @@ class CWalletTx const uint256& GetHash() const { return tx->GetHash(); } bool IsCoinBase() const { return tx->IsCoinBase(); } bool IsPlatformTransfer() const { return tx->IsPlatformTransfer(); } - bool IsImmatureCoinBase() const; // Disable copying of CWalletTx objects to prevent bugs where instances get // copied in and out of the mapWallet map, and fields are updated in the diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 970445f3b2efd..dccee2c3f56f9 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -610,7 +611,7 @@ bool CWallet::IsSpent(const uint256& hash, unsigned int n) const const uint256& wtxid = it->second; std::map::const_iterator mit = mapWallet.find(wtxid); if (mit != mapWallet.end()) { - int depth = mit->second.GetDepthInMainChain(); + int depth = GetTxDepthInMainChain(mit->second); if (depth > 0 || (depth == 0 && !mit->second.isAbandoned())) return true; // Spent } @@ -900,7 +901,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio } // Inserts only if not already there, returns tx inserted or tx found - auto ret = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(this, tx)); + auto ret = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(tx)); CWalletTx& wtx = (*ret.first).second; bool fInsertedNew = ret.second; bool fUpdated = update_wtx && update_wtx(wtx, fInsertedNew); @@ -984,7 +985,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx) { - const auto& ins = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(this, nullptr)); + const auto& ins = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(nullptr)); CWalletTx& wtx = ins.first->second; if (!fill_wtx(wtx, ins.second)) { return false; @@ -1097,14 +1098,14 @@ bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const { LOCK(cs_wallet); const CWalletTx* wtx = GetWalletTx(hashTx); - return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool(); + return wtx && !wtx->isAbandoned() && GetTxDepthInMainChain(*wtx) == 0 && !wtx->InMempool(); } bool CWallet::TransactionCanBeResent(const uint256& hashTx) const { LOCK(cs_wallet); const CWalletTx* wtx = GetWalletTx(hashTx); - return wtx && wtx->CanBeResent(); + return wtx && CanTxBeResent(*wtx); } void CWallet::MarkInputsDirty(const CTransactionRef& tx) @@ -1130,7 +1131,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) auto it = mapWallet.find(hashTx); assert(it != mapWallet.end()); const CWalletTx& origtx = it->second; - if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool() || IsTxLockedByInstantSend(origtx)) { + if (GetTxDepthInMainChain(origtx) != 0 || origtx.InMempool() || IsTxLockedByInstantSend(origtx)) { return false; } @@ -1143,7 +1144,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) auto it = mapWallet.find(now); assert(it != mapWallet.end()); CWalletTx& wtx = it->second; - int currentconfirm = wtx.GetDepthInMainChain(); + int currentconfirm = GetTxDepthInMainChain(wtx); // If the orig tx was not in block, none of its spends can be assert(currentconfirm <= 0); // if (currentconfirm < 0) {Tx and spends are already conflicted, no need to abandon} @@ -1183,7 +1184,7 @@ bool CWallet::ResendTransaction(const uint256& hashTx) CWalletTx& wtx = it->second; bilingual_str unused_err_string; - return wtx.SubmitMemoryPoolAndRelay(unused_err_string, true); + return SubmitTxMemoryPoolAndRelay(wtx, unused_err_string, true); } void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, const uint256& hashTx) @@ -1213,7 +1214,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c auto it = mapWallet.find(now); assert(it != mapWallet.end()); CWalletTx& wtx = it->second; - int currentconfirm = wtx.GetDepthInMainChain(); + int currentconfirm = GetTxDepthInMainChain(wtx); if (conflictconfirms < currentconfirm) { // Block is 'more conflicted' than current confirm; update. // Mark transaction as conflicted with this block. @@ -1786,7 +1787,7 @@ void CWallet::ReacceptWalletTransactions() CWalletTx& wtx = item.second; assert(wtx.GetHash() == wtxid); - int nDepth = wtx.GetDepthInMainChain(); + int nDepth = GetTxDepthInMainChain(wtx); if (!wtx.IsCoinBase() && (nDepth == 0 && !IsTxLockedByInstantSend(wtx) && !wtx.isAbandoned())) { mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); @@ -1797,34 +1798,34 @@ void CWallet::ReacceptWalletTransactions() for (const std::pair& item : mapSorted) { CWalletTx& wtx = *(item.second); bilingual_str unused_err_string; - wtx.SubmitMemoryPoolAndRelay(unused_err_string, false); + SubmitTxMemoryPoolAndRelay(wtx, unused_err_string, false); } } -bool CWalletTx::CanBeResent() const +bool CWallet::CanTxBeResent(const CWalletTx& wtx) const { - AssertLockHeld(pwallet->cs_wallet); + AssertLockHeld(cs_wallet); return // Can't relay if wallet is not broadcasting - pwallet->GetBroadcastTransactions() && + GetBroadcastTransactions() && // Don't relay abandoned transactions - !isAbandoned() && + !wtx.isAbandoned() && // Don't try to submit coinbase transactions. These would fail anyway but would // cause log spam. - !IsCoinBase() && + !wtx.IsCoinBase() && // Don't try to submit conflicted or confirmed transactions. - GetDepthInMainChain() == 0 && + GetTxDepthInMainChain(wtx) == 0 && // Don't try to submit transactions locked via InstantSend. - !pwallet->IsTxLockedByInstantSend(*this); + !IsTxLockedByInstantSend(wtx); } -bool CWalletTx::SubmitMemoryPoolAndRelay(bilingual_str& err_string, bool relay) +bool CWallet::SubmitTxMemoryPoolAndRelay(const CWalletTx& wtx, bilingual_str& err_string, bool relay) const { - AssertLockHeld(pwallet->cs_wallet); - if (!CanBeResent()) return false; + AssertLockHeld(cs_wallet); + if (!CanTxBeResent(wtx)) return false; // Submit transaction to mempool for relay - pwallet->WalletLogPrintf("Submitting wtx %s to mempool for relay\n", GetHash().ToString()); + WalletLogPrintf("Submitting wtx %s to mempool for relay\n", wtx.GetHash().ToString()); // We must set fInMempool here - while it will be re-set to true by the // entered-mempool callback, if we did not there would be a race where a // user could call sendmoney in a loop and hit spurious out of funds errors @@ -1834,19 +1835,18 @@ bool CWalletTx::SubmitMemoryPoolAndRelay(bilingual_str& err_string, bool relay) // Irrespective of the failure reason, un-marking fInMempool // out-of-order is incorrect - it should be unmarked when // TransactionRemovedFromMempool fires. - bool ret = pwallet->chain().broadcastTransaction(tx, pwallet->m_default_max_tx_fee, relay, err_string); - fInMempool |= ret; + bool ret = chain().broadcastTransaction(wtx.tx, m_default_max_tx_fee, relay, err_string); + wtx.fInMempool |= ret; return ret; } -std::set CWalletTx::GetConflicts() const +std::set CWallet::GetTxConflicts(const CWalletTx& wtx) const { + AssertLockHeld(cs_wallet); std::set result; - if (pwallet != nullptr) { - AssertLockHeld(pwallet->cs_wallet); - uint256 myHash = GetHash(); - result = pwallet->GetConflicts(myHash); + uint256 myHash = wtx.GetHash(); + result = GetConflicts(myHash); result.erase(myHash); } return result; @@ -1884,11 +1884,11 @@ void CWallet::ResendWalletTransactions() for (std::pair& item : mapWallet) { CWalletTx& wtx = item.second; // Attempt to rebroadcast all txes more than 5 minutes older than - // the last block. SubmitMemoryPoolAndRelay() will not rebroadcast + // the last block. SubmitTxMemoryPoolAndRelay() will not rebroadcast // any confirmed or conflicting txs. if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue; bilingual_str unused_err_string; - if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true)) ++submitted_tx_count; + if (SubmitTxMemoryPoolAndRelay(wtx, unused_err_string, true)) ++submitted_tx_count; } } // cs_wallet @@ -2081,7 +2081,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve } bilingual_str err_string; - if (!wtx.SubmitMemoryPoolAndRelay(err_string, true)) { + if (!SubmitTxMemoryPoolAndRelay(wtx, err_string, true)) { WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", err_string.original); // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. } @@ -3427,13 +3427,12 @@ CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool fInternalIn) fInternal = fInternalIn; } -int CWalletTx::GetDepthInMainChain() const +int CWallet::GetTxDepthInMainChain(const CWalletTx& wtx) const { - assert(pwallet != nullptr); - AssertLockHeld(pwallet->cs_wallet); - if (isUnconfirmed() || isAbandoned()) return 0; + AssertLockHeld(cs_wallet); + if (wtx.isUnconfirmed() || wtx.isAbandoned()) return 0; - return (pwallet->GetLastBlockHeight() - m_confirm.block_height + 1) * (isConflicted() ? -1 : 1); + return (GetLastBlockHeight() - wtx.m_confirm.block_height + 1) * (wtx.isConflicted() ? -1 : 1); } bool CWallet::IsTxLockedByInstantSend(const CWalletTx& wtx) const @@ -3459,19 +3458,19 @@ bool CWallet::IsTxChainLocked(const CWalletTx& wtx) const return wtx.fIsChainlocked; } -int CWalletTx::GetBlocksToMaturity() const +int CWallet::GetTxBlocksToMaturity(const CWalletTx& wtx) const { - if (!IsCoinBase()) + if (!wtx.IsCoinBase()) return 0; - int chain_depth = GetDepthInMainChain(); + int chain_depth = GetTxDepthInMainChain(wtx); assert(chain_depth >= 0); // coinbase tx should not be conflicted return std::max(0, (COINBASE_MATURITY+1) - chain_depth); } -bool CWalletTx::IsImmatureCoinBase() const +bool CWallet::IsTxImmatureCoinBase(const CWalletTx& wtx) const { // note GetBlocksToMaturity is 0 for non-coinbase tx - return GetBlocksToMaturity() > 0; + return GetTxBlocksToMaturity(wtx) > 0; } bool CWallet::IsCrypted() const diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index cd45bfbb77717..ff22ffe61263a 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -507,11 +506,41 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati bool coinjoin_available() { return m_coinjoin_loader != nullptr; } const CWalletTx* GetWalletTx(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool IsTrusted(const CWalletTx& wtx, std::set& trusted_parents) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct + // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation + // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to + // resolve the issue of member access into incomplete type CWallet. Note + // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" + // in place. + std::set GetTxConflicts(const CWalletTx& wtx) const NO_THREAD_SAFETY_ANALYSIS; + + /** + * Return depth of transaction in blockchain: + * <0 : conflicts with a transaction this deep in the blockchain + * 0 : in memory pool, waiting to be included in a block + * >=1 : this many blocks deep in the main chain + */ + // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct + // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation + // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to + // resolve the issue of member access into incomplete type CWallet. Note + // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" + // in place. + int GetTxDepthInMainChain(const CWalletTx& wtx) const NO_THREAD_SAFETY_ANALYSIS; + bool IsTxInMainChain(const CWalletTx& wtx) const { return GetTxDepthInMainChain(wtx) > 0; } bool IsTxLockedByInstantSend(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool IsTxChainLocked(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + /** + * @return number of blocks to maturity for this transaction: + * 0 : is not a coinbase transaction, or is a mature coinbase transaction + * >0 : is a coinbase transaction which matures in this many blocks + */ + int GetTxBlocksToMaturity(const CWalletTx& wtx) const; + bool IsTxImmatureCoinBase(const CWalletTx& wtx) const; + //! check whether we support the named feature bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return IsFeatureSupported(nWalletVersion, wf); } @@ -657,18 +686,6 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override; void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void ResendWalletTransactions(); - struct Balance { - CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more - CAmount m_mine_untrusted_pending{0}; //!< Untrusted, but in mempool (pending) - CAmount m_mine_immature{0}; //!< Immature coinbases in the main chain - CAmount m_watchonly_trusted{0}; - CAmount m_watchonly_untrusted_pending{0}; - CAmount m_watchonly_immature{0}; - CAmount m_anonymized{0}; - CAmount m_denominated_trusted{0}; - CAmount m_denominated_untrusted_pending{0}; - }; - Balance GetBalance(const int min_depth = 0, const bool avoid_reuse = true, const bool fAddLocked = false) const; CAmount GetAnonymizableBalance(bool fSkipDenominated = false, bool fSkipUnconfirmed = true) const; float GetAverageAnonymizedRounds() const; @@ -733,6 +750,12 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati */ void CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector> orderForm); + /** Will SubmitTxMemoryPoolAndRelay() consider wtx if supplied */ + bool CanTxBeResent(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + /** Pass this transaction to node for mempool insertion and relay to peers if flag set to true */ + bool SubmitTxMemoryPoolAndRelay(const CWalletTx& wtx, bilingual_str& err_string, bool relay) const + EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool DummySignTx(CMutableTransaction &txNew, const std::set &txouts, bool use_max_sig = false) const { std::vector v_txouts(txouts.size()); @@ -781,9 +804,6 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati std::optional GetOldestKeyPoolTime() const; - std::set> GetAddressGroupings() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - std::map GetAddressBalances() const; - // Filter struct for 'ListAddrBookAddresses' struct AddrBookFilter { // Fetch addresses with the provided label @@ -820,25 +840,16 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati isminetype IsMine(const CTxDestination& dest) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); isminetype IsMine(const CScript& script) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - isminetype IsMine(const CTxIn& txin) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Returns amount of debit if the input matches the * filter, otherwise returns 0 */ CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const; isminetype IsMine(const CTxOut& txout) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - CAmount GetCredit(const CTxOut& txout, const isminefilter& filter) const; - bool IsChange(const CTxOut& txout) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool IsChange(const CScript& script) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - CAmount GetChange(const CTxOut& txout) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool IsMine(const CTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** should probably be renamed to IsRelevantToMe */ bool IsFromMe(const CTransaction& tx) const; CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const; - /** Returns whether all of the inputs match the filter */ - bool IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const; - CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const; - CAmount GetChange(const CTransaction& tx) const; void chainStateFlushed(const CBlockLocator& loc) override; DBErrors LoadWallet(); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index f7749d9a4e42e..0a545496a47db 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -968,7 +968,7 @@ DBErrors WalletBatch::FindWalletTx(std::vector& vTxHash, std::list> hash; vTxHash.push_back(hash); - vWtx.emplace_back(nullptr /* wallet */, nullptr /* tx */); + vWtx.emplace_back(nullptr /* tx */); ssValue >> vWtx.back(); } } diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index bfaead0ca5c63..bff53f9fbd7e7 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -93,6 +93,7 @@ "qt/guiutil -> qt/optionsdialog -> qt/guiutil", "qt/guiutil -> qt/optionsdialog -> qt/optionsmodel -> qt/guiutil", "qt/guiutil -> qt/qvalidatedlineedit -> qt/guiutil", + "wallet/coinjoin -> wallet/receive -> wallet/coinjoin", ) CODE_DIR = "src" From f6375167e3a883cc43e1f4cf01c8d7fe11ab50cf Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 22 Apr 2025 20:32:06 +0000 Subject: [PATCH 07/13] merge bitcoin#22100: Clean up new wallet spend, receive files added bitcoin#21207 Includes changes that primarily affect `wallet/spend.cpp`, resolves circular dependency. --- src/bench/coin_selection.cpp | 3 +- src/coinjoin/client.cpp | 3 +- src/coinjoin/util.cpp | 5 +- src/rpc/evo.cpp | 6 +- src/rpc/governance.cpp | 3 +- src/rpc/masternode.cpp | 3 +- src/wallet/coincontrol.h | 2 +- src/wallet/coinjoin.cpp | 7 +- src/wallet/interfaces.cpp | 7 +- src/wallet/load.cpp | 1 + src/wallet/rpcwallet.cpp | 7 +- src/wallet/spend.cpp | 231 ++++++++++++------------ src/wallet/spend.h | 67 +++++++ src/wallet/test/coinjoin_tests.cpp | 3 +- src/wallet/test/coinselector_tests.cpp | 7 +- src/wallet/test/spend_tests.cpp | 3 +- src/wallet/test/wallet_tests.cpp | 39 ++-- src/wallet/transaction.h | 3 +- src/wallet/wallet.cpp | 1 - src/wallet/wallet.h | 65 ------- test/functional/wallet_basic.py | 2 +- test/lint/lint-circular-dependencies.py | 4 - 22 files changed, 241 insertions(+), 231 deletions(-) diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index 8722e77df456f..c9f27fd217eb9 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -54,7 +55,7 @@ static void CoinSelection(benchmark::Bench& bench) bench.run([&] { std::set setCoinsRet; CAmount nValueRet; - bool success = wallet.AttemptSelection(1003 * COIN, filter_standard, coins, setCoinsRet, nValueRet, coin_selection_params); + bool success = AttemptSelection(wallet, 1003 * COIN, filter_standard, coins, setCoinsRet, nValueRet, coin_selection_params); assert(success); assert(nValueRet == 1003 * COIN); assert(setCoinsRet.size() == 2); diff --git a/src/coinjoin/client.cpp b/src/coinjoin/client.cpp index 8f7eafc01761b..8ae8a666c30a6 100644 --- a/src/coinjoin/client.cpp +++ b/src/coinjoin/client.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -1553,7 +1554,7 @@ bool CCoinJoinClientSession::CreateCollateralTransaction(CMutableTransaction& tx CCoinControl coin_control; coin_control.nCoinType = CoinType::ONLY_COINJOIN_COLLATERAL; - m_wallet->AvailableCoins(vCoins, &coin_control); + AvailableCoins(*m_wallet, vCoins, &coin_control); if (vCoins.empty()) { strReason = strprintf("%s requires a collateral transaction and could not locate an acceptable input!", gCoinJoinName); diff --git a/src/coinjoin/util.cpp b/src/coinjoin/util.cpp index 0a379a054f867..11ea354494476 100644 --- a/src/coinjoin/util.cpp +++ b/src/coinjoin/util.cpp @@ -7,9 +7,10 @@ #include #include #include