|
| 1 | +// Copyright (c) 2017 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 | +#include <util.h> |
| 7 | +#include <utilmoneystr.h> |
| 8 | + |
| 9 | +// Descending order comparator |
| 10 | +struct { |
| 11 | + bool operator()(const CInputCoin& a, const CInputCoin& b) const |
| 12 | + { |
| 13 | + return a.effective_value > b.effective_value; |
| 14 | + } |
| 15 | +} descending; |
| 16 | + |
| 17 | +/* |
| 18 | + * This is the Branch and Bound Coin Selection algorithm designed by Murch. It searches for an input |
| 19 | + * set that can pay for the spending target and does not exceed the spending target by more than the |
| 20 | + * cost of creating and spending a change output. The algorithm uses a depth-first search on a binary |
| 21 | + * tree. In the binary tree, each node corresponds to the inclusion or the omission of a UTXO. UTXOs |
| 22 | + * are sorted by their effective values and the trees is explored deterministically per the inclusion |
| 23 | + * branch first. At each node, the algorithm checks whether the selection is within the target range. |
| 24 | + * While the selection has not reached the target range, more UTXOs are included. When a selection's |
| 25 | + * value exceeds the target range, the complete subtree deriving from this selection can be omitted. |
| 26 | + * At that point, the last included UTXO is deselected and the corresponding omission branch explored |
| 27 | + * instead. The search ends after the complete tree has been searched or after a limited number of tries. |
| 28 | + * |
| 29 | + * The search continues to search for better solutions after one solution has been found. The best |
| 30 | + * solution is chosen by minimizing the waste metric. The waste metric is defined as the cost to |
| 31 | + * spend the current inputs at the given fee rate minus the long term expected cost to spend the |
| 32 | + * inputs, plus the amount the selection exceeds the spending target: |
| 33 | + * |
| 34 | + * waste = selectionTotal - target + inputs × (currentFeeRate - longTermFeeRate) |
| 35 | + * |
| 36 | + * The algorithm uses two additional optimizations. A lookahead keeps track of the total value of |
| 37 | + * the unexplored UTXOs. A subtree is not explored if the lookahead indicates that the target range |
| 38 | + * cannot be reached. Further, it is unnecessary to test equivalent combinations. This allows us |
| 39 | + * to skip testing the inclusion of UTXOs that match the effective value and waste of an omitted |
| 40 | + * predecessor. |
| 41 | + * |
| 42 | + * The Branch and Bound algorithm is described in detail in Murch's Master Thesis: |
| 43 | + * https://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf |
| 44 | + * |
| 45 | + * @param const std::vector<CInputCoin>& utxo_pool The set of UTXOs that we are choosing from. |
| 46 | + * These UTXOs will be sorted in descending order by effective value and the CInputCoins' |
| 47 | + * values are their effective values. |
| 48 | + * @param const CAmount& target_value This is the value that we want to select. It is the lower |
| 49 | + * bound of the range. |
| 50 | + * @param const CAmount& cost_of_change This is the cost of creating and spending a change output. |
| 51 | + * This plus target_value is the upper bound of the range. |
| 52 | + * @param std::set<CInputCoin>& out_set -> This is an output parameter for the set of CInputCoins |
| 53 | + * that have been selected. |
| 54 | + * @param CAmount& value_ret -> This is an output parameter for the total value of the CInputCoins |
| 55 | + * that were selected. |
| 56 | + * @param CAmount not_input_fees -> The fees that need to be paid for the outputs and fixed size |
| 57 | + * overhead (version, locktime, marker and flag) |
| 58 | + */ |
| 59 | + |
| 60 | +static const size_t TOTAL_TRIES = 100000; |
| 61 | + |
| 62 | +bool SelectCoinsBnB(std::vector<CInputCoin>& utxo_pool, const CAmount& target_value, const CAmount& cost_of_change, std::set<CInputCoin>& out_set, CAmount& value_ret, CAmount not_input_fees) |
| 63 | +{ |
| 64 | + out_set.clear(); |
| 65 | + CAmount curr_value = 0; |
| 66 | + |
| 67 | + std::vector<bool> curr_selection; // select the utxo at this index |
| 68 | + curr_selection.reserve(utxo_pool.size()); |
| 69 | + CAmount actual_target = not_input_fees + target_value; |
| 70 | + |
| 71 | + // Calculate curr_available_value |
| 72 | + CAmount curr_available_value = 0; |
| 73 | + for (const CInputCoin& utxo : utxo_pool) { |
| 74 | + // Assert that this utxo is not negative. It should never be negative, effective value calculation should have removed it |
| 75 | + assert(utxo.effective_value > 0); |
| 76 | + curr_available_value += utxo.effective_value; |
| 77 | + } |
| 78 | + if (curr_available_value < actual_target) { |
| 79 | + return false; |
| 80 | + } |
| 81 | + |
| 82 | + // Sort the utxo_pool |
| 83 | + std::sort(utxo_pool.begin(), utxo_pool.end(), descending); |
| 84 | + |
| 85 | + CAmount curr_waste = 0; |
| 86 | + std::vector<bool> best_selection; |
| 87 | + CAmount best_waste = MAX_MONEY; |
| 88 | + |
| 89 | + // Depth First search loop for choosing the UTXOs |
| 90 | + for (size_t i = 0; i < TOTAL_TRIES; ++i) { |
| 91 | + // Conditions for starting a backtrack |
| 92 | + bool backtrack = false; |
| 93 | + if (curr_value + curr_available_value < actual_target || // Cannot possibly reach target with the amount remaining in the curr_available_value. |
| 94 | + curr_value > actual_target + cost_of_change || // Selected value is out of range, go back and try other branch |
| 95 | + (curr_waste > best_waste && (utxo_pool.at(0).fee - utxo_pool.at(0).long_term_fee) > 0)) { // Don't select things which we know will be more wasteful if the waste is increasing |
| 96 | + backtrack = true; |
| 97 | + } else if (curr_value >= actual_target) { // Selected value is within range |
| 98 | + curr_waste += (curr_value - actual_target); // This is the excess value which is added to the waste for the below comparison |
| 99 | + // Adding another UTXO after this check could bring the waste down if the long term fee is higher than the current fee. |
| 100 | + // However we are not going to explore that because this optimization for the waste is only done when we have hit our target |
| 101 | + // value. Adding any more UTXOs will be just burning the UTXO; it will go entirely to fees. Thus we aren't going to |
| 102 | + // explore any more UTXOs to avoid burning money like that. |
| 103 | + if (curr_waste <= best_waste) { |
| 104 | + best_selection = curr_selection; |
| 105 | + best_selection.resize(utxo_pool.size()); |
| 106 | + best_waste = curr_waste; |
| 107 | + } |
| 108 | + curr_waste -= (curr_value - actual_target); // Remove the excess value as we will be selecting different coins now |
| 109 | + backtrack = true; |
| 110 | + } |
| 111 | + |
| 112 | + // Backtracking, moving backwards |
| 113 | + if (backtrack) { |
| 114 | + // Walk backwards to find the last included UTXO that still needs to have its omission branch traversed. |
| 115 | + while (!curr_selection.empty() && !curr_selection.back()) { |
| 116 | + curr_selection.pop_back(); |
| 117 | + curr_available_value += utxo_pool.at(curr_selection.size()).effective_value; |
| 118 | + }; |
| 119 | + |
| 120 | + if (curr_selection.empty()) { // We have walked back to the first utxo and no branch is untraversed. All solutions searched |
| 121 | + break; |
| 122 | + } |
| 123 | + |
| 124 | + // Output was included on previous iterations, try excluding now. |
| 125 | + curr_selection.back() = false; |
| 126 | + CInputCoin& utxo = utxo_pool.at(curr_selection.size() - 1); |
| 127 | + curr_value -= utxo.effective_value; |
| 128 | + curr_waste -= utxo.fee - utxo.long_term_fee; |
| 129 | + } else { // Moving forwards, continuing down this branch |
| 130 | + CInputCoin& utxo = utxo_pool.at(curr_selection.size()); |
| 131 | + |
| 132 | + // Remove this utxo from the curr_available_value utxo amount |
| 133 | + curr_available_value -= utxo.effective_value; |
| 134 | + |
| 135 | + // Avoid searching a branch if the previous UTXO has the same value and same waste and was excluded. Since the ratio of fee to |
| 136 | + // 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. |
| 137 | + if (!curr_selection.empty() && !curr_selection.back() && |
| 138 | + utxo.effective_value == utxo_pool.at(curr_selection.size() - 1).effective_value && |
| 139 | + utxo.fee == utxo_pool.at(curr_selection.size() - 1).fee) { |
| 140 | + curr_selection.push_back(false); |
| 141 | + } else { |
| 142 | + // Inclusion branch first (Largest First Exploration) |
| 143 | + curr_selection.push_back(true); |
| 144 | + curr_value += utxo.effective_value; |
| 145 | + curr_waste += utxo.fee - utxo.long_term_fee; |
| 146 | + } |
| 147 | + } |
| 148 | + } |
| 149 | + |
| 150 | + // Check for solution |
| 151 | + if (best_selection.empty()) { |
| 152 | + return false; |
| 153 | + } |
| 154 | + |
| 155 | + // Set output set |
| 156 | + value_ret = 0; |
| 157 | + for (size_t i = 0; i < best_selection.size(); ++i) { |
| 158 | + if (best_selection.at(i)) { |
| 159 | + out_set.insert(utxo_pool.at(i)); |
| 160 | + value_ret += utxo_pool.at(i).txout.nValue; |
| 161 | + } |
| 162 | + } |
| 163 | + |
| 164 | + return true; |
| 165 | +} |
0 commit comments