diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp index 3fd0280b8b5..5ea9b7fad39 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -63,7 +63,7 @@ struct { static const size_t TOTAL_TRIES = 100000; -std::optional SelectCoinsBnB(std::vector& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change) +util::Result SelectCoinsBnB(std::vector& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change) { SelectionResult result(selection_target, SelectionAlgorithm::BNB); CAmount curr_value = 0; @@ -78,7 +78,7 @@ std::optional SelectCoinsBnB(std::vector& utxo_poo curr_available_value += utxo.GetSelectionAmount(); } if (curr_available_value < selection_target) { - return std::nullopt; + return util::Error(); } // Sort the utxo_pool @@ -152,7 +152,7 @@ std::optional SelectCoinsBnB(std::vector& utxo_poo // Check for solution if (best_selection.empty()) { - return std::nullopt; + return util::Error(); } // Set output set @@ -165,7 +165,7 @@ std::optional SelectCoinsBnB(std::vector& utxo_poo return result; } -std::optional SelectCoinsSRD(const std::vector& utxo_pool, CAmount target_value, FastRandomContext& rng) +util::Result SelectCoinsSRD(const std::vector& utxo_pool, CAmount target_value, FastRandomContext& rng) { SelectionResult result(target_value, SelectionAlgorithm::SRD); @@ -190,7 +190,7 @@ std::optional SelectCoinsSRD(const std::vector& ut return result; } } - return std::nullopt; + return util::Error(); } /** Find a subset of the OutputGroups that is at least as large as, but as close as possible to, the @@ -252,7 +252,7 @@ static void ApproximateBestSubset(FastRandomContext& insecure_rand, const std::v } } -std::optional KnapsackSolver(std::vector& groups, const CAmount& nTargetValue, +util::Result KnapsackSolver(std::vector& groups, const CAmount& nTargetValue, CAmount change_target, FastRandomContext& rng) { SelectionResult result(nTargetValue, SelectionAlgorithm::KNAPSACK); @@ -286,7 +286,7 @@ std::optional KnapsackSolver(std::vector& groups, } if (nTotalLower < nTargetValue) { - if (!lowest_larger) return std::nullopt; + if (!lowest_larger) return util::Error(); result.AddInput(*lowest_larger); return result; } diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h index 3abd22c2071..95a3bb2bc82 100644 --- a/src/wallet/coinselection.h +++ b/src/wallet/coinselection.h @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -408,7 +409,7 @@ public: int GetWeight() const { return m_weight; } }; -std::optional SelectCoinsBnB(std::vector& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change); +util::Result SelectCoinsBnB(std::vector& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change); /** Select coins by Single Random Draw. OutputGroups are selected randomly from the eligible * outputs until the target is satisfied @@ -417,10 +418,10 @@ std::optional SelectCoinsBnB(std::vector& utxo_poo * @param[in] target_value The target value to select for * @returns If successful, a SelectionResult, otherwise, std::nullopt */ -std::optional SelectCoinsSRD(const std::vector& utxo_pool, CAmount target_value, FastRandomContext& rng); +util::Result SelectCoinsSRD(const std::vector& utxo_pool, CAmount target_value, FastRandomContext& rng); // Original coin selection algorithm as a fallback -std::optional KnapsackSolver(std::vector& groups, const CAmount& nTargetValue, +util::Result KnapsackSolver(std::vector& groups, const CAmount& nTargetValue, CAmount change_target, FastRandomContext& rng); } // namespace wallet diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index a8ecce119af..a55e4c45a8f 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -554,25 +554,34 @@ util::Result ChooseSelectionResult(const CAmount& nTargetValue, { // Vector of results. We will choose the best one based on waste. std::vector results; + std::vector> errors; + auto append_error = [&] (const util::Result& result) { + // If any specific error message appears here, then something different from a simple "no selection found" happened. + // Let's save it, so it can be retrieved to the user if no other selection algorithm succeeded. + if (HasErrorMsg(result)) { + errors.emplace_back(result); + } + }; if (auto bnb_result{SelectCoinsBnB(groups.positive_group, nTargetValue, coin_selection_params.m_cost_of_change)}) { results.push_back(*bnb_result); - } + } else append_error(bnb_result); // The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here. if (auto knapsack_result{KnapsackSolver(groups.mixed_group, nTargetValue, coin_selection_params.m_min_change_target, coin_selection_params.rng_fast)}) { knapsack_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee); results.push_back(*knapsack_result); - } + } else append_error(knapsack_result); if (auto srd_result{SelectCoinsSRD(groups.positive_group, nTargetValue, coin_selection_params.rng_fast)}) { srd_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee); results.push_back(*srd_result); - } + } else append_error(srd_result); if (results.empty()) { - // No solution found - return util::Error(); + // No solution found, retrieve the first explicit error (if any). + // future: add 'severity level' to errors so the worst one can be retrieved instead of the first one. + return errors.empty() ? util::Error() : errors.front(); } std::vector eligible_results;