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
wallet: bugfix, load a wallet with an unknown/corrupt descriptor causes a fatal error #26021
wallet: bugfix, load a wallet with an unknown/corrupt descriptor causes a fatal error #26021
Conversation
12b0169
to
f59489c
Compare
This also happens when loading a wallet containing a miniscript descriptor. Catching the error is good, though I still wonder if we should use a flag or something like that to prevent loading future descriptor types. |
Hmm, if you load unrecognized descriptor types, the parsing process should always fail with the current flow. It's not different to parsing corrupted data. Will add test coverage for both scenarios and expand the returned error mentioning the last soft version that opened the wallet if it is newer than the current one. A new feature that could be handy for users is "partial wallet loads" (or "force wallet loads"), meaning that previous soft versions will be able to load every wallet (until where they can), without loading the unrecognized descriptors/data. Where, implementation wise, could be a startup flag that tells the wallet to skip all the entries related to any unrecognized descriptor instead of aborting right away. So, end result would look like this:
What do you think? |
I would add an argument to the |
When attempting to load a wallet with an unknown descriptor via the GUI, there is a segfault. This does not occur when using the Backtrace:
|
@furszy I think a "partial wallet load" would be a footgun and shouldn't be supported. |
Same cause as #26005, "bug 2". |
src/wallet/walletdb.cpp
Outdated
@@ -832,6 +839,9 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) | |||
// Set tx_corrupt back to false so that the error is only printed once (per corrupt tx) | |||
wss.tx_corrupt = false; | |||
result = DBErrors::CORRUPT; | |||
} else if (wss.descriptor_corrupt) { | |||
pwallet->WalletLogPrintf("Error: Corrupt wallet descriptor found in wallet %s. This can be fixed by removing wallet from the settings so it's not loaded at startup.\n", pwallet->GetName()); |
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.
This is way too complicated for a typical user.
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.
Are you talking about GUI users?
For them, I think that the best would be to trigger a dialog and ask if want to continue without this wallet. But, that would require a bit more work that could tackle here or in a follow-up work
For bitcoind users, as the app cannot proceed without further interaction, shouldn't be a problem for them to remove a line in the settings file. But.. another option could be to not load/open certain wallet/s, even if they are on the settings file, by providing a startup flag "-unloadwallet=<wallet_name>" (need to check if we have an available flag for this already or will have to create one). What do you think?
Note: In both cases, I don't think that skipping certain wallet/s from loading at startup without the user interaction (nor the user knowing about it) is any better than this (would actually say that it's worst).
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 mean both should get the error details without jumping through extra hoops
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.
Pushed a different text version.
At this point of the code we don't have much information aside from the "unknown wallet descriptor found" (at least for now, will work on it on a follow-up PR). We just know that a certain descriptor parse failed. Thus why added the extra information at the end, to provide the user a workaround if still want to run other wallets (just need to unload the failing one).
But.. for the sake of simplicity, removed the extra information as well. Let me know what you think.
Better to work on the GUI "unload wallet confirmation dialog" and the "unload wallet" startup flag on a different PR so this bugfix can get merged for this release.
src/wallet/walletdb.cpp
Outdated
@@ -832,6 +839,9 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) | |||
// Set tx_corrupt back to false so that the error is only printed once (per corrupt tx) | |||
wss.tx_corrupt = false; | |||
result = DBErrors::CORRUPT; | |||
} else if (wss.descriptor_corrupt) { | |||
pwallet->WalletLogPrintf("Error: Corrupt wallet descriptor found in wallet %s. This can be fixed by removing wallet from the settings so it's not loaded at startup.\n", pwallet->GetName()); |
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.
Probably should export message via strErr
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.
yeah, briefly thought about it as well and.. by doing it, remove code duplication that comes with it. But.. as this involves the introduction of the util::Result
class and.. I have tendencies of wanting to change too much in single PRs was balancing on whether that should be here or not.
Let me see how it will looks like.
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.
strErr
is already used here, it doesn't need util::Result
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.
Yes, this should just use strErr
. Any error put there should already make it out to the user on the RPC or trigger an error dialog in the GUI. This shouldn't need any other changes.
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.
strErr
isn't propagated to the GUI nor RPC. Is internal to WalletBatch::LoadWallet
, declared prior to call ReadKeyValue
and only used to log the error at the end of every record read while we walk through all the db records.
To propagate the error to the upper layers need to add a ref string into the function signature (or use the result class and kill two birds with the same stone, cleaning more code in the way).
If you check the line below this logging, I’m returning right away, not letting the process continue reading all the db records. The reason is that you can have a corrupted descriptor that has an “active descriptor“ entry, which will crash the wallet once the process calls to LoadActiveScriptPubKeyMan
(on the map.at(id) line) as the descriptor which the active entry points was never added to the wallet.
This is something that was going to push later but essentially you can crash the wallet at startup by adding an ACTIVEEXTERNALSPK
or ACTIVEINTERNALSPK
record to the db that points to a random spkm id.
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.
Extra note: checked how the util::result connection would look like and changes aren't straightforward to do. It needs further code reorganizations as DBErrors
enum mixes errors, warnings and the good result (I think that the best would be to decouple this enum into two enums errors and warnings or.. even get rid of the enum completely like it was done in #25308. but.. will leave that for another PR).
So, options:
-
Move forward by adding the ref string arg here. The same error string will be propagated to the upper layers, removing one line of code duplication but having to add the std::string ref arg in all the
WalletBatch::LoadWallet
calls (which we do have a good number of them). -
Leave it as is now, which essentially means to act the same as with all the corrupted wallet errors: having the string duplicated inside
WalletBatch::LoadWallet
and its callerCWallet::Create
(wallet.cpp
). And implement the proper changes all at once in a follow-up PR.
Personally, I'm inclined to go with option (2) as it's inlined with what we currently have and doesn't introduce changes that will most likely be removed later.
Thoughts?
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.
For a bugfix for this release, we should just do the minimum to get a message out to the user. We can do more granulated error message propagation in follow-ups.
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, let's continue with option 2 then.
Conceptually, it's similar to how we treat other compatibility issues. Old versions aren't aware of new stuff but are still able to run what they have (skipping / not verifying some data). While on this scenario, previous versions are simply not compatible with newer wallets. It's a debatable topic. Might be good to continue being incompatible actually. |
I agree with @luke-jr, it's a footgun and we should avoid doing that. The difference is that a partial load may be missing critical information in things that it cannot understand and so may be allowing spending where it shouldn't, or failing to detect transactions when it should. There is a reason that those things return errors. With our backwards compatibility, the newly added things are specifically designed so that they do not induce errors in older versions and are also designed such that they are not critical to operation. Furthermore, we do actually have downgrade protection to prevent loading of newer wallets if new critical data has been added. New/unknown descriptors are part of the critical data and so must cause older software to fail to load the wallet. The decision to use descriptor parsing failures as the downgrade protection method was intentional as using more wallet flags for each new descriptor type is not scalable. |
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. 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. |
f59489c
to
e6d4425
Compare
e6d4425
to
b574285
Compare
b7e9f0f
to
1703678
Compare
Thanks for the review, feedback tackled. Plus, for the sake of completeness, added test coverage for it as well. |
1703678
to
6cbe5bd
Compare
6cbe5bd
to
a76f9b2
Compare
tACK a76f9b2 @achow101 wrote:
This happens to me as well. That's not new in this PR though, running without this PR:
cc @hebasto Don't forget to update the PR description. I also suggest moving this comment out of the commit and into the PR description (since there's no guarantee such "follow-up commits" get merged):
You could also drop |
Already answered it. @Sjors, please check #26021 (comment)
Ok, will do. |
If the descriptor entry is unrecognized/corrupt, the unserialization fails and `LoadWallet` instead of stop there and return the error, continues reading all the db records. As other records tied to the unrecognized/corrupted descriptor are scanned, a fatal error is thrown.
Previously, this was crashing the wallet.
a76f9b2
to
e066763
Compare
Feedback tackled per @Sjors review. Thanks No code diff, only commits messages were changed. |
ACK e066763 |
re-utACK e066763 |
…rrupt descriptor causes a fatal error e066763 wallet: coverage for loading an unknown descriptor (furszy) d26c3cc wallet: bugfix, load wallet with an unknown descriptor cause fatal error (furszy) Pull request description: Fixes bitcoin#26015 If the descriptor entry is unrecognized (due a soft downgrade) or corrupt, the unserialization fails and `LoadWallet`, instead of stop there and return the error, continues reading all the db records. As other records tied to the unrecognized or corrupt descriptor are scanned, a fatal error is being thrown. This fixes it by catching the descriptor parse failure and return which wallet failed. Logging its name/path, so the user can remove it from the settings file, to prevent its load at startup. Note: added the test in a separate file intentionally. Will continue adding coverage for the wallet load process in follow-up PRs. ACKs for top commit: achow101: ACK e066763 Sjors: re-utACK e066763 Tree-SHA512: d1f1a5d7e944c89c97a33b25b4411a36a11edae172c22f8524f69c84a035f84c570b284679f901fe60f1300f781b76a6c17b015a8e7ad44ebd25a0c295ef260f
Fixes #26015
If the descriptor entry is unrecognized (due a soft downgrade) or corrupt, the
unserialization fails and
LoadWallet
, instead of stop there and return the error,continues reading all the db records. As other records tied to the unrecognized
or corrupt descriptor are scanned, a fatal error is being thrown.
This fixes it by catching the descriptor parse failure and return which wallet failed.
Logging its name/path, so the user can remove it from the settings file, to prevent
its load at startup.
Note: added the test in a separate file intentionally.
Will continue adding coverage for the wallet load process in follow-up PRs.