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.
251// With maximum m_effective_feerate 10'000 s/vB and n_input_bytes = 20'000 B, input_fee <= MAX_MONEY.252const int n_input_bytes{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 20'000)};253const CAmount input_fee = coin_params.m_effective_feerate.GetFee(n_input_bytes);254// Ensure that each UTXO has at least an effective value of 1 sat255const CAmount eff_value{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(1, MAX_MONEY + group_pos.size() - max_spendable - max_output_groups)};256const CAmount amount{eff_value + input_fee};257std::vector<COutput> temp_utxo_pool;259AddCoin(amount, /*n_input=*/0, n_input_bytes, ++next_locktime, temp_utxo_pool, coin_params.m_effective_feerate);260max_spendable += eff_value;262auto output_group = OutputGroup(coin_params);263output_group.Insert(std::make_shared<COutput>(temp_utxo_pool.at(0)), /*ancestors=*/0, /*cluster_count=*/0);264group_pos.push_back(output_group);265}266size_t num_groups = group_pos.size();267assert(num_groups <= max_output_groups);269// Only choose targets below max_spendable270const CAmount target{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(1, std::max(CAmount{1}, max_spendable - coin_params.m_cost_of_change))};272// Brute force optimal solution (lowest waste, but cannot be superset of another solution)273std::vector<uint32_t> solutions;274CAmount best_waste{std::numeric_limits<int64_t>::max()};275int best_weight{std::numeric_limits<int>::max()};276for (uint32_t pattern = 1; (pattern >> num_groups) == 0; ++pattern) {277// BnB does not permit adding more inputs to a solution, i.e. a superset of a solution cannot ever be a solution.278// The search pattern guarantees that any superset will only be visited after all its subsets have been traversed.279bool is_superset = false;280for (uint32_t sol : solutions) {281if ((pattern & sol) == sol) {282is_superset = true;283break;284}285}286if (is_superset) {287continue;288}290CAmount subset_amount{0};291CAmount subset_waste{0};292int subset_weight{0};293for (unsigned i = 0; i < num_groups; ++i) {294if ((pattern >> i) & 1) {295subset_amount += group_pos[i].GetSelectionAmount();296subset_waste += group_pos[i].fee - group_pos[i].long_term_fee;297subset_weight += group_pos[i].m_weight;298}299}300if (subset_amount >= target && subset_amount <= target + coin_params.m_cost_of_change) {301solutions.push_back(pattern);302// Add the excess (overselection that gets dropped to fees) to waste score303CAmount excess = subset_amount - target;304subset_waste += excess;305SelectionResult result_bf(target, SelectionAlgorithm::MANUAL);307for (unsigned i = 0; i < num_groups; ++i) {308if ((pattern >> i) & 1) {309result_bf.AddInput(group_pos[i]);310}311}312if (subset_waste < best_waste) {313best_waste = subset_waste;314result_bf.RecalculateWaste(coin_params.min_viable_change, coin_params.m_cost_of_change, coin_params.m_change_fee);315assert(result_bf.GetWaste() == best_waste);316best_weight = subset_weight;317}318}319}321int high_max_selection_weight = fuzzed_data_provider.ConsumeIntegralInRange<int>(best_weight, std::numeric_limits<int>::max());322auto result_bnb = SelectCoinsBnB(group_pos, target, coin_params.m_cost_of_change, high_max_selection_weight);324if (!solutions.size() || !result_bnb) {325// Either both BnB and Brute Force find a solution or neither does.326assert(!result_bnb == !solutions.size());327} else {328// If brute forcing found a solution with an acceptable weight, BnB must find at least one solution with at most 16 output groups329assert(result_bnb);330result_bnb->RecalculateWaste(coin_params.min_viable_change, coin_params.m_cost_of_change, coin_params.m_change_fee);331assert(result_bnb->GetWeight() <= high_max_selection_weight);332assert(result_bnb->GetSelectedEffectiveValue() >= target);333assert(result_bnb->GetSelectedEffectiveValue() <= target + coin_params.m_cost_of_change);334assert(best_waste <= result_bnb->GetWaste());335if (result_bnb->GetAlgoCompleted()) {336// If BnB exhausted the search space, it must return an optimal solution (tied on waste score)337assert(best_waste == result_bnb->GetWaste());338}339}340}342enum class CoinSelectionAlgorithm {343BNB,344SRD,345KNAPSACK,346};348template<CoinSelectionAlgorithm Algorithm>349void FuzzCoinSelectionAlgorithm(std::span<const uint8_t> buffer) {350SeedRandomStateForTest(SeedRand::ZEROS);351FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};352std::vector<COutput> utxo_pool;354const CFeeRate long_term_fee_rate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};355const CFeeRate effective_fee_rate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};356// Discard feerate must be at least dust relay feerate357const CFeeRate discard_fee_rate{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(DUST_RELAY_TX_FEE, COIN)};358const CAmount target{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(1, MAX_MONEY)};359const bool subtract_fee_outputs{fuzzed_data_provider.ConsumeBool()};361FastRandomContext fast_random_context{ConsumeUInt256(fuzzed_data_provider)};362CoinSelectionParams coin_params{fast_random_context};363coin_params.m_subtract_fee_outputs = subtract_fee_outputs;364coin_params.m_long_term_feerate = long_term_fee_rate;365coin_params.m_effective_feerate = effective_fee_rate;366coin_params.change_output_size = fuzzed_data_provider.ConsumeIntegralInRange(1, MAX_SCRIPT_SIZE);367coin_params.m_change_fee = effective_fee_rate.GetFee(coin_params.change_output_size);368coin_params.m_discard_feerate = discard_fee_rate;369coin_params.change_spend_size = fuzzed_data_provider.ConsumeIntegralInRange<int>(41, 1000);370const auto change_spend_fee{coin_params.m_discard_feerate.GetFee(coin_params.change_spend_size)};371coin_params.m_cost_of_change = coin_params.m_change_fee + change_spend_fee;372CScript change_out_script = CScript() << std::vector<unsigned char>(coin_params.change_output_size, OP_TRUE);373const auto dust{GetDustThreshold(CTxOut{/*nValueIn=*/0, change_out_script}, coin_params.m_discard_feerate)};374coin_params.min_viable_change = std::max(change_spend_fee + 1, dust);376int next_locktime{0};377CAmount total_balance{CreateCoins(fuzzed_data_provider, utxo_pool, coin_params, next_locktime)};379std::vector<OutputGroup> group_pos;380GroupCoins(fuzzed_data_provider, utxo_pool, coin_params, /*positive_only=*/true, group_pos);382int max_selection_weight = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max());384std::optional<SelectionResult> result;386if constexpr (Algorithm == CoinSelectionAlgorithm::BNB) {387if (!coin_params.m_subtract_fee_outputs) {388auto result_bnb = SelectCoinsBnB(group_pos, target, coin_params.m_cost_of_change, max_selection_weight);389if (result_bnb) {390result = *result_bnb;391assert(result_bnb->GetChange(coin_params.min_viable_change, coin_params.m_change_fee) == 0);392assert(result_bnb->GetSelectedValue() >= target);393assert(result_bnb->GetWeight() <= max_selection_weight);394(void)result_bnb->GetShuffledInputVector();395(void)result_bnb->GetInputSet();396}397}398}400if constexpr (Algorithm == CoinSelectionAlgorithm::SRD) {401auto result_srd = SelectCoinsSRD(group_pos, target, coin_params.m_change_fee, fast_random_context, max_selection_weight);402if (result_srd) {403result = *result_srd;404assert(result_srd->GetSelectedValue() >= target);405assert(result_srd->GetChange(CHANGE_LOWER, coin_params.m_change_fee) > 0);406assert(result_srd->GetWeight() <= max_selection_weight);407result_srd->SetBumpFeeDiscount(ConsumeMoney(fuzzed_data_provider));408result_srd->RecalculateWaste(coin_params.min_viable_change, coin_params.m_cost_of_change, coin_params.m_change_fee);409(void)result_srd->GetShuffledInputVector();410(void)result_srd->GetInputSet();411}412}414if constexpr (Algorithm == CoinSelectionAlgorithm::KNAPSACK) {415std::vector<OutputGroup> group_all;416GroupCoins(fuzzed_data_provider, utxo_pool, coin_params, /*positive_only=*/false, group_all);418for (const OutputGroup& group : group_all) {419const CoinEligibilityFilter filter{fuzzed_data_provider.ConsumeIntegral<int>(), fuzzed_data_provider.ConsumeIntegral<int>(), fuzzed_data_provider.ConsumeIntegral<uint64_t>()};420(void)group.EligibleForSpending(filter);421}423CAmount change_target{GenerateChangeTarget(target, coin_params.m_change_fee, fast_random_context)};424auto result_knapsack = KnapsackSolver(group_all, target, change_target, fast_random_context, max_selection_weight);425// If the total balance is sufficient for the target and we are not using426// effective values, Knapsack should always find a solution (unless the selection exceeded the max tx weight).427if (total_balance >= target && subtract_fee_outputs && !HasErrorMsg(result_knapsack)) {428assert(result_knapsack);429}430if (result_knapsack) {431result = *result_knapsack;432assert(result_knapsack->GetSelectedValue() >= target);433assert(result_knapsack->GetWeight() <= max_selection_weight);434result_knapsack->SetBumpFeeDiscount(ConsumeMoney(fuzzed_data_provider));435result_knapsack->RecalculateWaste(coin_params.min_viable_change, coin_params.m_cost_of_change, coin_params.m_change_fee);436(void)result_knapsack->GetShuffledInputVector();437(void)result_knapsack->GetInputSet();438}439}441std::vector<COutput> utxos;442CAmount new_total_balance{CreateCoins(fuzzed_data_provider, utxos, coin_params, next_locktime)};443if (new_total_balance > 0) {444OutputSet new_utxo_pool;445for (const auto& utxo : utxos) {446new_utxo_pool.insert(std::make_shared<COutput>(utxo));447}448if (result) {449const auto weight{result->GetWeight()};450result->AddInputs(new_utxo_pool, subtract_fee_outputs);451assert(result->GetWeight() > weight);452}453}455std::vector<COutput> manual_inputs;456CAmount manual_balance{CreateCoins(fuzzed_data_provider, manual_inputs, coin_params, next_locktime)};457if (manual_balance == 0) return;458auto manual_selection{ManualSelection(manual_inputs, manual_balance, coin_params.m_subtract_fee_outputs)};459if (result) {460const CAmount old_target{result->GetTarget()};461const OutputSet input_set{result->GetInputSet()};462const int old_weight{result->GetWeight()};463result->Merge(manual_selection);464assert(result->GetInputSet().size() == input_set.size() + manual_inputs.size());465assert(result->GetTarget() == old_target + manual_selection.GetTarget());466assert(result->GetWeight() == old_weight + manual_selection.GetWeight());467}468}470FUZZ_TARGET(coinselection_bnb) {471FuzzCoinSelectionAlgorithm<CoinSelectionAlgorithm::BNB>(buffer);472}474FUZZ_TARGET(coinselection_srd) {475FuzzCoinSelectionAlgorithm<CoinSelectionAlgorithm::SRD>(buffer);476}478FUZZ_TARGET(coinselection_knapsack) {479FuzzCoinSelectionAlgorithm<CoinSelectionAlgorithm::KNAPSACK>(buffer);480}482} // namespace wallet
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.