Skip to content

Commit afcd7c0

Browse files
committed
Merge #9830: Add safe flag to listunspent result
dcf2112 Add safe flag to listunspent result (NicolasDorier) af61d9f Add COutput::fSafe member for safe handling of unconfirmed outputs (Russell Yanofsky) Tree-SHA512: 311edb6fa8075b3ede5b24cb8c6e5d133ccd8ac9ecafea07b604ffa812ee4f071337e31695e662d8573590a0460af20aaaeb39d49c9ea87924449ea50bdfb0b3
2 parents f8a7091 + dcf2112 commit afcd7c0

File tree

7 files changed

+40
-20
lines changed

7 files changed

+40
-20
lines changed

qa/rpc-tests/listtransactions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,11 @@ def get_unconfirmed_utxo_entry(node, txid_to_match):
126126
assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_1}, {"bip125-replaceable":"no"})
127127

128128
# Tx2 will build off txid_1, still not opting in to RBF.
129+
utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[0], txid_1)
130+
assert_equal(utxo_to_use["safe"], True)
129131
utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[1], txid_1)
132+
utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[1], txid_1)
133+
assert_equal(utxo_to_use["safe"], False)
130134

131135
# Create tx2 using createrawtransaction
132136
inputs = [{"txid":utxo_to_use["txid"], "vout":utxo_to_use["vout"]}]

src/bench/coin_selection.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<CO
2020
CWalletTx* wtx = new CWalletTx(&wallet, MakeTransactionRef(std::move(tx)));
2121

2222
int nAge = 6 * 24;
23-
COutput output(wtx, nInput, nAge, true, true);
23+
COutput output(wtx, nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */);
2424
vCoins.push_back(output);
2525
}
2626

src/qt/walletmodel.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ void WalletModel::getOutputs(const std::vector<COutPoint>& vOutpoints, std::vect
580580
if (!wallet->mapWallet.count(outpoint.hash)) continue;
581581
int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain();
582582
if (nDepth < 0) continue;
583-
COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true, true);
583+
COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true /* spendable */, true /* solvable */, true /* safe */);
584584
vOutputs.push_back(out);
585585
}
586586
}
@@ -607,7 +607,7 @@ void WalletModel::listCoins(std::map<QString, std::vector<COutput> >& mapCoins)
607607
if (!wallet->mapWallet.count(outpoint.hash)) continue;
608608
int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain();
609609
if (nDepth < 0) continue;
610-
COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true, true);
610+
COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true /* spendable */, true /* solvable */, true /* safe */);
611611
if (outpoint.n < out.tx->tx->vout.size() && wallet->IsMine(out.tx->tx->vout[outpoint.n]) == ISMINE_SPENDABLE)
612612
vCoins.push_back(out);
613613
}
@@ -619,7 +619,7 @@ void WalletModel::listCoins(std::map<QString, std::vector<COutput> >& mapCoins)
619619
while (wallet->IsChange(cout.tx->tx->vout[cout.i]) && cout.tx->tx->vin.size() > 0 && wallet->IsMine(cout.tx->tx->vin[0]))
620620
{
621621
if (!wallet->mapWallet.count(cout.tx->tx->vin[0].prevout.hash)) break;
622-
cout = COutput(&wallet->mapWallet[cout.tx->tx->vin[0].prevout.hash], cout.tx->tx->vin[0].prevout.n, 0, true, true);
622+
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 */);
623623
}
624624

625625
CTxDestination address;

src/wallet/rpcwallet.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2506,9 +2506,7 @@ UniValue listunspent(const JSONRPCRequest& request)
25062506
" ,...\n"
25072507
" ]\n"
25082508
"4. include_unsafe (bool, optional, default=true) Include outputs that are not safe to spend\n"
2509-
" because they come from unconfirmed untrusted transactions or unconfirmed\n"
2510-
" replacement transactions (cases where we are less sure that a conflicting\n"
2511-
" transaction won't be mined).\n"
2509+
" See description of \"safe\" attribute below.\n"
25122510
"\nResult\n"
25132511
"[ (array of json object)\n"
25142512
" {\n"
@@ -2521,7 +2519,10 @@ UniValue listunspent(const JSONRPCRequest& request)
25212519
" \"confirmations\" : n, (numeric) The number of confirmations\n"
25222520
" \"redeemScript\" : n (string) The redeemScript if scriptPubKey is P2SH\n"
25232521
" \"spendable\" : xxx, (bool) Whether we have the private keys to spend this output\n"
2524-
" \"solvable\" : xxx (bool) Whether we know how to spend this output, ignoring the lack of keys\n"
2522+
" \"solvable\" : xxx, (bool) Whether we know how to spend this output, ignoring the lack of keys\n"
2523+
" \"safe\" : xxx (bool) Whether this output is considered safe to spend. Unconfirmed transactions\n"
2524+
" from outside keys and unconfirmed replacement transactions are considered unsafe\n"
2525+
" and are not eligible for spending by fundrawtransaction and sendtoaddress.\n"
25252526
" }\n"
25262527
" ,...\n"
25272528
"]\n"
@@ -2606,6 +2607,7 @@ UniValue listunspent(const JSONRPCRequest& request)
26062607
entry.push_back(Pair("confirmations", out.nDepth));
26072608
entry.push_back(Pair("spendable", out.fSpendable));
26082609
entry.push_back(Pair("solvable", out.fSolvable));
2610+
entry.push_back(Pair("safe", out.fSafe));
26092611
results.push_back(entry);
26102612
}
26112613

src/wallet/test/wallet_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = fa
5454
wtx->fDebitCached = true;
5555
wtx->nDebitCached = 1;
5656
}
57-
COutput output(wtx.get(), nInput, nAge, true, true);
57+
COutput output(wtx.get(), nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */);
5858
vCoins.push_back(output);
5959
wtxn.emplace_back(std::move(wtx));
6060
}

src/wallet/wallet.cpp

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1946,7 +1946,7 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const
19461946
return nTotal;
19471947
}
19481948

1949-
void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl, bool fIncludeZeroValue) const
1949+
void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe, const CCoinControl *coinControl, bool fIncludeZeroValue) const
19501950
{
19511951
vCoins.clear();
19521952

@@ -1960,9 +1960,6 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed,
19601960
if (!CheckFinalTx(*pcoin))
19611961
continue;
19621962

1963-
if (fOnlyConfirmed && !pcoin->IsTrusted())
1964-
continue;
1965-
19661963
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
19671964
continue;
19681965

@@ -1975,6 +1972,8 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed,
19751972
if (nDepth == 0 && !pcoin->InMempool())
19761973
continue;
19771974

1975+
bool safeTx = pcoin->IsTrusted();
1976+
19781977
// We should not consider coins from transactions that are replacing
19791978
// other transactions.
19801979
//
@@ -1990,8 +1989,8 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed,
19901989
// be a 1-block reorg away from the chain where transactions A and C
19911990
// were accepted to another chain where B, B', and C were all
19921991
// accepted.
1993-
if (nDepth == 0 && fOnlyConfirmed && pcoin->mapValue.count("replaces_txid")) {
1994-
continue;
1992+
if (nDepth == 0 && pcoin->mapValue.count("replaces_txid")) {
1993+
safeTx = false;
19951994
}
19961995

19971996
// Similarly, we should not consider coins from transactions that
@@ -2002,7 +2001,11 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed,
20022001
// intending to replace A', but potentially resulting in a scenario
20032002
// where A, A', and D could all be accepted (instead of just B and
20042003
// D, or just A and A' like the user would want).
2005-
if (nDepth == 0 && fOnlyConfirmed && pcoin->mapValue.count("replaced_by_txid")) {
2004+
if (nDepth == 0 && pcoin->mapValue.count("replaced_by_txid")) {
2005+
safeTx = false;
2006+
}
2007+
2008+
if (fOnlySafe && !safeTx) {
20062009
continue;
20072010
}
20082011

@@ -2014,7 +2017,7 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed,
20142017
vCoins.push_back(COutput(pcoin, i, nDepth,
20152018
((mine & ISMINE_SPENDABLE) != ISMINE_NO) ||
20162019
(coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO),
2017-
(mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO));
2020+
(mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO, safeTx));
20182021
}
20192022
}
20202023
}

src/wallet/wallet.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -466,12 +466,23 @@ class COutput
466466
const CWalletTx *tx;
467467
int i;
468468
int nDepth;
469+
470+
/** Whether we have the private keys to spend this output */
469471
bool fSpendable;
472+
473+
/** Whether we know how to spend this output, ignoring the lack of keys */
470474
bool fSolvable;
471475

472-
COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn)
476+
/**
477+
* Whether this output is considered safe to spend. Unconfirmed transactions
478+
* from outside keys and unconfirmed replacement transactions are considered
479+
* unsafe and will not be used to fund new spending transactions.
480+
*/
481+
bool fSafe;
482+
483+
COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn)
473484
{
474-
tx = txIn; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn;
485+
tx = txIn; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn;
475486
}
476487

477488
std::string ToString() const;
@@ -740,7 +751,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
740751
/**
741752
* populate vCoins with vector of available COutputs.
742753
*/
743-
void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed=true, const CCoinControl *coinControl = NULL, bool fIncludeZeroValue=false) const;
754+
void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = NULL, bool fIncludeZeroValue=false) const;
744755

745756
/**
746757
* Shuffle and select coins until nTargetValue is reached while avoiding

0 commit comments

Comments
 (0)