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: Reload watchonly and solvables wallets after migration #28609
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code CoverageFor detailed information about the code coverage, see the test coverage report. 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. |
1fe21d2
to
af91cfa
Compare
af91cfa
to
deb0b31
Compare
src/wallet/transaction.cpp
Outdated
nTimeReceived = _tx.nTimeReceived; | ||
nTimeSmart = _tx.nTimeSmart; | ||
fFromMe = _tx.fFromMe; | ||
nOrderPos = _tx.nOrderPos; |
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 4ccec8b:
Doesn't make much sense to copy nOrderPos
. The field is strictly connected to the parent wallet. Depending on what the wallet is watching, the tx order position could be higher or lower.
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'd prefer to keep this function generic so it should copy everything even if we will overwrite stuff in actual usage.
src/wallet/wallet.cpp
Outdated
assert(local_wallet.use_count() == 1); | ||
wallet_dirs.insert(fs::PathFromString(local_wallet->GetDatabase().Filename()).parent_path()); |
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 6eb3df7:
Why this line?. Isn't the local wallet datadir added at line 4282?
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 case the wallet fails to reload, we can still cleanup at the end.
src/wallet/wallet.cpp
Outdated
if (success && res.watchonly_wallet) { | ||
assert(res.watchonly_wallet.use_count() == 1); | ||
std::string watchonly_wallet_name = res.watchonly_wallet->GetName(); | ||
wallet_dirs.insert(fs::PathFromString(res.watchonly_wallet->GetDatabase().Filename()).parent_path()); |
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 6eb3df7:
Why this line?. The watch-only wallet datadir is added at line 4295.
Same for the solvable one.
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.
If the wallet fails to reload, we can still know to clean it up.
deb0b31
to
014231c
Compare
|
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.
Code review ACK 014231c
Feel free to ignore the nits.
014231c
to
13dd5e5
Compare
13dd5e5
to
e2dfe76
Compare
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.
diff ACK e2dfe76
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.
Code review ACK e2dfe76. This really seems like 3 unrelated changes smashed into one PR, but all the changes look good.
As far as I understand there is no test coverage to verify the fix in the third commit b57218e, but with the asserts in #28546 added, existing tests should fail. I'm happy to rebase #28546 on top of this after it is merged, though to avoid making this PR bigger.
std::string name = to_reload->GetName(); | ||
wallet_dirs.insert(fs::PathFromString(to_reload->GetDatabase().Filename()).parent_path()); | ||
to_reload.reset(); | ||
to_reload = LoadWallet(context, name, /*load_on_start=*/std::nullopt, options, status, error, warnings); |
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 commit "wallet: Reload watchonly and solvables wallets after migration" (b57218e)
Just a thought, not for this PR, but maybe it would be good to load the new wallet on startup if the previous wallet loaded on startup.
Also it would be good to improve error reporting here. It seems like errors/warnings just get discarded which might make problems hard to debug. Though I guess we don't expect failure loading a newly creating wallet.
} | ||
|
||
// Delete the wallet directories | ||
for (fs::path& dir : wallet_dirs) { | ||
for (const fs::path& dir : wallet_dirs) { | ||
fs::remove_all(dir); |
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 commit "wallet: Reload watchonly and solvables wallets after migration" (b57218e)
Not directly related to this PR, but this remove_all seems not great because if there were any non-wallet files in the wallet directory they will be recursively removed. I think it would be preferable to only delete files that we know about and leave files that we do not recognize alone.
This would also avoid the need to copy the backup file from the wallet directory to the wallet parent directory, and then copy it back in again when the wallet directory is recreated.
A lot of this logic seems pretty fragile and possible to simplify (Maybe furszy already has a PR to do it?)
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.
Not in a PR, but I do have something related in progress. Still, we should move forward with your suggestion; it is much more scoped than what I'm doing.
I'm rewriting the process, so the bdb->sqlite migration, the removal of "no longer ours" database records, and the backup creation steps are not needed.
The goal is to leave the legacy wallet untouched for the entire migration process. Then, only if migration succeeds, execute the path rename. So, in case the user faces a crash or abortion during the process, the legacy wallet will open as if nothing has happened. Which is what the process lacks today; If it crashes right now, the user has to manually do the cleanup and previous wallet recovery.
This would indirectly solve the issue you mentioned because, in case of a failure, the process would only need to delete the newly created wallet directories and nothing else.
b927000
to
7d6d6d6
Compare
When moving a tx to the watchonly wallet during migration, make sure that all of the CWalletTx data follows it.
When migrating, create the watchonly and solvables wallets without a context. Then unload and reload them after migration completes, as we do for the actual wallet. There is also additional handling for a failed reload.
7d6d6d6
to
4814e40
Compare
Two of the changes are actually required for the third. The primary change here is to create the watchonly and solvables wallets without a chain context. This necessitates the second change of reloading at the end of migration. This also required changes to the cleanup, which prompted me to write a test for that. It then turned out that |
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.
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.
Code review ACK 4814e40. Just implemented the suggested orderpos, copyfrom, and path set comments since last review
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.
ACK 4814e40
Open topic #28609 (comment).
} | ||
|
||
// Delete the wallet directories | ||
for (fs::path& dir : wallet_dirs) { | ||
for (const fs::path& dir : wallet_dirs) { | ||
fs::remove_all(dir); |
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.
Not in a PR, but I do have something related in progress. Still, we should move forward with your suggestion; it is much more scoped than what I'm doing.
I'm rewriting the process, so the bdb->sqlite migration, the removal of "no longer ours" database records, and the backup creation steps are not needed.
The goal is to leave the legacy wallet untouched for the entire migration process. Then, only if migration succeeds, execute the path rename. So, in case the user faces a crash or abortion during the process, the legacy wallet will open as if nothing has happened. Which is what the process lacks today; If it crashes right now, the user has to manually do the cleanup and previous wallet recovery.
This would indirectly solve the issue you mentioned because, in case of a failure, the process would only need to delete the newly created wallet directories and nothing else.
// Disable copying of CWalletTx objects to prevent bugs where instances get | ||
// copied in and out of the mapWallet map, and fields are updated in the | ||
// wrong copy. | ||
CWalletTx(CWalletTx const &) = delete; | ||
void operator=(CWalletTx const &x) = delete; | ||
CWalletTx(const CWalletTx&) = default; |
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.
Should the comment above be updated?
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 it's still valid when these are private
.
…ith asserts, comments, and refactoring f06016d wallet: Add asserts to detect unset transaction height values (Ryan Ofsky) 262a78b wallet, refactor: Add CWalletTx::updateState function (Ryan Ofsky) Pull request description: Originally, this PR fixed a wallet migration bug that could cause the watchonly wallet created by legacy wallet migration to have incorrect transaction height values. A different fix for the bug was implemented in #28609, but that PR did not add any test coverage that would have caught the bug, and didn't include other changes from this PR intended to prevent problems from invalid transaction heights. This PR adds new asserts to catch invalid transaction heights, which would trigger test failures without bugfix in #28609. This PR also refactors code and adds comments to clarify assumptions and make it less likely a bug from invalid transaction height values would be introduced. ACKs for top commit: achow101: ACK f06016d Sjors: utACK f06016d furszy: Code review ACK f06016d Tree-SHA512: 82657c403724d60354f7676b53bcfcc95bdc5864e051a2eb8bfad09d8ad35615393b2d6b432b46f908def9be37bebded3a55ec9ae19e19371d35897fe842c92e
…fter migration 4814e40 test: Check tx metadata is migrated to watchonly (Andrew Chow) d616d30 wallet: Reload watchonly and solvables wallets after migration (Andrew Chow) 118f2d7 wallet: Copy all tx metadata to watchonly wallet (Andrew Chow) 9af87cf test: Check that a failed wallet migration is cleaned up (Andrew Chow) Pull request description: Some incomplete/incorrect state as a result of migration can be mitigated/cleaned up by simply restarting the migrated wallets. We already do this for a wallet when it is migrated, but we do not for the new watchonly and solvables wallets that may be created. This PR introduces this behavior, in addition to creating those wallets initially without an attached chain. While implementing this, I noticed that not all `CWalletTx` metadata was being copied over to the watchonly wallet and so some data, such as time received, was being lost. This PR fixes this as a side effect of not having a chain attached to the watchonly wallet. A test has also been added. ACKs for top commit: ishaanam: light code review ACK 4814e40 ryanofsky: Code review ACK 4814e40. Just implemented the suggested orderpos, copyfrom, and path set comments since last review furszy: ACK 4814e40 Tree-SHA512: 0b992430df9f452cb252c2212df8e876613f43564fcd1dc00c6c31fa497adb84dfff6b5ef597590f9b288c5f64cb455f108fcc9b6c9d1fe9eb2c39e7f2c12a89
Some incomplete/incorrect state as a result of migration can be mitigated/cleaned up by simply restarting the migrated wallets. We already do this for a wallet when it is migrated, but we do not for the new watchonly and solvables wallets that may be created. This PR introduces this behavior, in addition to creating those wallets initially without an attached chain.
While implementing this, I noticed that not all
CWalletTx
metadata was being copied over to the watchonly wallet and so some data, such as time received, was being lost. This PR fixes this as a side effect of not having a chain attached to the watchonly wallet. A test has also been added.