diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp index bf822196cdc6d..5b074104ae4f1 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -542,6 +542,34 @@ class MinOutputGroupComparator } }; +util::Result LargestFirst(std::vector& utxo_pool, const CAmount& selection_target, CAmount change_target, int max_weight) +{ + SelectionResult result(selection_target, SelectionAlgorithm::LF); + + // Include a minimum change budget for Largest First as we want to avoid + // making really small change if the selection just barely meets the target. + const CAmount total_target = selection_target + change_target; + + std::sort(utxo_pool.begin(), utxo_pool.end(), descending); + CAmount selected_amount = 0; + int weight = 0; + + for (const OutputGroup& group : utxo_pool) { + weight += group.m_weight; + selected_amount += group.GetSelectionAmount(); + result.AddInput(group); + if (weight > max_weight) { + return ErrorMaxWeightExceeded(); + } + if (selected_amount >= total_target) { + return result; + } + } + + // Didn’t find a solution + return util::Error(); +} + util::Result SandCompactor(std::vector& utxo_pool, const CAmount& selection_target, CAmount change_target, int max_weight) { SelectionResult result(selection_target, SelectionAlgorithm::SC); diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h index e823fd1bcf9a1..238d8ad13d2fb 100644 --- a/src/wallet/coinselection.h +++ b/src/wallet/coinselection.h @@ -313,7 +313,8 @@ enum class SelectionAlgorithm : uint8_t SRD = 2, CG = 3, SC = 4, - MANUAL = 5, + LF = 5, + MANUAL = 6, }; std::string GetAlgorithmName(const SelectionAlgorithm algo); @@ -450,6 +451,17 @@ util::Result SelectCoinsBnB(std::vector& utxo_pool util::Result CoinGrinder(std::vector& utxo_pool, const CAmount& selection_target, CAmount change_target, int max_weight); +/** + * Largest first selector picks UTXOs from highest effective value to lowest effective value. + * + * @param[in] utxo_pool The OutputGroups eligible for selection + * @param[in] selection_target The target value to select for + * @param[in] change_target The minimum budget necessary to create a change output + * @param[in] max_weight The maximum allowed weight for a selection result to be valid + * @returns If successful, a valid SelectionResult, otherwise, util::Error + */ +util::Result LargestFirst(std::vector& utxo_pool, const CAmount& selection_target, CAmount change_target, int max_weight); + /** * Sand Compactor selects OutputGroups by descending confirmation count (FIFO). If there are multiple UTXOs in an diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 5090afffdbe6e..c617fd047da54 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -718,6 +718,11 @@ util::Result ChooseSelectionResult(interfaces::Chain& chain, co } } + // Allow Largest First to spend UTXOs with negative effective value at any feerate + if (auto lf_result{LargestFirst(groups.mixed_group, nTargetValue, coin_selection_params.m_min_change_target, max_inputs_weight)}) { + results.push_back(*lf_result); + } else append_error(lf_result); + // Allow SandCompactor to spend UTXOs with negative effective value at any feerate if (auto sc_result{SandCompactor(groups.mixed_group, nTargetValue, coin_selection_params.m_min_change_target, max_inputs_weight)}) { results.push_back(*sc_result);