Skip to content
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

Merged
merged 4 commits into from Oct 23, 2023

Conversation

achow101
Copy link
Member

@achow101 achow101 commented Oct 6, 2023

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.

@DrahtBot
Copy link
Contributor

DrahtBot commented Oct 6, 2023

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage

For detailed information about the code coverage, see the test coverage report.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK ishaanam, ryanofsky, furszy

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #28610 (wallet: Migrate entire address book entries to watchonly and solvables too by achow101)
  • #28546 (bugfix: watchonly wallets created after migration have incorrect height values by ryanofsky)
  • #28264 (test: refactor: support sending funds with outpoint result by theStack)
  • #27865 (wallet: Track no-longer-spendable TXOs separately by achow101)
  • #26596 (wallet: Migrate legacy wallets to descriptor wallets without requiring BDB by achow101)

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.

src/wallet/wallet.cpp Outdated Show resolved Hide resolved
src/wallet/wallet.cpp Outdated Show resolved Hide resolved
src/wallet/transaction.cpp Outdated Show resolved Hide resolved
src/wallet/wallet.cpp Outdated Show resolved Hide resolved
nTimeReceived = _tx.nTimeReceived;
nTimeSmart = _tx.nTimeSmart;
fFromMe = _tx.fFromMe;
nOrderPos = _tx.nOrderPos;
Copy link
Member

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.

Copy link
Member Author

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.

assert(local_wallet.use_count() == 1);
wallet_dirs.insert(fs::PathFromString(local_wallet->GetDatabase().Filename()).parent_path());
Copy link
Member

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?

Copy link
Member Author

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.

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());
Copy link
Member

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.

Copy link
Member Author

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.

@DrahtBot
Copy link
Contributor

test/functional/wallet_migration.py:863:15: E275 missing whitespace after keyword
test/functional/wallet_migration.py:864:15: E275 missing whitespace after keyword
test/functional/wallet_migration.py:865:15: E275 missing whitespace after keyword
test/functional/wallet_migration.py:867:15: E275 missing whitespace after keyword
test/functional/wallet_migration.py:869:15: E275 missing whitespace after keyword

Copy link
Member

@furszy furszy left a 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.

src/wallet/wallet.cpp Outdated Show resolved Hide resolved
test/functional/wallet_migration.py Show resolved Hide resolved
Copy link
Member

@furszy furszy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

diff ACK e2dfe76

Copy link
Contributor

@ryanofsky ryanofsky left a 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.

src/wallet/transaction.cpp Outdated Show resolved Hide resolved
src/wallet/wallet.cpp Outdated Show resolved Hide resolved
src/wallet/wallet.cpp Show resolved Hide resolved
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);
Copy link
Contributor

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.

src/wallet/wallet.cpp Show resolved Hide resolved
}

// Delete the wallet directories
for (fs::path& dir : wallet_dirs) {
for (const fs::path& dir : wallet_dirs) {
fs::remove_all(dir);
Copy link
Contributor

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?)

Copy link
Member

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.

@achow101 achow101 force-pushed the reload-all-migrated branch 2 times, most recently from b927000 to 7d6d6d6 Compare October 19, 2023 22:05
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.
@achow101
Copy link
Member Author

This really seems like 3 unrelated changes smashed into one PR

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 AddToWallet requires a chain, and without one it segfaults. So this required changing the copying to use LoadToWallet. This has the side effect of copying the metadata, and when I realized that, I also wrote a test for it.

Copy link
Contributor

@ishaanam ishaanam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

light code review ACK 4814e40

This looks good so far, might review d616d30 a bit more later.

Copy link
Contributor

@ryanofsky ryanofsky left a 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

Copy link
Member

@furszy furszy left a 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).

src/wallet/transaction.cpp Outdated Show resolved Hide resolved
}

// Delete the wallet directories
for (fs::path& dir : wallet_dirs) {
for (const fs::path& dir : wallet_dirs) {
fs::remove_all(dir);
Copy link
Member

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;
Copy link
Contributor

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?

Copy link
Member Author

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.

@bitcoin bitcoin deleted a comment Oct 23, 2023
@ryanofsky ryanofsky merged commit d724bb5 into bitcoin:master Oct 23, 2023
16 checks passed
achow101 added a commit that referenced this pull request Nov 7, 2023
…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
Frank-GER pushed a commit to syscoin/syscoin that referenced this pull request Nov 28, 2023
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants