LCOV - code coverage report
Current view: top level - src/wallet - coinselection.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 274 305 89.8 %
Date: 2026-06-25 07:23:43 Functions: 24 29 82.8 %

          Line data    Source code
       1             : // Copyright (c) 2017-2021 The Bitcoin Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <wallet/coinselection.h>
       6             : 
       7             : #include <policy/feerate.h>
       8             : #include <util/check.h>
       9             : #include <util/system.h>
      10             : #include <util/moneystr.h>
      11             : 
      12             : #include <coinjoin/common.h>
      13             : 
      14             : #include <numeric>
      15             : #include <optional>
      16             : #include <queue>
      17             : 
      18             : namespace wallet {
      19          12 : static util::Result<SelectionResult> ErrorMaxWeightExceeded()
      20             : {
      21          12 :     return util::Error{_("The inputs size exceeds the maximum weight. "
      22             :                          "Please try sending a smaller amount or manually consolidating your wallet's UTXOs")};
      23           0 : }
      24             : 
      25      185292 : static int GetSelectionWeight(const OutputGroup& group)
      26             : {
      27      370584 :     return std::accumulate(group.m_outputs.cbegin(), group.m_outputs.cend(), 0, [](int sum, const COutput& coin) {
      28      185292 :         return sum + std::max(coin.input_bytes, 0);
      29             :     });
      30             : }
      31             : 
      32        3267 : static int GetSelectionWeight(const SelectionResult& result)
      33             : {
      34      151100 :     return std::accumulate(result.GetInputSet().cbegin(), result.GetInputSet().cend(), 0, [](int sum, const COutput& coin) {
      35      147833 :         return sum + std::max(coin.input_bytes, 0);
      36             :     });
      37             : }
      38             : 
      39             : // Descending order comparator
      40             : struct {
      41     1909766 :     bool operator()(const OutputGroup& a, const OutputGroup& b) const
      42             :     {
      43     1909766 :         return a.GetSelectionAmount() > b.GetSelectionAmount();
      44             :     }
      45             : } descending;
      46             : 
      47             : /*
      48             :  * This is the Branch and Bound Coin Selection algorithm designed by Murch. It searches for an input
      49             :  * set that can pay for the spending target and does not exceed the spending target by more than the
      50             :  * cost of creating and spending a change output. The algorithm uses a depth-first search on a binary
      51             :  * tree. In the binary tree, each node corresponds to the inclusion or the omission of a UTXO. UTXOs
      52             :  * are sorted by their effective values and the tree is explored deterministically per the inclusion
      53             :  * branch first. At each node, the algorithm checks whether the selection is within the target range.
      54             :  * While the selection has not reached the target range, more UTXOs are included. When a selection's
      55             :  * value exceeds the target range, the complete subtree deriving from this selection can be omitted.
      56             :  * At that point, the last included UTXO is deselected and the corresponding omission branch explored
      57             :  * instead. The search ends after the complete tree has been searched or after a limited number of tries.
      58             :  *
      59             :  * The search continues to search for better solutions after one solution has been found. The best
      60             :  * solution is chosen by minimizing the waste metric. The waste metric is defined as the cost to
      61             :  * spend the current inputs at the given fee rate minus the long term expected cost to spend the
      62             :  * inputs, plus the amount by which the selection exceeds the spending target:
      63             :  *
      64             :  * waste = selectionTotal - target + inputs × (currentFeeRate - longTermFeeRate)
      65             :  *
      66             :  * The algorithm uses two additional optimizations. A lookahead keeps track of the total value of
      67             :  * the unexplored UTXOs. A subtree is not explored if the lookahead indicates that the target range
      68             :  * cannot be reached. Further, it is unnecessary to test equivalent combinations. This allows us
      69             :  * to skip testing the inclusion of UTXOs that match the effective value and waste of an omitted
      70             :  * predecessor.
      71             :  *
      72             :  * The Branch and Bound algorithm is described in detail in Murch's Master Thesis:
      73             :  * https://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf
      74             :  *
      75             :  * @param const std::vector<OutputGroup>& utxo_pool The set of UTXO groups that we are choosing from.
      76             :  *        These UTXO groups will be sorted in descending order by effective value and the OutputGroups'
      77             :  *        values are their effective values.
      78             :  * @param const CAmount& selection_target This is the value that we want to select. It is the lower
      79             :  *        bound of the range.
      80             :  * @param const CAmount& cost_of_change This is the cost of creating and spending a change output.
      81             :  *        This plus selection_target is the upper bound of the range.
      82             :  * @returns The result of this coin selection algorithm, or std::nullopt
      83             :  */
      84             : 
      85             : static const size_t TOTAL_TRIES = 100000;
      86             : 
      87       22374 : util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change,
      88             :                                              int max_weight)
      89             : {
      90       22374 :     if (utxo_pool.empty()) return util::Error();
      91             : 
      92         113 :     SelectionResult result(selection_target, SelectionAlgorithm::BNB);
      93         113 :     CAmount curr_value = 0;
      94         113 :     std::vector<size_t> curr_selection; // selected utxo indexes
      95         113 :     int curr_selection_weight = 0; // sum of selected utxo weight
      96             : 
      97             :     // Calculate curr_available_value
      98         113 :     CAmount curr_available_value = 0;
      99       51816 :     for (const OutputGroup& utxo : utxo_pool) {
     100             :         // Assert that this utxo is not negative. It should never be negative,
     101             :         // effective value calculation should have removed it
     102       51703 :         assert(utxo.GetSelectionAmount() > 0);
     103       51703 :         curr_available_value += utxo.GetSelectionAmount();
     104             :     }
     105         113 :     if (curr_available_value < selection_target) {
     106           2 :         return util::Error();
     107             :     }
     108             : 
     109             :     // Sort the utxo_pool
     110         111 :     std::sort(utxo_pool.begin(), utxo_pool.end(), descending);
     111             : 
     112         111 :     CAmount curr_waste = 0;
     113         111 :     std::vector<size_t> best_selection;
     114         111 :     CAmount best_waste = MAX_MONEY;
     115             : 
     116         111 :     bool is_feerate_high = utxo_pool.at(0).fee > utxo_pool.at(0).long_term_fee;
     117         111 :     bool max_tx_weight_exceeded = false;
     118             : 
     119             :     // Depth First search loop for choosing the UTXOs
     120      285301 :     for (size_t curr_try = 0, utxo_pool_index = 0; curr_try < TOTAL_TRIES; ++curr_try, ++utxo_pool_index) {
     121             :         // Conditions for starting a backtrack
     122      285299 :         bool backtrack = false;
     123      285299 :         if (curr_value + curr_available_value < selection_target || // Cannot possibly reach target with the amount remaining in the curr_available_value.
     124      248810 :             curr_value > selection_target + cost_of_change || // Selected value is out of range, go back and try other branch
     125      192610 :             (curr_waste > best_waste && is_feerate_high)) { // Don't select things which we know will be more wasteful if the waste is increasing
     126       92689 :             backtrack = true;
     127      285299 :         } else if (curr_selection_weight > max_weight) { // Exceeding weight for standard tx, cannot find more solutions by adding more inputs
     128           0 :             max_tx_weight_exceeded = true;
     129           0 :             backtrack = true;
     130      192610 :         } else if (curr_value >= selection_target) {       // Selected value is within range
     131          11 :             curr_waste += (curr_value - selection_target); // This is the excess value which is added to the waste for the below comparison
     132             :             // Adding another UTXO after this check could bring the waste down if the long term fee is higher than the current fee.
     133             :             // However we are not going to explore that because this optimization for the waste is only done when we have hit our target
     134             :             // value. Adding any more UTXOs will be just burning the UTXO; it will go entirely to fees. Thus we aren't going to
     135             :             // explore any more UTXOs to avoid burning money like that.
     136          11 :             if (curr_waste <= best_waste) {
     137          11 :                 best_selection = curr_selection;
     138          11 :                 best_waste = curr_waste;
     139          11 :             }
     140          11 :             curr_waste -= (curr_value - selection_target); // Remove the excess value as we will be selecting different coins now
     141          11 :             backtrack = true;
     142          11 :         }
     143             : 
     144      285299 :         if (backtrack) { // Backtracking, moving backwards
     145       92700 :             if (curr_selection.empty()) { // We have walked back to the first utxo and no branch is untraversed. All solutions searched
     146         109 :                 break;
     147             :             }
     148             : 
     149             :             // Add omitted UTXOs back to lookahead before traversing the omission branch of last included UTXO.
     150      233536 :             for (--utxo_pool_index; utxo_pool_index > curr_selection.back(); --utxo_pool_index) {
     151      140945 :                 curr_available_value += utxo_pool.at(utxo_pool_index).GetSelectionAmount();
     152      140945 :             }
     153             : 
     154             :             // Output was included on previous iterations, try excluding now.
     155       92591 :             assert(utxo_pool_index == curr_selection.back());
     156       92591 :             OutputGroup& utxo = utxo_pool.at(utxo_pool_index);
     157       92591 :             curr_value -= utxo.GetSelectionAmount();
     158       92591 :             curr_waste -= utxo.fee - utxo.long_term_fee;
     159       92591 :             curr_selection_weight -= GetSelectionWeight(utxo);
     160       92591 :             curr_selection.pop_back();
     161       92591 :         } else { // Moving forwards, continuing down this branch
     162      192599 :             OutputGroup& utxo = utxo_pool.at(utxo_pool_index);
     163             : 
     164             :             // Remove this utxo from the curr_available_value utxo amount
     165      192599 :             curr_available_value -= utxo.GetSelectionAmount();
     166             : 
     167      292587 :             if (curr_selection.empty() ||
     168             :                 // The previous index is included and therefore not relevant for exclusion shortcut
     169      190970 :                 (utxo_pool_index - 1) == curr_selection.back() ||
     170             :                 // Avoid searching a branch if the previous UTXO has the same value and same waste and was excluded.
     171             :                 // Since the ratio of fee to long term fee is the same, we only need to check if one of those values match in order to know that the waste is the same.
     172      154570 :                 utxo.GetSelectionAmount() != utxo_pool.at(utxo_pool_index - 1).GetSelectionAmount() ||
     173       99988 :                 utxo.fee != utxo_pool.at(utxo_pool_index - 1).fee)
     174             :             {
     175             :                 // Inclusion branch first (Largest First Exploration)
     176       92611 :                 curr_selection.push_back(utxo_pool_index);
     177       92611 :                 curr_value += utxo.GetSelectionAmount();
     178       92611 :                 curr_waste += utxo.fee - utxo.long_term_fee;
     179       92611 :                 curr_selection_weight += GetSelectionWeight(utxo);
     180       92611 :             }
     181             :         }
     182      285190 :     }
     183             : 
     184             :     // Check for solution
     185         111 :     if (best_selection.empty()) {
     186         103 :         return max_tx_weight_exceeded ? ErrorMaxWeightExceeded() : util::Error();
     187             :     }
     188             : 
     189             :     // Set output set
     190          37 :     for (const size_t& i : best_selection) {
     191          29 :         result.AddInput(utxo_pool.at(i));
     192             :     }
     193           8 :     result.ComputeAndSetWaste(CAmount{0});
     194           8 :     assert(best_waste == result.GetWaste());
     195             : 
     196           8 :     return result;
     197       22374 : }
     198             : 
     199             : class MinOutputGroupComparator
     200             : {
     201             : public:
     202         322 :     bool operator()(const OutputGroup& group1, const OutputGroup& group2) const
     203             :     {
     204         322 :         return group1.GetSelectionAmount() > group2.GetSelectionAmount();
     205             :     }
     206             : };
     207             : 
     208       22263 : util::Result<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng,
     209             :                                              int max_weight)
     210             : {
     211       22263 :     if (utxo_pool.empty()) return util::Error();
     212             : 
     213           3 :     SelectionResult result(target_value, SelectionAlgorithm::SRD);
     214           3 :     std::priority_queue<OutputGroup, std::vector<OutputGroup>, MinOutputGroupComparator> heap;
     215             : 
     216           3 :     std::vector<size_t> indexes;
     217           3 :     indexes.resize(utxo_pool.size());
     218           3 :     std::iota(indexes.begin(), indexes.end(), 0);
     219           3 :     Shuffle(indexes.begin(), indexes.end(), rng);
     220             : 
     221           3 :     CAmount selected_eff_value = 0;
     222           3 :     int weight = 0;
     223           3 :     bool max_tx_weight_exceeded = false;
     224          86 :     for (const size_t i : indexes) {
     225          84 :         const OutputGroup& group = utxo_pool.at(i);
     226          84 :         Assume(group.GetSelectionAmount() > 0);
     227             : 
     228          84 :         heap.push(group);
     229          84 :         selected_eff_value += group.GetSelectionAmount();
     230          84 :         weight += GetSelectionWeight(group);
     231             : 
     232          84 :         if (weight > max_weight) {
     233           6 :             max_tx_weight_exceeded = true;
     234           6 :             do {
     235           6 :                 const OutputGroup& to_remove_group = heap.top();
     236           6 :                 selected_eff_value -= to_remove_group.GetSelectionAmount();
     237           6 :                 weight -= GetSelectionWeight(to_remove_group);
     238           6 :                 heap.pop();
     239           6 :             } while (!heap.empty() && weight > max_weight);
     240           6 :         }
     241             : 
     242          84 :         if (selected_eff_value >= target_value) {
     243          45 :             while (!heap.empty()) {
     244          44 :                 result.AddInput(heap.top());
     245          44 :                 heap.pop();
     246             :             }
     247           1 :             return result;
     248             :         }
     249             :     }
     250           2 :     return max_tx_weight_exceeded ? ErrorMaxWeightExceeded() : util::Error();
     251       22263 : }
     252             : 
     253             : /** Find a subset of the OutputGroups that is at least as large as, but as close as possible to, the
     254             :  * target amount; solve subset sum.
     255             :  * param@[in]   groups          OutputGroups to choose from, sorted by value in descending order.
     256             :  * param@[in]   nTotalLower     Total (effective) value of the UTXOs in groups.
     257             :  * param@[in]   nTargetValue    Subset sum target, not including change.
     258             :  * param@[out]  vfBest          Boolean vector representing the subset chosen that is closest to
     259             :  *                              nTargetValue, with indices corresponding to groups. If the ith
     260             :  *                              entry is true, that means the ith group in groups was selected.
     261             :  * param@[out]  nBest           Total amount of subset chosen that is closest to nTargetValue.
     262             :  * param@[in]   iterations      Maximum number of tries.
     263             :  */
     264        6095 : static void ApproximateBestSubset(FastRandomContext& insecure_rand, const std::vector<OutputGroup>& groups,
     265             :                                   const CAmount& nTotalLower, const CAmount& nTargetValue,
     266             :                                   std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
     267             : {
     268        6095 :     std::vector<char> vfIncluded;
     269             : 
     270             :     // Worst case "best" approximation is just all of the groups.
     271        6095 :     vfBest.assign(groups.size(), true);
     272        6095 :     nBest = nTotalLower;
     273        6095 :     int nBestInputCount = 0;
     274             : 
     275     4876023 :     for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
     276             :     {
     277   391897962 :         vfIncluded.assign(groups.size(), false);
     278     4869928 :         CAmount nTotal = 0;
     279     4869928 :         int nTotalInputCount = 0;
     280     4869928 :         bool fReachedTarget = false;
     281    11995547 :         for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++)
     282             :         {
     283   428432144 :             for (unsigned int i = 0; i < groups.size(); i++)
     284             :             {
     285             :                 //The solver here uses a randomized algorithm,
     286             :                 //the randomness serves no real security purpose but is just
     287             :                 //needed to prevent degenerate behavior and it is important
     288             :                 //that the rng is fast. We do not use a constant random sequence,
     289             :                 //because there may be some privacy improvement by making
     290             :                 //the selection random.
     291   421306525 :                 if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i])
     292             :                 {
     293   598718036 :                     nTotal += groups[i].GetSelectionAmount();
     294   598718036 :                     ++nTotalInputCount;
     295   598718036 :                     vfIncluded[i] = true;
     296   598718036 :                     if (nTotal >= nTargetValue)
     297             :                     {
     298   575857344 :                         fReachedTarget = true;
     299             :                         // If the total is between nTargetValue and nBest, it's our new best
     300             :                         // approximation.
     301   575857344 :                         if (nTotal < nBest || (nTotal == nBest && nTotalInputCount < nBestInputCount))
     302             :                         {
     303   387052954 :                             nBest = nTotal;
     304   387052954 :                             nBestInputCount = nTotalInputCount;
     305   387052954 :                             vfBest = vfIncluded;
     306       24920 :                         }
     307   188829310 :                         nTotal -= groups[i].GetSelectionAmount();
     308   188829310 :                         --nTotalInputCount;
     309   188829310 :                         vfIncluded[i] = false;
     310   188829310 :                     }
     311   211690002 :                 }
     312   421306525 :             }
     313     7125619 :         }
     314     4869928 :     }
     315  1161078007 : }
     316             : 
     317             : struct CompareByPriority
     318             : {
     319           0 :     bool operator()(const OutputGroup& group1,
     320             :                     const OutputGroup& group2) const
     321             :     {
     322           0 :         return CoinJoin::CalculateAmountPriority(group1.m_value) > CoinJoin::CalculateAmountPriority(group2.m_value);
     323             :     }
     324             : };
     325             : 
     326             : // move denoms down
     327     1895630 : bool less_then_denom (const OutputGroup& group1, const OutputGroup& group2)
     328             : {
     329     1895630 :     bool found1 = false;
     330     1895630 :     bool found2 = false;
     331    11373780 :     for (const auto& d : CoinJoin::GetStandardDenominations()) // loop through predefined denoms
     332             :     {
     333     9478150 :         if(group1.m_value == d) found1 = true;
     334     9478150 :         if(group2.m_value == d) found2 = true;
     335             :     }
     336     1895630 :     return (!found1 && found2);
     337             : }
     338             : 
     339       32733 : util::Result<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue,
     340             :                                              CAmount change_target, FastRandomContext& rng, int max_weight, bool fFullyMixedOnly,
     341             :                                              CAmount maxTxFee)
     342             : {
     343       32733 :     if (groups.empty()) return util::Error();
     344             : 
     345       22547 :     SelectionResult result(nTargetValue, SelectionAlgorithm::KNAPSACK);
     346             : 
     347             :     // List of values less than target
     348       22547 :     std::optional<OutputGroup> lowest_larger;
     349             :     // Groups with selection amount smaller than the target and any change we might produce.
     350             :     // Don't include groups larger than this, because they will only cause us to overshoot.
     351       22547 :     std::vector<OutputGroup> applicable_groups;
     352       22547 :     CAmount nTotalLower = 0;
     353             : 
     354       22547 :     Shuffle(groups.begin(), groups.end(), rng);
     355             : 
     356       17675 :     int tryDenomStart = 0;
     357             : 
     358       17675 :     if (fFullyMixedOnly) {
     359             :         // larger denoms first
     360           0 :         std::sort(groups.rbegin(), groups.rend(), CompareByPriority());
     361             :         // we actually want denoms only, so let's skip "non-denom only" step
     362           0 :         tryDenomStart = 1;
     363             :         // no change is allowed
     364           0 :         change_target = 0;
     365           0 :     } else {
     366             :         // move denoms down on the list
     367             :         // try not to use denominated coins when not needed, save denoms for coinjoin
     368       17675 :         std::sort(groups.begin(), groups.end(), less_then_denom);
     369             :     }
     370             : 
     371             :     // try to find nondenom first to prevent unneeded spending of mixed coins
     372       18608 :     for (unsigned int tryDenom = tryDenomStart; tryDenom < 2; tryDenom++) {
     373       18608 :         LogPrint(BCLog::SELECTCOINS, "tryDenom: %d\n", tryDenom);
     374       18608 :         applicable_groups.clear();
     375       18608 :         nTotalLower = 0;
     376      864450 :         for (const OutputGroup& group : groups) {
     377      847069 :             if (tryDenom == 0 && CoinJoin::IsDenominatedAmount(group.m_value)) {
     378           2 :                 continue; // we don't want denom values on first run
     379             :             }
     380      847067 :             if (group.GetSelectionAmount() == nTargetValue) {
     381        1227 :                 result.AddInput(group);
     382        1227 :                 return result;
     383      845840 :             } else if (group.GetSelectionAmount() < nTargetValue + change_target) {
     384      396607 :                 applicable_groups.push_back(group);
     385      396607 :                 nTotalLower += group.GetSelectionAmount();
     386      845840 :             } else if (!lowest_larger || group.GetSelectionAmount() < lowest_larger->GetSelectionAmount()) {
     387       19942 :                 lowest_larger = group;
     388       19942 :             }
     389             :         }
     390             : 
     391       17381 :         if (nTotalLower == nTargetValue) {
     392        2759 :             for (const auto& group : applicable_groups) {
     393        2212 :                 result.AddInput(group);
     394             :             }
     395         547 :             return result;
     396             :         }
     397             : 
     398       16834 :         if (nTotalLower < nTargetValue) {
     399       13141 :             if (!lowest_larger) { // there is no input larger than nTargetValue
     400        1866 :                 if (tryDenom == 0)
     401             :                     // we didn't look at denom yet, let's do it
     402         933 :                     continue;
     403             :                 else
     404             :                     // we looked at everything possible and didn't find anything, no luck
     405         933 :                     return util::Error();
     406             :             }
     407       11275 :             result.AddInput(*lowest_larger);
     408             :             // There is no change in PS, so we know the fee beforehand,
     409             :             // can see if we exceeded the max fee and thus fail quickly.
     410       11275 :             if (!fFullyMixedOnly || (result.GetSelectedValue() - nTargetValue <= maxTxFee)) {
     411       11275 :                 return result;
     412             :             }
     413           0 :             return util::Error();
     414             :         }
     415             : 
     416             :         // nTotalLower > nTargetValue
     417        3693 :         break;
     418             :     }
     419             : 
     420             :     // Solve subset sum by stochastic approximation
     421        3693 :     std::sort(applicable_groups.begin(), applicable_groups.end(), descending);
     422        8565 :     std::vector<char> vfBest;
     423             :     CAmount nBest;
     424             : 
     425        8565 :     ApproximateBestSubset(rng, applicable_groups, nTotalLower, nTargetValue, vfBest, nBest);
     426        3693 :     if (nBest != nTargetValue && change_target != 0 && nTotalLower >= nTargetValue + change_target) {
     427        2402 :         ApproximateBestSubset(rng, applicable_groups, nTotalLower, nTargetValue + change_target, vfBest, nBest);
     428        2402 :     }
     429             : 
     430             :     // If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
     431             :     //                                   or the next bigger coin is closer), return the bigger coin
     432        5276 :     if (lowest_larger &&
     433        1780 :         ((nBest != nTargetValue && nBest < nTargetValue + change_target) || lowest_larger->GetSelectionAmount() <= nBest)) {
     434        2862 :         result.AddInput(*lowest_larger);
     435         426 :     } else {
     436        1913 :         std::string log_message{"Coin selection best subset: "};
     437      380062 :         for (unsigned int i = 0; i < applicable_groups.size(); i++) {
     438      376795 :             if (vfBest[i]) {
     439      146037 :                 result.AddInput(applicable_groups[i]);
     440      146037 :                 log_message += strprintf("%s ", FormatMoney(applicable_groups[i].m_value));
     441      146037 :             }
     442      376795 :         }
     443             : 
     444        3267 :         if (GetSelectionWeight(result) > max_weight) {
     445          12 :             if (!lowest_larger) return ErrorMaxWeightExceeded();
     446             : 
     447           1 :             result.Clear();
     448           1 :             result.AddInput(*lowest_larger);
     449           1 :         }
     450             : 
     451        3256 :         LogPrint(BCLog::SELECTCOINS, "%stotal %s\n", log_message, FormatMoney(nBest));
     452        3267 :     }
     453             : 
     454             :     // There is no change in PS, so we know the fee beforehand,
     455             :     // can see if we exceeded the max fee and thus fail quickly.
     456        3682 :     if (!fFullyMixedOnly || (result.GetSelectedValue() - nTargetValue <= maxTxFee)) {
     457        3682 :         return result;
     458             :     }
     459           0 :     return util::Error();
     460       47349 : }
     461             : 
     462             : /******************************************************************************
     463             : 
     464             :  OutputGroup
     465             : 
     466             :  ******************************************************************************/
     467             : 
     468     2658039 : void OutputGroup::Insert(const COutput& output, size_t ancestors, size_t descendants, bool positive_only) {
     469             :     // Filter for positive only here before adding the coin
     470     2658039 :     if (positive_only && output.GetEffectiveValue() <= 0) return;
     471             : 
     472     2657967 :     m_outputs.push_back(output);
     473     2657967 :     COutput& coin = m_outputs.back();
     474             : 
     475     2657967 :     fee += coin.GetFee();
     476             : 
     477     2657967 :     coin.long_term_fee = coin.input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.input_bytes);
     478     2657967 :     long_term_fee += coin.long_term_fee;
     479             : 
     480     2657967 :     effective_value += coin.GetEffectiveValue();
     481             : 
     482     2657967 :     m_from_me &= coin.from_me;
     483     2657967 :     m_value += coin.txout.nValue;
     484     2657967 :     m_depth = std::min(m_depth, coin.depth);
     485             :     // ancestors here express the number of ancestors the new coin will end up having, which is
     486             :     // the sum, rather than the max; this will overestimate in the cases where multiple inputs
     487             :     // have common ancestors
     488     2657967 :     m_ancestors += ancestors;
     489             :     // descendants is the count as seen from the top ancestor, not the descendants as seen from the
     490             :     // coin itself; thus, this value is counted as the max, not the sum
     491     2657967 :     m_descendants = std::max(m_descendants, descendants);
     492     2658039 : }
     493             : 
     494     2010179 : bool OutputGroup::EligibleForSpending(const CoinEligibilityFilter& eligibility_filter, bool isISLocked) const
     495             : {
     496     4020358 :     return (m_depth >= (m_from_me ? eligibility_filter.conf_mine : eligibility_filter.conf_theirs) || isISLocked)
     497     2010179 :         && m_ancestors <= eligibility_filter.max_ancestors
     498     2010179 :         && m_descendants <= eligibility_filter.max_descendants;
     499             : }
     500             : 
     501   409064191 : CAmount OutputGroup::GetSelectionAmount() const
     502             : {
     503   409064191 :     return m_subtract_fee_outputs ? m_value : effective_value;
     504             : }
     505             : 
     506       14119 : CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost, CAmount target, bool use_effective_value)
     507             : {
     508             :     // This function should not be called with empty inputs as that would mean the selection failed
     509       14119 :     assert(!inputs.empty());
     510             : 
     511             :     // Always consider the cost of spending an input now vs in the future.
     512       14119 :     CAmount waste = 0;
     513       14119 :     CAmount selected_effective_value = 0;
     514      100816 :     for (const COutput& coin : inputs) {
     515       86697 :         waste += coin.GetFee() - coin.long_term_fee;
     516       86697 :         selected_effective_value += use_effective_value ? coin.GetEffectiveValue() : coin.txout.nValue;
     517             :     }
     518             : 
     519       14119 :     if (change_cost) {
     520             :         // Consider the cost of making change and spending it in the future
     521             :         // If we aren't making change, the caller should've set change_cost to 0
     522       13998 :         assert(change_cost > 0);
     523       13998 :         waste += change_cost;
     524       13998 :     } else {
     525             :         // When we are not making change (change_cost == 0), consider the excess we are throwing away to fees
     526         121 :         assert(selected_effective_value >= target);
     527         121 :         waste += selected_effective_value - target;
     528             :     }
     529             : 
     530       14119 :     return waste;
     531             : }
     532             : 
     533       14296 : CAmount GenerateChangeTarget(CAmount payment_value, FastRandomContext& rng)
     534             : {
     535       14296 :     if (payment_value <= CHANGE_LOWER / 2) {
     536        1935 :         return CHANGE_LOWER;
     537             :     } else {
     538             :         // random value between 50ksat and min (payment_value * 2, 1milsat)
     539       12361 :         const auto upper_bound = std::min(payment_value * 2, CHANGE_UPPER);
     540       12361 :         return rng.randrange(upper_bound - CHANGE_LOWER) + CHANGE_LOWER;
     541             :     }
     542       14296 : }
     543             : 
     544       14107 : void SelectionResult::ComputeAndSetWaste(CAmount change_cost)
     545             : {
     546       14107 :     m_waste = GetSelectionWaste(m_selected_inputs, change_cost, m_target, m_use_effective);
     547       14107 : }
     548             : 
     549           8 : CAmount SelectionResult::GetWaste() const
     550             : {
     551           8 :     return *Assert(m_waste);
     552             : }
     553             : 
     554       32807 : CAmount SelectionResult::GetSelectedValue() const
     555             : {
     556      255306 :     return std::accumulate(m_selected_inputs.cbegin(), m_selected_inputs.cend(), CAmount{0}, [](CAmount sum, const auto& coin) { return sum + coin.txout.nValue; });
     557             : }
     558             : 
     559          12 : void SelectionResult::Clear()
     560             : {
     561          12 :     m_selected_inputs.clear();
     562          12 :     m_waste.reset();
     563          12 : }
     564             : 
     565      175462 : void SelectionResult::AddInput(const OutputGroup& group)
     566             : {
     567      175462 :     util::insert(m_selected_inputs, group.m_outputs);
     568      175462 :     m_use_effective = !group.m_subtract_fee_outputs;
     569      175462 : }
     570             : 
     571       26965 : const std::set<COutput>& SelectionResult::GetInputSet() const
     572             : {
     573       26965 :     return m_selected_inputs;
     574             : }
     575             : 
     576           0 : std::vector<COutput> SelectionResult::GetShuffledInputVector() const
     577             : {
     578           0 :     std::vector<COutput> coins(m_selected_inputs.begin(), m_selected_inputs.end());
     579           0 :     Shuffle(coins.begin(), coins.end(), FastRandomContext());
     580           0 :     return coins;
     581           0 : }
     582             : 
     583           0 : bool SelectionResult::operator<(SelectionResult other) const
     584             : {
     585           0 :     Assert(m_waste.has_value());
     586           0 :     Assert(other.m_waste.has_value());
     587             :     // As this operator is only used in std::min_element, we want the result that has more inputs when waste are equal.
     588           0 :     return *m_waste < *other.m_waste || (*m_waste == *other.m_waste && m_selected_inputs.size() > other.m_selected_inputs.size());
     589             : }
     590             : 
     591           0 : std::string COutput::ToString() const
     592             : {
     593           0 :     return strprintf("COutput(%s, %d, %d) [%s]", outpoint.hash.ToString(), outpoint.n, depth, FormatMoney(txout.nValue));
     594           0 : }
     595             : 
     596           0 : std::string GetAlgorithmName(const SelectionAlgorithm algo)
     597             : {
     598           0 :     switch (algo)
     599             :     {
     600           0 :     case SelectionAlgorithm::BNB: return "bnb";
     601           0 :     case SelectionAlgorithm::KNAPSACK: return "knapsack";
     602           0 :     case SelectionAlgorithm::SRD: return "srd";
     603           0 :     case SelectionAlgorithm::MANUAL: return "manual";
     604             :     // No default case to allow for compiler to warn
     605             :     }
     606           0 :     assert(false);
     607           0 : }
     608             : } // namespace wallet

Generated by: LCOV version 1.16