test: Init coin selection params with feerate
What changed, and why it matters
This commit only changes Bitcoin Core's internal coin-selection unit tests. It refactors how test parameters are initialized so that the fee rate can be passed in directly, and it makes sure one group of failure tests uses the same parameters as the rest of the test loop. There is no change to production wallet code, network behavior, or consensus rules, and no security issue is introduced or fixed.
No security action needed. Treat as a normal test-refactoring commit during code review.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff modifies src/wallet/test/coinselection_tests.cpp. It renames init_default_params() to init_cs_params(int eff_feerate = 5000), threads the effective feerate through CoinSelectionParams construction, and updates dependent fields (m_change_fee, m_cost_of_change) to be derived from the passed feerate. It also updates TestBnBFail to accept a CoinSelectionParams argument and wires cs_params through the bnb_test loop so BnB failure cases use the feerate-specific cost_of_change instead of the global default. The bnb_feerate_sensitivity_test is similarly simplified. No production code paths are altered.
Changed components
src/wallet/test/coinselection_tests.cppInspect captured patch +28 / −30
diff --git a/src/wallet/test/coinselection_tests.cpp b/src/wallet/test/coinselection_tests.cpp
index 4a0eed8f..46b0b463 100644
--- a/src/wallet/test/coinselection_tests.cpp
+++ b/src/wallet/test/coinselection_tests.cpp
@@ -18,30 +18,31 @@ static FastRandomContext default_rand;
static const int P2WPKH_INPUT_VSIZE = 68;
static const int P2WPKH_OUTPUT_VSIZE = 31;
-/** Default coin selection parameters (dcsp) allow us to only explicitly set
- * parameters when a diverging value is relevant in the context of a test.
- * We use P2WPKH input and output weights for the change weights. */
-static CoinSelectionParams init_default_params()
+/** Default coin selection parameters allow us to only explicitly set
+ * parameters when a diverging value is relevant in the context of a test,
+ * without reiterating the defaults in every test. We use P2WPKH input and
+ * output weights for the change weights. */
+static CoinSelectionParams init_cs_params(int eff_feerate = 5000)
{
- CoinSelectionParams dcsp{
+ CoinSelectionParams csp{
/*rng_fast=*/default_rand,
/*change_output_size=*/P2WPKH_OUTPUT_VSIZE,
/*change_spend_size=*/P2WPKH_INPUT_VSIZE,
/*min_change_target=*/50'000,
- /*effective_feerate=*/CFeeRate(5000),
+ /*effective_feerate=*/CFeeRate(eff_feerate),
/*long_term_feerate=*/CFeeRate(10'000),
/*discard_feerate=*/CFeeRate(3000),
/*tx_noinputs_size=*/11 + P2WPKH_OUTPUT_VSIZE, //static header size + output size
/*avoid_partial=*/false,
};
- dcsp.m_change_fee = /*155 sats=*/dcsp.m_effective_feerate.GetFee(dcsp.change_output_size);
- dcsp.min_viable_change = /*204 sats=*/dcsp.m_discard_feerate.GetFee(dcsp.change_spend_size);
- dcsp.m_cost_of_change = /*204 + 155 sats=*/dcsp.min_viable_change + dcsp.m_change_fee;
- dcsp.m_subtract_fee_outputs = false;
- return dcsp;
+ csp.m_change_fee = csp.m_effective_feerate.GetFee(csp.change_output_size); // 155 sats for default feerate of 5000 s/kvB
+ csp.min_viable_change = /*204 sats=*/csp.m_discard_feerate.GetFee(csp.change_spend_size);
+ csp.m_cost_of_change = csp.min_viable_change + csp.m_change_fee; // 204 + 155 sats for default feerate of 5000 s/kvB
+ csp.m_subtract_fee_outputs = false;
+ return csp;
}
-static const CoinSelectionParams default_cs_params = init_default_params();
+static const CoinSelectionParams default_cs_params = init_cs_params();
/** Make one OutputGroup with a single UTXO that either has a given effective value (default) or a given amount (`is_eff_value = false`). */
static OutputGroup MakeCoin(const CAmount& amount, bool is_eff_value = true, CoinSelectionParams cs_params = default_cs_params, int custom_spending_vsize = P2WPKH_INPUT_VSIZE)
@@ -106,15 +107,15 @@ static void TestBnBSuccess(std::string test_title, std::vector<OutputGroup>& utx
expected_result.AddInput(group);
}
- const auto result = SelectCoinsBnB(utxo_pool, selection_target, /*cost_of_change=*/default_cs_params.m_cost_of_change, max_selection_weight);
+ const auto result = SelectCoinsBnB(utxo_pool, selection_target, /*cost_of_change=*/cs_params.m_cost_of_change, max_selection_weight);
BOOST_CHECK_MESSAGE(result, "Falsy result in BnB-Success: " + test_title);
BOOST_CHECK_MESSAGE(HaveEquivalentValues(expected_result, *result), strprintf("Result mismatch in BnB-Success: %s. Expected %s, but got %s", test_title, InputAmountsToString(expected_result), InputAmountsToString(*result)));
BOOST_CHECK_MESSAGE(result->GetSelectedValue() == expected_amount, strprintf("Selected amount mismatch in BnB-Success: %s. Expected %d, but got %d", test_title, expected_amount, result->GetSelectedValue()));
}
-static void TestBnBFail(std::string test_title, std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, int max_selection_weight = MAX_STANDARD_TX_WEIGHT, const bool expect_max_weight_exceeded = false)
+static void TestBnBFail(std::string test_title, std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CoinSelectionParams& cs_params = default_cs_params, int max_selection_weight = MAX_STANDARD_TX_WEIGHT, const bool expect_max_weight_exceeded = false)
{
- const auto result = SelectCoinsBnB(utxo_pool, selection_target, /*cost_of_change=*/default_cs_params.m_cost_of_change, max_selection_weight);
+ const auto result = SelectCoinsBnB(utxo_pool, selection_target, /*cost_of_change=*/cs_params.m_cost_of_change, max_selection_weight);
BOOST_CHECK_MESSAGE(!result, "BnB-Fail: " + test_title);
bool max_weight_exceeded = util::ErrorString(result).original.find("The inputs size exceeds the maximum weight") != std::string::npos;
BOOST_CHECK(expect_max_weight_exceeded == max_weight_exceeded);
@@ -127,11 +128,9 @@ BOOST_AUTO_TEST_CASE(bnb_test)
for (int feerate : feerates) {
std::vector<OutputGroup> utxo_pool;
- CoinSelectionParams cs_params = init_default_params();
- cs_params.m_effective_feerate = CFeeRate{feerate};
+ const CoinSelectionParams cs_params = init_cs_params(feerate);
- // Fail for empty UTXO pool
- TestBnBFail("Empty UTXO pool", utxo_pool, /*selection_target=*/1 * CENT);
+ TestBnBFail("Empty UTXO pool", utxo_pool, /*selection_target=*/1 * CENT, cs_params);
AddCoins(utxo_pool, {1 * CENT, 3 * CENT, 5 * CENT}, cs_params);
@@ -143,19 +142,19 @@ BOOST_AUTO_TEST_CASE(bnb_test)
TestBnBSuccess("Select all UTXOs", utxo_pool, /*selection_target=*/9 * CENT, /*expected_input_amounts=*/{1 * CENT, 3 * CENT, 5 * CENT}, cs_params);
// BnB finds changeless solution while overshooting by up to cost_of_change
- TestBnBSuccess("Select upper bound", utxo_pool, /*selection_target=*/4 * CENT - default_cs_params.m_cost_of_change, /*expected_input_amounts=*/{1 * CENT, 3 * CENT}, cs_params);
+ TestBnBSuccess("Select upper bound", utxo_pool, /*selection_target=*/4 * CENT - cs_params.m_cost_of_change, /*expected_input_amounts=*/{1 * CENT, 3 * CENT}, cs_params);
// BnB fails to find changeless solution when overshooting by cost_of_change + 1 sat
- TestBnBFail("Overshoot upper bound", utxo_pool, /*selection_target=*/4 * CENT - default_cs_params.m_cost_of_change - 1);
+ TestBnBFail("Overshoot upper bound", utxo_pool, /*selection_target=*/4 * CENT - cs_params.m_cost_of_change - 1, cs_params);
TestBnBSuccess("Select max weight", utxo_pool, /*selection_target=*/4 * CENT, /*expected_input_amounts=*/{1 * CENT, 3 * CENT}, cs_params, /*custom_spending_vsize=*/P2WPKH_INPUT_VSIZE, /*max_selection_weight=*/4 * 2 * P2WPKH_INPUT_VSIZE);
- TestBnBFail("Exceed max weight", utxo_pool, /*selection_target=*/4 * CENT, /*max_selection_weight=*/4 * 2 * P2WPKH_INPUT_VSIZE - 1, /*expect_max_weight_exceeded=*/true);
+ TestBnBFail("Exceed max weight", utxo_pool, /*selection_target=*/4 * CENT, cs_params, /*max_selection_weight=*/4 * 2 * P2WPKH_INPUT_VSIZE - 1, /*expect_max_weight_exceeded=*/true);
// Simple cases without BnB solution
- TestBnBFail("Smallest combination too big", utxo_pool, /*selection_target=*/0.5 * CENT);
- TestBnBFail("No UTXO combination in target window", utxo_pool, /*selection_target=*/7 * CENT);
- TestBnBFail("Select more than available", utxo_pool, /*selection_target=*/10 * CENT);
+ TestBnBFail("Smallest combination too big", utxo_pool, /*selection_target=*/0.5 * CENT, cs_params);
+ TestBnBFail("No UTXO combination in target window", utxo_pool, /*selection_target=*/7 * CENT, cs_params);
+ TestBnBFail("Select more than available", utxo_pool, /*selection_target=*/10 * CENT, cs_params);
// Test skipping of equivalent input sets
std::vector<OutputGroup> clone_pool;
@@ -189,7 +188,7 @@ BOOST_AUTO_TEST_CASE(bnb_test)
expected_inputs.push_back(doppelgangers[i]);
} else {
// Any eight UTXOs including at least one UTXO with the added cost_of_change will exceed target window
- doppelgangers.push_back(1 * CENT + default_cs_params.m_cost_of_change + i);
+ doppelgangers.push_back(1 * CENT + cs_params.m_cost_of_change + i);
}
}
AddCoins(doppelganger_pool, doppelgangers, cs_params);
@@ -197,8 +196,8 @@ BOOST_AUTO_TEST_CASE(bnb_test)
TestBnBSuccess("Combine smallest 8 of 17 unique UTXOs", doppelganger_pool, /*selection_target=*/8 * CENT, /*expected_input_amounts=*/expected_inputs, cs_params);
// Starting with 18 unique UTXOs of similar effective value we will not find the solution due to exceeding the attempt limit
- AddCoins(doppelganger_pool, {1 * CENT + default_cs_params.m_cost_of_change + 17}, cs_params);
- TestBnBFail("Exhaust looking for smallest 8 of 18 unique UTXOs", doppelganger_pool, /*selection_target=*/8 * CENT);
+ AddCoins(doppelganger_pool, {1 * CENT + cs_params.m_cost_of_change + 17}, cs_params);
+ TestBnBFail("Exhaust looking for smallest 8 of 18 unique UTXOs", doppelganger_pool, /*selection_target=*/8 * CENT, cs_params);
}
}
@@ -209,8 +208,7 @@ BOOST_AUTO_TEST_CASE(bnb_feerate_sensitivity_test)
AddCoins(low_feerate_pool, {2 * CENT, 3 * CENT, 5 * CENT, 10 * CENT});
TestBnBSuccess("Select many inputs at low feerates", low_feerate_pool, /*selection_target=*/10 * CENT, /*expected_input_amounts=*/{2 * CENT, 3 * CENT, 5 * CENT});
- CoinSelectionParams high_feerate_params = init_default_params();
- high_feerate_params.m_effective_feerate = CFeeRate{25'000};
+ const CoinSelectionParams high_feerate_params = init_cs_params(/*eff_feerate=*/25'000);
std::vector<OutputGroup> 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}, high_feerate_params);
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.