-
Notifications
You must be signed in to change notification settings - Fork 37.7k
wallet: Construct ScriptPubKeyMans with all data rather than loaded progressively #28333
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/28333. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. 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. |
@@ -391,40 +391,18 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string& | |||
return nullptr; | |||
} | |||
|
|||
// Unset the blank flag if not specified by the user | |||
if (!create_blank) { |
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 change of behaviour or not?
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 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.
|
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 seem to be getting onboard with this idea as I can notice readability improvements on a cursory glance. Do you anticipate any performance improvements as well?
The last sentence related to legacy SPKMs of the last paragraph in the PR description can be removed now.
Will do full review soon.
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.
Concept ACK 93c3729
This PR makes DescriptorScriptPubKeyMan
class RAII compliant by adding 4 constructors that are responsible for allocating all the resources (eg: memory) required by this class during initialisation itself. I feel that it improves the readability quite a lot as the reader can easily gauge what all is needed by this class at one place and there is no need for multiple scattered class-write function calls like the one below that used to appear before.
- WITH_LOCK(desc_spk_man->cs_desc_man, desc_spk_man->AddDescriptorKeyWithDB(batch, key, key.GetPubKey()));
- desc_spk_man->TopUpWithDB(batch);
I noticed that this style now allows us to add more error checking that makes the code more robust imo.
Error: Wallet contains both unencrypted and encrypted keys
Perhaps a Refactoring label is required in the PR?
@@ -420,30 +420,18 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string& | |||
return nullptr; | |||
} | |||
|
|||
// Unset the blank flag if not specified by the user | |||
if (!create_blank) { |
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 32dd0e3 "wallet: Consolidate generation setup callers into one function"
Fine as-is but perhaps this block can be put inside the if
block below because from what I see if not set by the user, this flag can only be set in case of passphrase
not being empty on line 388. This wrapped inside a tighter constraint such as the one below looks better to me.
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 blank flag needs to be unset before EncryptWallet
so that EncryptWallet
can generate the new keys after encrypting. Otherwise, the newly created wallet will be encrypted but blank, which is not what we want in this scenario.
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 had something like this in mind and I should have left a diff earlier for clarity. It seemed to me that only for encrypted wallets the blank flag was set by the code & hence, believed this flag unsetting could be nested inside the below condition.
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index bd1b36b4a9..5a3ecc1d19 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -381,9 +381,6 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string&
Assert(wallet_creation_flags & WALLET_FLAG_DESCRIPTORS);
options.require_format = DatabaseFormat::SQLITE;
- // Indicate that the wallet is actually supposed to be blank and not just blank to make it encrypted
- bool create_blank = (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET);
-
// Born encrypted wallets need to be created blank first.
if (!passphrase.empty()) {
wallet_creation_flags |= WALLET_FLAG_BLANK_WALLET;
@@ -420,13 +417,10 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string&
return nullptr;
}
- // Unset the blank flag if not specified by the user
- if (!create_blank) {
- wallet->UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
- }
-
// Encrypt the wallet
if (!passphrase.empty()) {
+ // Unset the blank flag that was purposefully set above for encrypted wallets
+ wallet->UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
if (!wallet->EncryptWallet(passphrase)) {
error = Untranslated("Error: Wallet created but failed to encrypt.");
status = DatabaseStatus::FAILED_ENCRYPT;
But I check now that this fails the test for intentionally blank encrypted wallets. So, this suggestion is incorrect and need not be implemented.
def test_encrypted(self):
self.log.info("Test createwalletdescriptor with encrypted wallets")
def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
self.nodes[0].createwallet("encrypted", blank=True, passphrase="pass")
src/wallet/scriptpubkeyman.h
Outdated
using WatchOnlySet = std::set<CScript>; | ||
using WatchKeyMap = std::map<CKeyID, CPubKey>; | ||
using KeyMap = std::map<CKeyID, CKey>; | ||
using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>; | ||
using ScriptPubKeyMap = std::map<CScript, int32_t>; // Map of scripts to descriptor range index | ||
using PubKeyMap = std::map<CPubKey, int32_t>; // Map of pubkeys involved in scripts to descriptor range index |
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 fdba094 "wallet: Load everything into DescSPKM on construction"
Some of them are now being used in wallet.cpp
& external_signer_scriptpubkeyman.h
and I suggested using couple in walletdb.cpp
too. Maybe they can now go in a utility file like walletutil.h
?
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 leaving them here is fine.
Instead of creating a DescSPKM that is then progressively loaded, we should instead create it all at once in a constructor when loading.
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.
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.
@@ -420,30 +420,18 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string& | |||
return nullptr; | |||
} | |||
|
|||
// Unset the blank flag if not specified by the user | |||
if (!create_blank) { |
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 had something like this in mind and I should have left a diff earlier for clarity. It seemed to me that only for encrypted wallets the blank flag was set by the code & hence, believed this flag unsetting could be nested inside the below condition.
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index bd1b36b4a9..5a3ecc1d19 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -381,9 +381,6 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string&
Assert(wallet_creation_flags & WALLET_FLAG_DESCRIPTORS);
options.require_format = DatabaseFormat::SQLITE;
- // Indicate that the wallet is actually supposed to be blank and not just blank to make it encrypted
- bool create_blank = (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET);
-
// Born encrypted wallets need to be created blank first.
if (!passphrase.empty()) {
wallet_creation_flags |= WALLET_FLAG_BLANK_WALLET;
@@ -420,13 +417,10 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string&
return nullptr;
}
- // Unset the blank flag if not specified by the user
- if (!create_blank) {
- wallet->UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
- }
-
// Encrypt the wallet
if (!passphrase.empty()) {
+ // Unset the blank flag that was purposefully set above for encrypted wallets
+ wallet->UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
if (!wallet->EncryptWallet(passphrase)) {
error = Untranslated("Error: Wallet created but failed to encrypt.");
status = DatabaseStatus::FAILED_ENCRYPT;
But I check now that this fails the test for intentionally blank encrypted wallets. So, this suggestion is incorrect and need not be implemented.
def test_encrypted(self):
self.log.info("Test createwalletdescriptor with encrypted wallets")
def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
self.nodes[0].createwallet("encrypted", blank=True, passphrase="pass")
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.