What changed, and why it matters
This commit only adds and reorganizes automated tests for the wallet's coin-selection algorithm. It does not change any production code that handles Bitcoin transactions, keys, networking, or consensus rules. There is no security vulnerability here.
No security action required. Treat as normal test maintenance.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch is a pure test-only change in src/wallet/test/coinselection_tests.cpp and src/wallet/test/coinselector_tests.cpp. It adds weight assertions to existing BnB and SRD success helpers, introduces new SRD test cases around maximum selection weight and eviction behavior, and removes an older, redundant set of SRD tests from coinselector_tests.cpp. No wallet, node, or consensus logic is modified.
Changed components
src/wallet/test/coinselection_tests.cppsrc/wallet/test/coinselector_tests.cppInspect captured patch +8 / −73
diff --git a/src/wallet/test/coinselection_tests.cpp b/src/wallet/test/coinselection_tests.cpp
index 34b71887..6e60af1c 100644
--- a/src/wallet/test/coinselection_tests.cpp
+++ b/src/wallet/test/coinselection_tests.cpp
@@ -126,6 +126,7 @@ static void TestBnBSuccess(std::string test_title, std::vector<OutputGroup>& utx
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()));
+ BOOST_CHECK_MESSAGE(result->GetWeight() <= max_selection_weight, strprintf("Selected weight is higher than permitted in BnB-Success: %s. Expected %d, but got %d", test_title, max_selection_weight, result->GetWeight()));
}
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)
@@ -172,7 +173,7 @@ BOOST_AUTO_TEST_CASE(bnb_test)
// Test skipping of equivalent input sets
std::vector<OutputGroup> clone_pool;
AddCoins(clone_pool, {2 * CENT, 7 * CENT, 7 * CENT}, cs_params);
- AddDuplicateCoins(clone_pool, 50'000, 5 * CENT, cs_params);
+ AddDuplicateCoins(clone_pool, /*count=*/50'000, /*amount=*/5 * CENT, cs_params);
TestBnBSuccess("Skip equivalent input sets", clone_pool, /*selection_target=*/16 * CENT, /*expected_input_amounts=*/{2 * CENT, 7 * CENT, 7 * CENT}, cs_params);
/* Test BnB attempt limit (`TOTAL_TRIES`)
@@ -244,6 +245,7 @@ static void TestSRDSuccess(std::string test_title, std::vector<OutputGroup>& utx
BOOST_CHECK_MESSAGE(result, "Falsy result in SRD-Success: " + test_title);
const CAmount selected_effective_value = result->GetSelectedEffectiveValue();
BOOST_CHECK_MESSAGE(selected_effective_value >= expected_min_amount, strprintf("Selected effective value is lower than expected in SRD-Success: %s. Expected %d, but got %d", test_title, expected_min_amount, selected_effective_value));
+ BOOST_CHECK_MESSAGE(result->GetWeight() <= max_selection_weight, strprintf("Selected weight is higher than permitted in SRD-Success: %s. Expected %d, but got %d", test_title, max_selection_weight, result->GetWeight()));
}
static void TestSRDFail(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)
@@ -276,6 +278,11 @@ BOOST_AUTO_TEST_CASE(srd_test)
TestSRDFail("Undershoot minimum change by one sat", utxo_pool, /*selection_target=*/9 * CENT - cs_params.m_change_fee - CHANGE_LOWER + 1, cs_params);
TestSRDFail("Spend more than available", utxo_pool, /*selection_target=*/9 * CENT + 1, cs_params);
TestSRDFail("Spend everything", utxo_pool, /*selection_target=*/9 * CENT, cs_params);
+
+ AddDuplicateCoins(utxo_pool, /*count=*/100, /*amount=*/5 * CENT, cs_params);
+ AddDuplicateCoins(utxo_pool, /*count=*/3, /*amount=*/7 * CENT, cs_params);
+ TestSRDSuccess("Select most valuable UTXOs for acceptable weight", utxo_pool, /*selection_target=*/20 * CENT, cs_params, /*max_selection_weight=*/4 * 4 * (P2WPKH_INPUT_VSIZE - 1 ));
+ TestSRDFail("No acceptable weight possible", utxo_pool, /*selection_target=*/25 * CENT, cs_params, /*max_selection_weight=*/4 * 3 * P2WPKH_INPUT_VSIZE, /*expect_max_weight_exceeded=*/true);
}
}
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index 740079d2..ee6a639a 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -1185,78 +1185,6 @@ BOOST_AUTO_TEST_CASE(coin_grinder_tests)
}
}
-static util::Result<SelectionResult> SelectCoinsSRD(const CAmount& target,
- const CoinSelectionParams& cs_params,
- const node::NodeContext& m_node,
- int max_selection_weight,
- std::function<CoinsResult(CWallet&)> coin_setup)
-{
- std::unique_ptr<CWallet> wallet = NewWallet(m_node);
- CoinEligibilityFilter filter(0, 0, 0); // accept all coins without ancestors
- Groups group = GroupOutputs(*wallet, coin_setup(*wallet), cs_params, {{filter}})[filter].all_groups;
- return SelectCoinsSRD(group.positive_group, target, cs_params.m_change_fee, cs_params.rng_fast, max_selection_weight);
-}
-
-BOOST_AUTO_TEST_CASE(srd_tests)
-{
- // Test SRD:
- // 1) Insufficient funds, select all provided coins and fail.
- // 2) Exceeded max weight, coin selection always surpasses the max allowed weight.
- // 3) Select coins without surpassing the max weight (some coins surpasses the max allowed weight, some others not)
-
- FastRandomContext rand;
- CoinSelectionParams dummy_params{ // Only used to provide the 'avoid_partial' flag.
- rand,
- /*change_output_size=*/34,
- /*change_spend_size=*/68,
- /*min_change_target=*/CENT,
- /*effective_feerate=*/CFeeRate(0),
- /*long_term_feerate=*/CFeeRate(0),
- /*discard_feerate=*/CFeeRate(0),
- /*tx_noinputs_size=*/10 + 34, // static header size + output size
- /*avoid_partial=*/false,
- };
-
- {
- // ###########################
- // 2) Test max weight exceeded
- // ###########################
- CAmount target = 49.5L * COIN;
- int max_selection_weight = 3000;
- const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
- CoinsResult available_coins;
- for (int j = 0; j < 10; ++j) {
- /* 10 × 1 BTC + 10 × 2 BTC = 30 BTC. 20 × 272 WU = 5440 WU */
- add_coin(available_coins, wallet, CAmount(1 * COIN), CFeeRate(0), 144, false, 0, true);
- add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(0), 144, false, 0, true);
- }
- return available_coins;
- });
- BOOST_CHECK(!res);
- BOOST_CHECK(util::ErrorString(res).original.find("The inputs size exceeds the maximum weight") != std::string::npos);
- }
-
- {
- // ################################################################################################################
- // 3) Test that SRD result does not exceed the max weight
- // ################################################################################################################
- CAmount target = 25.33L * COIN;
- int max_selection_weight = 10000; // WU
- const auto& res = SelectCoinsSRD(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {
- CoinsResult available_coins;
- for (int j = 0; j < 60; ++j) { // 60 UTXO --> 19,8 BTC total --> 60 × 272 WU = 16320 WU
- add_coin(available_coins, wallet, CAmount(0.33 * COIN), CFeeRate(0), 144, false, 0, true);
- }
- for (int i = 0; i < 10; i++) { // 10 UTXO --> 20 BTC total --> 10 × 272 WU = 2720 WU
- add_coin(available_coins, wallet, CAmount(2 * COIN), CFeeRate(0), 144, false, 0, true);
- }
- return available_coins;
- });
- BOOST_CHECK(res);
- BOOST_CHECK(res->GetWeight() <= max_selection_weight);
- }
-}
-
static util::Result<SelectionResult> select_coins(const CAmount& target, const CoinSelectionParams& cs_params, const CCoinControl& cc, std::function<CoinsResult(CWallet&)> coin_setup, const node::NodeContext& m_node)
{
std::unique_ptr<CWallet> wallet = NewWallet(m_node);
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.