mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-08-24 19:51:29 +02:00
Merge bitcoin/bitcoin#33060: test: Slay BnB Mutants
a3cf623364
test: Test max_selection_weight edge cases (Murch)57fe8acc8a
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: ACKa3cf623364
jlest01: ACKa3cf623364
brunoerg: code review ACKa3cf623364
Tree-SHA512: db67c52127ed98f809f64a903c6b3a012e56cf665a0cd851457af7c85c37ec3af8bb72035d7ad370dd883f99cf3014464e3576559899e37c1d6ee01230511754
This commit is contained in:
@@ -15,6 +15,9 @@ BOOST_FIXTURE_TEST_SUITE(coinselection_tests, TestingSetup)
|
|||||||
static int next_lock_time = 0;
|
static int next_lock_time = 0;
|
||||||
static FastRandomContext default_rand;
|
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
|
/** Default coin selection parameters (dcsp) allow us to only explicitly set
|
||||||
* parameters when a diverging value is relevant in the context of a test.
|
* parameters when a diverging value is relevant in the context of a test.
|
||||||
* We use P2WPKH input and output weights for the change weights. */
|
* We use P2WPKH input and output weights for the change weights. */
|
||||||
@@ -22,13 +25,13 @@ static CoinSelectionParams init_default_params()
|
|||||||
{
|
{
|
||||||
CoinSelectionParams dcsp{
|
CoinSelectionParams dcsp{
|
||||||
/*rng_fast*/default_rand,
|
/*rng_fast*/default_rand,
|
||||||
/*change_output_size=*/31,
|
/*change_output_size=*/P2WPKH_OUTPUT_VSIZE,
|
||||||
/*change_spend_size=*/68,
|
/*change_spend_size=*/P2WPKH_INPUT_VSIZE,
|
||||||
/*min_change_target=*/50'000,
|
/*min_change_target=*/50'000,
|
||||||
/*effective_feerate=*/CFeeRate(5000),
|
/*effective_feerate=*/CFeeRate(5000),
|
||||||
/*long_term_feerate=*/CFeeRate(10'000),
|
/*long_term_feerate=*/CFeeRate(10'000),
|
||||||
/*discard_feerate=*/CFeeRate(3000),
|
/*discard_feerate=*/CFeeRate(3000),
|
||||||
/*tx_noinputs_size=*/11 + 31, //static header size + output size
|
/*tx_noinputs_size=*/11 + P2WPKH_OUTPUT_VSIZE, //static header size + output size
|
||||||
/*avoid_partial=*/false,
|
/*avoid_partial=*/false,
|
||||||
};
|
};
|
||||||
dcsp.m_change_fee = /*155 sats=*/dcsp.m_effective_feerate.GetFee(dcsp.change_output_size);
|
dcsp.m_change_fee = /*155 sats=*/dcsp.m_effective_feerate.GetFee(dcsp.change_output_size);
|
||||||
@@ -41,7 +44,7 @@ static CoinSelectionParams init_default_params()
|
|||||||
static const CoinSelectionParams default_cs_params = init_default_params();
|
static const CoinSelectionParams default_cs_params = init_default_params();
|
||||||
|
|
||||||
/** Make one OutputGroup with a single UTXO that either has a given effective value (default) or a given amount (`is_eff_value = false`). */
|
/** 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 = 68)
|
static OutputGroup MakeCoin(const CAmount& amount, bool is_eff_value = true, CoinSelectionParams cs_params = default_cs_params, int custom_spending_vsize = P2WPKH_INPUT_VSIZE)
|
||||||
{
|
{
|
||||||
// Always assume that we only have one input
|
// Always assume that we only have one input
|
||||||
CMutableTransaction tx;
|
CMutableTransaction tx;
|
||||||
@@ -93,7 +96,7 @@ static std::string InputAmountsToString(const SelectionResult& selection)
|
|||||||
return "[" + util::Join(selection.GetInputSet(), " ", [](const auto& input){ return util::ToString(input->txout.nValue);}) + "]";
|
return "[" + util::Join(selection.GetInputSet(), " ", [](const auto& input){ return util::ToString(input->txout.nValue);}) + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
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)
|
||||||
{
|
{
|
||||||
SelectionResult expected_result(CAmount(0), SelectionAlgorithm::BNB);
|
SelectionResult expected_result(CAmount(0), SelectionAlgorithm::BNB);
|
||||||
CAmount expected_amount = 0;
|
CAmount expected_amount = 0;
|
||||||
@@ -103,15 +106,18 @@ static void TestBnBSuccess(std::string test_title, std::vector<OutputGroup>& utx
|
|||||||
expected_result.AddInput(group);
|
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=*/MAX_STANDARD_TX_WEIGHT);
|
const auto result = SelectCoinsBnB(utxo_pool, selection_target, /*cost_of_change=*/default_cs_params.m_cost_of_change, max_selection_weight);
|
||||||
BOOST_CHECK_MESSAGE(result, "Falsy result in BnB-Success: " + test_title);
|
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(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->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)
|
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)
|
||||||
{
|
{
|
||||||
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);
|
const auto result = SelectCoinsBnB(utxo_pool, selection_target, /*cost_of_change=*/default_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);
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(bnb_test)
|
BOOST_AUTO_TEST_CASE(bnb_test)
|
||||||
@@ -142,6 +148,10 @@ BOOST_AUTO_TEST_CASE(bnb_test)
|
|||||||
// BnB fails to find changeless solution when overshooting by cost_of_change + 1 sat
|
// 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 - default_cs_params.m_cost_of_change - 1);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
// Simple cases without BnB solution
|
// Simple cases without BnB solution
|
||||||
TestBnBFail("Smallest combination too big", utxo_pool, /*selection_target=*/0.5 * CENT);
|
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("No UTXO combination in target window", utxo_pool, /*selection_target=*/7 * CENT);
|
||||||
|
Reference in New Issue
Block a user