coinselection: BnB skip exploring high waste

At high feerates adding more inputs will increase the waste score. If
the current waste is already higher than the best selection’s we cannot
improve upon the best selection. All solutions that include the current
selection with more additional inputs must be worse than the best
selection so far: SHIFT

This optimization only works at high feerates, because at low feerates,
adding more inputs decreases waste, so this condition would exit
prematurely. We would never attempt input sets with higher weight than
the prior best selection, even though we would prefer those at low
feerates.
This commit is contained in:
Murch
2025-03-26 18:17:27 -07:00
parent 7ecea1dc5d
commit fa226ab902
2 changed files with 11 additions and 2 deletions

View File

@@ -132,6 +132,8 @@ util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool
size_t curr_try = 0;
SelectionResult result(selection_target, SelectionAlgorithm::BNB);
// We dont have access to the feerate here, but fee to long_term_fee is as feerate to LTFRE
bool is_feerate_high = utxo_pool.at(0).fee > utxo_pool.at(0).long_term_fee;
while (true) {
bool should_shift{false}, should_cut{false};
// Select `next_utxo`
@@ -151,6 +153,13 @@ util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool
} else if (curr_amount > selection_target + cost_of_change) {
// Overshot target range: SHIFT
should_shift = true;
} else if (is_feerate_high && curr_selection_waste > best_waste) {
// At high feerates adding more inputs will increase the waste score. If the current waste is already worse
// than the best selections while we have insufficient funds, it is impossible for this partial selection
// to beat the best selection by adding more inputs: SHIFT
// At low feerates, additional inputs lower the waste score, and using this would cause us to skip exploring
// combinations with more inputs of lower amounts.
should_shift = true;
} else if (curr_amount >= selection_target) {
// Selection is within target window: potential solution
// Adding more UTXOs only increases fees and cannot be better: SHIFT

View File

@@ -226,7 +226,7 @@ BOOST_AUTO_TEST_CASE(bnb_feerate_sensitivity_test)
const CoinSelectionParams high_feerate_params = init_cs_params(/*eff_feerate=*/25'000);
std::vector<OutputGroup> high_feerate_pool; // 25sat/vB (greater than long_term_feerate of 10sat/vB)
AddCoins(high_feerate_pool, {2 * CENT, 3 * CENT, 5 * CENT, 10 * CENT}, high_feerate_params);
TestBnBSuccess("Select one input at high feerates", high_feerate_pool, /*selection_target=*/10 * CENT, /*expected_input_amounts=*/{10 * CENT}, /*expected_attempts=*/8, high_feerate_params);
TestBnBSuccess("Select one input at high feerates", high_feerate_pool, /*selection_target=*/10 * CENT, /*expected_input_amounts=*/{10 * CENT}, /*expected_attempts=*/7, high_feerate_params);
// Add heavy inputs {6, 7} to existing {2, 3, 5, 10}
low_feerate_pool.push_back(MakeCoin(6 * CENT, true, default_cs_params, /*custom_spending_vsize=*/500));
@@ -235,7 +235,7 @@ BOOST_AUTO_TEST_CASE(bnb_feerate_sensitivity_test)
high_feerate_pool.push_back(MakeCoin(6 * CENT, true, high_feerate_params, /*custom_spending_vsize=*/500));
high_feerate_pool.push_back(MakeCoin(7 * CENT, true, high_feerate_params, /*custom_spending_vsize=*/500));
TestBnBSuccess("Prefer two light inputs over two heavy inputs at high feerates", high_feerate_pool, /*selection_target=*/13 * CENT, /*expected_input_amounts=*/{3 * CENT, 10 * CENT}, /*expected_attempts=*/28, high_feerate_params);
TestBnBSuccess("Prefer two light inputs over two heavy inputs at high feerates", high_feerate_pool, /*selection_target=*/13 * CENT, /*expected_input_amounts=*/{3 * CENT, 10 * CENT}, /*expected_attempts=*/15, high_feerate_params);
}
static void TestSRDSuccess(std::string test_title, std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CoinSelectionParams& cs_params = default_cs_params, const int max_selection_weight = MAX_STANDARD_TX_WEIGHT)