0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-02-09 10:43:19 -05:00

coin selection: heap-ify SRD, don't return selection if exceeds max tx weight

Uses a min-effective-value heap, so we can remove the least valuable input/s
while the selected weight exceeds the maximum allowed weight.

Co-authored-by: Murch <murch@murch.one>
This commit is contained in:
furszy 2022-12-18 10:39:19 -03:00
parent 6107ec2229
commit 9d9689e5a6
No known key found for this signature in database
GPG key ID: 5DD23CCC686AA623
4 changed files with 46 additions and 7 deletions

View file

@ -13,6 +13,7 @@
#include <numeric>
#include <optional>
#include <queue>
namespace wallet {
// Common selection error across the algorithms
@ -172,9 +173,20 @@ util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool
return result;
}
util::Result<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng)
class MinOutputGroupComparator
{
public:
int operator() (const OutputGroup& group1, const OutputGroup& group2) const
{
return group1.GetSelectionAmount() > group2.GetSelectionAmount();
}
};
util::Result<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng,
int max_weight)
{
SelectionResult result(target_value, SelectionAlgorithm::SRD);
std::priority_queue<OutputGroup, std::vector<OutputGroup>, MinOutputGroupComparator> heap;
// Include change for SRD as we want to avoid making really small change if the selection just
// barely meets the target. Just use the lower bound change target instead of the randomly
@ -188,16 +200,40 @@ util::Result<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utx
Shuffle(indexes.begin(), indexes.end(), rng);
CAmount selected_eff_value = 0;
int weight = 0;
bool max_tx_weight_exceeded = false;
for (const size_t i : indexes) {
const OutputGroup& group = utxo_pool.at(i);
Assume(group.GetSelectionAmount() > 0);
// Add group to selection
heap.push(group);
selected_eff_value += group.GetSelectionAmount();
result.AddInput(group);
weight += group.m_weight;
// If the selection weight exceeds the maximum allowed size, remove the least valuable inputs until we
// are below max weight.
if (weight > max_weight) {
max_tx_weight_exceeded = true; // mark it in case we don't find any useful result.
do {
const OutputGroup& to_remove_group = heap.top();
selected_eff_value -= to_remove_group.GetSelectionAmount();
weight -= to_remove_group.m_weight;
heap.pop();
} while (!heap.empty() && weight > max_weight);
}
// Now check if we are above the target
if (selected_eff_value >= target_value) {
// Result found, add it.
while (!heap.empty()) {
result.AddInput(heap.top());
heap.pop();
}
return result;
}
}
return util::Error();
return max_tx_weight_exceeded ? ErrorMaxWeightExceeded() : util::Error();
}
/** Find a subset of the OutputGroups that is at least as large as, but as close as possible to, the

View file

@ -416,9 +416,12 @@ util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool
*
* @param[in] utxo_pool The positive effective value OutputGroups eligible for selection
* @param[in] target_value The target value to select for
* @returns If successful, a SelectionResult, otherwise, std::nullopt
* @param[in] rng The randomness source to shuffle coins
* @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<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,
int max_weight);
// Original coin selection algorithm as a fallback
util::Result<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue,

View file

@ -579,7 +579,7 @@ util::Result<SelectionResult> ChooseSelectionResult(const CAmount& nTargetValue,
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, max_inputs_weight)}) {
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);

View file

@ -90,7 +90,7 @@ FUZZ_TARGET(coinselection)
// Run coinselection algorithms
const auto result_bnb = SelectCoinsBnB(group_pos, target, cost_of_change);
auto result_srd = SelectCoinsSRD(group_pos, target, fast_random_context);
auto result_srd = SelectCoinsSRD(group_pos, target, fast_random_context, MAX_STANDARD_TX_WEIGHT);
if (result_srd) result_srd->ComputeAndSetWaste(cost_of_change, cost_of_change, 0);
CAmount change_target{GenerateChangeTarget(target, coin_params.m_change_fee, fast_random_context)};