wallet: refactor coin selection algos to return util::Result

so the selection processes can retrieve different errors and not
uninformative std::nullopt
This commit is contained in:
furszy committed 2023-03-07 09:01:57 -03:00
1 parent 86bacd75e7
commit 1284223691
3 files changed
+25 -15

No files matched your search

+7 -7
View File
@@ -63,7 +63,7 @@ struct {
static const size_t TOTAL_TRIES = 100000; static const size_t TOTAL_TRIES = 100000;
std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change) util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change)
{ {
SelectionResult result(selection_target, SelectionAlgorithm::BNB); SelectionResult result(selection_target, SelectionAlgorithm::BNB);
CAmount curr_value = 0; CAmount curr_value = 0;
@@ -78,7 +78,7 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
curr_available_value += utxo.GetSelectionAmount(); curr_available_value += utxo.GetSelectionAmount();
} }
if (curr_available_value < selection_target) { if (curr_available_value < selection_target) {
return std::nullopt; return util::Error();
} }
// Sort the utxo_pool // Sort the utxo_pool
@@ -152,7 +152,7 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
// Check for solution // Check for solution
if (best_selection.empty()) { if (best_selection.empty()) {
return std::nullopt; return util::Error();
} }
// Set output set // Set output set
@@ -165,7 +165,7 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
return result; return result;
} }
std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng) util::Result<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng)
{ {
SelectionResult result(target_value, SelectionAlgorithm::SRD); SelectionResult result(target_value, SelectionAlgorithm::SRD);
@@ -190,7 +190,7 @@ std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& ut
return result; 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 /** 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<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue, util::Result<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue,
CAmount change_target, FastRandomContext& rng) CAmount change_target, FastRandomContext& rng)
{ {
SelectionResult result(nTargetValue, SelectionAlgorithm::KNAPSACK); SelectionResult result(nTargetValue, SelectionAlgorithm::KNAPSACK);
@@ -286,7 +286,7 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups,
} }
if (nTotalLower < nTargetValue) { if (nTotalLower < nTargetValue) {
if (!lowest_larger) return std::nullopt; if (!lowest_larger) return util::Error();
result.AddInput(*lowest_larger); result.AddInput(*lowest_larger);
return result; return result;
} }
+4 -3
View File
@@ -13,6 +13,7 @@
#include <random.h> #include <random.h>
#include <util/system.h> #include <util/system.h>
#include <util/check.h> #include <util/check.h>
#include <util/result.h>
#include <optional> #include <optional>
@@ -408,7 +409,7 @@ public:
int GetWeight() const { return m_weight; } int GetWeight() const { return m_weight; }
}; };
std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change); util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change);
/** Select coins by Single Random Draw. OutputGroups are selected randomly from the eligible /** Select coins by Single Random Draw. OutputGroups are selected randomly from the eligible
* outputs until the target is satisfied * outputs until the target is satisfied
@@ -417,10 +418,10 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
* @param[in] target_value The target value to select for * @param[in] target_value The target value to select for
* @returns If successful, a SelectionResult, otherwise, std::nullopt * @returns If successful, a SelectionResult, otherwise, std::nullopt
*/ */
std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng); util::Result<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng);
// Original coin selection algorithm as a fallback // Original coin selection algorithm as a fallback
std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue, util::Result<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue,
CAmount change_target, FastRandomContext& rng); CAmount change_target, FastRandomContext& rng);
} // namespace wallet } // namespace wallet
+14 -5
View File
@@ -554,25 +554,34 @@ util::Result<SelectionResult> ChooseSelectionResult(const CAmount& nTargetValue,
{ {
// Vector of results. We will choose the best one based on waste. // Vector of results. We will choose the best one based on waste.
std::vector<SelectionResult> results; std::vector<SelectionResult> results;
std::vector<util::Result<SelectionResult>> errors;
auto append_error = [&] (const util::Result<SelectionResult>& 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)}) { if (auto bnb_result{SelectCoinsBnB(groups.positive_group, nTargetValue, coin_selection_params.m_cost_of_change)}) {
results.push_back(*bnb_result); 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. // 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)}) { 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); 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); results.push_back(*knapsack_result);
} } else append_error(knapsack_result);
if (auto srd_result{SelectCoinsSRD(groups.positive_group, nTargetValue, coin_selection_params.rng_fast)}) { 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); 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); results.push_back(*srd_result);
} } else append_error(srd_result);
if (results.empty()) { if (results.empty()) {
// No solution found // No solution found, retrieve the first explicit error (if any).
return util::Error(); // 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<SelectionResult> eligible_results; std::vector<SelectionResult> eligible_results;