Skip to content

Commit 9d9689e

Browse files
furszymurchandamus
andcommitted
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 <[email protected]>
1 parent 6107ec2 commit 9d9689e

File tree

4 files changed

+46
-7
lines changed

4 files changed

+46
-7
lines changed

src/wallet/coinselection.cpp

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include <numeric>
1515
#include <optional>
16+
#include <queue>
1617

1718
namespace wallet {
1819
// Common selection error across the algorithms
@@ -172,9 +173,20 @@ util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool
172173
return result;
173174
}
174175

175-
util::Result<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng)
176+
class MinOutputGroupComparator
177+
{
178+
public:
179+
int operator() (const OutputGroup& group1, const OutputGroup& group2) const
180+
{
181+
return group1.GetSelectionAmount() > group2.GetSelectionAmount();
182+
}
183+
};
184+
185+
util::Result<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng,
186+
int max_weight)
176187
{
177188
SelectionResult result(target_value, SelectionAlgorithm::SRD);
189+
std::priority_queue<OutputGroup, std::vector<OutputGroup>, MinOutputGroupComparator> heap;
178190

179191
// Include change for SRD as we want to avoid making really small change if the selection just
180192
// 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
188200
Shuffle(indexes.begin(), indexes.end(), rng);
189201

190202
CAmount selected_eff_value = 0;
203+
int weight = 0;
204+
bool max_tx_weight_exceeded = false;
191205
for (const size_t i : indexes) {
192206
const OutputGroup& group = utxo_pool.at(i);
193207
Assume(group.GetSelectionAmount() > 0);
208+
209+
// Add group to selection
210+
heap.push(group);
194211
selected_eff_value += group.GetSelectionAmount();
195-
result.AddInput(group);
212+
weight += group.m_weight;
213+
214+
// If the selection weight exceeds the maximum allowed size, remove the least valuable inputs until we
215+
// are below max weight.
216+
if (weight > max_weight) {
217+
max_tx_weight_exceeded = true; // mark it in case we don't find any useful result.
218+
do {
219+
const OutputGroup& to_remove_group = heap.top();
220+
selected_eff_value -= to_remove_group.GetSelectionAmount();
221+
weight -= to_remove_group.m_weight;
222+
heap.pop();
223+
} while (!heap.empty() && weight > max_weight);
224+
}
225+
226+
// Now check if we are above the target
196227
if (selected_eff_value >= target_value) {
228+
// Result found, add it.
229+
while (!heap.empty()) {
230+
result.AddInput(heap.top());
231+
heap.pop();
232+
}
197233
return result;
198234
}
199235
}
200-
return util::Error();
236+
return max_tx_weight_exceeded ? ErrorMaxWeightExceeded() : util::Error();
201237
}
202238

203239
/** Find a subset of the OutputGroups that is at least as large as, but as close as possible to, the

src/wallet/coinselection.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -416,9 +416,12 @@ util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool
416416
*
417417
* @param[in] utxo_pool The positive effective value OutputGroups eligible for selection
418418
* @param[in] target_value The target value to select for
419-
* @returns If successful, a SelectionResult, otherwise, std::nullopt
419+
* @param[in] rng The randomness source to shuffle coins
420+
* @param[in] max_weight The maximum allowed weight for a selection result to be valid
421+
* @returns If successful, a valid SelectionResult, otherwise, util::Error
420422
*/
421-
util::Result<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng);
423+
util::Result<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng,
424+
int max_weight);
422425

423426
// Original coin selection algorithm as a fallback
424427
util::Result<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue,

src/wallet/spend.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ util::Result<SelectionResult> ChooseSelectionResult(const CAmount& nTargetValue,
579579
results.push_back(*knapsack_result);
580580
} else append_error(knapsack_result);
581581

582-
if (auto srd_result{SelectCoinsSRD(groups.positive_group, nTargetValue, coin_selection_params.rng_fast)}) {
582+
if (auto srd_result{SelectCoinsSRD(groups.positive_group, nTargetValue, coin_selection_params.rng_fast, max_inputs_weight)}) {
583583
srd_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
584584
results.push_back(*srd_result);
585585
} else append_error(srd_result);

src/wallet/test/fuzz/coinselection.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ FUZZ_TARGET(coinselection)
9090
// Run coinselection algorithms
9191
const auto result_bnb = SelectCoinsBnB(group_pos, target, cost_of_change);
9292

93-
auto result_srd = SelectCoinsSRD(group_pos, target, fast_random_context);
93+
auto result_srd = SelectCoinsSRD(group_pos, target, fast_random_context, MAX_STANDARD_TX_WEIGHT);
9494
if (result_srd) result_srd->ComputeAndSetWaste(cost_of_change, cost_of_change, 0);
9595

9696
CAmount change_target{GenerateChangeTarget(target, coin_params.m_change_fee, fast_random_context)};

0 commit comments

Comments
 (0)