From fa226ab902fe1aa51ec039a2cdfe586855b75f9d Mon Sep 17 00:00:00 2001 From: Murch Date: Wed, 26 Mar 2025 18:17:27 -0700 Subject: [PATCH] coinselection: BnB skip exploring high waste MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/wallet/coinselection.cpp | 9 +++++++++ src/wallet/test/coinselection_tests.cpp | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp index e671c3d091f..cce4c59ca15 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -132,6 +132,8 @@ util::Result SelectCoinsBnB(std::vector& utxo_pool size_t curr_try = 0; SelectionResult result(selection_target, SelectionAlgorithm::BNB); + // We don’t 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 SelectCoinsBnB(std::vector& 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 selection’s 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 diff --git a/src/wallet/test/coinselection_tests.cpp b/src/wallet/test/coinselection_tests.cpp index 05e36167a09..6a0d5e48d21 100644 --- a/src/wallet/test/coinselection_tests.cpp +++ b/src/wallet/test/coinselection_tests.cpp @@ -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 high_feerate_pool; // 25 sat/vB (greater than long_term_feerate of 10 sat/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& utxo_pool, const CAmount& selection_target, const CoinSelectionParams& cs_params = default_cs_params, const int max_selection_weight = MAX_STANDARD_TX_WEIGHT)