This PR adds a filter to the CoinControl to select only specific utxos by type. It allows the fundrawtransaction and walletcreatefundedpsbt rpc calls to filter inputs by type.
Closes #25181.
This PR adds a filter to the CoinControl to select only specific utxos by type. It allows the fundrawtransaction and walletcreatefundedpsbt rpc calls to filter inputs by type.
Closes #25181.
Concept ACK
<!--e57a25ab6845829454e8d69fc972939a-->
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
<!--174a7506f384e20aa4161008e828411d-->
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.
@aureleoules can we achieve the same result by using descriptors wallet with only segwit descriptors?
Concept ACK
I think you should squash the commits since the test won't work without the implementation.
@aureleoules can we achieve the same result by using descriptors wallet with only segwit descriptors?
I am not very familiar with wallet descriptors. Do you mean using a wallet that does not have legacy descriptors imported? I think @achow101 answed that yes (https://github.com/bitcoin/bitcoin/issues/25181#issuecomment-1134923482).
But if I understand correctly, this feature might still be useful in cases where you don't control the wallet.
791 | @@ -787,7 +792,8 @@ RPCHelpMan fundrawtransaction() 792 | "so the maximum DER signatures size of 73 bytes should be used when considering ECDSA signatures." 793 | "Remember to convert serialized sizes to weight units when necessary."}, 794 | }, 795 | - }, 796 | + }, 797 | + {"inputs_witness_only", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to only use inputs with witness data for transaction."},
The name here seems confusing to me. Maybe "segwit_inputs_only"?
Indeed, I changed it.
192 | + self.log.info("Testing fundrawtxn with witness inputs only") 193 | + 194 | + inputs = [ ] 195 | + 196 | + # make sure legacy inputs are not accepted in witness only mode if no witness inputs are found 197 | + self.generatetoaddress(self.nodes[2], 2, self.nodes[2].getnewaddress(address_type='legacy'))
This may actually be testing something else than intended: given that the coinbase output is not 100 blocks old yet, I suspect the "Insufficient funds" would be foremost caused by the inputs being immature.
This test could be improved by creating a scenario in which a transaction is first successfully funded by using legacy inputs, but then toggling the 'segwit_inputs_only': true causes a repeated attempt to fund the transaction to fail.
Thank you for reviewing the code!
This may actually be testing something else than intended: given that the coinbase output is not 100 blocks old yet, I suspect the "Insufficient funds" would be foremost caused by the inputs being immature.
If I increase the amount of blocks to generate in legacy (400+), the "Insufficient funds" error is still raised. There are already blocks generated before the new test here so there should be enough legacy coins.
https://github.com/bitcoin/bitcoin/blob/1c5cfd84b3dc14ab886acd47c7891c11eb2457ec/test/functional/rpc_fundrawtransaction.py#L103-L104
Wrong node my bad.
This test could be improved by creating a scenario in which a transaction is first successfully funded by using legacy inputs, but then toggling the 'segwit_inputs_only': true causes a repeated attempt to fund the transaction to fail.
I'll look into that, this seems better.
Would it be useful to add the "segwit_inputs_only" option to walletcreatefundedpsbt as well? They both call FundTransaction so I think you would just have to include it in the walletcreatefundedpsbt options.
But if I understand correctly, this feature might still be useful in cases where you don't control the wallet. @aureleoules Do you have a concrete use-case in mind? I'm not sure, but it seems #25181 supposes that we do control the wallet.
Instead of adding the segwit_inputs_only filter only, what if we add a general input type filter? Something like filter_inputs_by=<OutputType>
So we aren't forced to add a new type*_inputs_only flag in every RPC command every time that a new inputs type filter is required.
Do you have a concrete use-case in mind? I'm not sure, but it seems #25181 supposes that we do control the wallet.
I believe if your wallet already has mixed inputs and you want to filter these, this feature may be useful.
11 | @@ -12,6 +12,7 @@ 12 | #include <script/keyorigin.h> 13 | #include <script/signingprovider.h> 14 | #include <script/standard.h> 15 | +#include <unordered_set>
This should go with the other std lib includes.
Fixed, thanks.
Instead of adding the
segwit_inputs_onlyfilter only, what if we add a general input type filter? Something likefilter_inputs_by=<OutputType>So we aren't forced to add a new
type*_inputs_onlyflag in every RPC command every time that a new inputs type filter is required.
Thanks for the feedback, I've added it.
Concept ACK
This is coming along nicely! I do have a few suggestions:
I think the correct place to do the filtering is in AvailableCoins. This would make it more useful outside of just funding a transaction. I recently opened #25734 which should make this much easier. All we would need is your update to coincontrol.h and a small change to AvailableCoins and then any RPC can filter by just adding filters to m_filter_inputs (like you've done with fundrawtransaction.
I rebased this off #25734 , added my suggested changes, and verified that the functional tests still ran. Feel free to cherry pick:
Also, small nit (feel free to ignore): I'd suggest breaking up your commits to make it easier to review.. something like 1) make changes to AvailableCoins 2) make changes to the RPC 3) make changes to the functional test. Makes it much easier from a review perspective, imo
503 | + while (ss.good()) { 504 | + getline(ss, item, ','); 505 | + result.push_back(item); 506 | + } 507 | + return result; 508 | +}
this function should also handle lists like bech32, bech32m, as most users will probably pass them with spaces. i tested that this currently fails by passing bech32, bech32m in the functional test.
Thanks, I fixed it
146 | @@ -145,6 +147,8 @@ struct CoinSelectionParams { 147 | * associated with the same address. This helps reduce privacy leaks resulting from address 148 | * reuse. Dust outputs are not eligible to be added to output groups and thus not considered. */ 149 | bool m_avoid_partial_spends = false; 150 | + /** Filter transaction inputs by type */ 151 | + std::unordered_set<OutputType> m_filter_inputs;
if you do the filtering in AvailableCoins, we don't need to add this to coinselection.h
800 | @@ -776,6 +801,7 @@ static BResult<CreatedTransactionResult> CreateTransactionInternal( 801 | 802 | CoinSelectionParams coin_selection_params{rng_fast}; // Parameters for coin selection, init with dummy 803 | coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends; 804 | + coin_selection_params.m_filter_inputs = coin_control.m_filter_inputs;
same as above, this isn't needed if we filter in AvailableCoins
190 | + if not info['iswitness']: 191 | + return False 192 | + 193 | + return True 194 | + 195 | + def test_witness_only(self):
it would be great to test the case where we filter inputs to a particular type (or multiple types) and then verify that the inputs in the funded tx only contain those types. you can look at test/functioanal/wallet_avoid_mixing_output_types.py for an example of checking the inputs to a transaction and verifying they are all of a certain OutputType
Thanks, I've added these tests.
I included some helpers from wallet_avoid_mixing_output_types to the test framework to reuse them.
Thank you @josibake for the review and the commits! I added your commits and fixed the list parsing. I will address the test a bit later.
38 | @@ -38,6 +39,8 @@ class CCoinControl 39 | //! If true, the selection process can add extra unselected inputs from the wallet 40 | //! while requires all selected inputs be used 41 | bool m_allow_other_inputs = false; 42 | + //! Filter inputs by type 43 | + std::unordered_set<OutputType> m_filter_inputs;
Small idea, what about using an optional bit-field here? So you later can skip coins with a fast:
if (m_filter_inputs && !(*m_filter_inputs & coin_type)) continue;
great idea, I pushed it
git diff 0a9d345 89ea8fc
16 | @@ -17,6 +17,7 @@ 17 | #include <algorithm> 18 | #include <map> 19 | #include <set> 20 | +#include <unordered_set>
can remove this include now.
564 | + UniValue v = options["filter_inputs_by"]; 565 | + std::stringstream ss(v.get_str()); 566 | + const auto& outputTypes = parseStringList(ss); 567 | + coinControl.m_filter_inputs = 0; 568 | + for (size_t i = 0; i < outputTypes.size(); i++) { 569 | + *coinControl.m_filter_inputs |= static_cast<unsigned int>(parseOutputType(outputTypes[i]));
No need to implement the function parseStringList, we already have such functionality. Can directly provide the array and read it in the following way:
UniValue array = options["filter_inputs_by"].get_array();
for (size_t i = 0; i < array.size(); i++) {
const auto& type = parseOutputType(array[I].get_str());
if (!type) throw exception;
coinControl->m_filter_inputs |= static_cast<unsigned int>(*type);
}
(remember to change the test to provide an array instead of a string separated by commas as well)
thanks, i pushed it
add m_filter_inputs to coinControl, which then
allows us to filter by OutputType in AvailableCoins
Do you have a concrete use-case in mind? I'm not sure, but it seems #25181 supposes that we do control the wallet.
I believe if your wallet already has mixed inputs and you want to filter these, this feature may be useful.
I was thinking about this proposal and I'm still not convinced there is a clear understanding how this would be actually used in the real world.
The declared benefit is non-malleability for lightning wallets (#25181). For new lightning wallets this is a non-issue as they shouldn't have non-segwit descriptors in the first place. IIUC the proposed solution for existing wallets is to filter malleable inputs when creating channel opening txs in fundrawtransaction and walletcreatefundedpsbt. This approach renders a portion of wallet funds unusable and have a number of problems:
walletprocesspsbt or when bumping psbt?It seems what you really want is to "unmix" the wallet, i.e. sweep all malleable inputs as a one-time operation. Today, I think, there are two main approaches users can take to achieve this:
While 2nd approach has some downsides, maybe it's just good enough. I don't know whether there are enough users affected and whether these downsides outweigh the added complexity.
<!--cf906140f33d8803c4a75a2196329ecb-->
🐙 This pull request conflicts with the target branch and needs rebase.
<sub>Want to unsubscribe from rebase notifications on this pull request? Just convert this pull request to a "draft".</sub>