From af61d9f78bec62ff3688d88409a53df9ff5bc591 Mon Sep 17 00:00:00 2001 From: Russell Yanofsky Date: Thu, 23 Feb 2017 11:20:16 -0500 Subject: [PATCH 1/2] Add COutput::fSafe member for safe handling of unconfirmed outputs This exposes a value computed in CWallet::AvailableCoins so it can used for other things, like inclusion in listunspent output. --- src/bench/coin_selection.cpp | 2 +- src/qt/walletmodel.cpp | 6 +++--- src/wallet/test/wallet_tests.cpp | 2 +- src/wallet/wallet.cpp | 19 +++++++++++-------- src/wallet/wallet.h | 17 ++++++++++++++--- 5 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index 29fbd3463..06882f151 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -20,7 +20,7 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector& vOutpoints, std::vect if (!wallet->mapWallet.count(outpoint.hash)) continue; int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain(); if (nDepth < 0) continue; - COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true, true); + COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true /* spendable */, true /* solvable */, true /* safe */); vOutputs.push_back(out); } } @@ -607,7 +607,7 @@ void WalletModel::listCoins(std::map >& mapCoins) if (!wallet->mapWallet.count(outpoint.hash)) continue; int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain(); if (nDepth < 0) continue; - COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true, true); + COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true /* spendable */, true /* solvable */, true /* safe */); if (outpoint.n < out.tx->tx->vout.size() && wallet->IsMine(out.tx->tx->vout[outpoint.n]) == ISMINE_SPENDABLE) vCoins.push_back(out); } @@ -619,7 +619,7 @@ void WalletModel::listCoins(std::map >& mapCoins) while (wallet->IsChange(cout.tx->tx->vout[cout.i]) && cout.tx->tx->vin.size() > 0 && wallet->IsMine(cout.tx->tx->vin[0])) { if (!wallet->mapWallet.count(cout.tx->tx->vin[0].prevout.hash)) break; - cout = COutput(&wallet->mapWallet[cout.tx->tx->vin[0].prevout.hash], cout.tx->tx->vin[0].prevout.n, 0, true, true); + cout = COutput(&wallet->mapWallet[cout.tx->tx->vin[0].prevout.hash], cout.tx->tx->vin[0].prevout.n, 0 /* depth */, true /* spendable */, true /* solvable */, true /* safe */); } CTxDestination address; diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index c94491ca2..67e5e9022 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -54,7 +54,7 @@ static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = fa wtx->fDebitCached = true; wtx->nDebitCached = 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 965fba67f..9e3c8be3f 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1946,7 +1946,7 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const return nTotal; } -void CWallet::AvailableCoins(std::vector& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl, bool fIncludeZeroValue) const +void CWallet::AvailableCoins(std::vector& vCoins, bool fOnlySafe, const CCoinControl *coinControl, bool fIncludeZeroValue) const { vCoins.clear(); @@ -1960,9 +1960,6 @@ void CWallet::AvailableCoins(std::vector& vCoins, bool fOnlyConfirmed, if (!CheckFinalTx(*pcoin)) continue; - if (fOnlyConfirmed && !pcoin->IsTrusted()) - continue; - if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) continue; @@ -1975,6 +1972,8 @@ void CWallet::AvailableCoins(std::vector& vCoins, bool fOnlyConfirmed, if (nDepth == 0 && !pcoin->InMempool()) continue; + bool safeTx = pcoin->IsTrusted(); + // We should not consider coins from transactions that are replacing // other transactions. // @@ -1990,8 +1989,8 @@ void CWallet::AvailableCoins(std::vector& vCoins, bool fOnlyConfirmed, // be a 1-block reorg away from the chain where transactions A and C // were accepted to another chain where B, B', and C were all // accepted. - if (nDepth == 0 && fOnlyConfirmed && pcoin->mapValue.count("replaces_txid")) { - continue; + if (nDepth == 0 && pcoin->mapValue.count("replaces_txid")) { + safeTx = false; } // Similarly, we should not consider coins from transactions that @@ -2002,7 +2001,11 @@ void CWallet::AvailableCoins(std::vector& vCoins, bool fOnlyConfirmed, // intending to replace A', but potentially resulting in a scenario // where A, A', and D could all be accepted (instead of just B and // D, or just A and A' like the user would want). - if (nDepth == 0 && fOnlyConfirmed && pcoin->mapValue.count("replaced_by_txid")) { + if (nDepth == 0 && pcoin->mapValue.count("replaced_by_txid")) { + safeTx = false; + } + + if (fOnlySafe && !safeTx) { continue; } @@ -2014,7 +2017,7 @@ void CWallet::AvailableCoins(std::vector& vCoins, bool fOnlyConfirmed, vCoins.push_back(COutput(pcoin, i, nDepth, ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO), - (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO)); + (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO, safeTx)); } } } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index cae92a0b0..80201e8ce 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -466,12 +466,23 @@ public: 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) + /** + * 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; + tx = txIn; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; } std::string ToString() const; @@ -740,7 +751,7 @@ public: /** * populate vCoins with vector of available COutputs. */ - void AvailableCoins(std::vector& vCoins, bool fOnlyConfirmed=true, const CCoinControl *coinControl = NULL, bool fIncludeZeroValue=false) const; + void AvailableCoins(std::vector& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = NULL, bool fIncludeZeroValue=false) const; /** * Shuffle and select coins until nTargetValue is reached while avoiding From dcf2112de6ec5a7a5e97076d1ce826eb233a1042 Mon Sep 17 00:00:00 2001 From: NicolasDorier Date: Thu, 23 Feb 2017 05:58:31 +0000 Subject: [PATCH 2/2] Add safe flag to listunspent result --- qa/rpc-tests/listtransactions.py | 4 ++++ src/wallet/rpcwallet.cpp | 10 ++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/qa/rpc-tests/listtransactions.py b/qa/rpc-tests/listtransactions.py index 92fb96c80..68d14093c 100755 --- a/qa/rpc-tests/listtransactions.py +++ b/qa/rpc-tests/listtransactions.py @@ -126,7 +126,11 @@ class ListTransactionsTest(BitcoinTestFramework): assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_1}, {"bip125-replaceable":"no"}) # Tx2 will build off txid_1, still not opting in to RBF. + utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[0], txid_1) + assert_equal(utxo_to_use["safe"], True) utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[1], txid_1) + utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[1], txid_1) + assert_equal(utxo_to_use["safe"], False) # Create tx2 using createrawtransaction inputs = [{"txid":utxo_to_use["txid"], "vout":utxo_to_use["vout"]}] diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 7d5cb930a..84e7eb60d 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2506,9 +2506,7 @@ UniValue listunspent(const JSONRPCRequest& request) " ,...\n" " ]\n" "4. include_unsafe (bool, optional, default=true) Include outputs that are not safe to spend\n" - " because they come from unconfirmed untrusted transactions or unconfirmed\n" - " replacement transactions (cases where we are less sure that a conflicting\n" - " transaction won't be mined).\n" + " See description of \"safe\" attribute below.\n" "\nResult\n" "[ (array of json object)\n" " {\n" @@ -2521,7 +2519,10 @@ UniValue listunspent(const JSONRPCRequest& request) " \"confirmations\" : n, (numeric) The number of confirmations\n" " \"redeemScript\" : n (string) The redeemScript if scriptPubKey is P2SH\n" " \"spendable\" : xxx, (bool) Whether we have the private keys to spend this output\n" - " \"solvable\" : xxx (bool) Whether we know how to spend this output, ignoring the lack of keys\n" + " \"solvable\" : xxx, (bool) Whether we know how to spend this output, ignoring the lack of keys\n" + " \"safe\" : xxx (bool) Whether this output is considered safe to spend. Unconfirmed transactions\n" + " from outside keys and unconfirmed replacement transactions are considered unsafe\n" + " and are not eligible for spending by fundrawtransaction and sendtoaddress.\n" " }\n" " ,...\n" "]\n" @@ -2606,6 +2607,7 @@ UniValue listunspent(const JSONRPCRequest& request) entry.push_back(Pair("confirmations", out.nDepth)); entry.push_back(Pair("spendable", out.fSpendable)); entry.push_back(Pair("solvable", out.fSolvable)); + entry.push_back(Pair("safe", out.fSafe)); results.push_back(entry); }