send
and fundrawtransaction
.
send
and fundrawtransaction
.
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
Reviewers, this pull request conflicts with the following ones:
util::Result
structure by theStack)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.
options
object used for send
and fundrawtransaction
.
ACK d6d634094744569d99b35bbb20f295ecdfda32bc
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.
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
So something like the maxapsfee
?
How does this conflict with BnB improvements?
So something like the
maxapsfee
?
Not sure what maxapsfee
means, but I was thinking about a config parameter that would decrease waste metric for changeless solutions by the specified amount.
How does this conflict with BnB improvements?
There is an idea to drop BnB upper limit. This means there will be always be a BnB solution however terrible it is. Combined with the proposed configuration parameter a user could end up with a changeless tx that drop horrendous amounts to fees.
maxapsfee
is a config option that specifies the maximum amount of extra fees a user is willing to pay to avoid partial spends
Hi, @S3RK. Thanks for the comments, I didn’t review #23367 yet, hope to do it soon.
When you force a solution without change you forego a more economically efficient solution. Yes, this is the purpose of this config, sometimes users don’t want the more economically efficient solution, but that one that may provide a little bit more privacy (e.g. not generating change).
Combined with the proposed configuration parameter a user could end up with a changeless tx that drop horrendous amounts to fees.
Interesting. Interesting. As I said before, I haven’t checked this PR yet. But I think this config option can verify that the BnB solution is not generating horrible amounts of fees. Basically, if we have a ‘decent’ BnB solution, prioritize it.
49@@ -50,7 +50,7 @@ void DummyWalletInit::AddWalletOptions(ArgsManager& argsman) const
50 "-flushwallet",
51 "-privdb",
52 "-walletrejectlongchains",
53- "-unsafesqlitesync",
54+ "-unsafesqlitesync"
In b6269b012fe2cad261f94480711c09ebbf47c7fa “wallet: add config to prioritize a solution that doesn’t create change in coin selection”
This change is unrelated.
52@@ -53,6 +53,8 @@ class CCoinControl
53 bool m_avoid_partial_spends = DEFAULT_AVOIDPARTIALSPENDS;
54 //! Forbids inclusion of dirty (previously used) addresses
55 bool m_avoid_address_reuse = false;
56+ //! Prioritize a solution that doesn't generate change when selecting coins
57+ bool m_prioritize_no_change = false;
CoinSelectionParams
instead?
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
?
64@@ -65,7 +65,7 @@ static void CoinSelection(benchmark::Bench& bench)
65 const CoinSelectionParams coin_selection_params(/* change_output_size= */ 34,
66 /* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0),
67 /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0),
68- /* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
69+ /* tx_noinputs_size= */ 0, /* avoid_partial= */ false, /* prioritize no change */ false);
0 /*tx_noinputs_size=*/0, /*avoid_partial=*/false, /*prioritise_no_change=*/false);
416@@ -417,6 +417,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
417 {"change_type", UniValueType(UniValue::VSTR)},
418 {"includeWatching", UniValueType(UniValue::VBOOL)},
419 {"include_watching", UniValueType(UniValue::VBOOL)},
420+ {"prioritize_no_change", UniValueType(UniValue::VBOOL)},
0 {"prioritise_no_change", UniValueType(UniValue::VBOOL)},
Correct spelling at least in the API :p
avoid_change
, looks better.
Prioritize
is the north american spelling; prioritise
is the british spelling.112 m_long_term_feerate(long_term_feerate),
113 m_discard_feerate(discard_feerate),
114 tx_noinputs_size(tx_noinputs_size),
115- m_avoid_partial_spends(avoid_partial)
116+ m_avoid_partial_spends(avoid_partial),
117+ m_prioritize_no_change(prioritize_no_change)
166@@ -167,7 +167,7 @@ inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>
167 CoinSelectionParams coin_selection_params(/* change_output_size= */ 0,
168 /* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(0),
169 /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0),
170- /* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
171+ /* tx_noinputs_size= */ 0, /* avoid_partial= */ false, /* prioritize_no_change */ false);
716@@ -717,7 +717,7 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
717 CoinSelectionParams cs_params(/* change_output_size= */ 34,
718 /* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0),
719 /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0),
720- /* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
721+ /* tx_noinputs_size= */ 0, /* avoid_partial= */ false, /* prioritize_no_change */ false);
Regarding the name, I prefer avoiding negations in variable, option or parameter names. What do you think about
avoid_change
orprefer_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?
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
avoid_change
: always use changeless solution if it exists
prefer_changeless
: use changeless solution if it’s not too much worse than the best solution
avoid_change
My questions are:
avoid_change
or prefer_changeless
option?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.
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!
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.
383@@ -384,6 +384,10 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
384 // Note that unlike KnapsackSolver, we do not include the fee for creating a change output as BnB will not create a change output.
385 std::vector<OutputGroup> positive_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, true /* positive_only */);
386 if (auto bnb_result{SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change)}) {
387+ bnb_result->ComputeAndSetWaste(CAmount(0));
1247@@ -1241,6 +1248,11 @@ RPCHelpMan send()
1248 // be overridden by options.add_inputs.
1249 coin_control.m_add_inputs = rawTx.vin.size() == 0;
1250 SetOptionsInputWeights(options["inputs"], options);
1251+
1252+ if (options.exists("avoid_change")) {
prefer_changeless
instead of avoid_change
, but I think that my comments are applicable under either option.
🐙 This pull request conflicts with the target branch and needs rebase.