diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp index bb0ad5da39..2fe7ab79a1 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -1,332 +1,377 @@ // Copyright (c) 2017 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include // Descending order comparator struct { bool operator()(const CInputCoin &a, const CInputCoin &b) const { return a.effective_value > b.effective_value; } } descending; /** * This is the Branch and Bound Coin Selection algorithm designed by Murch. It * searches for an input set that can pay for the spending target and does not * exceed the spending target by more than the cost of creating and spending a * change output. The algorithm uses a depth-first search on a binary tree. In * the binary tree, each node corresponds to the inclusion or the omission of a * UTXO. UTXOs are sorted by their effective values and the trees is explored * deterministically per the inclusion branch first. At each node, the algorithm * checks whether the selection is within the target range. While the selection * has not reached the target range, more UTXOs are included. When a selection's * value exceeds the target range, the complete subtree deriving from this * selection can be omitted. At that point, the last included UTXO is deselected * and the corresponding omission branch explored instead. The search ends after * the complete tree has been searched or after a limited number of tries. * * The search continues to search for better solutions after one solution has * been found. The best solution is chosen by minimizing the waste metric. The * waste metric is defined as the cost to spend the current inputs at the given * fee rate minus the long term expected cost to spend the inputs, plus the * amount the selection exceeds the spending target: * * waste = selectionTotal - target + inputs × (currentFeeRate - longTermFeeRate) * * The algorithm uses two additional optimizations. A lookahead keeps track of * the total value of the unexplored UTXOs. A subtree is not explored if the * lookahead indicates that the target range cannot be reached. Further, it is * unnecessary to test equivalent combinations. This allows us to skip testing * the inclusion of UTXOs that match the effective value and waste of an omitted * predecessor. * * The Branch and Bound algorithm is described in detail in Murch's Master * Thesis: * https://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf * * @param const std::vector& utxo_pool The set of UTXOs that we are * choosing from. These UTXOs will be sorted in descending order by effective * value and the CInputCoins' values are their effective values. * @param const Amount& target_value This is the value that we want to select. * It is the lower bound of the range. * @param const Amount& cost_of_change This is the cost of creating and * spending a change output. This plus target_value is the upper bound of the * range. * @param std::set& out_set -> This is an output parameter for the * set of CInputCoins that have been selected. * @param Amount& value_ret -> This is an output parameter for the total value * of the CInputCoins that were selected. * @param Amount not_input_fees -> The fees that need to be paid for the * outputs and fixed size overhead (version, locktime, marker and flag) */ static const size_t TOTAL_TRIES = 100000; bool SelectCoinsBnB(std::vector &utxo_pool, const Amount &target_value, const Amount &cost_of_change, std::set &out_set, Amount &value_ret, const Amount not_input_fees) { out_set.clear(); Amount curr_value = Amount::zero(); // select the utxo at this index std::vector curr_selection; curr_selection.reserve(utxo_pool.size()); Amount actual_target = not_input_fees + target_value; // Calculate curr_available_value Amount curr_available_value = Amount::zero(); for (const CInputCoin &utxo : utxo_pool) { // Assert that this utxo is not negative. It should never be negative, // effective value calculation should have removed it assert(utxo.effective_value > Amount::zero()); curr_available_value += utxo.effective_value; } if (curr_available_value < actual_target) { return false; } // Sort the utxo_pool std::sort(utxo_pool.begin(), utxo_pool.end(), descending); Amount curr_waste = Amount::zero(); std::vector best_selection; Amount best_waste = MAX_MONEY; // Depth First search loop for choosing the UTXOs for (size_t i = 0; i < TOTAL_TRIES; ++i) { // Conditions for starting a backtrack bool backtrack = false; if (curr_value + curr_available_value < actual_target || // Cannot possibly reach target with the amount // remaining in the curr_available_value. curr_value > actual_target + cost_of_change || // Selected value is out of range, go back // and try other branch (curr_waste > best_waste && (utxo_pool.at(0).fee - utxo_pool.at(0).long_term_fee) > Amount::zero())) { // Don't select things which we know will be more wasteful if the // waste is increasing backtrack = true; } // Selected value is within range else if (curr_value >= actual_target) { // This is the excess value which is added to the waste for the // below comparison. Adding another UTXO after this check could // bring the waste down if the long term fee is higher than the // current fee. However we are not going to explore that because // this optimization for the waste is only done when we have hit our // target value. Adding any more UTXOs will be just burning the // UTXO; it will go entirely to fees. Thus we aren't going to // explore any more UTXOs to avoid burning money like that. curr_waste += (curr_value - actual_target); if (curr_waste <= best_waste) { best_selection = curr_selection; best_selection.resize(utxo_pool.size()); best_waste = curr_waste; } // Remove the excess value as we will be selecting different coins // now curr_waste -= (curr_value - actual_target); backtrack = true; } // Backtracking, moving backwards if (backtrack) { // Walk backwards to find the last included UTXO that still needs to // have its omission branch traversed. while (!curr_selection.empty() && !curr_selection.back()) { curr_selection.pop_back(); curr_available_value += utxo_pool.at(curr_selection.size()).effective_value; } if (curr_selection.empty()) { // We have walked back to the first utxo and no branch is // untraversed. All solutions searched break; } // Output was included on previous iterations, try excluding now. curr_selection.back() = false; CInputCoin &utxo = utxo_pool.at(curr_selection.size() - 1); curr_value -= utxo.effective_value; curr_waste -= utxo.fee - utxo.long_term_fee; } // Moving forwards, continuing down this branch else { CInputCoin &utxo = utxo_pool.at(curr_selection.size()); // Remove this utxo from the curr_available_value utxo amount curr_available_value -= utxo.effective_value; // Avoid searching a branch if the previous UTXO has the same value // and same waste and was excluded. 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. if (!curr_selection.empty() && !curr_selection.back() && utxo.effective_value == utxo_pool.at(curr_selection.size() - 1).effective_value && utxo.fee == utxo_pool.at(curr_selection.size() - 1).fee) { curr_selection.push_back(false); } else { // Inclusion branch first (Largest First Exploration) curr_selection.push_back(true); curr_value += utxo.effective_value; curr_waste += utxo.fee - utxo.long_term_fee; } } } // Check for solution if (best_selection.empty()) { return false; } // Set output set value_ret = Amount::zero(); for (size_t i = 0; i < best_selection.size(); ++i) { if (best_selection.at(i)) { out_set.insert(utxo_pool.at(i)); value_ret += utxo_pool.at(i).txout.nValue; } } return true; } static void ApproximateBestSubset(const std::vector &vValue, const Amount &nTotalLower, const Amount &nTargetValue, std::vector &vfBest, Amount &nBest, int iterations = 1000) { std::vector vfIncluded; vfBest.assign(vValue.size(), true); nBest = nTotalLower; FastRandomContext insecure_rand; for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++) { vfIncluded.assign(vValue.size(), false); Amount nTotal = Amount::zero(); bool fReachedTarget = false; for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++) { for (size_t i = 0; i < vValue.size(); i++) { // The solver here uses a randomized algorithm, the randomness // serves no real security purpose but is just needed to prevent // degenerate behavior and it is important that the rng is fast. // We do not use a constant random sequence, because there may // be some privacy improvement by making the selection random. if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i]) { nTotal += vValue[i].txout.nValue; vfIncluded[i] = true; if (nTotal >= nTargetValue) { fReachedTarget = true; if (nTotal < nBest) { nBest = nTotal; vfBest = vfIncluded; } nTotal -= vValue[i].txout.nValue; vfIncluded[i] = false; } } } } } } bool KnapsackSolver(const Amount nTargetValue, std::vector &vCoins, std::set &setCoinsRet, Amount &nValueRet) { setCoinsRet.clear(); nValueRet = Amount::zero(); // List of values less than target boost::optional coinLowestLarger; std::vector vValue; Amount nTotalLower = Amount::zero(); random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt); for (const CInputCoin &coin : vCoins) { if (coin.txout.nValue == nTargetValue) { setCoinsRet.insert(coin); nValueRet += coin.txout.nValue; return true; } else if (coin.txout.nValue < nTargetValue + MIN_CHANGE) { vValue.push_back(coin); nTotalLower += coin.txout.nValue; } else if (!coinLowestLarger || coin.txout.nValue < coinLowestLarger->txout.nValue) { coinLowestLarger = coin; } } if (nTotalLower == nTargetValue) { for (const auto &input : vValue) { setCoinsRet.insert(input); nValueRet += input.txout.nValue; } return true; } if (nTotalLower < nTargetValue) { if (!coinLowestLarger) { return false; } setCoinsRet.insert(coinLowestLarger.get()); nValueRet += coinLowestLarger->txout.nValue; return true; } // Solve subset sum by stochastic approximation std::sort(vValue.begin(), vValue.end(), descending); std::vector vfBest; Amount nBest; ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest); if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE) { ApproximateBestSubset(vValue, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest); } // If we have a bigger coin and (either the stochastic approximation didn't // find a good solution, or the next bigger coin is closer), return the // bigger coin if (coinLowestLarger && ((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) || coinLowestLarger->txout.nValue <= nBest)) { setCoinsRet.insert(coinLowestLarger.get()); nValueRet += coinLowestLarger->txout.nValue; } else { for (size_t i = 0; i < vValue.size(); i++) { if (vfBest[i]) { setCoinsRet.insert(vValue[i]); nValueRet += vValue[i].txout.nValue; } } if (LogAcceptCategory(BCLog::SELECTCOINS)) { LogPrint(BCLog::SELECTCOINS, "SelectCoins() best subset: "); for (size_t i = 0; i < vValue.size(); i++) { if (vfBest[i]) { LogPrint(BCLog::SELECTCOINS, "%s ", FormatMoney(vValue[i].txout.nValue)); } } LogPrint(BCLog::SELECTCOINS, "total %s\n", FormatMoney(nBest)); } } return true; } + +/****************************************************************************** + + OutputGroup + + ******************************************************************************/ + +void OutputGroup::Insert(const CInputCoin &output, int depth, bool from_me, + size_t ancestors, size_t descendants) { + m_outputs.push_back(output); + m_from_me &= from_me; + m_value += output.effective_value; + m_depth = std::min(m_depth, depth); + // m_ancestors is currently the max ancestor count for all coins in the + // group; however, this is not ideal, as a wallet will consider e.g. thirty + // 2-ancestor coins as having two ancestors, when in reality it has 60 + // ancestors. + m_ancestors = std::max(m_ancestors, ancestors); + // m_descendants is the count as seen from the top ancestor, not the + // descendants as seen from the coin itself; thus, this value is accurate + m_descendants = std::max(m_descendants, descendants); + effective_value = m_value; +} + +std::vector::iterator +OutputGroup::Discard(const CInputCoin &output) { + auto it = m_outputs.begin(); + while (it != m_outputs.end() && it->outpoint != output.outpoint) { + ++it; + } + if (it == m_outputs.end()) { + return it; + } + m_value -= output.effective_value; + effective_value -= output.effective_value; + return m_outputs.erase(it); +} + +bool OutputGroup::EligibleForSpending( + const CoinEligibilityFilter &eligibility_filter) const { + return m_depth >= (m_from_me ? eligibility_filter.conf_mine + : eligibility_filter.conf_theirs) && + m_ancestors <= eligibility_filter.max_ancestors && + m_descendants <= eligibility_filter.max_descendants; +} diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h index 09ebdc21df..2b4a82b5a1 100644 --- a/src/wallet/coinselection.h +++ b/src/wallet/coinselection.h @@ -1,87 +1,115 @@ // Copyright (c) 2017 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_WALLET_COINSELECTION_H #define BITCOIN_WALLET_COINSELECTION_H #include #include #include //! target minimum change amount static const Amount MIN_CHANGE = CENT; //! final minimum change amount after paying for fees static const Amount MIN_FINAL_CHANGE = MIN_CHANGE / 2; class CInputCoin { public: CInputCoin(const CTransactionRef &tx, unsigned int i) { if (!tx) { throw std::invalid_argument("tx should not be null"); } if (i >= tx->vout.size()) { throw std::out_of_range("The output index is out of range"); } outpoint = COutPoint(tx->GetId(), i); txout = tx->vout[i]; effective_value = txout.nValue; } CInputCoin(const CTransactionRef &tx, unsigned int i, int input_bytes) : CInputCoin(tx, i) { m_input_bytes = input_bytes; } COutPoint outpoint; CTxOut txout; Amount effective_value; Amount fee = Amount::zero(); Amount long_term_fee = Amount::zero(); /** * Pre-computed estimated size of this output as a fully-signed input in a * transaction. Can be -1 if it could not be calculated. */ int m_input_bytes{-1}; bool operator<(const CInputCoin &rhs) const { return outpoint < rhs.outpoint; } bool operator!=(const CInputCoin &rhs) const { return outpoint != rhs.outpoint; } bool operator==(const CInputCoin &rhs) const { return outpoint == rhs.outpoint; } }; struct CoinEligibilityFilter { const int conf_mine; const int conf_theirs; const uint64_t max_ancestors; const uint64_t max_descendants; CoinEligibilityFilter(int conf_mine_, int conf_theirs_, uint64_t max_ancestors_) : conf_mine(conf_mine_), conf_theirs(conf_theirs_), max_ancestors(max_ancestors_), max_descendants(max_ancestors_) {} CoinEligibilityFilter(int conf_mine_, int conf_theirs_, uint64_t max_ancestors_, uint64_t max_descendants_) : conf_mine(conf_mine_), conf_theirs(conf_theirs_), max_ancestors(max_ancestors_), max_descendants(max_descendants_) {} }; +struct OutputGroup { + std::vector m_outputs; + bool m_from_me{true}; + Amount m_value = Amount::zero(); + int m_depth{999}; + size_t m_ancestors{0}; + size_t m_descendants{0}; + Amount effective_value = Amount::zero(); + Amount fee = Amount::zero(); + Amount long_term_fee = Amount::zero(); + + OutputGroup() {} + OutputGroup(std::vector &&outputs, bool from_me, Amount value, + int depth, size_t ancestors, size_t descendants) + : m_outputs(std::move(outputs)), m_from_me(from_me), m_value(value), + m_depth(depth), m_ancestors(ancestors), m_descendants(descendants) {} + OutputGroup(const CInputCoin &output, int depth, bool from_me, + size_t ancestors, size_t descendants) + : OutputGroup() { + Insert(output, depth, from_me, ancestors, descendants); + } + void Insert(const CInputCoin &output, int depth, bool from_me, + size_t ancestors, size_t descendants); + std::vector::iterator Discard(const CInputCoin &output); + bool + EligibleForSpending(const CoinEligibilityFilter &eligibility_filter) const; +}; + bool SelectCoinsBnB(std::vector &utxo_pool, const Amount &target_value, const Amount &cost_of_change, std::set &out_set, Amount &value_ret, const Amount not_input_fees); // Original coin selection algorithm as a fallback bool KnapsackSolver(const Amount nTargetValue, std::vector &vCoins, std::set &setCoinsRet, Amount &nValueRet); #endif // BITCOIN_WALLET_COINSELECTION_H diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index f831ea3d33..54cb7e9593 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1,4642 +1,4683 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for IsDeprecatedRPCEnabled #include #include