Repository: bitcoin/bitcoin
File: src/wallet/spend.cpp (C++)

Code before:
        }
    };

    // Maximum allowed weight
    int max_inputs_weight = MAX_STANDARD_TX_WEIGHT - (coin_selection_params.tx_noinputs_size * WITNESS_SCALE_FACTOR);

    // SFFO frequently causes issues in the context of changeless input sets: skip BnB when SFFO is active
    if (!coin_selection_params.m_subtract_fee_outputs) {
        if (auto bnb_result{SelectCoinsBnB(groups.positive_group, nTargetValue, coin_selection_params.m_cost_of_change, max_inputs_weight)}) {
            results.push_back(*bnb_result);
        } else append_error(std::move(bnb_result));
    }

    // As Knapsack and SRD can create change, also deduce change weight.
    max_inputs_weight -= (coin_selection_params.change_output_size * WITNESS_SCALE_FACTOR);

    // The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here.
    if (auto knapsack_result{KnapsackSolver(groups.mixed_group, nTargetValue, coin_selection_params.m_min_change_target, coin_selection_params.rng_fast, max_inputs_weight)}) {
        results.push_back(*knapsack_result);
    } else append_error(std::move(knapsack_result));

    if (coin_selection_params.m_effective_feerate > CFeeRate{3 * coin_selection_params.m_long_term_feerate}) { // Minimize input set for feerates of at least 3×LTFRE (default: 30 ṩ/vB+)
        if (auto cg_result{CoinGrinder(groups.positive_group, nTargetValue, coin_selection_params.m_min_change_target, max_inputs_weight)}) {
            cg_result->RecalculateWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
            results.push_back(*cg_result);
        } else {
            append_error(std::move(cg_result));
        }
    }

    if (auto srd_result{SelectCoinsSRD(groups.positive_group, nTargetValue, coin_selection_params.m_change_fee, coin_selection_params.rng_fast, max_inputs_weight)}) {
        results.push_back(*srd_result);
    } else append_error(std::move(srd_result));

    if (results.empty()) {
        // No solution found, retrieve the first explicit error (if any).
        // future: add 'severity level' to errors so the worst one can be retrieved instead of the first one.
        return errors.empty() ? util::Error() : std::move(errors.front());
    }

    // If the chosen input set has unconfirmed inputs, check for synergies from overlapping ancestry
    for (auto& result : results) {
        std::vector<COutPoint> outpoints;
        std::set<std::shared_ptr<COutput>> coins = result.GetInputSet();
        CAmount summed_bump_fees = 0;
        for (auto& coin : coins) {
            if (coin->depth > 0) continue; // Bump fees only exist for unconfirmed inputs
            outpoints.push_back(coin->outpoint);
            summed_bump_fees += coin->ancestor_bump_fees;
        }
        std::optional<CAmount> combined_bump_fee = chain.calculateCombinedBumpFee(outpoints, coin_selection_params.m_effective_feerate);

Review comment (suggestion):
tiny nit:
```suggestion
    max_selection_weight -= change_outputs_weight;
```

Code after:
        }
    };

    // Maximum allowed weight for selected coins.
    int max_transaction_weight = coin_selection_params.m_max_tx_weight.value_or(MAX_STANDARD_TX_WEIGHT);
    int tx_weight_no_input = coin_selection_params.tx_noinputs_size * WITNESS_SCALE_FACTOR;
    int max_selection_weight = max_transaction_weight - tx_weight_no_input;
    if (max_selection_weight <= 0) {
        return util::Error{_("Maximum transaction weight is less than transaction weight without inputs")};
    }

    // SFFO frequently causes issues in the context of changeless input sets: skip BnB when SFFO is active
    if (!coin_selection_params.m_subtract_fee_outputs) {
        if (auto bnb_result{SelectCoinsBnB(groups.positive_group, nTargetValue, coin_selection_params.m_cost_of_change, max_selection_weight)}) {
            results.push_back(*bnb_result);
        } else append_error(std::move(bnb_result));
    }

    // Deduct change weight because remaining Coin Selection algorithms can create change output
    int change_outputs_weight = coin_selection_params.change_output_size * WITNESS_SCALE_FACTOR;
    max_selection_weight -= change_outputs_weight;
    if (max_selection_weight < 0 && results.empty()) {
        return util::Error{_("Maximum transaction weight is too low, can not accommodate change output")};
    }

    // The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here.
    if (auto knapsack_result{KnapsackSolver(groups.mixed_group, nTargetValue, coin_selection_params.m_min_change_target, coin_selection_params.rng_fast, max_selection_weight)}) {
        results.push_back(*knapsack_result);
    } else append_error(std::move(knapsack_result));

    if (coin_selection_params.m_effective_feerate > CFeeRate{3 * coin_selection_params.m_long_term_feerate}) { // Minimize input set for feerates of at least 3×LTFRE (default: 30 ṩ/vB+)
        if (auto cg_result{CoinGrinder(groups.positive_group, nTargetValue, coin_selection_params.m_min_change_target, max_selection_weight)}) {
            cg_result->RecalculateWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
            results.push_back(*cg_result);
        } else {
            append_error(std::move(cg_result));
        }
    }

    if (auto srd_result{SelectCoinsSRD(groups.positive_group, nTargetValue, coin_selection_params.m_change_fee, coin_selection_params.rng_fast, max_selection_weight)}) {
        results.push_back(*srd_result);
    } else append_error(std::move(srd_result));

    if (results.empty()) {
        // No solution found, retrieve the first explicit error (if any).
        // future: add 'severity level' to errors so the worst one can be retrieved instead of the first one.
        return errors.empty() ? util::Error() : std::move(errors.front());
    }

    // If the chosen input set has unconfirmed inputs, check for synergies from overlapping ancestry
    for (auto& result : results) {