diff --git a/src/sapling/sapling_operation.cpp b/src/sapling/sapling_operation.cpp index b513e33d16f31..006e905584c61 100644 --- a/src/sapling/sapling_operation.cpp +++ b/src/sapling/sapling_operation.cpp @@ -287,7 +287,7 @@ OperationResult SaplingOperation::loadUtxos(TxValues& txValues) const auto* tx = wallet->GetWalletTx(outpoint.outPoint.hash); if (!tx) continue; nSelectedValue += tx->tx->vout[outpoint.outPoint.n].nValue; - selectedUTXOInputs.emplace_back(tx, outpoint.outPoint.n, 0, true, true); + selectedUTXOInputs.emplace_back(tx, outpoint.outPoint.n, 0, true, true, true); } return loadUtxos(txValues, selectedUTXOInputs, nSelectedValue); } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index e0a879746589d..5051c7567fdac 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3713,11 +3713,12 @@ UniValue listunspent(const JSONRPCRequest& request) CCoinControl coinControl; coinControl.fAllowWatchOnly = nWatchonlyConfig == 2; + coinFilter.fOnlySafe = false; + UniValue results(UniValue::VARR); std::vector vecOutputs; LOCK2(cs_main, pwallet->cs_wallet); - coinFilter.fOnlyConfirmed = false; pwallet->AvailableCoins(&vecOutputs, &coinControl, coinFilter); for (const COutput& out : vecOutputs) { if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth) diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 483073906c1fa..09213a4b9fe8f 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -53,7 +53,7 @@ static void add_coin(std::unique_ptr& pwallet, const CAmount& nValue, i if (fIsFromMe) { wtx->m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, 1); } - COutput output(wtx.get(), nInput, nAge, true, true); + COutput output(wtx.get(), nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */); vCoins.push_back(output); wtxn.emplace_back(std::move(wtx)); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index d7b86d9bee64a..c61c56eee3a7f 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2439,6 +2439,8 @@ void CWallet::GetAvailableP2CSCoins(std::vector& vCoins) const { if (fConflicted || nDepth < 0) continue; + bool fSafe = pcoin->IsTrusted(); + if (pcoin->tx->HasP2CSOutputs()) { for (int i = 0; i < (int) pcoin->tx->vout.size(); i++) { const auto &utxo = pcoin->tx->vout[i]; @@ -2451,7 +2453,7 @@ void CWallet::GetAvailableP2CSCoins(std::vector& vCoins) const { bool isMineSpendable = mine & ISMINE_SPENDABLE_DELEGATED; if (mine & ISMINE_COLD || isMineSpendable) // Depth and solvability members are not used, no need waste resources and set them for now. - vCoins.emplace_back(pcoin, i, 0, isMineSpendable, true); + vCoins.emplace_back(pcoin, i, 0, isMineSpendable, true, fSafe); } } } @@ -2463,9 +2465,9 @@ void CWallet::GetAvailableP2CSCoins(std::vector& vCoins) const { /** * Test if the transaction is spendable. */ -static bool CheckTXAvailabilityInternal(const CWalletTx* pcoin, bool fOnlyConfirmed, int& nDepth) +static bool CheckTXAvailabilityInternal(const CWalletTx* pcoin, bool fOnlySafe, int& nDepth) { - if (fOnlyConfirmed && !pcoin->IsTrusted()) return false; + if (fOnlySafe && !pcoin->IsTrusted()) return false; if (pcoin->GetBlocksToMaturity() > 0) return false; nDepth = pcoin->GetDepthInMainChain(); @@ -2478,22 +2480,22 @@ static bool CheckTXAvailabilityInternal(const CWalletTx* pcoin, bool fOnlyConfir } // cs_main lock required -static bool CheckTXAvailability(const CWalletTx* pcoin, bool fOnlyConfirmed, int& nDepth) +static bool CheckTXAvailability(const CWalletTx* pcoin, bool fOnlySafe, int& nDepth) { AssertLockHeld(cs_main); if (!CheckFinalTx(pcoin->tx)) return false; - return CheckTXAvailabilityInternal(pcoin, fOnlyConfirmed, nDepth); + return CheckTXAvailabilityInternal(pcoin, fOnlySafe, nDepth); } // cs_main lock NOT required static bool CheckTXAvailability(const CWalletTx* pcoin, - bool fOnlyConfirmed, + bool fOnlySafe, int& nDepth, int nBlockHeight) { // Mimic CheckFinalTx without cs_main lock if (!IsFinalTx(pcoin->tx, nBlockHeight + 1, GetAdjustedTime())) return false; - return CheckTXAvailabilityInternal(pcoin, fOnlyConfirmed, nDepth); + return CheckTXAvailabilityInternal(pcoin, fOnlySafe, nDepth); } bool CWallet::GetMasternodeVinAndKeys(CTxIn& txinRet, CPubKey& pubKeyRet, CKey& keyRet, std::string strTxHash, std::string strOutputIndex, std::string& strError) @@ -2566,7 +2568,7 @@ bool CWallet::GetMasternodeVinAndKeys(CTxIn& txinRet, CPubKey& pubKeyRet, CKey& } return GetVinAndKeysFromOutput( - COutput(wtx, nOutputIndex, nDepth, true, true), + COutput(wtx, nOutputIndex, nDepth, true, true, true), txinRet, pubKeyRet, keyRet); @@ -2639,12 +2641,14 @@ bool CWallet::AvailableCoins(std::vector* pCoins, // --> populates // Check if the tx is selectable int nDepth; - if (!CheckTXAvailability(pcoin, coinsFilter.fOnlyConfirmed, nDepth, m_last_block_processed_height)) + if (!CheckTXAvailability(pcoin, coinsFilter.fOnlySafe, nDepth, m_last_block_processed_height)) continue; // Check min depth filtering requirements if (nDepth < coinsFilter.minDepth) continue; + bool safeTx = pcoin->IsTrusted(); + for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) { const auto& output = pcoin->tx->vout[i]; @@ -2680,7 +2684,7 @@ bool CWallet::AvailableCoins(std::vector* pCoins, // --> populates // found valid coin if (!pCoins) return true; - pCoins->emplace_back(pcoin, (int) i, nDepth, res.spendable, res.solvable); + pCoins->emplace_back(pcoin, (int) i, nDepth, res.spendable, res.solvable, safeTx); // Checks the sum amount of all UTXO's. if (coinsFilter.nMinimumSumAmount != 0) { @@ -2701,11 +2705,11 @@ bool CWallet::AvailableCoins(std::vector* pCoins, // --> populates } } -std::map > CWallet::AvailableCoinsByAddress(bool fConfirmed, CAmount maxCoinValue, bool fIncludeColdStaking) +std::map > CWallet::AvailableCoinsByAddress(bool fOnlySafe, CAmount maxCoinValue, bool fIncludeColdStaking) { CWallet::AvailableCoinsFilter coinFilter; coinFilter.fIncludeColdStaking = true; - coinFilter.fOnlyConfirmed = fConfirmed; + coinFilter.fOnlySafe = fOnlySafe; coinFilter.fIncludeColdStaking = fIncludeColdStaking; coinFilter.nMaxOutValue = maxCoinValue; std::vector vCoins; @@ -2810,7 +2814,7 @@ bool CWallet::StakeableCoins(std::vector* pCoins) // found valid coin if (!pCoins) return true; if (!pindex) pindex = mapBlockIndex.at(pcoin->m_confirm.hashBlock); - pCoins->emplace_back(CStakeableOutput(pcoin, (int) index, nDepth, res.spendable, res.solvable, pindex)); + pCoins->emplace_back(pcoin, (int) index, nDepth, pindex); } } return (pCoins && !pCoins->empty()); @@ -4959,7 +4963,10 @@ const CWDestination* CAddressBookIterator::GetDestKey() return &it->first; } -CStakeableOutput::CStakeableOutput(const CWalletTx* txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, - const CBlockIndex*& _pindex) : COutput(txIn, iIn, nDepthIn, fSpendableIn, fSolvableIn), - pindex(_pindex) {} - +CStakeableOutput::CStakeableOutput(const CWalletTx* txIn, + int iIn, + int nDepthIn, + const CBlockIndex*& _pindex) : + COutput(txIn, iIn, nDepthIn, true /*fSpendable*/, true/*fSolvable*/, true/*fSafe*/), + pindex(_pindex) +{} diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index df02b8048f3dc..64e821ed1ec5d 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -766,7 +766,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface AvailableCoinsFilter() {} AvailableCoinsFilter(bool _fIncludeDelegated, bool _fIncludeColdStaking, - bool _fOnlyConfirmed, + bool _fOnlySafe, bool _fOnlySpendable, std::set* _onlyFilteredDest, int _minDepth, @@ -774,7 +774,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface CAmount _nMaxOutValue = 0) : fIncludeDelegated(_fIncludeDelegated), fIncludeColdStaking(_fIncludeColdStaking), - fOnlyConfirmed(_fOnlyConfirmed), + fOnlySafe(_fOnlySafe), fOnlySpendable(_fOnlySpendable), onlyFilteredDest(_onlyFilteredDest), minDepth(_minDepth), @@ -783,7 +783,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool fIncludeDelegated{true}; bool fIncludeColdStaking{false}; - bool fOnlyConfirmed{true}; + bool fOnlySafe{true}; bool fOnlySpendable{false}; std::set* onlyFilteredDest{nullptr}; int minDepth{0}; @@ -1238,11 +1238,23 @@ class COutput const CWalletTx* tx; int i; int nDepth; + + /** Whether we have the private keys to spend this output */ bool fSpendable; + + /** Whether we know how to spend this output, ignoring the lack of keys */ bool fSolvable; - COutput(const CWalletTx* txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn) : - tx(txIn), i(iIn), nDepth(nDepthIn), fSpendable(fSpendableIn), fSolvable(fSolvableIn) {} + /** + * Whether this output is considered safe to spend. Unconfirmed transactions + * from outside keys and unconfirmed replacement transactions are considered + * unsafe and will not be used to fund new spending transactions. + */ + bool fSafe; + + COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn) : + tx(txIn), i(iIn), nDepth(nDepthIn), fSpendable(fSpendableIn), fSolvable(fSolvableIn), fSafe(fSafeIn) + {} CAmount Value() const { return tx->tx->vout[i].nValue; } std::string ToString() const; @@ -1253,7 +1265,7 @@ class CStakeableOutput : public COutput public: const CBlockIndex* pindex{nullptr}; - CStakeableOutput(const CWalletTx* txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, + CStakeableOutput(const CWalletTx* txIn, int iIn, int nDepthIn, const CBlockIndex*& pindex); };