-
Notifications
You must be signed in to change notification settings - Fork 35.5k
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: prevent bugs from invalid transaction heights with asserts, comments, and refactoring #28546
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. |
Hmm, I wonder if we should instead make both the watchonly and solvables wallets with empty contexts (no chain), and then reload them when migration is done? That would also solve this problem. |
Yes, that would be less fragile than the current approach. Also I'm not sure why the main wallet needs to be created with an empty context anyway. It seems like the best solution would just be to create all the wallets normally and not need to reload any of them. I do think the other changes in this PR make sense no matter how this issue is fixed, though. And I like that the fix implemented in this PR (0648dbb) is just a one line change. |
It has an empty context so that there isn't any possibility for an inconsistent state if a block and transaction is found. |
I see, so a way to prevent the wallet from changing while it is being migrated. In that case, I think building all the wallets offline and then reloading them like you suggested does make sense. Would be happy to review a PR if you have an idea of how to implement that. (Feel free to take any changes from this PR if they'd be useful. Or we could fix the bug directly with this PR and then make the change the loading in a later PR.) |
ACK dbed701 |
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 has an empty context so that there isn't any possibility for an inconsistent state if a block and transaction is found.
I see, so a way to prevent the wallet from changing while it is being migrated.
In that case, I think building all the wallets offline and then reloading them like you suggested does make sense. Would be happy to review a PR if you have an idea of how to implement that. (Feel free to take any changes from this PR if they'd be useful. Or we could fix the bug directly with this PR and then make the change the loading in a later PR.)
+1.
Not seeing a reason to create the watch-only and solvables wallets with a chain context then. They could also enter in an inconsistent state if/when a block reorg or transaction is found during migration.
src/wallet/wallet.cpp
Outdated
wtx->updateState(data.watchonly_wallet->chain()); | ||
// Add to watchonly wallet | ||
if (!data.watchonly_wallet->AddToWallet(wtx->tx, wtx->m_state)) { |
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 really for this PR but.. shouldn't we also need to transfer the other wtx members too?
Like mapValue
(for the "replaced_by_txid" and "replaces_txid"), nTimeReceived
, nTimeSmart
, fFromMe
.
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.
nvm, just saw #28609 implementing it.
I believe we can move to #28609, it fixes this issue and others in a more comprehensive manner. |
Removing from the 26.0 milestone in favor of #28609. |
No change in behavior, this just moves code which updates transaction state to a new method so it can be used after offline processes such as wallet migration.
Also document GetTxDepthInMainChain preconditions better
Rebased dbed701 -> f06016d ( Also updated title and PR description since the original bug was fixed #28609, and now this PR is just adding asserts and cleanup to prevent related bugs. Original description was:
|
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.
Do you think the first commit is still necessary? I'm not seeing a reason for doing it when no other piece of code will use it.
I think the first commit is useful to make it clear that some of the CWalletTx fields aren't serialized in the wallet and need to be loaded separately from the blocks database, and to have one function responsible for loading them. Otherwise the fields are just set conditionally in LoadToWallet away from the rest of transaction state code. The new function could also be useful in the future if we want to add more fields to transaction objects to improve conflict tracking, or if we want to add features that avoid keeping all transactions in memory. It's also a move-only change that should be easy to 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.
I think the first commit is useful to make it clear that some of the CWalletTx fields aren't serialized in the wallet and need to be loaded separately from the blocks database, and to have one function responsible for loading them.
Wouldn't be clearer to move the non-serialized members to optional?
But other than that, what make some noise on me about the approach is that we would be "hiding" a cs_main
locking cause. Because chain.findBlock()
locks the mutex internally.
Which, I know that it is currently not really important, but maybe in the future we would like to batch calls that lock cs_main
as much as possible in the wallet.
Still, isn't that I'm against the refactoring, just would like to discuss alternatives.
Very much agree it makes sense to make locking and lookups explicit, and this change helps that, doesn't hurt it. It moves lookups to a function that explicitly is meant to do lookups, and explicitly takes a Chain parameter and doesn't have access to other wallet state. (CWalletTx used to have a CWallet* backpointer, but this was removed in b11a195 to force this explicitness.) The current block lookup code before this PR is in the middle of a Taking your batching example, if you wanted to look up the block heights as a batch, instead of between loading each transaction, this change gets closer to that goal, because it moves the lookup code out of the per-transaction LoadToWallet to function to a separate function exactly like you would do if you were making that change. On your first idea of making heights use std::optional instead of -1 when they are uninitialized, that would be a fine change but it would not make code more explicit or improve code organization, so I see it as being more shallow. It would be complementary though. |
Thanks for that. Looks ugly.
In this example, I'm not sure if I would place it inside edit: |
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.
utACK f06016d
void CWalletTx::updateState(interfaces::Chain& chain) | ||
{ | ||
bool active; | ||
auto lookup_block = [&](const uint256& hash, int& height, TxState& state) { |
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.
262a78b: could expand this comment:
// Find block by hash and fill in the state height.
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 f06016d
Have to admit that I'm still not sure about the first commit, but it isn't blocking if others are happy with it.
ACK f06016d |
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.