Skip to content

Conversation

brunoerg
Copy link
Contributor

@brunoerg brunoerg commented Nov 9, 2021

Based on #22009, Bitcoin Core execute all the coin selection algorithms and decide what solution will be used based on the waste metric. However, some users may prefer a solution that doesn't create change (privacy reason). This PR adds a config option to prioritize the BnB solution in send and fundrawtransaction.

@fanquake fanquake added the Wallet label Nov 9, 2021
@brunoerg brunoerg force-pushed the 2021-11-coinselection-option branch from 7ed827a to d3a434d Compare November 9, 2021 12:20
@DrahtBot
Copy link
Contributor

DrahtBot commented Nov 10, 2021

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #26720 (wallet: coin selection, don't return results that exceed the max allowed weight by furszy)
  • #26129 (wallet, refactor: FundTransaction(): return out-params as util::Result structure by theStack)
  • #25806 (wallet: group outputs only once, decouple it from Coin Selection by furszy)
  • #25273 (wallet: Pass through transaction locktime and preset input sequences and scripts to CreateTransaction by achow101)

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.

Copy link
Contributor

@lsilva01 lsilva01 left a comment

Choose a reason for hiding this comment

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

Code Review ACK d3a434d
Perhaps a functional test for this new parameter can be added.

@brunoerg
Copy link
Contributor Author

Code Review ACK d3a434d Perhaps a functional test for this new parameter can be added.

Functional test for it would be great, will work on it.

@brunoerg brunoerg force-pushed the 2021-11-coinselection-option branch from d3a434d to 83e33d5 Compare November 18, 2021 10:49
@ghost
Copy link

ghost commented Nov 18, 2021

Concept ACK

@achow101
Copy link
Member

I don't think that this should be a startup option. The preferences for coin selection may be different per-wallet, or even on a per-transaction basis, so having it be global and apply to all wallets, all the time, seems incorrect. IMO the correct approach is to add the option to the options object used for send and fundrawtransaction.

@brunoerg
Copy link
Contributor Author

Thanks, @achow101. I agree with you, the better approach would be add the option to the options object. Going to change it, thanks again.

@brunoerg
Copy link
Contributor Author

brunoerg commented Jan 6, 2022

force-pushed addressing comments from @achow101

@Rspigler
Copy link
Contributor

Rspigler commented Jan 6, 2022

ACK d6d6340

I see prioritize_no_change bool under the options argument for send as well as fundrawtransaction. Need to do more testing with spending different UTXOs. A GUI PR would be a good followup.

@S3RK
Copy link
Contributor

S3RK commented Jan 10, 2022

Changeless txs are already somewhat prioritised since creating and spending a change increases waste metric. When you force a solution without change you forego a more economically efficient solution. The question is how much would you pay for the changeless tx? Current UX hides the price of privacy from user and could lead to confusion. May a better UX would be to specify the amount you would pay for the changeless tx (if possible).

This also conflicts with possible future improvements for BnB #23367

@brunoerg
Copy link
Contributor Author

Force-pushed rebasing and addressing last @achow101's comment.

Comment on lines 56 to 57
//! Prioritize a solution that doesn't generate change when selecting coins
bool m_prioritize_no_change = false;
Copy link
Member

Choose a reason for hiding this comment

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

Should this be in CoinSelectionParams instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's in both I guess

Copy link
Contributor

@murchandamus murchandamus left a comment

Choose a reason for hiding this comment

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

Like @S3RK, I'm a bit worried that always using the result of one coin selection algorithm could have costly outcomes. Since BnB will only use UTXOs smaller than the target, we could see results such as using 50 inputs at 100 sat/vB, while there is a single UTXO that is greater than the target.

I would prefer if this option were guarded in some fashion, e.g. that its waste must be below 3× the waste of the best solution, or the option take a parameter to define a discount for the waste of BnB solutions.

Regarding the name, I prefer avoiding negations in variable, option or parameter names. What do you think about avoid_change or prefer_changeless?

@@ -65,7 +65,7 @@ static void CoinSelection(benchmark::Bench& bench)
const CoinSelectionParams coin_selection_params(/* change_output_size= */ 34,
/* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0),
/* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0),
/* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
/* tx_noinputs_size= */ 0, /* avoid_partial= */ false, /* prioritize no change */ false);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/* tx_noinputs_size= */ 0, /* avoid_partial= */ false, /* prioritize no change */ false);
/*tx_noinputs_size=*/0, /*avoid_partial=*/false, /*prioritise_no_change=*/false);

@@ -417,6 +417,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
{"change_type", UniValueType(UniValue::VSTR)},
{"includeWatching", UniValueType(UniValue::VBOOL)},
{"include_watching", UniValueType(UniValue::VBOOL)},
{"prioritize_no_change", UniValueType(UniValue::VBOOL)},
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
{"prioritize_no_change", UniValueType(UniValue::VBOOL)},
{"prioritise_no_change", UniValueType(UniValue::VBOOL)},

Correct spelling at least in the API :p

Copy link
Contributor Author

Choose a reason for hiding this comment

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

English is not my first language but I think both are correct, aren't they?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

btw, I am gonna change it to avoid_change, looks better.

Copy link
Contributor

Choose a reason for hiding this comment

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

Prioritize is the north american spelling; prioritise is the british spelling.
I believe that in our docs we use the North American spelling

change_output_size(change_output_size),
change_spend_size(change_spend_size),
m_effective_feerate(effective_feerate),
m_long_term_feerate(long_term_feerate),
m_discard_feerate(discard_feerate),
tx_noinputs_size(tx_noinputs_size),
m_avoid_partial_spends(avoid_partial)
m_avoid_partial_spends(avoid_partial),
m_prioritize_no_change(prioritize_no_change)
Copy link
Member

Choose a reason for hiding this comment

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

IMO would be nice to stop this long-constructor stuff and just assign it after creation.

@@ -167,7 +167,7 @@ inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>
CoinSelectionParams coin_selection_params(/* change_output_size= */ 0,
/* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(0),
/* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0),
/* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
/* tx_noinputs_size= */ 0, /* avoid_partial= */ false, /* prioritize_no_change */ false);
Copy link
Member

Choose a reason for hiding this comment

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

as above

@@ -717,7 +717,7 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
CoinSelectionParams cs_params(/* change_output_size= */ 34,
/* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0),
/* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0),
/* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
/* tx_noinputs_size= */ 0, /* avoid_partial= */ false, /* prioritize_no_change */ false);
Copy link
Member

Choose a reason for hiding this comment

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

as above

@brunoerg
Copy link
Contributor Author

Regarding the name, I prefer avoiding negations in variable, option or parameter names. What do you think about avoid_change or prefer_changeless?

avoid_change is really good, I will adopt it.

Since BnB will only use UTXOs smaller than the target, we could see results such as using 50 inputs at 100 sat/vB, while there is a single UTXO that is greater than the target.

I understand your point, but if you are using avoid_change, it means you don't matter about the cost, you just want to avoid change even it costing much more, isn't it?

@brunoerg brunoerg force-pushed the 2021-11-coinselection-option branch from 5768c9f to 97190d1 Compare April 26, 2022 16:11
@brunoerg
Copy link
Contributor Author

Force-pushed rebasing and addressing avoid_change as suggested by @xekyo

@murchandamus
Copy link
Contributor

murchandamus commented May 27, 2022

Some thoughts on two approaches I see here. I'll compare to the status quo and refer to the approaches as avoid_change and prefer_changeless.

status quo: use SelectionResult with the best waste score

  • Implementation: run all coin selection algorithms, prefer SelectionResult with lowest waste
  • Pro:
    • waste score directly correlates with fee expenses for this transaction, likely produces least fees overall
    • changeless solutions are usually cheaper than solutions that create change with similarly composed input sets and get automatically preferred
  • Contra:
    • Will sometimes not use an available changeless solution

avoid_change: always use changeless solution if it exists

  • Implementation: run BnB first, if there is a solution, use that solution. Only run fallback if BnB didn't find a solution
  • Pro:
    • probably increases overall count of changeless solutions
  • Contra:
    • will occasionally spend a chunk more in fees when the BnB solution ends up being much worse than the least wasteful solution

prefer_changeless: use changeless solution if it's not too much worse than the best solution

  • Implementation: reduce waste score by a fixed or relative amount for changeless SelectionResults, e.g. reduce changeless solution's waste score by 5,000 ṩ, or reduce waste score of changeless solutions by 50%.
  • Pro:
    • probably increases overall count of changeless solutions, but maybe not as much as avoid_change
    • limits exposure to overpaying
  • Contra:
    • may skip some unattractive changeless solutions

My questions are:

  • What will users think they're opting into when they activate an avoid_change or prefer_changeless option?
  • How does the option change the overall cost?
  • Preferring changeless solutions in general, may generally reduce the UTXO pool which actually could overall lower the frequency of changeless solutions. Would such an option actually increase or decrease changeless solutions? E.g. on wallet: increase BnB upper limit #24752, increasing the allowance for creating changeless solutions surprised us in that it reduced the overall count of changeless solutions.

Given that BnB doesn't always have a solution, the wallet will sometimes need to create change anyway, how much headway would we actually make with this change?

My suggestion for a next step would be that we simulate this branch with avoid_change set to true and false on a few different scenarios and see how the count of changeless transactions and overall costs are affected.

@brunoerg
Copy link
Contributor Author

Thanks, @xekyo!

What will users think they're opting into when they activate an avoid_change or prefer_changeless option?

I see two scenarios:
Using avoid_change users would be saying: "Please, don't generate change even if it is expensive, this is the cost I am paying for privacy". In this case, I agree avoid_change is so much aggressive but I think anyone using this option should be aware of the pros and cons.

Using prefer_changeless users would be saying: "I prefer a changeless solution but only if this one is not so expensive compared to the other ones". It's less aggressive compared to the previous one and it would have similar effects.

Q: Only run fallback if BnB didn't find a solution: If user is using avoid_change should we run fallback if BnB didn't find a solution or should we skip it and return an error like: "It wasn't able to create a changeless solution"? Thinking about it, maybe changing the approach to prefer_changeless would be better. It's not a good experience for the user to have an avoid_change option which can generate a solution with change (users don't understand our algorithms and can open an issue relating a bug, for example).


My suggestion for a next step would be that we simulate this branch with avoid_change set to true and false on a few different scenarios and see how the count of changeless transactions and overall costs are affected.

Yeah, great, I am working on it!

@murchandamus
Copy link
Contributor

That's a good point about not running other algorithms at all when BnB doesn't return a solution. I think that a prefer_changeless option would be less of a footgun and would have a better UX. Perhaps avoid_change would work better in a flow where the user reviews the final transaction before sending it.

Copy link
Contributor

@ishaanam ishaanam left a comment

Choose a reason for hiding this comment

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

I know that the author of this PR may implement prefer_changeless instead of avoid_change, but I think that my comments are applicable under either option.

@@ -384,6 +384,10 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
// Note that unlike KnapsackSolver, we do not include the fee for creating a change output as BnB will not create a change output.
std::vector<OutputGroup> positive_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, true /* positive_only */);
if (auto bnb_result{SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change)}) {
bnb_result->ComputeAndSetWaste(CAmount(0));
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this line necessary? I think SelectCoinsBnB() already calls ComputeAndSetWaste() for this result.

@@ -1241,6 +1248,11 @@ RPCHelpMan send()
// be overridden by options.add_inputs.
coin_control.m_add_inputs = rawTx.vin.size() == 0;
SetOptionsInputWeights(options["inputs"], options);

if (options.exists("avoid_change")) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this can be removed from send() because it is already run in FundTransaction().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, interesting! Gonna check it

@DrahtBot
Copy link
Contributor

DrahtBot commented Mar 7, 2023

🐙 This pull request conflicts with the target branch and needs rebase.

@brunoerg
Copy link
Contributor Author

Closing for now as I haven't been working on it for a while

@brunoerg brunoerg closed this Mar 23, 2023
@bitcoin bitcoin locked and limited conversation to collaborators Mar 22, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.