My fuzz server crashed due to Branch and Bound producing change. I could reproduce the issue with the following test:
0BOOST_AUTO_TEST_CASE(bnb_change)
1{
2 FastRandomContext fast_random_context{};
3 CoinSelectionParams coin_params{
4 /*rng_fast*/fast_random_context,
5 /*change_output_size=*/31,
6 /*change_spend_size=*/68,
7 /*min_change_target=*/50'000,
8 /*effective_feerate=*/CFeeRate(5000),
9 /*long_term_feerate=*/CFeeRate(10'000),
10 /*discard_feerate=*/CFeeRate(3000),
11 /*tx_noinputs_size=*/11 + 31, //static header size + output size
12 /*avoid_partial=*/false,
13 };
14 coin_params.m_change_fee = /*155 sats=*/coin_params.m_effective_feerate.GetFee(coin_params.change_output_size);
15 coin_params.min_viable_change = /*204 sats=*/coin_params.m_discard_feerate.GetFee(coin_params.change_spend_size);
16 coin_params.m_cost_of_change = /*204 + 155 sats=*/coin_params.min_viable_change + coin_params.m_change_fee;
17 coin_params.m_subtract_fee_outputs = false;
18
19 std::vector<COutput> utxo_pool;
20 CMutableTransaction tx;
21 tx.vout.resize(8);
22 tx.vout[7].nValue = 1395186823946715;
23 tx.nLockTime = 1; // all transactions get different hashes
24 utxo_pool.emplace_back(COutPoint(tx.GetHash(), 7), tx.vout.at(7), /*depth=*/1, /*input_bytes=*/1578, /*spendable=*/true, /*solvable=*/true, /*safe=*/true, /*time=*/0, /*from_me=*/true, coin_params.m_effective_feerate.GetFee(1578));
25
26 std::vector<OutputGroup> output_groups;
27 auto output_group = OutputGroup(coin_params);
28 output_group.Insert(std::make_shared<COutput>(utxo_pool[0]), /*ancestors=*/0, /*descendants=*/0);
29 output_groups.push_back(output_group);
30
31 const auto target{1395186823938466};
32 auto result_bnb = SelectCoinsBnB(output_groups, target, coin_params.m_cost_of_change, 400000);
33 if (result_bnb) {
34 assert(result_bnb->GetChange(coin_params.min_viable_change, coin_params.m_change_fee) == 0);
35 }
36}
I noted that change
and min_viable_change
have the same values. For reference: https://bitcoincore.space/src/wallet/coinselection.cpp#987