Skip to content

Commit d405bee

Browse files
committed
Merge #12333: Make CWallet::ListCoins atomic
2f960b5 [wallet] Indent only change of CWallet::AvailableCoins (João Barbosa) 1beea7a [wallet] Make CWallet::ListCoins atomic (João Barbosa) Pull request description: Fix a potencial race in `CWallet::ListCoins`. Replaces `cs_main` and `cs_wallet` locks by assertions in `CWallet::AvailableCoins`. Tree-SHA512: 09109f44a08b4b53f7605d950ab506d3f748490ab9aed474aa200e93f7b0b9f96f9bf60abe1c5f658240fd13d9e3267c0dd43fd3c1695d82384198ce1da8109f
2 parents 663911e + 2f960b5 commit d405bee

File tree

2 files changed

+91
-87
lines changed

2 files changed

+91
-87
lines changed

src/wallet/test/wallet_tests.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -676,18 +676,24 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
676676
BOOST_CHECK_EQUAL(list.begin()->second.size(), 2);
677677

678678
// Lock both coins. Confirm number of available coins drops to 0.
679-
std::vector<COutput> available;
680-
wallet->AvailableCoins(available);
681-
BOOST_CHECK_EQUAL(available.size(), 2);
679+
{
680+
LOCK2(cs_main, wallet->cs_wallet);
681+
std::vector<COutput> available;
682+
wallet->AvailableCoins(available);
683+
BOOST_CHECK_EQUAL(available.size(), 2);
684+
}
682685
for (const auto& group : list) {
683686
for (const auto& coin : group.second) {
684687
LOCK(wallet->cs_wallet);
685688
wallet->LockCoin(COutPoint(coin.tx->GetHash(), coin.i));
686689
}
687690
}
688-
wallet->AvailableCoins(available);
689-
BOOST_CHECK_EQUAL(available.size(), 0);
690-
691+
{
692+
LOCK2(cs_main, wallet->cs_wallet);
693+
std::vector<COutput> available;
694+
wallet->AvailableCoins(available);
695+
BOOST_CHECK_EQUAL(available.size(), 0);
696+
}
691697
// Confirm ListCoins still returns same result as before, despite coins
692698
// being locked.
693699
list = wallet->ListCoins();

src/wallet/wallet.cpp

Lines changed: 79 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -2198,111 +2198,109 @@ CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const
21982198

21992199
void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, const uint64_t nMaximumCount, const int nMinDepth, const int nMaxDepth) const
22002200
{
2201+
AssertLockHeld(cs_main);
2202+
AssertLockHeld(cs_wallet);
2203+
22012204
vCoins.clear();
2205+
CAmount nTotal = 0;
22022206

2207+
for (const auto& entry : mapWallet)
22032208
{
2204-
LOCK2(cs_main, cs_wallet);
2209+
const uint256& wtxid = entry.first;
2210+
const CWalletTx* pcoin = &entry.second;
22052211

2206-
CAmount nTotal = 0;
2212+
if (!CheckFinalTx(*pcoin->tx))
2213+
continue;
22072214

2208-
for (const auto& entry : mapWallet)
2209-
{
2210-
const uint256& wtxid = entry.first;
2211-
const CWalletTx* pcoin = &entry.second;
2215+
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
2216+
continue;
22122217

2213-
if (!CheckFinalTx(*pcoin->tx))
2214-
continue;
2218+
int nDepth = pcoin->GetDepthInMainChain();
2219+
if (nDepth < 0)
2220+
continue;
22152221

2216-
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
2217-
continue;
2222+
// We should not consider coins which aren't at least in our mempool
2223+
// It's possible for these to be conflicted via ancestors which we may never be able to detect
2224+
if (nDepth == 0 && !pcoin->InMempool())
2225+
continue;
22182226

2219-
int nDepth = pcoin->GetDepthInMainChain();
2220-
if (nDepth < 0)
2221-
continue;
2227+
bool safeTx = pcoin->IsTrusted();
2228+
2229+
// We should not consider coins from transactions that are replacing
2230+
// other transactions.
2231+
//
2232+
// Example: There is a transaction A which is replaced by bumpfee
2233+
// transaction B. In this case, we want to prevent creation of
2234+
// a transaction B' which spends an output of B.
2235+
//
2236+
// Reason: If transaction A were initially confirmed, transactions B
2237+
// and B' would no longer be valid, so the user would have to create
2238+
// a new transaction C to replace B'. However, in the case of a
2239+
// one-block reorg, transactions B' and C might BOTH be accepted,
2240+
// when the user only wanted one of them. Specifically, there could
2241+
// be a 1-block reorg away from the chain where transactions A and C
2242+
// were accepted to another chain where B, B', and C were all
2243+
// accepted.
2244+
if (nDepth == 0 && pcoin->mapValue.count("replaces_txid")) {
2245+
safeTx = false;
2246+
}
22222247

2223-
// We should not consider coins which aren't at least in our mempool
2224-
// It's possible for these to be conflicted via ancestors which we may never be able to detect
2225-
if (nDepth == 0 && !pcoin->InMempool())
2226-
continue;
2248+
// Similarly, we should not consider coins from transactions that
2249+
// have been replaced. In the example above, we would want to prevent
2250+
// creation of a transaction A' spending an output of A, because if
2251+
// transaction B were initially confirmed, conflicting with A and
2252+
// A', we wouldn't want to the user to create a transaction D
2253+
// intending to replace A', but potentially resulting in a scenario
2254+
// where A, A', and D could all be accepted (instead of just B and
2255+
// D, or just A and A' like the user would want).
2256+
if (nDepth == 0 && pcoin->mapValue.count("replaced_by_txid")) {
2257+
safeTx = false;
2258+
}
22272259

2228-
bool safeTx = pcoin->IsTrusted();
2229-
2230-
// We should not consider coins from transactions that are replacing
2231-
// other transactions.
2232-
//
2233-
// Example: There is a transaction A which is replaced by bumpfee
2234-
// transaction B. In this case, we want to prevent creation of
2235-
// a transaction B' which spends an output of B.
2236-
//
2237-
// Reason: If transaction A were initially confirmed, transactions B
2238-
// and B' would no longer be valid, so the user would have to create
2239-
// a new transaction C to replace B'. However, in the case of a
2240-
// one-block reorg, transactions B' and C might BOTH be accepted,
2241-
// when the user only wanted one of them. Specifically, there could
2242-
// be a 1-block reorg away from the chain where transactions A and C
2243-
// were accepted to another chain where B, B', and C were all
2244-
// accepted.
2245-
if (nDepth == 0 && pcoin->mapValue.count("replaces_txid")) {
2246-
safeTx = false;
2247-
}
2260+
if (fOnlySafe && !safeTx) {
2261+
continue;
2262+
}
22482263

2249-
// Similarly, we should not consider coins from transactions that
2250-
// have been replaced. In the example above, we would want to prevent
2251-
// creation of a transaction A' spending an output of A, because if
2252-
// transaction B were initially confirmed, conflicting with A and
2253-
// A', we wouldn't want to the user to create a transaction D
2254-
// intending to replace A', but potentially resulting in a scenario
2255-
// where A, A', and D could all be accepted (instead of just B and
2256-
// D, or just A and A' like the user would want).
2257-
if (nDepth == 0 && pcoin->mapValue.count("replaced_by_txid")) {
2258-
safeTx = false;
2259-
}
2264+
if (nDepth < nMinDepth || nDepth > nMaxDepth)
2265+
continue;
22602266

2261-
if (fOnlySafe && !safeTx) {
2267+
for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) {
2268+
if (pcoin->tx->vout[i].nValue < nMinimumAmount || pcoin->tx->vout[i].nValue > nMaximumAmount)
22622269
continue;
2263-
}
22642270

2265-
if (nDepth < nMinDepth || nDepth > nMaxDepth)
2271+
if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i)))
22662272
continue;
22672273

2268-
for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) {
2269-
if (pcoin->tx->vout[i].nValue < nMinimumAmount || pcoin->tx->vout[i].nValue > nMaximumAmount)
2270-
continue;
2271-
2272-
if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i)))
2273-
continue;
2274-
2275-
if (IsLockedCoin(entry.first, i))
2276-
continue;
2277-
2278-
if (IsSpent(wtxid, i))
2279-
continue;
2274+
if (IsLockedCoin(entry.first, i))
2275+
continue;
22802276

2281-
isminetype mine = IsMine(pcoin->tx->vout[i]);
2277+
if (IsSpent(wtxid, i))
2278+
continue;
22822279

2283-
if (mine == ISMINE_NO) {
2284-
continue;
2285-
}
2280+
isminetype mine = IsMine(pcoin->tx->vout[i]);
22862281

2287-
bool fSpendableIn = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO);
2288-
bool fSolvableIn = (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO;
2282+
if (mine == ISMINE_NO) {
2283+
continue;
2284+
}
22892285

2290-
vCoins.push_back(COutput(pcoin, i, nDepth, fSpendableIn, fSolvableIn, safeTx));
2286+
bool fSpendableIn = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO);
2287+
bool fSolvableIn = (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO;
22912288

2292-
// Checks the sum amount of all UTXO's.
2293-
if (nMinimumSumAmount != MAX_MONEY) {
2294-
nTotal += pcoin->tx->vout[i].nValue;
2289+
vCoins.push_back(COutput(pcoin, i, nDepth, fSpendableIn, fSolvableIn, safeTx));
22952290

2296-
if (nTotal >= nMinimumSumAmount) {
2297-
return;
2298-
}
2299-
}
2291+
// Checks the sum amount of all UTXO's.
2292+
if (nMinimumSumAmount != MAX_MONEY) {
2293+
nTotal += pcoin->tx->vout[i].nValue;
23002294

2301-
// Checks the maximum number of UTXO's.
2302-
if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) {
2295+
if (nTotal >= nMinimumSumAmount) {
23032296
return;
23042297
}
23052298
}
2299+
2300+
// Checks the maximum number of UTXO's.
2301+
if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) {
2302+
return;
2303+
}
23062304
}
23072305
}
23082306
}
@@ -2320,11 +2318,11 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const
23202318
// avoid adding some extra complexity to the Qt code.
23212319

23222320
std::map<CTxDestination, std::vector<COutput>> result;
2323-
23242321
std::vector<COutput> availableCoins;
2325-
AvailableCoins(availableCoins);
23262322

23272323
LOCK2(cs_main, cs_wallet);
2324+
AvailableCoins(availableCoins);
2325+
23282326
for (auto& coin : availableCoins) {
23292327
CTxDestination address;
23302328
if (coin.fSpendable &&

0 commit comments

Comments
 (0)