From 12ec29d3bb0d46c61712210fe9bb96a0d543204a Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Mon, 5 Mar 2018 16:37:24 -0500 Subject: [PATCH 01/13] Calculate and store the number of bytes required to spend an input --- src/consensus/validation.h | 5 +++ src/policy/policy.cpp | 5 +++ src/policy/policy.h | 1 + src/script/sign.cpp | 9 +++- src/script/sign.h | 1 + src/wallet/feebumper.cpp | 27 ------------ src/wallet/wallet.cpp | 87 +++++++++++++++++++++++++++++++++----- src/wallet/wallet.h | 60 ++++++++++++++------------ 8 files changed, 127 insertions(+), 68 deletions(-) diff --git a/src/consensus/validation.h b/src/consensus/validation.h index c2a343c15..757df518a 100644 --- a/src/consensus/validation.h +++ b/src/consensus/validation.h @@ -101,5 +101,10 @@ static inline int64_t GetBlockWeight(const CBlock& block) { return ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION); } +static inline int64_t GetTransationInputWeight(const CTxIn& txin) +{ + // scriptWitness size is added here because witnesses and txins are split up in segwit serialization. + return ::GetSerializeSize(txin, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(txin, SER_NETWORK, PROTOCOL_VERSION) + ::GetSerializeSize(txin.scriptWitness.stack, SER_NETWORK, PROTOCOL_VERSION); +} #endif // BITCOIN_CONSENSUS_VALIDATION_H diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index bff58932b..41f967c98 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -258,3 +258,8 @@ int64_t GetVirtualTransactionSize(const CTransaction& tx, int64_t nSigOpCost) { return GetVirtualTransactionSize(GetTransactionWeight(tx), nSigOpCost); } + +int64_t GetVirtualTransactionInputSize(const CTxIn& txin, int64_t nSigOpCost) +{ + return GetVirtualTransactionSize(GetTransationInputWeight(txin), nSigOpCost); +} diff --git a/src/policy/policy.h b/src/policy/policy.h index 3d96406bb..e4eda4b63 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -102,5 +102,6 @@ extern unsigned int nBytesPerSigOp; /** Compute the virtual transaction size (weight reinterpreted as bytes). */ int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost); int64_t GetVirtualTransactionSize(const CTransaction& tx, int64_t nSigOpCost = 0); +int64_t GetVirtualTransactionInputSize(const CTxIn& tx, int64_t nSigOpCost = 0); #endif // BITCOIN_POLICY_POLICY_H diff --git a/src/script/sign.cpp b/src/script/sign.cpp index aaba5e592..baa712dc2 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -194,11 +194,16 @@ SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nI return data; } +void UpdateInput(CTxIn& input, const SignatureData& data) +{ + input.scriptSig = data.scriptSig; + input.scriptWitness = data.scriptWitness; +} + void UpdateTransaction(CMutableTransaction& tx, unsigned int nIn, const SignatureData& data) { assert(tx.vin.size() > nIn); - tx.vin[nIn].scriptSig = data.scriptSig; - tx.vin[nIn].scriptWitness = data.scriptWitness; + UpdateInput(tx.vin[nIn], data); } bool SignSignature(const CKeyStore &keystore, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType) diff --git a/src/script/sign.h b/src/script/sign.h index 97c0014cd..2c749521c 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -80,6 +80,7 @@ SignatureData CombineSignatures(const CScript& scriptPubKey, const BaseSignature /** Extract signature data from a transaction, and insert it. */ SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn); void UpdateTransaction(CMutableTransaction& tx, unsigned int nIn, const SignatureData& data); +void UpdateInput(CTxIn& input, const SignatureData& data); /* Check whether we know how to sign for an output like this, assuming we * have all private keys. While this function does not need private keys, the passed diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 9cae660c6..9b85176ca 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -16,33 +16,6 @@ #include #include -// Calculate the size of the transaction assuming all signatures are max size -// Use DummySignatureCreator, which inserts 72 byte signatures everywhere. -// TODO: re-use this in CWallet::CreateTransaction (right now -// CreateTransaction uses the constructed dummy-signed tx to do a priority -// calculation, but we should be able to refactor after priority is removed). -// NOTE: this requires that all inputs must be in mapWallet (eg the tx should -// be IsAllFromMe). -static int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet) -{ - CMutableTransaction txNew(tx); - std::vector vCoins; - // Look up the inputs. We should have already checked that this transaction - // IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our - // wallet, with a valid index into the vout array. - for (auto& input : tx.vin) { - const auto mi = wallet->mapWallet.find(input.prevout.hash); - assert(mi != wallet->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size()); - vCoins.emplace_back(CInputCoin(&(mi->second), input.prevout.n)); - } - if (!wallet->DummySignTx(txNew, vCoins)) { - // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE) - // implies that we can sign for every input. - return -1; - } - return GetVirtualTransactionSize(txNew); -} - //! Check whether transaction has descendant in wallet or mempool, or has been //! mined, or conflicts with a mined transaction. Return a feebumper::Result. static feebumper::Result PreconditionChecks(const CWallet* wallet, const CWalletTx& wtx, std::vector& errors) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0c468878e..a739077a3 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1543,6 +1543,79 @@ int CWalletTx::GetRequestCount() const return nRequests; } +// Helper for producing a max-sized low-S signature (eg 72 bytes) +bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout) const +{ + // Fill in dummy signatures for fee calculation. + const CScript& scriptPubKey = txout.scriptPubKey; + SignatureData sigdata; + + if (!ProduceSignature(DummySignatureCreator(this), scriptPubKey, sigdata)) + { + return false; + } else { + UpdateInput(tx_in, sigdata); + } + return true; +} + +// Helper for producing a bunch of max-sized low-S signatures (eg 72 bytes) +bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector &txouts) const +{ + // Fill in dummy signatures for fee calculation. + int nIn = 0; + for (const auto& txout : txouts) + { + if (!DummySignInput(txNew.vin[nIn], txout)) { + return false; + } + + nIn++; + } + return true; +} + +int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet) +{ + std::vector txouts; + // Look up the inputs. We should have already checked that this transaction + // IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our + // wallet, with a valid index into the vout array, and the ability to sign. + for (auto& input : tx.vin) { + const auto mi = wallet->mapWallet.find(input.prevout.hash); + if (mi == wallet->mapWallet.end()) { + return -1; + } + assert(input.prevout.n < mi->second.tx->vout.size()); + txouts.emplace_back(mi->second.tx->vout[input.prevout.n]); + } + return CalculateMaximumSignedTxSize(tx, wallet, txouts); +} + +// txouts needs to be in the order of tx.vin +int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector& txouts) +{ + CMutableTransaction txNew(tx); + if (!wallet->DummySignTx(txNew, txouts)) { + // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE) + // implies that we can sign for every input. + return -1; + } + return GetVirtualTransactionSize(txNew); +} + +int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet) +{ + CMutableTransaction txn; + txn.vin.push_back(CTxIn(COutPoint())); + if (!wallet->DummySignInput(txn.vin[0], txout)) { + // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE) + // implies that we can sign for every input. + return -1; + } + return GetVirtualTransactionInputSize(txn.vin[0]); +} + void CWalletTx::GetAmounts(std::list& listReceived, std::list& listSent, CAmount& nFee, std::string& strSentAccount, const isminefilter& filter) const { @@ -2752,7 +2825,7 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, CWalletT assert(txNew.nLockTime < LOCKTIME_THRESHOLD); FeeCalculation feeCalc; CAmount nFeeNeeded; - unsigned int nBytes; + int nBytes; { std::set setCoins; LOCK2(cs_main, cs_wallet); @@ -2903,20 +2976,12 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, CWalletT txNew.vin.push_back(CTxIn(coin.outpoint,CScript(), nSequence)); - // Fill in dummy signatures for fee calculation. - if (!DummySignTx(txNew, setCoins)) { + nBytes = CalculateMaximumSignedTxSize(txNew, this); + if (nBytes < 0) { strFailReason = _("Signing transaction failed"); return false; } - nBytes = GetVirtualTransactionSize(txNew); - - // Remove scriptSigs to eliminate the fee calculation dummy signatures - for (auto& vin : txNew.vin) { - vin.scriptSig = CScript(); - vin.scriptWitness.SetNull(); - } - nFeeNeeded = GetMinimumFee(nBytes, coin_control, ::mempool, ::feeEstimator, &feeCalc); if (feeCalc.reason == FeeReason::FALLBACK && !g_wallet_allow_fallback_fee) { // eventually allow a fallback fee diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 3e2d1794d..fbb87353c 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -269,6 +269,9 @@ public: bool IsCoinBase() const { return tx->IsCoinBase(); } }; +//Get the marginal bytes of spending the specified output +int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet); + /** * A transaction with a bunch of additional info that only the owner cares about. * It includes any unrecorded transactions needed to link it back to the block chain. @@ -462,6 +465,12 @@ public: CAmount GetAvailableWatchOnlyCredit(const bool fUseCache=true) const; CAmount GetChange() const; + // Get the marginal bytes if spending the specified output from this transaction + int GetSpendSize(unsigned int out) const + { + return CalculateMaximumSignedInputSize(tx->vout[out], pwallet); + } + void GetAmounts(std::list& listReceived, std::list& listSent, CAmount& nFee, std::string& strSentAccount, const isminefilter& filter) const; @@ -525,6 +534,9 @@ public: int i; int nDepth; + /** 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 nInputBytes; + /** Whether we have the private keys to spend this output */ bool fSpendable; @@ -540,7 +552,12 @@ public: COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn) { - tx = txIn; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; + tx = txIn; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; nInputBytes = -1; + // If known and signable by the given wallet, compute nInputBytes + // Failure will keep this value -1 + if (fSpendable && tx) { + nInputBytes = tx->GetSpendSize(i); + } } std::string ToString() const; @@ -981,8 +998,14 @@ public: void ListAccountCreditDebit(const std::string& strAccount, std::list& entries); bool AddAccountingEntry(const CAccountingEntry&); bool AddAccountingEntry(const CAccountingEntry&, CWalletDB *pwalletdb); - template - bool DummySignTx(CMutableTransaction &txNew, const ContainerType &coins) const; + bool DummySignTx(CMutableTransaction &txNew, const std::set &txouts) const + { + std::vector v_txouts(txouts.size()); + std::copy(txouts.begin(), txouts.end(), v_txouts.begin()); + return DummySignTx(txNew, v_txouts); + } + bool DummySignTx(CMutableTransaction &txNew, const std::vector &txouts) const; + bool DummySignInput(CTxIn &tx_in, const CTxOut &txout) const; static CFeeRate minTxFee; static CFeeRate fallbackFee; @@ -1227,31 +1250,6 @@ public: } }; -// Helper for producing a bunch of max-sized low-S signatures (eg 72 bytes) -// ContainerType is meant to hold pair, and be iterable -// so that each entry corresponds to each vIn, in order. -template -bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins) const -{ - // Fill in dummy signatures for fee calculation. - int nIn = 0; - for (const auto& coin : coins) - { - const CScript& scriptPubKey = coin.txout.scriptPubKey; - SignatureData sigdata; - - if (!ProduceSignature(DummySignatureCreator(this), scriptPubKey, sigdata)) - { - return false; - } else { - UpdateTransaction(txNew, nIn, sigdata); - } - - nIn++; - } - return true; -} - OutputType ParseOutputType(const std::string& str, OutputType default_type = OUTPUT_TYPE_DEFAULT); const std::string& FormatOutputType(OutputType type); @@ -1299,4 +1297,10 @@ public: } }; +// Calculate the size of the transaction assuming all signatures are max size +// Use DummySignatureCreator, which inserts 72 byte signatures everywhere. +// NOTE: this requires that all inputs must be in mapWallet (eg the tx should +// be IsAllFromMe). +int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet); +int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector& txouts); #endif // BITCOIN_WALLET_WALLET_H From f84fed8eb6a8518a54a997044e55332dd37e223d Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Fri, 9 Mar 2018 20:51:22 -0500 Subject: [PATCH 02/13] Store effective value, fee, and long term fee in CInputCoin Have CInputCOin store effective value information. This includes the effective value itself, the fee, and the long term fee for the input --- src/wallet/wallet.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index fbb87353c..89dc4f399 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -513,6 +513,9 @@ public: COutPoint outpoint; CTxOut txout; + CAmount effective_value; + CAmount fee = 0; + CAmount long_term_fee = 0; bool operator<(const CInputCoin& rhs) const { return outpoint < rhs.outpoint; From 0185939be6f7c5554b864e33657ce610fd434e18 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Mon, 5 Mar 2018 16:29:37 -0500 Subject: [PATCH 03/13] Implement Branch and Bound coin selection in a new file Create a new file for coin selection logic and implement the BnB algorithm in it. --- src/Makefile.am | 2 + src/wallet/coinselection.cpp | 165 +++++++++++++++++++++++++++++++++++ src/wallet/coinselection.h | 15 ++++ src/wallet/wallet.cpp | 1 + src/wallet/wallet.h | 1 + 5 files changed, 184 insertions(+) create mode 100644 src/wallet/coinselection.cpp create mode 100644 src/wallet/coinselection.h diff --git a/src/Makefile.am b/src/Makefile.am index 0a9370c85..7c2fe56d9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -172,6 +172,7 @@ BITCOIN_CORE_H = \ wallet/wallet.h \ wallet/walletdb.h \ wallet/walletutil.h \ + wallet/coinselection.h \ warnings.h \ zmq/zmqabstractnotifier.h \ zmq/zmqconfig.h\ @@ -253,6 +254,7 @@ libbitcoin_wallet_a_SOURCES = \ wallet/wallet.cpp \ wallet/walletdb.cpp \ wallet/walletutil.cpp \ + wallet/coinselection.cpp \ $(BITCOIN_CORE_H) # crypto primitives library diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp new file mode 100644 index 000000000..05bcdc12b --- /dev/null +++ b/src/wallet/coinselection.cpp @@ -0,0 +1,165 @@ +// 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 CAmount& target_value This is the value that we want to select. It is the lower + * bound of the range. + * @param const CAmount& 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 CAmount& value_ret -> This is an output parameter for the total value of the CInputCoins + * that were selected. + * @param CAmount 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 CAmount& target_value, const CAmount& cost_of_change, std::set& out_set, CAmount& value_ret, CAmount not_input_fees) +{ + out_set.clear(); + CAmount curr_value = 0; + + std::vector curr_selection; // select the utxo at this index + curr_selection.reserve(utxo_pool.size()); + CAmount actual_target = not_input_fees + target_value; + + // Calculate curr_available_value + CAmount curr_available_value = 0; + 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 > 0); + 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); + + CAmount curr_waste = 0; + std::vector best_selection; + CAmount 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) > 0)) { // Don't select things which we know will be more wasteful if the waste is increasing + backtrack = true; + } else if (curr_value >= actual_target) { // Selected value is within range + curr_waste += (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. + if (curr_waste <= best_waste) { + best_selection = curr_selection; + best_selection.resize(utxo_pool.size()); + best_waste = curr_waste; + } + curr_waste -= (curr_value - actual_target); // Remove the excess value as we will be selecting different coins now + 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; + } else { // Moving forwards, continuing down this branch + 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 = 0; + 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; +} diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h new file mode 100644 index 000000000..a98f1cc7c --- /dev/null +++ b/src/wallet/coinselection.h @@ -0,0 +1,15 @@ +// 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_COINSELECTION_H +#define BITCOIN_COINSELECTION_H + +#include +#include +#include +#include + +bool SelectCoinsBnB(std::vector& utxo_pool, const CAmount& target_value, const CAmount& cost_of_change, std::set& out_set, CAmount& value_ret, CAmount not_input_fees); + +#endif // BITCOIN_COINSELECTION_H diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a739077a3..e797a63dd 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 89dc4f399..a2fab3c88 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -509,6 +509,7 @@ public: outpoint = COutPoint(walletTx->GetHash(), i); txout = walletTx->tx->vout[i]; + effective_value = txout.nValue; } COutPoint outpoint; From ce7435cf1ef36109595be9a3a3955afdff1d63e4 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Fri, 9 Mar 2018 17:21:27 -0500 Subject: [PATCH 04/13] Move output eligibility to a separate function --- src/wallet/wallet.cpp | 28 ++++++++++++++++------------ src/wallet/wallet.h | 3 +++ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index e797a63dd..639e00f2b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2484,6 +2484,20 @@ static void ApproximateBestSubset(const std::vector& vValue, const C } } +bool CWallet::OutputEligibleForSpending(const COutput& output, const int nConfMine, const int nConfTheirs, const uint64_t nMaxAncestors) const +{ + if (!output.fSpendable) + return false; + + if (output.nDepth < (output.tx->IsFromMe(ISMINE_ALL) ? nConfMine : nConfTheirs)) + return false; + + if (!mempool.TransactionWithinChainLimit(output.tx->GetHash(), nMaxAncestors)) + return false; + + return true; +} + bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const int nConfMine, const int nConfTheirs, const uint64_t nMaxAncestors, std::vector vCoins, std::set& setCoinsRet, CAmount& nValueRet) const { @@ -2499,20 +2513,10 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const int nConfMin for (const COutput &output : vCoins) { - if (!output.fSpendable) + if (!OutputEligibleForSpending(output, nConfMine, nConfTheirs, nMaxAncestors)) continue; - const CWalletTx *pcoin = output.tx; - - if (output.nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? nConfMine : nConfTheirs)) - continue; - - if (!mempool.TransactionWithinChainLimit(pcoin->GetHash(), nMaxAncestors)) - continue; - - int i = output.i; - - CInputCoin coin = CInputCoin(pcoin, i); + CInputCoin coin = CInputCoin(output.tx, output.i); if (coin.txout.nValue == nTargetValue) { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index a2fab3c88..d2389ae2b 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1190,6 +1190,9 @@ public: * This function will automatically add the necessary scripts to the wallet. */ CTxDestination AddAndGetDestinationForScript(const CScript& script, OutputType); + + /** Whether a given output is spendable by this wallet */ + bool OutputEligibleForSpending(const COutput& output, const int nConfMine, const int nConfTheirs, const uint64_t nMaxAncestors) const; }; /** A key allocated from the key pool. */ From 7d77eb1a5b980a7d56acbf0b3861434c5e195c54 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Fri, 9 Mar 2018 23:11:20 -0500 Subject: [PATCH 05/13] Use a struct for output eligibility Instead of specifying 3 parameters, use a struct for those parameters in order to reduce the number of arguments to SelectCoinsMinConf. --- src/bench/coin_selection.cpp | 3 +- src/wallet/test/wallet_tests.cpp | 78 +++++++++++++++++--------------- src/wallet/wallet.cpp | 24 +++++----- src/wallet/wallet.h | 12 ++++- 4 files changed, 65 insertions(+), 52 deletions(-) diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index 98965840c..55ff7b61f 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -44,7 +44,8 @@ static void CoinSelection(benchmark::State& state) std::set setCoinsRet; CAmount nValueRet; - bool success = wallet.SelectCoinsMinConf(1003 * COIN, 1, 6, 0, vCoins, setCoinsRet, nValueRet); + CoinEligibilityFilter filter_standard(1, 6, 0); + bool success = wallet.SelectCoinsMinConf(1003 * COIN, filter_standard, vCoins, setCoinsRet, nValueRet); assert(success); assert(nValueRet == 1003 * COIN); assert(setCoinsRet.size() == 2); diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 41348b50a..003d18207 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -74,6 +74,10 @@ static bool equal_sets(CoinSet a, CoinSet b) return ret.first == a.end() && ret.second == b.end(); } +CoinEligibilityFilter filter_standard(1, 6, 0); +CoinEligibilityFilter filter_confirmed(1, 1, 0); +CoinEligibilityFilter filter_standard_extra(6, 6, 0); + BOOST_AUTO_TEST_CASE(coin_selection_tests) { CoinSet setCoinsRet, setCoinsRet2; @@ -87,24 +91,24 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) empty_wallet(); // with an empty wallet we can't even pay one cent - BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet)); add_coin(1*CENT, 4); // add a new 1 cent coin // with a new 1 cent coin, we still can't find a mature 1 cent - BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet)); // but we can find a new 1 cent - BOOST_CHECK( testWallet.SelectCoinsMinConf( 1 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf( 1 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); add_coin(2*CENT); // add a mature 2 cent coin // we can't make 3 cents of mature coins - BOOST_CHECK(!testWallet.SelectCoinsMinConf( 3 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(!testWallet.SelectCoinsMinConf( 3 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet)); // we can make 3 cents of new coins - BOOST_CHECK( testWallet.SelectCoinsMinConf( 3 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf( 3 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 3 * CENT); add_coin(5*CENT); // add a mature 5 cent coin, @@ -114,33 +118,33 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) // now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38 // we can't make 38 cents only if we disallow new coins: - BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet)); // we can't even make 37 cents if we don't allow new coins even if they're from us - BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, 6, 6, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, filter_standard_extra, vCoins, setCoinsRet, nValueRet)); // but we can make 37 cents if we accept new coins from ourself - BOOST_CHECK( testWallet.SelectCoinsMinConf(37 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf(37 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 37 * CENT); // and we can make 38 cents if we accept all new coins - BOOST_CHECK( testWallet.SelectCoinsMinConf(38 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf(38 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 38 * CENT); // try making 34 cents from 1,2,5,10,20 - we can't do it exactly - BOOST_CHECK( testWallet.SelectCoinsMinConf(34 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf(34 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 35 * CENT); // but 35 cents is closest BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible) // when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5 - BOOST_CHECK( testWallet.SelectCoinsMinConf( 7 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf( 7 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 7 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // when we try making 8 cents, the smaller coins (1,2,5) are exactly enough. - BOOST_CHECK( testWallet.SelectCoinsMinConf( 8 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf( 8 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK(nValueRet == 8 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10) - BOOST_CHECK( testWallet.SelectCoinsMinConf( 9 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf( 9 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 10 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); @@ -154,30 +158,30 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total // check that we have 71 and not 72 - BOOST_CHECK( testWallet.SelectCoinsMinConf(71 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK(!testWallet.SelectCoinsMinConf(72 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf(71 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(!testWallet.SelectCoinsMinConf(72 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet)); // now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20 - BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total // now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20 - BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30 // and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18 - BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // because in the event of a tie, the biggest coin wins // now try making 11 cents. we should get 5+6 - BOOST_CHECK( testWallet.SelectCoinsMinConf(11 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf(11 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 11 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); @@ -186,11 +190,11 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) add_coin( 2*COIN); add_coin( 3*COIN); add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents - BOOST_CHECK( testWallet.SelectCoinsMinConf(95 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf(95 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); - BOOST_CHECK( testWallet.SelectCoinsMinConf(195 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf(195 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); @@ -205,14 +209,14 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) // try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE // we'll get change smaller than MIN_CHANGE whatever happens, so can expect MIN_CHANGE exactly - BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // but if we add a bigger coin, small change is avoided add_coin(1111*MIN_CHANGE); // try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 - BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount // if we add more small coins: @@ -220,7 +224,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) add_coin(MIN_CHANGE * 7 / 10); // and try again to make 1.0 * MIN_CHANGE - BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount // run the 'mtgox' test (see http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf) @@ -229,7 +233,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) for (int j = 0; j < 20; j++) add_coin(50000 * COIN); - BOOST_CHECK( testWallet.SelectCoinsMinConf(500000 * COIN, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf(500000 * COIN, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten coins @@ -242,7 +246,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) add_coin(MIN_CHANGE * 6 / 10); add_coin(MIN_CHANGE * 7 / 10); add_coin(1111 * MIN_CHANGE); - BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); // we get the bigger coin BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); @@ -252,7 +256,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) add_coin(MIN_CHANGE * 6 / 10); add_coin(MIN_CHANGE * 8 / 10); add_coin(1111 * MIN_CHANGE); - BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // we should get the exact amount BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two coins 0.4+0.6 @@ -263,12 +267,12 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) add_coin(MIN_CHANGE * 100); // trying to make 100.01 from these three coins - BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 10001 / 100, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 10001 / 100, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE * 10105 / 100); // we should get all coins BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change - BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 9990 / 100, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 9990 / 100, filter_confirmed, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE); BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); @@ -278,7 +282,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) // Create 676 inputs (= (old MAX_STANDARD_TX_SIZE == 100000) / 148 bytes per input) for (uint16_t j = 0; j < 676; j++) add_coin(amt); - BOOST_CHECK(testWallet.SelectCoinsMinConf(2000, 1, 1, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(testWallet.SelectCoinsMinConf(2000, filter_confirmed, vCoins, setCoinsRet, nValueRet)); if (amt - 2000 < MIN_CHANGE) { // needs more than one input: uint16_t returnSize = std::ceil((2000.0 + MIN_CHANGE)/amt); @@ -300,8 +304,8 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) // picking 50 from 100 coins doesn't depend on the shuffle, // but does depend on randomness in the stochastic approximation code - BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, 1, 6, 0, vCoins, setCoinsRet , nValueRet)); - BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, 1, 6, 0, vCoins, setCoinsRet2, nValueRet)); + BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, filter_standard, vCoins, setCoinsRet , nValueRet)); + BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, filter_standard, vCoins, setCoinsRet2, nValueRet)); BOOST_CHECK(!equal_sets(setCoinsRet, setCoinsRet2)); int fails = 0; @@ -309,8 +313,8 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) { // selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time // run the test RANDOM_REPEATS times and only complain if all of them fail - BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, 1, 6, 0, vCoins, setCoinsRet , nValueRet)); - BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, 1, 6, 0, vCoins, setCoinsRet2, nValueRet)); + BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, filter_standard, vCoins, setCoinsRet , nValueRet)); + BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, filter_standard, vCoins, setCoinsRet2, nValueRet)); if (equal_sets(setCoinsRet, setCoinsRet2)) fails++; } @@ -330,8 +334,8 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) { // selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time // run the test RANDOM_REPEATS times and only complain if all of them fail - BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, 1, 6, 0, vCoins, setCoinsRet , nValueRet)); - BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, 1, 6, 0, vCoins, setCoinsRet2, nValueRet)); + BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, filter_standard, vCoins, setCoinsRet , nValueRet)); + BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, filter_standard, vCoins, setCoinsRet2, nValueRet)); if (equal_sets(setCoinsRet, setCoinsRet2)) fails++; } @@ -355,7 +359,7 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset) add_coin(1000 * COIN); add_coin(3 * COIN); - BOOST_CHECK(testWallet.SelectCoinsMinConf(1003 * COIN, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(testWallet.SelectCoinsMinConf(1003 * COIN, filter_standard, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 1003 * COIN); BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 639e00f2b..fe9e803d2 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2484,21 +2484,21 @@ static void ApproximateBestSubset(const std::vector& vValue, const C } } -bool CWallet::OutputEligibleForSpending(const COutput& output, const int nConfMine, const int nConfTheirs, const uint64_t nMaxAncestors) const +bool CWallet::OutputEligibleForSpending(const COutput& output, const CoinEligibilityFilter& eligibilty_filter) const { if (!output.fSpendable) return false; - if (output.nDepth < (output.tx->IsFromMe(ISMINE_ALL) ? nConfMine : nConfTheirs)) + if (output.nDepth < (output.tx->IsFromMe(ISMINE_ALL) ? eligibilty_filter.conf_mine : eligibilty_filter.conf_theirs)) return false; - if (!mempool.TransactionWithinChainLimit(output.tx->GetHash(), nMaxAncestors)) + if (!mempool.TransactionWithinChainLimit(output.tx->GetHash(), eligibilty_filter.max_ancestors)) return false; return true; } -bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const int nConfMine, const int nConfTheirs, const uint64_t nMaxAncestors, std::vector vCoins, +bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibilty_filter, std::vector vCoins, std::set& setCoinsRet, CAmount& nValueRet) const { setCoinsRet.clear(); @@ -2513,7 +2513,7 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const int nConfMin for (const COutput &output : vCoins) { - if (!OutputEligibleForSpending(output, nConfMine, nConfTheirs, nMaxAncestors)) + if (!OutputEligibleForSpending(output, eligibilty_filter)) continue; CInputCoin coin = CInputCoin(output.tx, output.i); @@ -2646,13 +2646,13 @@ bool CWallet::SelectCoins(const std::vector& vAvailableCoins, const CAm bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS); bool res = nTargetValue <= nValueFromPresetInputs || - SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 6, 0, vCoins, setCoinsRet, nValueRet) || - SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 1, 0, vCoins, setCoinsRet, nValueRet) || - (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, 2, vCoins, setCoinsRet, nValueRet)) || - (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, std::min((size_t)4, nMaxChainLength/3), vCoins, setCoinsRet, nValueRet)) || - (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, nMaxChainLength/2, vCoins, setCoinsRet, nValueRet)) || - (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, nMaxChainLength, vCoins, setCoinsRet, nValueRet)) || - (bSpendZeroConfChange && !fRejectLongChains && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, std::numeric_limits::max(), vCoins, setCoinsRet, nValueRet)); + SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet) || + SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet) || + (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet)) || + (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::min((size_t)4, nMaxChainLength/3)), vCoins, setCoinsRet, nValueRet)) || + (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, nMaxChainLength/2), vCoins, setCoinsRet, nValueRet)) || + (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, nMaxChainLength), vCoins, setCoinsRet, nValueRet)) || + (bSpendZeroConfChange && !fRejectLongChains && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::numeric_limits::max()), vCoins, setCoinsRet, nValueRet)); // because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset setCoinsRet.insert(setPresetCoins.begin(), setPresetCoins.end()); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index d2389ae2b..4f195b2bc 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -679,6 +679,14 @@ private: std::vector _ssExtra; }; +struct CoinEligibilityFilter +{ + const int conf_mine; + const int conf_theirs; + const uint64_t max_ancestors; + + CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors) {} +}; class WalletRescanReserver; //forward declarations for ScanForWalletTransactions/RescanFromTime /** @@ -881,7 +889,7 @@ public: * completion the coin set and corresponding actual target value is * assembled */ - bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, uint64_t nMaxAncestors, std::vector vCoins, std::set& setCoinsRet, CAmount& nValueRet) const; + bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibilty_filter, std::vector vCoins, std::set& setCoinsRet, CAmount& nValueRet) const; bool IsSpent(const uint256& hash, unsigned int n) const; @@ -1192,7 +1200,7 @@ public: CTxDestination AddAndGetDestinationForScript(const CScript& script, OutputType); /** Whether a given output is spendable by this wallet */ - bool OutputEligibleForSpending(const COutput& output, const int nConfMine, const int nConfTheirs, const uint64_t nMaxAncestors) const; + bool OutputEligibleForSpending(const COutput& output, const CoinEligibilityFilter& eligibilty_filter) const; }; /** A key allocated from the key pool. */ From 4b2716da46e96c45206db869b83c28c5fc7889f4 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Wed, 7 Mar 2018 12:18:37 -0500 Subject: [PATCH 06/13] Remove coinselection.h -> wallet.h circular dependency Changes CInputCoin to coinselection and to use CTransactionRef in order to avoid a circular dependency. Also moves other coin selection specific variables out of wallet.h to coinselectoin.h --- src/wallet/coinselection.h | 39 +++++++++++++++++++++++++++++++++++++- src/wallet/wallet.cpp | 8 ++++---- src/wallet/wallet.h | 39 +------------------------------------- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h index a98f1cc7c..bbfa08a24 100644 --- a/src/wallet/coinselection.h +++ b/src/wallet/coinselection.h @@ -8,7 +8,44 @@ #include #include #include -#include + +//! target minimum change amount +static const CAmount MIN_CHANGE = CENT; +//! final minimum change amount after paying for fees +static const CAmount 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->GetHash(), i); + txout = tx->vout[i]; + effective_value = txout.nValue; + } + + COutPoint outpoint; + CTxOut txout; + CAmount effective_value; + CAmount fee = 0; + CAmount long_term_fee = 0; + + 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; + } +}; bool SelectCoinsBnB(std::vector& utxo_pool, const CAmount& target_value, const CAmount& cost_of_change, std::set& out_set, CAmount& value_ret, CAmount not_input_fees); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index fe9e803d2..fbd515c47 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2516,7 +2516,7 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil if (!OutputEligibleForSpending(output, eligibilty_filter)) continue; - CInputCoin coin = CInputCoin(output.tx, output.i); + CInputCoin coin = CInputCoin(output.tx->tx, output.i); if (coin.txout.nValue == nTargetValue) { @@ -2606,7 +2606,7 @@ bool CWallet::SelectCoins(const std::vector& vAvailableCoins, const CAm if (!out.fSpendable) continue; nValueRet += out.tx->tx->vout[out.i].nValue; - setCoinsRet.insert(CInputCoin(out.tx, out.i)); + setCoinsRet.insert(CInputCoin(out.tx->tx, out.i)); } return (nValueRet >= nTargetValue); } @@ -2628,7 +2628,7 @@ bool CWallet::SelectCoins(const std::vector& vAvailableCoins, const CAm if (pcoin->tx->vout.size() <= outpoint.n) return false; nValueFromPresetInputs += pcoin->tx->vout[outpoint.n].nValue; - setPresetCoins.insert(CInputCoin(pcoin, outpoint.n)); + setPresetCoins.insert(CInputCoin(pcoin->tx, outpoint.n)); } else return false; // TODO: Allow non-wallet inputs } @@ -2636,7 +2636,7 @@ bool CWallet::SelectCoins(const std::vector& vAvailableCoins, const CAm // remove preset inputs from vCoins for (std::vector::iterator it = vCoins.begin(); it != vCoins.end() && coinControl && coinControl->HasSelected();) { - if (setPresetCoins.count(CInputCoin(it->tx, it->i))) + if (setPresetCoins.count(CInputCoin(it->tx->tx, it->i))) it = vCoins.erase(it); else ++it; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 4f195b2bc..8f518bae6 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -17,6 +17,7 @@ #include