LCOV - code coverage report
Current view: top level - src/wallet - coinselection.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 274 305 89.8 %
Date: 2026-06-25 07:23:51 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        1761 : static int GetSelectionWeight(const SelectionResult& result)
      33             : {
      34      142050 :     return std::accumulate(result.GetInputSet().cbegin(), result.GetInputSet().cend(), 0, [](int sum, const COutput& coin) {
      35      140289 :         return sum + std::max(coin.input_bytes, 0);
      36             :     });
      37             : }
      38             : 
      39             : // Descending order comparator
      40             : struct {
      41     1782219 :     bool operator()(const OutputGroup& a, const OutputGroup& b) const
      42             :     {
      43     1782219 :         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         430 : util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change,
      88             :                                              int max_weight)
      89             : {
      90         430 :     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         430 : }
     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         319 : util::Result<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng,
     209             :                                              int max_weight)
     210             : {
     211         319 :     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         319 : }
     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        2973 : 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        2973 :     std::vector<char> vfIncluded;
     269             : 
     270             :     // Worst case "best" approximation is just all of the groups.
     271        2973 :     vfBest.assign(groups.size(), true);
     272        2973 :     nBest = nTotalLower;
     273        2973 :     int nBestInputCount = 0;
     274             : 
     275     1768883 :     for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
     276             :     {
     277   346313256 :         vfIncluded.assign(groups.size(), false);
     278     1765910 :         CAmount nTotal = 0;
     279     1765910 :         int nTotalInputCount = 0;
     280     1765910 :         bool fReachedTarget = false;
     281     4421730 :         for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++)
     282             :         {
     283   364851146 :             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   362195326 :                 if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i])
     292             :                 {
     293   526011638 :                     nTotal += groups[i].GetSelectionAmount();
     294   526011638 :                     ++nTotalInputCount;
     295   526011638 :                     vfIncluded[i] = true;
     296   526011638 :                     if (nTotal >= nTargetValue)
     297             :                     {
     298   512459488 :                         fReachedTarget = true;
     299             :                         // If the total is between nTargetValue and nBest, it's our new best
     300             :                         // approximation.
     301   512459488 :                         if (nTotal < nBest || (nTotal == nBest && nTotalInputCount < nBestInputCount))
     302             :                         {
     303   344564464 :                             nBest = nTotal;
     304   344564464 :                             nBestInputCount = nTotalInputCount;
     305   344564464 :                             vfBest = vfIncluded;
     306       17118 :                         }
     307   167912142 :                         nTotal -= groups[i].GetSelectionAmount();
     308   167912142 :                         --nTotalInputCount;
     309   167912142 :                         vfIncluded[i] = false;
     310   167912142 :                     }
     311   181464292 :                 }
     312   362195326 :             }
     313     2655820 :         }
     314     1765910 :     }
     315  1033639065 : }
     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     1463198 : bool less_then_denom (const OutputGroup& group1, const OutputGroup& group2)
     328             : {
     329     1463198 :     bool found1 = false;
     330     1463198 :     bool found2 = false;
     331     8779188 :     for (const auto& d : CoinJoin::GetStandardDenominations()) // loop through predefined denoms
     332             :     {
     333     7315990 :         if(group1.m_value == d) found1 = true;
     334     7315990 :         if(group2.m_value == d) found2 = true;
     335             :     }
     336     1463198 :     return (!found1 && found2);
     337             : }
     338             : 
     339        7739 : 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        7739 :     if (groups.empty()) return util::Error();
     344             : 
     345        7455 :     SelectionResult result(nTargetValue, SelectionAlgorithm::KNAPSACK);
     346             : 
     347             :     // List of values less than target
     348        7455 :     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        7455 :     std::vector<OutputGroup> applicable_groups;
     352        7455 :     CAmount nTotalLower = 0;
     353             : 
     354        7455 :     Shuffle(groups.begin(), groups.end(), rng);
     355             : 
     356        5633 :     int tryDenomStart = 0;
     357             : 
     358        5633 :     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        5633 :         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        6066 :     for (unsigned int tryDenom = tryDenomStart; tryDenom < 2; tryDenom++) {
     373        6066 :         LogPrint(BCLog::SELECTCOINS, "tryDenom: %d\n", tryDenom);
     374        6066 :         applicable_groups.clear();
     375        6066 :         nTotalLower = 0;
     376      608624 :         for (const OutputGroup& group : groups) {
     377      603667 :             if (tryDenom == 0 && CoinJoin::IsDenominatedAmount(group.m_value)) {
     378           2 :                 continue; // we don't want denom values on first run
     379             :             }
     380      603665 :             if (group.GetSelectionAmount() == nTargetValue) {
     381        1109 :                 result.AddInput(group);
     382        1109 :                 return result;
     383      602556 :             } else if (group.GetSelectionAmount() < nTargetValue + change_target) {
     384      364620 :                 applicable_groups.push_back(group);
     385      364620 :                 nTotalLower += group.GetSelectionAmount();
     386      602556 :             } else if (!lowest_larger || group.GetSelectionAmount() < lowest_larger->GetSelectionAmount()) {
     387        3113 :                 lowest_larger = group;
     388        3113 :             }
     389             :         }
     390             : 
     391        4957 :         if (nTotalLower == nTargetValue) {
     392        2435 :             for (const auto& group : applicable_groups) {
     393        1932 :                 result.AddInput(group);
     394             :             }
     395         503 :             return result;
     396             :         }
     397             : 
     398        4454 :         if (nTotalLower < nTargetValue) {
     399        2386 :             if (!lowest_larger) { // there is no input larger than nTargetValue
     400         866 :                 if (tryDenom == 0)
     401             :                     // we didn't look at denom yet, let's do it
     402         433 :                     continue;
     403             :                 else
     404             :                     // we looked at everything possible and didn't find anything, no luck
     405         433 :                     return util::Error();
     406             :             }
     407        1520 :             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        1520 :             if (!fFullyMixedOnly || (result.GetSelectedValue() - nTargetValue <= maxTxFee)) {
     411        1520 :                 return result;
     412             :             }
     413           0 :             return util::Error();
     414             :         }
     415             : 
     416             :         // nTotalLower > nTargetValue
     417        2068 :         break;
     418             :     }
     419             : 
     420             :     // Solve subset sum by stochastic approximation
     421        2068 :     std::sort(applicable_groups.begin(), applicable_groups.end(), descending);
     422        3890 :     std::vector<char> vfBest;
     423             :     CAmount nBest;
     424             : 
     425        3890 :     ApproximateBestSubset(rng, applicable_groups, nTotalLower, nTargetValue, vfBest, nBest);
     426        2068 :     if (nBest != nTargetValue && change_target != 0 && nTotalLower >= nTargetValue + change_target) {
     427         905 :         ApproximateBestSubset(rng, applicable_groups, nTotalLower, nTargetValue + change_target, vfBest, nBest);
     428         905 :     }
     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        2874 :     if (lowest_larger &&
     433         913 :         ((nBest != nTargetValue && nBest < nTargetValue + change_target) || lowest_larger->GetSelectionAmount() <= nBest)) {
     434        1218 :         result.AddInput(*lowest_larger);
     435         307 :     } else {
     436        1155 :         std::string log_message{"Coin selection best subset: "};
     437      352944 :         for (unsigned int i = 0; i < applicable_groups.size(); i++) {
     438      351183 :             if (vfBest[i]) {
     439      140289 :                 result.AddInput(applicable_groups[i]);
     440      140289 :                 log_message += strprintf("%s ", FormatMoney(applicable_groups[i].m_value));
     441      140289 :             }
     442      351183 :         }
     443             : 
     444        1761 :         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        1750 :         LogPrint(BCLog::SELECTCOINS, "%stotal %s\n", log_message, FormatMoney(nBest));
     452        1761 :     }
     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        2057 :     if (!fFullyMixedOnly || (result.GetSelectedValue() - nTargetValue <= maxTxFee)) {
     457        2057 :         return result;
     458             :     }
     459           0 :     return util::Error();
     460       13205 : }
     461             : 
     462             : /******************************************************************************
     463             : 
     464             :  OutputGroup
     465             : 
     466             :  ******************************************************************************/
     467             : 
     468      875842 : 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      875842 :     if (positive_only && output.GetEffectiveValue() <= 0) return;
     471             : 
     472      875840 :     m_outputs.push_back(output);
     473      875840 :     COutput& coin = m_outputs.back();
     474             : 
     475      875840 :     fee += coin.GetFee();
     476             : 
     477      875840 :     coin.long_term_fee = coin.input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.input_bytes);
     478      875840 :     long_term_fee += coin.long_term_fee;
     479             : 
     480      875840 :     effective_value += coin.GetEffectiveValue();
     481             : 
     482      875840 :     m_from_me &= coin.from_me;
     483      875840 :     m_value += coin.txout.nValue;
     484      875840 :     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      875840 :     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      875840 :     m_descendants = std::max(m_descendants, descendants);
     492      875842 : }
     493             : 
     494      598529 : bool OutputGroup::EligibleForSpending(const CoinEligibilityFilter& eligibility_filter, bool isISLocked) const
     495             : {
     496     1197058 :     return (m_depth >= (m_from_me ? eligibility_filter.conf_mine : eligibility_filter.conf_theirs) || isISLocked)
     497      598529 :         && m_ancestors <= eligibility_filter.max_ancestors
     498      598529 :         && m_descendants <= eligibility_filter.max_descendants;
     499             : }
     500             : 
     501   356037455 : CAmount OutputGroup::GetSelectionAmount() const
     502             : {
     503   356037455 :     return m_subtract_fee_outputs ? m_value : effective_value;
     504             : }
     505             : 
     506         306 : 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         306 :     assert(!inputs.empty());
     510             : 
     511             :     // Always consider the cost of spending an input now vs in the future.
     512         306 :     CAmount waste = 0;
     513         306 :     CAmount selected_effective_value = 0;
     514       35305 :     for (const COutput& coin : inputs) {
     515       34999 :         waste += coin.GetFee() - coin.long_term_fee;
     516       34999 :         selected_effective_value += use_effective_value ? coin.GetEffectiveValue() : coin.txout.nValue;
     517             :     }
     518             : 
     519         306 :     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         185 :         assert(change_cost > 0);
     523         185 :         waste += change_cost;
     524         185 :     } 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         306 :     return waste;
     531             : }
     532             : 
     533         226 : CAmount GenerateChangeTarget(CAmount payment_value, FastRandomContext& rng)
     534             : {
     535         226 :     if (payment_value <= CHANGE_LOWER / 2) {
     536         121 :         return CHANGE_LOWER;
     537             :     } else {
     538             :         // random value between 50ksat and min (payment_value * 2, 1milsat)
     539         105 :         const auto upper_bound = std::min(payment_value * 2, CHANGE_UPPER);
     540         105 :         return rng.randrange(upper_bound - CHANGE_LOWER) + CHANGE_LOWER;
     541             :     }
     542         226 : }
     543             : 
     544         294 : void SelectionResult::ComputeAndSetWaste(CAmount change_cost)
     545             : {
     546         294 :     m_waste = GetSelectionWaste(m_selected_inputs, change_cost, m_target, m_use_effective);
     547         294 : }
     548             : 
     549           8 : CAmount SelectionResult::GetWaste() const
     550             : {
     551           8 :     return *Assert(m_waste);
     552             : }
     553             : 
     554        3302 : CAmount SelectionResult::GetSelectedValue() const
     555             : {
     556      119638 :     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      145572 : void SelectionResult::AddInput(const OutputGroup& group)
     566             : {
     567      145572 :     util::insert(m_selected_inputs, group.m_outputs);
     568      145572 :     m_use_effective = !group.m_subtract_fee_outputs;
     569      145572 : }
     570             : 
     571       10140 : const std::set<COutput>& SelectionResult::GetInputSet() const
     572             : {
     573       10140 :     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