Skip to content

Commit 50a92cd

Browse files
committed
Merge bitcoin/bitcoin#33060: test: Slay BnB Mutants
a3cf623 test: Test max_selection_weight edge cases (Murch) 57fe8ac test: Check max_weight_exceeded error (Murch) Pull request description: I tested all of the reported surviving mutants that @brunoerg reported in https://gist.github.com/brunoerg/834063398d5002f738506d741513e310. I found that all Mutants except for 12, 14, 17, 37, and 39 were now being caught by one of the existing tests. This fixes Mutants 14, 37, and 39. Mutant 17 is not fixed, because I consider it acceptable that running BnB for 100,001 instead of 100,000 comparisons doesn’t cause an issue, and Mutant 12 is not yet fixed, because at `fee` = `long_term_fee`, the waste of inputs is 0 and only excess matters, and I haven’t evaluated yet, whether it needs to be fixed. ACKs for top commit: achow101: ACK a3cf623 jlest01: ACK bitcoin/bitcoin@a3cf623 brunoerg: code review ACK a3cf623 Tree-SHA512: db67c52127ed98f809f64a903c6b3a012e56cf665a0cd851457af7c85c37ec3af8bb72035d7ad370dd883f99cf3014464e3576559899e37c1d6ee01230511754
2 parents 643bacd + a3cf623 commit 50a92cd

File tree

1 file changed

+18
-8
lines changed

1 file changed

+18
-8
lines changed

src/wallet/test/coinselection_tests.cpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,23 @@ BOOST_FIXTURE_TEST_SUITE(coinselection_tests, TestingSetup)
1515
static int next_lock_time = 0;
1616
static FastRandomContext default_rand;
1717

18+
static const int P2WPKH_INPUT_VSIZE = 68;
19+
static const int P2WPKH_OUTPUT_VSIZE = 31;
20+
1821
/** Default coin selection parameters (dcsp) allow us to only explicitly set
1922
* parameters when a diverging value is relevant in the context of a test.
2023
* We use P2WPKH input and output weights for the change weights. */
2124
static CoinSelectionParams init_default_params()
2225
{
2326
CoinSelectionParams dcsp{
2427
/*rng_fast*/default_rand,
25-
/*change_output_size=*/31,
26-
/*change_spend_size=*/68,
28+
/*change_output_size=*/P2WPKH_OUTPUT_VSIZE,
29+
/*change_spend_size=*/P2WPKH_INPUT_VSIZE,
2730
/*min_change_target=*/50'000,
2831
/*effective_feerate=*/CFeeRate(5000),
2932
/*long_term_feerate=*/CFeeRate(10'000),
3033
/*discard_feerate=*/CFeeRate(3000),
31-
/*tx_noinputs_size=*/11 + 31, //static header size + output size
34+
/*tx_noinputs_size=*/11 + P2WPKH_OUTPUT_VSIZE, //static header size + output size
3235
/*avoid_partial=*/false,
3336
};
3437
dcsp.m_change_fee = /*155 sats=*/dcsp.m_effective_feerate.GetFee(dcsp.change_output_size);
@@ -41,7 +44,7 @@ static CoinSelectionParams init_default_params()
4144
static const CoinSelectionParams default_cs_params = init_default_params();
4245

4346
/** Make one OutputGroup with a single UTXO that either has a given effective value (default) or a given amount (`is_eff_value = false`). */
44-
static OutputGroup MakeCoin(const CAmount& amount, bool is_eff_value = true, CoinSelectionParams cs_params = default_cs_params, int custom_spending_vsize = 68)
47+
static OutputGroup MakeCoin(const CAmount& amount, bool is_eff_value = true, CoinSelectionParams cs_params = default_cs_params, int custom_spending_vsize = P2WPKH_INPUT_VSIZE)
4548
{
4649
// Always assume that we only have one input
4750
CMutableTransaction tx;
@@ -93,7 +96,7 @@ static std::string InputAmountsToString(const SelectionResult& selection)
9396
return "[" + util::Join(selection.GetInputSet(), " ", [](const auto& input){ return util::ToString(input->txout.nValue);}) + "]";
9497
}
9598

96-
static void TestBnBSuccess(std::string test_title, std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const std::vector<CAmount>& expected_input_amounts, const CoinSelectionParams& cs_params = default_cs_params, int custom_spending_vsize = 68)
99+
static void TestBnBSuccess(std::string test_title, std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const std::vector<CAmount>& expected_input_amounts, const CoinSelectionParams& cs_params = default_cs_params, const int custom_spending_vsize = P2WPKH_INPUT_VSIZE, const int max_selection_weight = MAX_STANDARD_TX_WEIGHT)
97100
{
98101
SelectionResult expected_result(CAmount(0), SelectionAlgorithm::BNB);
99102
CAmount expected_amount = 0;
@@ -103,15 +106,18 @@ static void TestBnBSuccess(std::string test_title, std::vector<OutputGroup>& utx
103106
expected_result.AddInput(group);
104107
}
105108

106-
const auto result = SelectCoinsBnB(utxo_pool, selection_target, /*cost_of_change=*/default_cs_params.m_cost_of_change, /*max_selection_weight=*/MAX_STANDARD_TX_WEIGHT);
109+
const auto result = SelectCoinsBnB(utxo_pool, selection_target, /*cost_of_change=*/default_cs_params.m_cost_of_change, max_selection_weight);
107110
BOOST_CHECK_MESSAGE(result, "Falsy result in BnB-Success: " + test_title);
108111
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)));
109112
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()));
110113
}
111114

112-
static void TestBnBFail(std::string test_title, std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target)
115+
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)
113116
{
114-
BOOST_CHECK_MESSAGE(!SelectCoinsBnB(utxo_pool, selection_target, /*cost_of_change=*/default_cs_params.m_cost_of_change, /*max_selection_weight=*/MAX_STANDARD_TX_WEIGHT), "BnB-Fail: " + test_title);
117+
const auto result = SelectCoinsBnB(utxo_pool, selection_target, /*cost_of_change=*/default_cs_params.m_cost_of_change, max_selection_weight);
118+
BOOST_CHECK_MESSAGE(!result, "BnB-Fail: " + test_title);
119+
bool max_weight_exceeded = util::ErrorString(result).original.find("The inputs size exceeds the maximum weight") != std::string::npos;
120+
BOOST_CHECK(expect_max_weight_exceeded == max_weight_exceeded);
115121
}
116122

117123
BOOST_AUTO_TEST_CASE(bnb_test)
@@ -142,6 +148,10 @@ BOOST_AUTO_TEST_CASE(bnb_test)
142148
// BnB fails to find changeless solution when overshooting by cost_of_change + 1 sat
143149
TestBnBFail("Overshoot upper bound", utxo_pool, /*selection_target=*/4 * CENT - default_cs_params.m_cost_of_change - 1);
144150

151+
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);
152+
153+
TestBnBFail("Exceed max weight", utxo_pool, /*selection_target=*/4 * CENT, /*max_selection_weight=*/4 * 2 * P2WPKH_INPUT_VSIZE - 1, /*expect_max_weight_exceeded=*/true);
154+
145155
// Simple cases without BnB solution
146156
TestBnBFail("Smallest combination too big", utxo_pool, /*selection_target=*/0.5 * CENT);
147157
TestBnBFail("No UTXO combination in target window", utxo_pool, /*selection_target=*/7 * CENT);

0 commit comments

Comments
 (0)