-
Notifications
You must be signed in to change notification settings - Fork 37.7k
wallet: add config to prioritize a solution that doesn't create change in coin selection #23475
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
7ed827a
to
d3a434d
Compare
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsReviewers, 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. |
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.
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. |
d3a434d
to
83e33d5
Compare
Concept ACK |
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 |
Thanks, @achow101. I agree with you, the better approach would be add the option to the options object. Going to change it, thanks again. |
6428e61
to
a0da26c
Compare
a0da26c
to
d6d6340
Compare
force-pushed addressing comments from @achow101 |
ACK d6d6340 I see |
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 |
Force-pushed rebasing and addressing last @achow101's comment. |
src/wallet/coincontrol.h
Outdated
//! Prioritize a solution that doesn't generate change when selecting coins | ||
bool m_prioritize_no_change = false; |
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.
Should this be in CoinSelectionParams
instead?
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.
It's in both I guess
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.
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
?
src/bench/coin_selection.cpp
Outdated
@@ -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); |
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.
/* tx_noinputs_size= */ 0, /* avoid_partial= */ false, /* prioritize no change */ false); | |
/*tx_noinputs_size=*/0, /*avoid_partial=*/false, /*prioritise_no_change=*/false); |
src/wallet/rpc/spend.cpp
Outdated
@@ -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)}, |
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.
{"prioritize_no_change", UniValueType(UniValue::VBOOL)}, | |
{"prioritise_no_change", UniValueType(UniValue::VBOOL)}, |
Correct spelling at least in the API :p
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.
English is not my first language but I think both are correct, aren't they?
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.
btw, I am gonna change it to avoid_change
, looks better.
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.
Prioritize
is the north american spelling; prioritise
is the british spelling.
I believe that in our docs we use the North American spelling
src/wallet/coinselection.h
Outdated
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) |
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.
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); |
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.
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); |
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.
as above
I understand your point, but if you are using |
…e in coin selection
5768c9f
to
97190d1
Compare
Force-pushed rebasing and addressing |
Some thoughts on two approaches I see here. I'll compare to the status quo and refer to the approaches as status quo: use SelectionResult with the best waste score
My questions are:
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 |
Thanks, @xekyo!
I see two scenarios: Using Q:
Yeah, great, I am working on it! |
That's a good point about not running other algorithms at all when BnB doesn't return a solution. I think that a |
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 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)); |
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.
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")) { |
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 can be removed from send() because it is already run in FundTransaction().
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, interesting! Gonna check it
🐙 This pull request conflicts with the target branch and needs rebase. |
Closing for now as I haven't been working on it for a while |
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
andfundrawtransaction
.