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
This commit is contained in:
Wladimir J. van der Laan 2017-03-13 06:59:23 +01:00
commit afcd7c0e52
No known key found for this signature in database
GPG Key ID: 74810B012346C9A6
7 changed files with 40 additions and 20 deletions

View File

@ -126,7 +126,11 @@ class ListTransactionsTest(BitcoinTestFramework):
assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_1}, {"bip125-replaceable":"no"}) 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. # 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)
utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[1], txid_1)
assert_equal(utxo_to_use["safe"], False)
# Create tx2 using createrawtransaction # Create tx2 using createrawtransaction
inputs = [{"txid":utxo_to_use["txid"], "vout":utxo_to_use["vout"]}] inputs = [{"txid":utxo_to_use["txid"], "vout":utxo_to_use["vout"]}]

View File

@ -20,7 +20,7 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<CO
CWalletTx* wtx = new CWalletTx(&wallet, MakeTransactionRef(std::move(tx))); CWalletTx* wtx = new CWalletTx(&wallet, MakeTransactionRef(std::move(tx)));
int nAge = 6 * 24; int nAge = 6 * 24;
COutput output(wtx, nInput, nAge, true, true); COutput output(wtx, nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */);
vCoins.push_back(output); vCoins.push_back(output);
} }

View File

@ -580,7 +580,7 @@ void WalletModel::getOutputs(const std::vector<COutPoint>& vOutpoints, std::vect
if (!wallet->mapWallet.count(outpoint.hash)) continue; if (!wallet->mapWallet.count(outpoint.hash)) continue;
int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain(); int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain();
if (nDepth < 0) continue; 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); vOutputs.push_back(out);
} }
} }
@ -607,7 +607,7 @@ void WalletModel::listCoins(std::map<QString, std::vector<COutput> >& mapCoins)
if (!wallet->mapWallet.count(outpoint.hash)) continue; if (!wallet->mapWallet.count(outpoint.hash)) continue;
int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain(); int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain();
if (nDepth < 0) continue; 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) if (outpoint.n < out.tx->tx->vout.size() && wallet->IsMine(out.tx->tx->vout[outpoint.n]) == ISMINE_SPENDABLE)
vCoins.push_back(out); vCoins.push_back(out);
} }
@ -619,7 +619,7 @@ void WalletModel::listCoins(std::map<QString, std::vector<COutput> >& mapCoins)
while (wallet->IsChange(cout.tx->tx->vout[cout.i]) && cout.tx->tx->vin.size() > 0 && wallet->IsMine(cout.tx->tx->vin[0])) 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; 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; CTxDestination address;

View File

@ -2506,9 +2506,7 @@ UniValue listunspent(const JSONRPCRequest& request)
" ,...\n" " ,...\n"
" ]\n" " ]\n"
"4. include_unsafe (bool, optional, default=true) Include outputs that are not safe to spend\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" " See description of \"safe\" attribute below.\n"
" replacement transactions (cases where we are less sure that a conflicting\n"
" transaction won't be mined).\n"
"\nResult\n" "\nResult\n"
"[ (array of json object)\n" "[ (array of json object)\n"
" {\n" " {\n"
@ -2521,7 +2519,10 @@ UniValue listunspent(const JSONRPCRequest& request)
" \"confirmations\" : n, (numeric) The number of confirmations\n" " \"confirmations\" : n, (numeric) The number of confirmations\n"
" \"redeemScript\" : n (string) The redeemScript if scriptPubKey is P2SH\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" " \"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" " ,...\n"
"]\n" "]\n"
@ -2606,6 +2607,7 @@ UniValue listunspent(const JSONRPCRequest& request)
entry.push_back(Pair("confirmations", out.nDepth)); entry.push_back(Pair("confirmations", out.nDepth));
entry.push_back(Pair("spendable", out.fSpendable)); entry.push_back(Pair("spendable", out.fSpendable));
entry.push_back(Pair("solvable", out.fSolvable)); entry.push_back(Pair("solvable", out.fSolvable));
entry.push_back(Pair("safe", out.fSafe));
results.push_back(entry); results.push_back(entry);
} }

View File

@ -54,7 +54,7 @@ static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = fa
wtx->fDebitCached = true; wtx->fDebitCached = true;
wtx->nDebitCached = 1; 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); vCoins.push_back(output);
wtxn.emplace_back(std::move(wtx)); wtxn.emplace_back(std::move(wtx));
} }

View File

@ -1946,7 +1946,7 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const
return nTotal; return nTotal;
} }
void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl, bool fIncludeZeroValue) const void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe, const CCoinControl *coinControl, bool fIncludeZeroValue) const
{ {
vCoins.clear(); vCoins.clear();
@ -1960,9 +1960,6 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed,
if (!CheckFinalTx(*pcoin)) if (!CheckFinalTx(*pcoin))
continue; continue;
if (fOnlyConfirmed && !pcoin->IsTrusted())
continue;
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
continue; continue;
@ -1975,6 +1972,8 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed,
if (nDepth == 0 && !pcoin->InMempool()) if (nDepth == 0 && !pcoin->InMempool())
continue; continue;
bool safeTx = pcoin->IsTrusted();
// We should not consider coins from transactions that are replacing // We should not consider coins from transactions that are replacing
// other transactions. // other transactions.
// //
@ -1990,8 +1989,8 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed,
// be a 1-block reorg away from the chain where transactions A and C // 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 // were accepted to another chain where B, B', and C were all
// accepted. // accepted.
if (nDepth == 0 && fOnlyConfirmed && pcoin->mapValue.count("replaces_txid")) { if (nDepth == 0 && pcoin->mapValue.count("replaces_txid")) {
continue; safeTx = false;
} }
// Similarly, we should not consider coins from transactions that // Similarly, we should not consider coins from transactions that
@ -2002,7 +2001,11 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed,
// intending to replace A', but potentially resulting in a scenario // intending to replace A', but potentially resulting in a scenario
// where A, A', and D could all be accepted (instead of just B and // 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). // 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; continue;
} }
@ -2014,7 +2017,7 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed,
vCoins.push_back(COutput(pcoin, i, nDepth, vCoins.push_back(COutput(pcoin, i, nDepth,
((mine & ISMINE_SPENDABLE) != ISMINE_NO) || ((mine & ISMINE_SPENDABLE) != ISMINE_NO) ||
(coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != 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));
} }
} }
} }

View File

@ -466,12 +466,23 @@ public:
const CWalletTx *tx; const CWalletTx *tx;
int i; int i;
int nDepth; int nDepth;
/** Whether we have the private keys to spend this output */
bool fSpendable; bool fSpendable;
/** Whether we know how to spend this output, ignoring the lack of keys */
bool fSolvable; 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; std::string ToString() const;
@ -740,7 +751,7 @@ public:
/** /**
* populate vCoins with vector of available COutputs. * populate vCoins with vector of available COutputs.
*/ */
void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed=true, const CCoinControl *coinControl = NULL, bool fIncludeZeroValue=false) const; void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = NULL, bool fIncludeZeroValue=false) const;
/** /**
* Shuffle and select coins until nTargetValue is reached while avoiding * Shuffle and select coins until nTargetValue is reached while avoiding