-
Notifications
You must be signed in to change notification settings - Fork 38.6k
wallet/rpc: fix listdescriptors RPC fails to return descriptors with private key information when wallet contains descriptors missing any key #32471
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
base: master
Are you sure you want to change the base?
Conversation
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code Coverage & BenchmarksFor details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32471. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please copy-paste 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. LLM Linter (✨ experimental)Possible typos and grammar issues:
Possible places where named args for integral literals may be used (e.g.
2026-01-07 |
/Users/runner/work/bitcoin/bitcoin/src/test/fuzz/miniscript.cpp:1040:42: error: no matching member function for call to 'ToString'
std::optional<std::string> str{node->ToString(parser_ctx)};
~~~~~~^~~~~~~~
/Users/runner/work/bitcoin/bitcoin/src/script/miniscript.h:830:17: note: candidate function template not viable: requires 2 arguments, but 1 was provided
std::string ToString(const CTx& ctx, bool& has_priv_key) const {
^
/Users/runner/work/bitcoin/bitcoin/src/test/fuzz/miniscript.cpp:1248:31: error: no matching member function for call to 'ToString'
const auto str2 = parsed->ToString(parser_ctx);
~~~~~~~~^~~~~~~~
/Users/runner/work/bitcoin/bitcoin/src/script/miniscript.h:830:17: note: candidate function template not viable: requires 2 arguments, but 1 was provided
std::string ToString(const CTx& ctx, bool& has_priv_key) const {
^
2 errors generated. |
|
🚧 At least one of the CI tasks failed. HintsTry to run the tests locally, according to the documentation. However, a CI failure may still
Leave a comment here, if you need help tracking down a confusing failure. |
|
Putting draft while I fix some issues with the fuzz tests |
1ff2b00 to
4f4ca3a
Compare
4f4ca3a to
bb14e8a
Compare
|
Concept NACK The purpose of the |
|
Concept NACK It's unclear why someone should receive public keys with |
I have not checked this PR yet but the original intention came from this issue: #32078 (comment) The idea was to show results for descriptors that don't have all the private keys but do have at least one. This is not for the case when the descriptor doesn't have any private key. I do like how the Also, if the Regardless of the above point, I do feel that preventing
|
|
Maybe |
I think that's fine to show a result, but I don't think we should show any result if there are no private keys at all. |
I think there has been a misunderstanding. There are private keys to show. This PR doesn't change the behaviour of |
No result is shown if there are no private keys |
It is possible to have descriptors with missing private keys in a non-watchonly wallet. |
|
A similar problem happens in It appears to be coming from here when the bitcoin/src/wallet/rpc/wallet.cpp Line 822 in 7763e86
It's due to mismatch in the count of |
rkrux
left a comment
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.
reACK 2b16014
git range-diff 8b4d4b3...2b16014
Sjors
left a comment
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.
Was in the middle of a review, but had to leave. Some comments.
56b14fb to
3151f8d
Compare
3151f8d to
3297f24
Compare
|
Rebased on master. I updated the Miniscript implementation slightly due to changes from #31734 |
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 still need to (re-)review the non-test changes of 4dc37d8, but everything else looks fine.
I wrote an expansion of the fuzzer so it actually covers has_priv_key, see Eunovo#3, but it can wait for a followup.
The commit message for 5837973 walletrpc: reject listdes with priv key on w-only wallets could mention that this improves the error message.
| std::string desc_str; | ||
| bool watchonly = !desc->ToPrivateString(*this, desc_str); | ||
| if (watchonly && !m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { | ||
| if (!desc->HavePrivateKeys(*this) && !m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { |
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.
In 7e2af06 wallet/migration: use HavePrivateKeys in place of ToPrivateString: we should have a comment here to explain what this if condition if trying to achieve. The original comment (now deleted) wasn't great either. Try:
// If we can't provide all private keys for this inferred descriptor,
// but this wallet is not watch-only, migrate it to the watch-only wallet.
if (...It's useful to know, as you write in the PR description:
In order to keep the scope of this PR limited to the RPC behaviour, this PR uses a different method to determine watchonly descriptors for the purpose of wallet migration. A follow-up PR can be opened to update migration logic to exclude descriptors with some private keys from the watchonly migration wallet.
Not sure if we want to further expand the comment, but let's add that to the commit message. That way future devs can understand why migration is inconsistent with RPC behaviour.
I do agree it's good to limit the PR scope in this way.
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 added your comment as is and updated the commit message.
Previously, to determine if a desc is watchonly, `ToPrivateString()`, was used. It returns `false` if there is at least one pubkey in the descriptor for which the provider does not have a private key. ToPrivateString() behaviour will change in the following commits to only return `false` if no priv keys could be found for the pub keys in the descriptor. HavePrivateKeys() is added here to replace the use of ToPrivateString() for determining if a descriptor is 'watchonly'. Co-authored-by: rkrux <[email protected]>
ToPrivateString() behaviour will be modified in the following commits. In order to keep the scope of this PR limited to the RPC behaviour, this commit updates wallet migration to use 'Descriptor::HavePrivateKeys()' in place of 'Descriptor::ToPrivateString()' to determine watchonly descriptors. A follow-up PR can be opened to update migration logic to exclude descriptors with some private keys from the watchonly migration wallet.
This commit modifies the Pubkey providers to return the public string if private data is not available. This is setup for a future commit to make Descriptor::ToPrivateString return strings with missing private key information. Co-authored-by: rkrux <[email protected]>
src/script/descriptor.cpp
Outdated
| bool has_priv_key{false}; | ||
| auto res = m_node->ToString(StringMaker(arg, m_pubkey_args, type, cache), has_priv_key); | ||
| if (res) out = *res; | ||
| return type == StringType::PRIVATE ? has_priv_key : res.has_value(); |
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.
In 4dc37d8 descriptor: ToPrivateString() pass if at least 1 priv key exists:
The following makes it more clear why for private strings we only look at has_priv_key and don't care about whether res has a value:
if (type == StringType::PRIVATE) {
Assume(res.has_value());
return has_priv_key;
} else {
return res.has_value();
}Or a bit shorter:
return res.has_value() && (type != StringType::PRIVATE || has_priv_key);Though in the edge case of StringType::PRIVATE with no res, this would return false which is a bit unexpected.
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.
The Assume check here is useful because we expect res to always have a value, so I think the longer version is better. I have rebased the PR with the 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.
Sounds good, I rebased Sjors#108 on top of your latest push to see if it survives the current fuzz corpus sanity check.
3297f24 to
165f728
Compare
|
ACK 165f728 Just left one more (vague) suggestion above. |
- Refactor Descriptor::ToPrivateString() to allow descriptors with missing private keys to be printed. Useful in descriptors with multiple keys e.g tr() etc. - The existing behaviour of listdescriptors is preserved as much as possible, if no private keys are availablle ToPrivateString will return false
Co-authored-by: rkrux <[email protected]>
165f728 to
9c7e477
Compare
|
ACK 9c7e477 |
TLDR:
Currently,
listdescriptors [private=true]will fail for a non-watch-only wallet if any descriptor has a missing private key(e.gtr(),multi(), etc.). This PR changes that while making surelistdescriptors [private=true]still fails if there no private keys. Closes #32078In non-watch-only wallets, it's possible to import descriptors as long as at least one private key is included. It's important that users can still view these descriptors when they need to create a backup—even if some private keys are missing (#32078 (comment)). This change makes it possible to do so.
This change also helps prevent
listdescriptors truefrom failing completely, because one descriptor is missing some private keys.Notes
listdescriptors truestill fails for watch-only wallets to preserve existing behaviour Descriptor unit tests and simplifications #24361 (comment)Descriptor::ToPrivateString()to determine which descriptor was watchonly. This means that modifying theToPrivateString()behaviour caused descriptors that were previously recognized as "watchonly" to be "non-watchonly". In order to keep the scope of this PR limited to the RPC behaviour, this PR uses a different method to determinewatchonlydescriptors for the purpose of wallet migration. A follow-up PR can be opened to update migration logic to exclude descriptors with some private keys from thewatchonlymigration wallet.Relevant PRs
#24361
#32186
Testing
Functional tests were added to test the new behaviour
EDIT
listdescriptors [private=true]will still fail when there are no private keys because non-watchonly wallets must have private keys and callinglistdescriptors [private=true]for watchonly wallet returns an error