-
Notifications
You must be signed in to change notification settings - Fork 37.7k
Replace coin selection fallback strategy with Single Random Draw #13307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
src/wallet/coinselection.cpp
Outdated
|
||
// We have enough coins, stop selecting | ||
if (curr_value >= target_value + not_input_fees) { | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could return true here, return false if the loop exhausts, to avoid duplicating the condition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/wallet/wallet.cpp
Outdated
return true; | ||
} else { | ||
bnb_used = false; | ||
return SingleRandomDraw(nTargetValue, utxo_pool, setCoinsRet, nValueRet, not_input_fees); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could collapse this to boolean fallback at this point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/wallet/wallet.h
Outdated
@@ -849,8 +849,8 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface | |||
* completion the coin set and corresponding actual target value is | |||
* assembled | |||
*/ | |||
bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> vCoins, | |||
std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const; | |||
bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibilty_filter, std::vector<COutput> vCoins, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eligibilty_filter
typo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
80a9f2c
to
9eea461
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Calculate the transaction fees" <--- this commit probably needs more description, not sure what it's doing.
src/wallet/coinselection.cpp
Outdated
/* | ||
* Randomly selects coins until the target value is exceeded. Uses effective values | ||
*/ | ||
bool SingleRandomDraw(const CAmount& target_value, std::vector<CInputCoin>& utxo_pool, std::set<CInputCoin>& out_set, CAmount& value_ret, CAmount not_input_fees) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
non_input_fees
? It's not "not fees", rather "non-input". Easier for me to read.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/wallet/wallet.cpp
Outdated
@@ -2509,9 +2509,10 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil | |||
// Calculate the fees for things that aren't inputs | |||
CAmount not_input_fees = coin_selection_params.effective_fee.GetFee(coin_selection_params.tx_noinputs_size); | |||
|
|||
if (coin_selection_params.use_bnb) { | |||
// Start with BnB. If that fails, use SRD | |||
if (SelectCoinsBnB(utxo_pool, nTargetValue, cost_of_change, setCoinsRet, nValueRet, not_input_fees)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this new logic means that non-BnB will be tried more often? Instead of trying all variants of BnB(6 confirms, 1 confirm, small chain, etc), we seem to be trying 6 confirms for BnB, then 6 confirms for non-BnB
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, yes, that appears to be what is happening. I guess this depends on whether we want to select an exact match for coins with less confirmatoins, or whether we want confirmations over exact match.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good point. Perhaps a solution is to add a use_bnb
argument to SelectCoinsMinConf
(but not as part CoinSelectionParams
), and then have a sequence of attempts with and without use_bnb in SelectCoins
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer the behavior in master due to privacy reasons of change-less transactions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have implemented this change.
src/wallet/wallet.cpp
Outdated
@@ -2916,6 +2887,40 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac | |||
for (const auto& coin : setCoins) | |||
txNew.vin.push_back(CTxIn(coin.outpoint,CScript(), | |||
nSequence)); | |||
|
|||
// Fill vout | |||
bool fFirst = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe rename to first_output
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/wallet/wallet.cpp
Outdated
@@ -2498,7 +2499,7 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil | |||
continue; | |||
|
|||
CInputCoin coin(output.tx->tx, output.i); | |||
coin.effective_value = coin.txout.nValue - (output.nInputBytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(output.nInputBytes)); | |||
coin.effective_value = coin.txout.nValue - (output.nInputBytes < 0 || !use_effective ? 0 : coin_selection_params.effective_fee.GetFee(output.nInputBytes)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parenthesis please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Some thoughts. |
Reworded, hopefully it's better. |
nit: Somewhat better to revert the merge commit 9552dfb rather than the 2 pr commits, to be explicit that the whole PR was reverted (and which one). |
src/wallet/coinselection.cpp
Outdated
bool KnapsackSolver(const CAmount& nTargetValue, std::vector<CInputCoin>& vCoins, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet) | ||
/* | ||
* Randomly selects coins until the target value is exceeded. Uses effective values | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Better to document in the header, and use doxygen comment /**
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
CoinSelectionParams coin_selection_params_knapsack(false, 34, 148, CFeeRate(0), 0); | ||
CoinSelectionParams coin_selection_params_bnb(true, 34, 148, CFeeRate(0), 0); | ||
|
||
CoinSelectionParams coin_selection_params_bnb(34, 148, CFeeRate(0), 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_bnb
probably no longer relevant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed here and elsewhere
@@ -216,316 +214,13 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) | |||
} | |||
|
|||
// Make sure that effective value is working in SelectCoinsMinConf when BnB is used | |||
CoinSelectionParams coin_selection_params_bnb(true, 0, 0, CFeeRate(3000), 0); | |||
CoinSelectionParams coin_selection_params_bnb(0, 0, CFeeRate(3000), 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same
src/wallet/wallet.cpp
Outdated
@@ -2529,14 +2529,18 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm | |||
if (!out.fSpendable) | |||
continue; | |||
nValueRet += out.tx->tx->vout[out.i].nValue; | |||
setCoinsRet.insert(CInputCoin(out.tx->tx, out.i)); | |||
CInputCoin coin(out.tx->tx, out.i); | |||
coin.effective_value = coin.txout.nValue - (out.nInputBytes < 0 || !use_effective ? 0 : coin_selection_params.effective_fee.GetFee(out.nInputBytes)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: explicit precedence would be nice here: (out.nInputBytes < 0 || !use_effective)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought I did this earlier. Must have gotten lost in a rebase. Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't look changed fyi.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about now?
src/wallet/wallet.cpp
Outdated
it = vCoins.erase(it); | ||
else | ||
} | ||
else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: whitespace
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/wallet/coinselection.h
Outdated
CAmount long_term_fee = 0; | ||
mutable CAmount effective_value; | ||
mutable CAmount fee = 0; | ||
mutable CAmount long_term_fee = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Better to squash this change into the commit that calls for it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Done |
src/wallet/coinselection.cpp
Outdated
|
||
bool SingleRandomDraw(const CAmount& target_value, std::vector<CInputCoin>& utxo_pool, std::set<CInputCoin>& out_set, CAmount& value_ret, CAmount non_input_fees) | ||
{ | ||
random_shuffle(utxo_pool.begin(), utxo_pool.end(), GetRandInt); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two comments:
- Requesting new entropy for each element being sorted may be slow (each call to
GetRandInt
invokes OpenSSL for randomness) (this also applies to the code you copied, but you're going to remove that anyway). - It's overkill to permute all elements, as you're likely only going to use a few.
For the first, create a FastRandomContext
and use that in std::shuffle
(rather than random_shuffle
):
std::shuffle(utxo_pool.begin(), utxo_pool.end(), FastRandomContext());`
For the second, you can permute on the fly; for example:
FastRandomContext ctx;
for (size_t i = 0; i < utxo_pool.size; ++i) {
size_t pos = i + ctx.randrange(utxo_pool.size() - i); // randomly pick one of the remaining elements
std::swap(utxo_pool[i], utxo_pool[pos]);
const CInputCoin& utxo = utxo_pool[i];
...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/wallet/wallet.cpp
Outdated
@@ -2509,9 +2509,10 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil | |||
// Calculate the fees for things that aren't inputs | |||
CAmount not_input_fees = coin_selection_params.effective_fee.GetFee(coin_selection_params.tx_noinputs_size); | |||
|
|||
if (coin_selection_params.use_bnb) { | |||
// Start with BnB. If that fails, use SRD | |||
if (SelectCoinsBnB(utxo_pool, nTargetValue, cost_of_change, setCoinsRet, nValueRet, not_input_fees)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good point. Perhaps a solution is to add a use_bnb
argument to SelectCoinsMinConf
(but not as part CoinSelectionParams
), and then have a sequence of attempts with and without use_bnb in SelectCoins
?
src/wallet/wallet.cpp
Outdated
nSequence)); | ||
if (nSubtractFeeFromAmount != 0) { | ||
// If we are subtracting the fee from the amount, nChange cannot be less than 0 because nFeeRet is not involved. | ||
// We need to decrase nFeeRet by the change output fee so that we do not overpay. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: decrase
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
efda8e0
to
05dfc65
Compare
src/wallet/coinselection.cpp
Outdated
nValueRet = 0; | ||
FastRandomContext random_ctx; | ||
CAmount curr_value = 0; | ||
for (size_t i = 0; i < utxo_pool.size; ++i) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Travis fails because of size
(instead of size()
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops.
I'm not quite sure what is causing the travis failure. |
Fixed the test failure. |
SingleRandomDraw randomly chooses UTXOs until there is sufficient effective value.
Replaces the knapsack solver fallback with the SRD fallback. Also changes SelectCoinsMinConf to use exclusively effective value to work with BnB and SRD
Instead of the caller telling SelectCoinsMinConf when to use BnB or the fallback, have SelectCoinsMinConf do it itself. This removes the use_bnb field of coin_selection_params
We are now using effective value for coin selection, so there is no need for the loop or any of the fee checking things that were done within it. All fees ard handled by the effective value selection Move vout filling to after coins are selected so that the transaction fee is actually known to handle when users want to subtract the fee from the outputs.
bnb_used is no longer necessary since we account for fees when calculating how much change there is
For preset inputs, use their effective values
Fix tests that relied on non-deterministic coin selection to be able to handle the random coin selection.
Rebased |
Note to reviewers: This pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
Needs rebase |
There hasn't been much activity lately and the patch still needs rebase, so I am closing this for now. Please let me know when you want to continue working on this, so the pull request can be re-opened. |
When BnB selection fails to find an exact match, we fall back to the original Bitcoin Core algorithm. This PR replaces that fallback with the Single Random Draw strategy. Additionally, because SRD uses effective values, the previous fee increasing loop thing is now removed and effective value is used everywhere.
Some tests may fail spuriously because they may rely on semi-deterministic behavior from the original coin selection algorithm. I have been able to fix the ones that fail the most, but others may still have issues. Please let me know if you see any tests fail for coin selection reasons (e.g. insufficient funds, missing inputs, etc.) and I will try to fix them.
I will be working on doing simulations this week and will post the data once I finish them.