Skip to content

Commit c1801b7

Browse files
committed
wallet: Use wallet's TXO set in AvailableCoins
Instead of iterating every transaction and every output stored in wallet when trying to figure out what outputs can be spent, iterate the TXO set which should be a lot smaller.
1 parent dde7cbe commit c1801b7

File tree

1 file changed

+113
-104
lines changed

1 file changed

+113
-104
lines changed

src/wallet/spend.cpp

Lines changed: 113 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -330,137 +330,146 @@ CoinsResult AvailableCoins(const CWallet& wallet,
330330
std::vector<COutPoint> outpoints;
331331

332332
std::set<Txid> trusted_parents;
333-
for (const auto& entry : wallet.mapWallet)
334-
{
335-
const Txid& txid = entry.first;
336-
const CWalletTx& wtx = entry.second;
337-
338-
if (wallet.IsTxImmatureCoinBase(wtx) && !params.include_immature_coinbase)
339-
continue;
340-
341-
int nDepth = wallet.GetTxDepthInMainChain(wtx);
342-
if (nDepth < 0)
343-
continue;
344-
345-
// We should not consider coins which aren't at least in our mempool
346-
// It's possible for these to be conflicted via ancestors which we may never be able to detect
347-
if (nDepth == 0 && !wtx.InMempool())
348-
continue;
349-
350-
bool safeTx = CachedTxIsTrusted(wallet, wtx, trusted_parents);
351-
352-
// We should not consider coins from transactions that are replacing
353-
// other transactions.
354-
//
355-
// Example: There is a transaction A which is replaced by bumpfee
356-
// transaction B. In this case, we want to prevent creation of
357-
// a transaction B' which spends an output of B.
358-
//
359-
// Reason: If transaction A were initially confirmed, transactions B
360-
// and B' would no longer be valid, so the user would have to create
361-
// a new transaction C to replace B'. However, in the case of a
362-
// one-block reorg, transactions B' and C might BOTH be accepted,
363-
// when the user only wanted one of them. Specifically, there could
364-
// be a 1-block reorg away from the chain where transactions A and C
365-
// were accepted to another chain where B, B', and C were all
366-
// accepted.
367-
if (nDepth == 0 && wtx.mapValue.count("replaces_txid")) {
368-
safeTx = false;
369-
}
370-
371-
// Similarly, we should not consider coins from transactions that
372-
// have been replaced. In the example above, we would want to prevent
373-
// creation of a transaction A' spending an output of A, because if
374-
// transaction B were initially confirmed, conflicting with A and
375-
// A', we wouldn't want to the user to create a transaction D
376-
// intending to replace A', but potentially resulting in a scenario
377-
// where A, A', and D could all be accepted (instead of just B and
378-
// D, or just A and A' like the user would want).
379-
if (nDepth == 0 && wtx.mapValue.count("replaced_by_txid")) {
380-
safeTx = false;
381-
}
382-
383-
if (only_safe && !safeTx) {
384-
continue;
385-
}
333+
// Cache for whether each tx passes the tx level checks (first bool), and whether the transaction is "safe" (second bool)
334+
std::unordered_map<uint256, std::pair<bool, bool>, SaltedTxidHasher> tx_safe_cache;
335+
for (const auto& [outpoint, txo] : wallet.GetTXOs()) {
336+
const CWalletTx& wtx = txo.GetWalletTx();
337+
const CTxOut& output = txo.GetTxOut();
386338

387-
if (nDepth < min_depth || nDepth > max_depth) {
339+
if (tx_safe_cache.contains(outpoint.hash) && !tx_safe_cache.at(outpoint.hash).first) {
388340
continue;
389341
}
390342

391-
bool tx_from_me = CachedTxIsFromMe(wallet, wtx, ISMINE_ALL);
343+
int nDepth = wallet.GetTxDepthInMainChain(wtx);
392344

393-
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
394-
const CTxOut& output = wtx.tx->vout[i];
395-
const COutPoint outpoint(txid, i);
345+
// Perform tx level checks if we haven't already come across outputs from this tx before.
346+
if (!tx_safe_cache.contains(outpoint.hash)) {
347+
tx_safe_cache[outpoint.hash] = {false, false};
396348

397-
if (output.nValue < params.min_amount || output.nValue > params.max_amount)
349+
if (wallet.IsTxImmatureCoinBase(wtx) && !params.include_immature_coinbase)
398350
continue;
399351

400-
// Skip manually selected coins (the caller can fetch them directly)
401-
if (coinControl && coinControl->HasSelected() && coinControl->IsSelected(outpoint))
352+
if (nDepth < 0)
402353
continue;
403354

404-
if (wallet.IsLockedCoin(outpoint) && params.skip_locked)
355+
// We should not consider coins which aren't at least in our mempool
356+
// It's possible for these to be conflicted via ancestors which we may never be able to detect
357+
if (nDepth == 0 && !wtx.InMempool())
405358
continue;
406359

407-
if (wallet.IsSpent(outpoint))
408-
continue;
360+
bool safeTx = CachedTxIsTrusted(wallet, wtx, trusted_parents);
361+
362+
// We should not consider coins from transactions that are replacing
363+
// other transactions.
364+
//
365+
// Example: There is a transaction A which is replaced by bumpfee
366+
// transaction B. In this case, we want to prevent creation of
367+
// a transaction B' which spends an output of B.
368+
//
369+
// Reason: If transaction A were initially confirmed, transactions B
370+
// and B' would no longer be valid, so the user would have to create
371+
// a new transaction C to replace B'. However, in the case of a
372+
// one-block reorg, transactions B' and C might BOTH be accepted,
373+
// when the user only wanted one of them. Specifically, there could
374+
// be a 1-block reorg away from the chain where transactions A and C
375+
// were accepted to another chain where B, B', and C were all
376+
// accepted.
377+
if (nDepth == 0 && wtx.mapValue.count("replaces_txid")) {
378+
safeTx = false;
379+
}
409380

410-
isminetype mine = wallet.IsMine(output);
381+
// Similarly, we should not consider coins from transactions that
382+
// have been replaced. In the example above, we would want to prevent
383+
// creation of a transaction A' spending an output of A, because if
384+
// transaction B were initially confirmed, conflicting with A and
385+
// A', we wouldn't want to the user to create a transaction D
386+
// intending to replace A', but potentially resulting in a scenario
387+
// where A, A', and D could all be accepted (instead of just B and
388+
// D, or just A and A' like the user would want).
389+
if (nDepth == 0 && wtx.mapValue.count("replaced_by_txid")) {
390+
safeTx = false;
391+
}
411392

412-
if (mine == ISMINE_NO) {
393+
if (only_safe && !safeTx) {
413394
continue;
414395
}
415396

416-
if (!allow_used_addresses && wallet.IsSpentKey(output.scriptPubKey)) {
397+
if (nDepth < min_depth || nDepth > max_depth) {
417398
continue;
418399
}
419400

420-
std::unique_ptr<SigningProvider> provider = wallet.GetSolvingProvider(output.scriptPubKey);
421-
422-
int input_bytes = CalculateMaximumSignedInputSize(output, COutPoint(), provider.get(), can_grind_r, coinControl);
423-
// Because CalculateMaximumSignedInputSize infers a solvable descriptor to get the satisfaction size,
424-
// it is safe to assume that this input is solvable if input_bytes is greater than -1.
425-
bool solvable = input_bytes > -1;
426-
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
427-
428-
// Filter by spendable outputs only
429-
if (!spendable && params.only_spendable) continue;
430-
431-
// Obtain script type
432-
std::vector<std::vector<uint8_t>> script_solutions;
433-
TxoutType type = Solver(output.scriptPubKey, script_solutions);
434-
435-
// If the output is P2SH and solvable, we want to know if it is
436-
// a P2SH (legacy) or one of P2SH-P2WPKH, P2SH-P2WSH (P2SH-Segwit). We can determine
437-
// this from the redeemScript. If the output is not solvable, it will be classified
438-
// as a P2SH (legacy), since we have no way of knowing otherwise without the redeemScript
439-
bool is_from_p2sh{false};
440-
if (type == TxoutType::SCRIPTHASH && solvable) {
441-
CScript script;
442-
if (!provider->GetCScript(CScriptID(uint160(script_solutions[0])), script)) continue;
443-
type = Solver(script, script_solutions);
444-
is_from_p2sh = true;
445-
}
401+
tx_safe_cache[outpoint.hash] = {true, safeTx};
402+
}
403+
const auto& [tx_ok, tx_safe] = tx_safe_cache.at(outpoint.hash);
404+
if (!Assume(tx_ok)) {
405+
continue;
406+
}
446407

447-
result.Add(GetOutputType(type, is_from_p2sh),
448-
COutput(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate));
408+
if (output.nValue < params.min_amount || output.nValue > params.max_amount)
409+
continue;
449410

450-
outpoints.push_back(outpoint);
411+
// Skip manually selected coins (the caller can fetch them directly)
412+
if (coinControl && coinControl->HasSelected() && coinControl->IsSelected(outpoint))
413+
continue;
451414

452-
// Checks the sum amount of all UTXO's.
453-
if (params.min_sum_amount != MAX_MONEY) {
454-
if (result.GetTotalAmount() >= params.min_sum_amount) {
455-
return result;
456-
}
457-
}
415+
if (wallet.IsLockedCoin(outpoint) && params.skip_locked)
416+
continue;
417+
418+
if (wallet.IsSpent(outpoint))
419+
continue;
458420

459-
// Checks the maximum number of UTXO's.
460-
if (params.max_count > 0 && result.Size() >= params.max_count) {
421+
isminetype mine = wallet.IsMine(output);
422+
assert(mine != ISMINE_NO);
423+
424+
if (!allow_used_addresses && wallet.IsSpentKey(output.scriptPubKey)) {
425+
continue;
426+
}
427+
428+
bool tx_from_me = CachedTxIsFromMe(wallet, wtx, ISMINE_ALL);
429+
430+
std::unique_ptr<SigningProvider> provider = wallet.GetSolvingProvider(output.scriptPubKey);
431+
432+
int input_bytes = CalculateMaximumSignedInputSize(output, COutPoint(), provider.get(), can_grind_r, coinControl);
433+
// Because CalculateMaximumSignedInputSize infers a solvable descriptor to get the satisfaction size,
434+
// it is safe to assume that this input is solvable if input_bytes is greater than -1.
435+
bool solvable = input_bytes > -1;
436+
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
437+
438+
// Filter by spendable outputs only
439+
if (!spendable && params.only_spendable) continue;
440+
441+
// Obtain script type
442+
std::vector<std::vector<uint8_t>> script_solutions;
443+
TxoutType type = Solver(output.scriptPubKey, script_solutions);
444+
445+
// If the output is P2SH and solvable, we want to know if it is
446+
// a P2SH (legacy) or one of P2SH-P2WPKH, P2SH-P2WSH (P2SH-Segwit). We can determine
447+
// this from the redeemScript. If the output is not solvable, it will be classified
448+
// as a P2SH (legacy), since we have no way of knowing otherwise without the redeemScript
449+
bool is_from_p2sh{false};
450+
if (type == TxoutType::SCRIPTHASH && solvable) {
451+
CScript script;
452+
if (!provider->GetCScript(CScriptID(uint160(script_solutions[0])), script)) continue;
453+
type = Solver(script, script_solutions);
454+
is_from_p2sh = true;
455+
}
456+
457+
result.Add(GetOutputType(type, is_from_p2sh),
458+
COutput(outpoint, output, nDepth, input_bytes, spendable, solvable, tx_safe, wtx.GetTxTime(), tx_from_me, feerate));
459+
460+
outpoints.push_back(outpoint);
461+
462+
// Checks the sum amount of all UTXO's.
463+
if (params.min_sum_amount != MAX_MONEY) {
464+
if (result.GetTotalAmount() >= params.min_sum_amount) {
461465
return result;
462466
}
463467
}
468+
469+
// Checks the maximum number of UTXO's.
470+
if (params.max_count > 0 && result.Size() >= params.max_count) {
471+
return result;
472+
}
464473
}
465474

466475
if (feerate.has_value()) {

0 commit comments

Comments
 (0)