aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bench/coin_selection.cpp2
-rw-r--r--src/wallet/spend.cpp46
-rw-r--r--src/wallet/spend.h25
3 files changed, 50 insertions, 23 deletions
diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp
index bb3ced8406..a80ec3703c 100644
--- a/src/bench/coin_selection.cpp
+++ b/src/bench/coin_selection.cpp
@@ -76,7 +76,7 @@ static void CoinSelection(benchmark::Bench& bench)
/*avoid_partial=*/ false,
};
bench.run([&] {
- auto result = AttemptSelection(wallet, 1003 * COIN, filter_standard, available_coins, coin_selection_params);
+ auto result = AttemptSelection(wallet, 1003 * COIN, filter_standard, available_coins, coin_selection_params, /*allow_mixed_output_types=*/true);
assert(result);
assert(result->GetSelectedValue() == 1003 * COIN);
assert(result->GetInputSet().size() == 2);
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 9e7085f7d3..c00a2cd023 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -451,9 +451,34 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
}
std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const CoinsResult& available_coins,
- const CoinSelectionParams& coin_selection_params)
+ const CoinSelectionParams& coin_selection_params, bool allow_mixed_output_types)
{
- std::optional<SelectionResult> result = ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.all(), coin_selection_params);
+ // Run coin selection on each OutputType and compute the Waste Metric
+ std::vector<SelectionResult> results;
+ if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.legacy, coin_selection_params)}) {
+ results.push_back(*result);
+ }
+ if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.P2SH_segwit, coin_selection_params)}) {
+ results.push_back(*result);
+ }
+ if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.bech32, coin_selection_params)}) {
+ results.push_back(*result);
+ }
+ if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.bech32m, coin_selection_params)}) {
+ results.push_back(*result);
+ }
+
+ // If we can't fund the transaction from any individual OutputType, run coin selection
+ // over all available coins, else pick the best solution from the results
+ if (results.size() == 0) {
+ if (allow_mixed_output_types) {
+ if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.all(), coin_selection_params)}) {
+ return result;
+ }
+ }
+ return std::optional<SelectionResult>();
+ };
+ std::optional<SelectionResult> result{*std::min_element(results.begin(), results.end())};
return result;
};
@@ -601,26 +626,27 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
// If possible, fund the transaction with confirmed UTXOs only. Prefer at least six
// confirmations on outputs received from other wallets and only spend confirmed change.
- if (auto r1{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 6, 0), available_coins, coin_selection_params)}) return r1;
- if (auto r2{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 1, 0), available_coins, coin_selection_params)}) return r2;
+ if (auto r1{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 6, 0), available_coins, coin_selection_params, /*allow_mixed_output_types=*/false)}) return r1;
+ // Allow mixing only if no solution from any single output type can be found
+ if (auto r2{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 1, 0), available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) return r2;
// Fall back to using zero confirmation change (but with as few ancestors in the mempool as
// possible) if we cannot fund the transaction otherwise.
if (wallet.m_spend_zero_conf_change) {
- if (auto r3{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, 2), available_coins, coin_selection_params)}) return r3;
+ if (auto r3{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, 2), available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) return r3;
if (auto r4{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)),
- available_coins, coin_selection_params)}) {
+ available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) {
return r4;
}
if (auto r5{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2),
- available_coins, coin_selection_params)}) {
+ available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) {
return r5;
}
// If partial groups are allowed, relax the requirement of spending OutputGroups (groups
// of UTXOs sent to the same address, which are obviously controlled by a single wallet)
// in their entirety.
if (auto r6{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, true /* include_partial_groups */),
- available_coins, coin_selection_params)}) {
+ available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) {
return r6;
}
// Try with unsafe inputs if they are allowed. This may spend unconfirmed outputs
@@ -628,7 +654,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
if (coin_control.m_include_unsafe_inputs) {
if (auto r7{AttemptSelection(wallet, value_to_select,
CoinEligibilityFilter(0 /* conf_mine */, 0 /* conf_theirs */, max_ancestors-1, max_descendants-1, true /* include_partial_groups */),
- available_coins, coin_selection_params)}) {
+ available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) {
return r7;
}
}
@@ -638,7 +664,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
if (!fRejectLongChains) {
if (auto r8{AttemptSelection(wallet, value_to_select,
CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max(), true /* include_partial_groups */),
- available_coins, coin_selection_params)}) {
+ available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) {
return r8;
}
}
diff --git a/src/wallet/spend.h b/src/wallet/spend.h
index a9216e22ee..96ecd690be 100644
--- a/src/wallet/spend.h
+++ b/src/wallet/spend.h
@@ -91,22 +91,23 @@ const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const COutPoint&
std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<COutput>& outputs, const CoinSelectionParams& coin_sel_params, const CoinEligibilityFilter& filter, bool positive_only);
-
/**
- * Attempt to find a valid input set that meets the provided eligibility filter and target.
- * Multiple coin selection algorithms will be run and the input set that produces the least waste
- * (according to the waste metric) will be chosen.
+ * Attempt to find a valid input set that preserves privacy by not mixing OutputTypes.
+ * `ChooseSelectionResult()` will be called on each OutputType individually and the best
+ * the solution (according to the waste metric) will be chosen. If a valid input cannot be found from any
+ * single OutputType, fallback to running `ChooseSelectionResult()` over all available coins.
*
- * param@[in] wallet The wallet which provides solving data for the coins
- * param@[in] nTargetValue The target value
- * param@[in] eligilibity_filter A filter containing rules for which coins are allowed to be included in this selection
- * param@[in] available_coins The struct of coins, organized by OutputType, available for selection prior to filtering
- * param@[in] coin_selection_params Parameters for the coin selection
- * returns If successful, a SelectionResult containing the input set
- * If failed, a nullopt
+ * param@[in] wallet The wallet which provides solving data for the coins
+ * param@[in] nTargetValue The target value
+ * param@[in] eligilibity_filter A filter containing rules for which coins are allowed to be included in this selection
+ * param@[in] available_coins The struct of coins, organized by OutputType, available for selection prior to filtering
+ * param@[in] coin_selection_params Parameters for the coin selection
+ * param@[in] allow_mixed_output_types Relax restriction that SelectionResults must be of the same OutputType
+ * returns If successful, a SelectionResult containing the input set
+ * If failed, a nullopt
*/
std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const CoinsResult& available_coins,
- const CoinSelectionParams& coin_selection_params);
+ const CoinSelectionParams& coin_selection_params, bool allow_mixed_output_types);
/**
* Attempt to find a valid input set that meets the provided eligibility filter and target.