wallet: Construct ScriptPubKeyMans with all data rather than loaded progressively#28333
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/28333. 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 places where named args for integral literals may be used (e.g.
Possible places where comparison-specific test macros should replace generic comparisons:
2026-04-29 21:19:49 |
There was a problem hiding this comment.
Is this change of behaviour or not?
There was a problem hiding this comment.
It is not.
EncryptWallet does not end up calling the SetupGeneration functions when the blank flag is set. Previously, we would set those up manually after encrypting. While making this PR, I realized that we could just unset the blank flag before calling EncryptWallet so that it can do the setup for us and we don't have to do it again afterwards.
|
rkrux
left a comment
There was a problem hiding this comment.
Paused review at f4894a8.
Nice to see more activity on this PR, moving it closer to merge.
However, the PR seems to have changed a bit from last year when I reviewed it and I'm unable to place the second commit in the context of this PR refactor: wallet: Don't reuse WALLET_BLANK flag for born-encrypted wallets. now - it seems fine to do but going through it seems like a deviation while reviewing the PR that's supposed to construct SPKMs with all data rather than loaded progressively.
I see the second commit was added recently (#28333 (comment)) but I really feel it should be its own PR. I ended up adding suggestions in this commit but I don't see a reason why they should be addressed in this PR that's supposed to do something else.
| // Try to top up keypool. No-op if the wallet is locked. | ||
| walletInstance->TopUpKeyPool(); |
There was a problem hiding this comment.
In 03993ad "refactor: wallet: Don't reuse WALLET_BLANK flag for born-encrypted wallets."
This needs to be inside the is_encrypted conditional as well, I suppose. Seems like a no-op for encrypted wallet creation flow.
There was a problem hiding this comment.
It does not need to be, it's a no-op when the wallet has no descriptors. It's a no-op for blank wallets too.
polespinasa
left a comment
There was a problem hiding this comment.
reviewed 83c810a
left some comments
| } | ||
|
|
||
| // Encrypt the wallet | ||
| if (!passphrase.empty() && !(wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { |
There was a problem hiding this comment.
I don't think this is a good idea. Someone could call CreateNew with is_encrypted=true and WALLET_FLAG_DISABLE_PRIVATE_KEYS set.
I think a safer approach is to add an assertion to ensure it cannot happen.
assert(!passphrase.empty() || !(wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS))
|
|
||
| // Top up key pool, to generate scriptPubKeys | ||
| if (!TopUpWithDB(batch)) { | ||
| throw std::runtime_error("Could not top up scriptPubKeys"); |
There was a problem hiding this comment.
in fcffb36 wallet: include keys when constructing DescriptorSPKM during import
I am not really familiar with error handling, but the error type changed here. I think this way it cannot be translated? Which is a problem as this is propagated till importdescriptor RPC.
not sure of this tbh feel free to ignore if I am mistaken
There was a problem hiding this comment.
It doesn't matter for RPCs as the output to RPC is never translated. This could be an issue for the GUI once import descriptors in the GUI is implemented. However, I'm pretty sure that this condition can't be hit in practice as it would mean that the descriptor cannot be expanded even when the private keys are available. This is checked for within importdescriptors before we get to this point.
There was a problem hiding this comment.
It doesn't matter for RPCs as the output to RPC is never translated
didn't know it, make sense then :)
There was a problem hiding this comment.
Even if it can't be hit, I think it is never catch?
polespinasa
left a comment
There was a problem hiding this comment.
Thanks for addressing comments :)
Just three more things I just saw and I will ack :P
|
🚧 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. |
If a wallet has multiple HD chains that have the same seed, we should only migrate that seed a single time. This fixes a fuzz crash that occurs once the return value of AddDescriptorKeyWithDB is checked during descriptor construction.
…llets. With the split between LoadWallet and CreateNew, it's no longer necessary to utilize the blank flag to prevent the wallet from having descriptors automatically being generated. Instead, CreateNew can take a separate parameter to indicate whether the wallet is to be born encrypted and therefore should not have any keys generated.
Instead of creating a DescSPKM that is then progressively loaded, we should instead create it all at once in a factory function when loading.
…rypted keys fails.
When importing a descriptor, all of the descriptor data should be provided at the same time in the constructor.
Instead of constructing then setting the descriptor with SetupDescriptor, just pass in that descriptor to the constructor.
Instead of having a caller use SetupDescriptorGeneration, just have a constructor that takes those arguments and sets up the descriptor with the autogenerated key.
|
Added a commit to fix the fuzz crash. |
|
ACK 451fdd2 thanks for addressing all comments, the code lgtm :) |
| return spkm; | ||
| } | ||
|
|
||
| std::unique_ptr<DescriptorScriptPubKeyMan> DescriptorScriptPubKeyMan::CreateFromMigration(WalletStorage& storage, WalletBatch& batch, WalletDescriptor& descriptor, int64_t keypool_size, const FlatSigningProvider& provider) |
There was a problem hiding this comment.
In aa4f782 (wallet: include keys when constructing DescriptorSPKM during import)
disregard-worthy-nit: the names CreateFromImport() and CreateFromMigration() seem to be more about the callers than telling you anything about the difference between these two functions.
There was a problem hiding this comment.
Couldn't think of better names 🤷♀️
|
re crACK 451fdd2 I reviewed the range-diff, the main changes since my last review:
|
|
Possible that this change has resulted in the fuzz crash in #35434. |
It looks like specifically on aa4f782 it crashes to reproduce the crash |
Instead of constructing ScriptPubKeyMans with no data, and then loading data as we find it, we should gather everything first and then load it all on construction. If there actually is no data and we want to setup generation, then that should also occur in a constructor rather than afterwards.
This change is only applied to DescriptorScriptPubKeyMan and ExternalSignerScriptPubKeyMan, and should be done for any ScriptPubKeyMans added in the future. I don't think it's really worth it to do this for LegacyScriptPubKeyMan since it would make loading performance worse (or cause layer violations) and it's (supposed to be) going away soon.