Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
751// Waste with change is the change cost and difference between fee and long term fee752// With long term fee greater than fee, waste should be less than when long term fee is less than fee753SelectionResult selection3{target, SelectionAlgorithm::MANUAL};754add_coin(1 * COIN, 1, selection3, fee, fee + fee_diff);755add_coin(2 * COIN, 2, selection3, fee, fee + fee_diff);756selection3.RecalculateWaste(min_viable_change, change_cost, change_fee);757BOOST_CHECK_EQUAL(fee_diff * -2 + change_cost, selection3.GetWaste());758BOOST_CHECK_LT(selection3.GetWaste(), selection1.GetWaste());759}761{762// Waste without change is the excess and difference between fee and long term fee763SelectionResult selection_nochange1{exact_target - excess, SelectionAlgorithm::MANUAL};764add_coin(1 * COIN, 1, selection_nochange1, fee, fee - fee_diff);765add_coin(2 * COIN, 2, selection_nochange1, fee, fee - fee_diff);766selection_nochange1.RecalculateWaste(min_viable_change, change_cost, change_fee);767BOOST_CHECK_EQUAL(fee_diff * 2 + excess, selection_nochange1.GetWaste());769// Waste without change is the excess and difference between fee and long term fee770// With long term fee greater than fee, waste should be less than when long term fee is less than fee771SelectionResult selection_nochange2{exact_target - excess, SelectionAlgorithm::MANUAL};772add_coin(1 * COIN, 1, selection_nochange2, fee, fee + fee_diff);773add_coin(2 * COIN, 2, selection_nochange2, fee, fee + fee_diff);774selection_nochange2.RecalculateWaste(min_viable_change, change_cost, change_fee);775BOOST_CHECK_EQUAL(fee_diff * -2 + excess, selection_nochange2.GetWaste());776BOOST_CHECK_LT(selection_nochange2.GetWaste(), selection_nochange1.GetWaste());777}779{780// Waste with change and fee == long term fee is just cost of change781SelectionResult selection{target, SelectionAlgorithm::MANUAL};782add_coin(1 * COIN, 1, selection, fee, fee);783add_coin(2 * COIN, 2, selection, fee, fee);784selection.RecalculateWaste(min_viable_change, change_cost, change_fee);785BOOST_CHECK_EQUAL(change_cost, selection.GetWaste());786}788{789// Waste without change and fee == long term fee is just the excess790SelectionResult selection{exact_target - excess, SelectionAlgorithm::MANUAL};791add_coin(1 * COIN, 1, selection, fee, fee);792add_coin(2 * COIN, 2, selection, fee, fee);793selection.RecalculateWaste(min_viable_change, change_cost, change_fee);794BOOST_CHECK_EQUAL(excess, selection.GetWaste());795}797{798// Waste is 0 when fee == long_term_fee, no change, and no excess799SelectionResult selection{exact_target, SelectionAlgorithm::MANUAL};800add_coin(1 * COIN, 1, selection, fee, fee);801add_coin(2 * COIN, 2, selection, fee, fee);802selection.RecalculateWaste(min_viable_change, change_cost , change_fee);803BOOST_CHECK_EQUAL(0, selection.GetWaste());804}806{807// Waste is 0 when (fee - long_term_fee) == (-cost_of_change), and no excess808SelectionResult selection{target, SelectionAlgorithm::MANUAL};809add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);810add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);811selection.RecalculateWaste(min_viable_change, /*change_cost=*/fee_diff * 2, change_fee);812BOOST_CHECK_EQUAL(0, selection.GetWaste());813}815{816// Waste is 0 when (fee - long_term_fee) == (-excess), no change cost817const CAmount new_target{exact_target - /*excess=*/fee_diff * 2};818SelectionResult selection{new_target, SelectionAlgorithm::MANUAL};819add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);820add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);821selection.RecalculateWaste(min_viable_change, change_cost, change_fee);822BOOST_CHECK_EQUAL(0, selection.GetWaste());823}825{826// Negative waste when the long term fee is greater than the current fee and the selected value == target827SelectionResult selection{exact_target, SelectionAlgorithm::MANUAL};828const CAmount target_waste1{-2 * fee_diff}; // = (2 * fee) - (2 * (fee + fee_diff))829add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);830add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);831selection.RecalculateWaste(min_viable_change, change_cost, change_fee);832BOOST_CHECK_EQUAL(target_waste1, selection.GetWaste());833}835{836// Negative waste when the long term fee is greater than the current fee and change_cost < - (inputs * (fee - long_term_fee))837SelectionResult selection{target, SelectionAlgorithm::MANUAL};838const CAmount large_fee_diff{90};839const CAmount target_waste2{-2 * large_fee_diff + change_cost};840// = (2 * fee) - (2 * (fee + large_fee_diff)) + change_cost841// = (2 * 100) - (2 * (100 + 90)) + 125842// = 200 - 380 + 125 = -55843assert(target_waste2 == -55);844add_coin(1 * COIN, 1, selection, fee, fee + large_fee_diff);845add_coin(2 * COIN, 2, selection, fee, fee + large_fee_diff);846selection.RecalculateWaste(min_viable_change, change_cost, change_fee);847BOOST_CHECK_EQUAL(target_waste2, selection.GetWaste());848}849}852BOOST_AUTO_TEST_CASE(bump_fee_test)853{854const CAmount fee{100};855const CAmount min_viable_change{200};856const CAmount change_cost{125};857const CAmount change_fee{35};858const CAmount fee_diff{40};859const CAmount target{2 * COIN};861{862SelectionResult selection{target, SelectionAlgorithm::MANUAL};863add_coin(1 * COIN, 1, selection, /*fee=*/fee, /*long_term_fee=*/fee + fee_diff);864add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);865const std::vector<std::shared_ptr<COutput>> inputs = selection.GetShuffledInputVector();867for (size_t i = 0; i < inputs.size(); ++i) {868inputs[i]->ApplyBumpFee(20*(i+1));869}871selection.RecalculateWaste(min_viable_change, change_cost, change_fee);872CAmount expected_waste = fee_diff * -2 + change_cost + /*bump_fees=*/60;873BOOST_CHECK_EQUAL(expected_waste, selection.GetWaste());875selection.SetBumpFeeDiscount(30);876selection.RecalculateWaste(min_viable_change, change_cost, change_fee);877expected_waste = fee_diff * -2 + change_cost + /*bump_fees=*/60 - /*group_discount=*/30;878BOOST_CHECK_EQUAL(expected_waste, selection.GetWaste());879}881{882// Test with changeless transaction883//884// Bump fees and excess both contribute fully to the waste score,885// therefore, a bump fee group discount will not change the waste886// score as long as we do not create change in both instances.887CAmount changeless_target = 3 * COIN - 2 * fee - 100;888SelectionResult selection{changeless_target, SelectionAlgorithm::MANUAL};889add_coin(1 * COIN, 1, selection, /*fee=*/fee, /*long_term_fee=*/fee + fee_diff);890add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);891const std::vector<std::shared_ptr<COutput>> inputs = selection.GetShuffledInputVector();893for (size_t i = 0; i < inputs.size(); ++i) {894inputs[i]->ApplyBumpFee(20*(i+1));895}897selection.RecalculateWaste(min_viable_change, change_cost, change_fee);898CAmount expected_waste = fee_diff * -2 + /*bump_fees=*/60 + /*excess = 100 - bump_fees*/40;899BOOST_CHECK_EQUAL(expected_waste, selection.GetWaste());901selection.SetBumpFeeDiscount(30);902selection.RecalculateWaste(min_viable_change, change_cost, change_fee);903expected_waste = fee_diff * -2 + /*bump_fees=*/60 - /*group_discount=*/30 + /*excess = 100 - bump_fees + group_discount*/70;904BOOST_CHECK_EQUAL(expected_waste, selection.GetWaste());905}906}908BOOST_AUTO_TEST_CASE(effective_value_test)909{910const int input_bytes = 148;911const CFeeRate feerate(1000);912const CAmount nValue = 10000;913const int nInput = 0;915CMutableTransaction tx;916tx.vout.resize(1);917tx.vout[nInput].nValue = nValue;919// standard case, pass feerate in constructor920COutput output1(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/1, input_bytes, /*solvable=*/true, /*safe=*/true, /*time=*/0, /*from_me=*/false, feerate);921const CAmount expected_ev1 = 9852; // 10000 - 148922BOOST_CHECK_EQUAL(output1.GetEffectiveValue(), expected_ev1);924// input bytes unknown (input_bytes = -1), pass feerate in constructor925COutput output2(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/1, /*input_bytes=*/-1, /*solvable=*/true, /*safe=*/true, /*time=*/0, /*from_me=*/ false, feerate);926BOOST_CHECK_EQUAL(output2.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1928// negative effective value, pass feerate in constructor929COutput output3(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/1, input_bytes, /*solvable=*/true, /*safe=*/true, /*time=*/0, /*from_me=*/false, CFeeRate(100000));930const CAmount expected_ev3 = -4800; // 10000 - 14800931BOOST_CHECK_EQUAL(output3.GetEffectiveValue(), expected_ev3);933// standard case, pass fees in constructor934const CAmount fees = 148;935COutput output4(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/1, input_bytes, /*solvable=*/true, /*safe=*/true, /*time=*/0, /*from_me=*/false, fees);936BOOST_CHECK_EQUAL(output4.GetEffectiveValue(), expected_ev1);938// input bytes unknown (input_bytes = -1), pass fees in constructor939COutput output5(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/1, /*input_bytes=*/-1, /*solvable=*/true, /*safe=*/true, /*time=*/0, /*from_me=*/false, /*fees=*/0);940BOOST_CHECK_EQUAL(output5.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1941}943static util::Result<SelectionResult> CoinGrinder(const CAmount& target,944const CoinSelectionParams& cs_params,945const node::NodeContext& m_node,946int max_selection_weight,947std::function<CoinsResult(CWallet&)> coin_setup)948{949std::unique_ptr<CWallet> wallet = NewWallet(m_node);950CoinEligibilityFilter filter(0, 0, 0); // accept all coins without ancestors951Groups group = GroupOutputs(*wallet, coin_setup(*wallet), cs_params, {{filter}})[filter].all_groups;952return CoinGrinder(group.positive_group, target, cs_params.m_min_change_target, max_selection_weight);953}955BOOST_AUTO_TEST_CASE(coin_grinder_tests)956{957// Test Coin Grinder:958// 1) Insufficient funds, select all provided coins and fail.959// 2) Exceeded max weight, coin selection always surpasses the max allowed weight.960// 3) Select coins without surpassing the max weight (some coins surpasses the max allowed weight, some others not)961// 4) Test that two less valuable UTXOs with a combined lower weight are preferred over a more valuable heavier UTXO962// 5) Test finding a solution in a UTXO pool with mixed weights963// 6) Test that the lightest solution among many clones is found964// 7) Test that lots of tiny UTXOs can be skipped if they are too heavy while there are enough funds in lookahead966FastRandomContext rand;967CoinSelectionParams dummy_params{ // Only used to provide the 'avoid_partial' flag.968rand,969/*change_output_size=*/34,970/*change_spend_size=*/68,971/*min_change_target=*/CENT,972/*effective_feerate=*/CFeeRate(5000),973/*long_term_feerate=*/CFeeRate(2000),974/*discard_feerate=*/CFeeRate(1000),975/*tx_noinputs_size=*/10 + 34, // static header size + output size976/*avoid_partial=*/false,977};979{980// #########################################################981// 1) Insufficient funds, select all provided coins and fail982// #########################################################983CAmount target = 49.5L * COIN;984int max_selection_weight = 10'000; // high enough to not fail for this reason.985const auto& res = CoinGrinder(target, dummy_params, m_node, max_selection_weight, [&](CWallet& wallet) {986CoinsResult available_coins;987for (int j = 0; j < 10; ++j) {988add_coin(available_coins, wallet, CAmount(1 * COIN));989add_coin(available_coins, wallet, CAmount(2 * COIN));990}991return available_coins;992});993BOOST_CHECK(!res);994BOOST_CHECK(util::ErrorString(res).empty()); // empty means "insufficient funds"995}997{998// ###########################999// 2) Test max weight exceeded1000// ###########################
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.