Skip to content

Conversation

achow101
Copy link
Member

@achow101 achow101 commented May 23, 2018

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.

@achow101
Copy link
Member Author

cc @xekyo @instagibbs


// We have enough coins, stop selecting
if (curr_value >= target_value + not_input_fees) {
break;
Copy link
Contributor

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

return true;
} else {
bnb_used = false;
return SingleRandomDraw(nTargetValue, utxo_pool, setCoinsRet, nValueRet, not_input_fees);
Copy link
Contributor

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eligibilty_filter typo

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@achow101 achow101 force-pushed the srd-fallback branch 2 times, most recently from 80a9f2c to 9eea461 Compare May 23, 2018 17:40
Copy link
Member

@instagibbs instagibbs left a 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.

/*
* 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)
Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -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)) {
Copy link
Member

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

Copy link
Member Author

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.

Copy link
Member

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?

Copy link
Member

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.

Copy link
Member Author

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.

@@ -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;
Copy link
Member

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -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));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parenthesis please

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@instagibbs
Copy link
Member

Some thoughts.

@achow101
Copy link
Member Author

achow101 commented Jun 5, 2018

"Calculate the transaction fees" <--- this commit probably needs more description, not sure what it's doing.

Reworded, hopefully it's better.

@Empact
Copy link
Contributor

Empact commented Jun 8, 2018

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).

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
*/
Copy link
Contributor

@Empact Empact Jun 8, 2018

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 /**

Copy link
Member Author

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);
Copy link
Contributor

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

Copy link
Member Author

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

@@ -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));
Copy link
Contributor

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)

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about now?

it = vCoins.erase(it);
else
}
else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: whitespace

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

CAmount long_term_fee = 0;
mutable CAmount effective_value;
mutable CAmount fee = 0;
mutable CAmount long_term_fee = 0;
Copy link
Contributor

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@achow101
Copy link
Member Author

achow101 commented Jun 8, 2018

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).

Done


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);
Copy link
Member

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];
    ...
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -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)) {
Copy link
Member

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?

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: decrase

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@achow101 achow101 force-pushed the srd-fallback branch 2 times, most recently from efda8e0 to 05dfc65 Compare June 9, 2018 23:27
nValueRet = 0;
FastRandomContext random_ctx;
CAmount curr_value = 0;
for (size_t i = 0; i < utxo_pool.size; ++i) {
Copy link
Member

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()).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops.

@achow101
Copy link
Member Author

I'm not quite sure what is causing the travis failure.

@achow101
Copy link
Member Author

achow101 commented Jul 7, 2018

Fixed the test failure.

…et inputs"

This reverts commit 9552dfb, reversing
changes made to f686002.
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.
@achow101
Copy link
Member Author

Rebased

@DrahtBot
Copy link
Contributor

DrahtBot commented Sep 7, 2018

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.

@DrahtBot
Copy link
Contributor

Needs rebase

@DrahtBot
Copy link
Contributor

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.

@DrahtBot DrahtBot closed this Dec 12, 2018
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Dec 16, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants