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.
501});503const Txid& truc_txid = highest_value_truc_tx->first;504for (const auto& [type, output] : unconfirmed_truc_coins) {505if (output.outpoint.hash == truc_txid) {506result.Add(type, output);507}508}509}511if (feerate.has_value()) {512std::map<COutPoint, CAmount> map_of_bump_fees = wallet.chain().calculateIndividualBumpFees(outpoints, feerate.value());514for (auto& [_, outputs] : result.coins) {515for (auto& output : outputs) {516output.ApplyBumpFee(map_of_bump_fees.at(output.outpoint));517}518}519}521return result;522}524const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const COutPoint& outpoint)525{526AssertLockHeld(wallet.cs_wallet);527const CWalletTx* wtx{Assert(wallet.GetWalletTx(outpoint.hash))};529const CTransaction* ptx = wtx->GetTx().get();530int n = outpoint.n;531while (OutputIsChange(wallet, ptx->vout[n]) && ptx->vin.size() > 0) {532const COutPoint& prevout = ptx->vin[0].prevout;533const CWalletTx* it = wallet.GetWalletTx(prevout.hash);534if (!it || it->GetTx()->vout.size() <= prevout.n ||535!wallet.IsMine(it->GetTx()->vout[prevout.n])) {536break;537}538ptx = it->GetTx().get();539n = prevout.n;540}541return ptx->vout[n];542}544std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)545{546AssertLockHeld(wallet.cs_wallet);548std::map<CTxDestination, std::vector<COutput>> result;550CCoinControl coin_control;551CoinFilterParams coins_params;552coins_params.skip_locked = false;553for (const COutput& coin : AvailableCoins(wallet, &coin_control, /*feerate=*/std::nullopt, coins_params).All()) {554CTxDestination address;555if (!ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) {556// For backwards compatibility, we convert P2PK output scripts into PKHash destinations557if (auto pk_dest = std::get_if<PubKeyDestination>(&address)) {558address = PKHash(pk_dest->GetPubKey());559} else {560continue;561}562}563result[address].emplace_back(coin);564}565return result;566}568FilteredOutputGroups GroupOutputs(const CWallet& wallet,569const CoinsResult& coins,570const CoinSelectionParams& coin_sel_params,571const std::vector<SelectionFilter>& filters,572std::vector<OutputGroup>& ret_discarded_groups)573{574FilteredOutputGroups filtered_groups;576if (!coin_sel_params.m_avoid_partial_spends) {577// Allowing partial spends means no grouping. Each COutput gets its own OutputGroup578for (const auto& [type, outputs] : coins.coins) {579for (const COutput& output : outputs) {580// Get mempool info581size_t ancestors, cluster_count;582wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, cluster_count);584// Create a new group per output and add it to the all groups vector585OutputGroup group(coin_sel_params);586group.Insert(std::make_shared<COutput>(output), ancestors, cluster_count);588// Each filter maps to a different set of groups589bool accepted = false;590for (const auto& sel_filter : filters) {591const auto& filter = sel_filter.filter;592if (!group.EligibleForSpending(filter)) continue;593filtered_groups[filter].Push(group, type, /*insert_positive=*/true, /*insert_mixed=*/true);594accepted = true;595}596if (!accepted) ret_discarded_groups.emplace_back(group);597}598}599return filtered_groups;600}602// We want to combine COutputs that have the same scriptPubKey into single OutputGroups603// except when there are more than OUTPUT_GROUP_MAX_ENTRIES COutputs grouped in an OutputGroup.604// To do this, we maintain a map where the key is the scriptPubKey and the value is a vector of OutputGroups.605// For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput is added606// to the last OutputGroup in the vector for the scriptPubKey. When the last OutputGroup has607// OUTPUT_GROUP_MAX_ENTRIES COutputs, a new OutputGroup is added to the end of the vector.608typedef std::map<std::pair<CScript, OutputType>, std::vector<OutputGroup>> ScriptPubKeyToOutgroup;609const auto& insert_output = [&](610const std::shared_ptr<COutput>& output, OutputType type, size_t ancestors, size_t cluster_count,611ScriptPubKeyToOutgroup& groups_map) {612std::vector<OutputGroup>& groups = groups_map[std::make_pair(output->txout.scriptPubKey,type)];614if (groups.size() == 0) {615// No OutputGroups for this scriptPubKey yet, add one616groups.emplace_back(coin_sel_params);617}619// Get the last OutputGroup in the vector so that we can add the COutput to it620// A pointer is used here so that group can be reassigned later if it is full.621OutputGroup* group = &groups.back();623// Check if this OutputGroup is full. We limit to OUTPUT_GROUP_MAX_ENTRIES when using -avoidpartialspends624// to avoid surprising users with very high fees.625if (group->m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) {626// The last output group is full, add a new group to the vector and use that group for the insertion627groups.emplace_back(coin_sel_params);628group = &groups.back();629}631group->Insert(output, ancestors, cluster_count);632};634ScriptPubKeyToOutgroup spk_to_groups_map;635ScriptPubKeyToOutgroup spk_to_positive_groups_map;636for (const auto& [type, outs] : coins.coins) {637for (const COutput& output : outs) {638size_t ancestors, cluster_count;639wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, cluster_count);641const auto& shared_output = std::make_shared<COutput>(output);642// Filter for positive only before adding the output643if (output.GetEffectiveValue() > 0) {644insert_output(shared_output, type, ancestors, cluster_count, spk_to_positive_groups_map);645}647// 'All' groups648insert_output(shared_output, type, ancestors, cluster_count, spk_to_groups_map);649}650}652// Now we go through the entire maps and pull out the OutputGroups653const auto& push_output_groups = [&](const ScriptPubKeyToOutgroup& groups_map, bool positive_only) {654for (const auto& [script, groups] : groups_map) {655// Go through the vector backwards. This allows for the first item we deal with being the partial group.656for (auto group_it = groups.rbegin(); group_it != groups.rend(); group_it++) {657const OutputGroup& group = *group_it;659// Each filter maps to a different set of groups660bool accepted = false;661for (const auto& sel_filter : filters) {662const auto& filter = sel_filter.filter;663if (!group.EligibleForSpending(filter)) continue;665// Don't include partial groups if there are full groups too and we don't want partial groups666if (group_it == groups.rbegin() && groups.size() > 1 && !filter.m_include_partial_groups) {667continue;668}670OutputType type = script.second;671// Either insert the group into the positive-only groups or the mixed ones.672filtered_groups[filter].Push(group, type, positive_only, /*insert_mixed=*/!positive_only);673accepted = true;674}675if (!accepted) ret_discarded_groups.emplace_back(group);676}677}678};680push_output_groups(spk_to_groups_map, /*positive_only=*/ false);681push_output_groups(spk_to_positive_groups_map, /*positive_only=*/ true);683return filtered_groups;684}686FilteredOutputGroups GroupOutputs(const CWallet& wallet,687const CoinsResult& coins,688const CoinSelectionParams& params,689const std::vector<SelectionFilter>& filters)690{691std::vector<OutputGroup> unused;692return GroupOutputs(wallet, coins, params, filters, unused);693}695// Returns true if the result contains an error and the message is not empty696static bool HasErrorMsg(const util::Result<SelectionResult>& res) { return !util::ErrorString(res).empty(); }698util::Result<SelectionResult> AttemptSelection(interfaces::Chain& chain, const CAmount& nTargetValue, OutputGroupTypeMap& groups,699const CoinSelectionParams& coin_selection_params, bool allow_mixed_output_types)700{701// Run coin selection on each OutputType and compute the Waste Metric702std::vector<SelectionResult> results;703for (auto& [type, group] : groups.groups_by_type) {704auto result{ChooseSelectionResult(chain, nTargetValue, group, coin_selection_params)};705// If any specific error message appears here, then something particularly wrong happened.706if (HasErrorMsg(result)) return result; // So let's return the specific error.707// Append the favorable result.708if (result) results.push_back(*result);709}710// If we have at least one solution for funding the transaction without mixing, choose the minimum one according to waste metric711// and return the result712if (results.size() > 0) return *std::min_element(results.begin(), results.end());714// If we can't fund the transaction from any individual OutputType, run coin selection one last time715// over all available coins, which would allow mixing.716// If TypesCount() <= 1, there is nothing to mix.717if (allow_mixed_output_types && groups.TypesCount() > 1) {718return ChooseSelectionResult(chain, nTargetValue, groups.all_groups, coin_selection_params);719}720// Either mixing is not allowed and we couldn't find a solution from any single OutputType, or mixing was allowed and we still couldn't721// find a solution using all available coins722return util::Error();723};725util::Result<SelectionResult> ChooseSelectionResult(interfaces::Chain& chain, const CAmount& nTargetValue, Groups& groups, const CoinSelectionParams& coin_selection_params)726{727// Vector of results. We will choose the best one based on waste.728std::vector<SelectionResult> results;729std::vector<util::Result<SelectionResult>> errors;730auto append_error = [&] (util::Result<SelectionResult>&& result) {731// If any specific error message appears here, then something different from a simple "no selection found" happened.732// Let's save it, so it can be retrieved to the user if no other selection algorithm succeeded.733if (HasErrorMsg(result)) {734errors.emplace_back(std::move(result));735}736};738// Maximum allowed weight for selected coins.739int max_transaction_weight = coin_selection_params.m_max_tx_weight.value_or(MAX_STANDARD_TX_WEIGHT);740int tx_weight_no_input = coin_selection_params.tx_noinputs_size * WITNESS_SCALE_FACTOR;741int max_selection_weight = max_transaction_weight - tx_weight_no_input;742if (max_selection_weight <= 0) {743return util::Error{_("Maximum transaction weight is less than transaction weight without inputs")};744}746// SFFO frequently causes issues in the context of changeless input sets: skip BnB when SFFO is active747if (!coin_selection_params.m_subtract_fee_outputs) {748if (auto bnb_result{SelectCoinsBnB(groups.positive_group, nTargetValue, coin_selection_params.m_cost_of_change, max_selection_weight)}) {749results.push_back(*bnb_result);750} else append_error(std::move(bnb_result));