wallet: Remove calls to Chain::Lock methods#17954
Conversation
|
Concept ACK |
|
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. |
| uint256 GetLastBlockHash() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) | ||
| { | ||
| AssertLockHeld(cs_wallet); | ||
| assert(m_last_block_processed_height >= 0); |
There was a problem hiding this comment.
It just occurs to me than this assert and the one in GetLastBlockHeight (than I introduced in 5aacc3e) are unsafe if we don't have a Chain interface from which to query block height at wallet creation (CreateWalletFromFile) but that's something to keep in mind if in the future you can run the wallet without a chain.
There was a problem hiding this comment.
It just occurs to me than this assert and the one in GetLastBlockHeight (than I introduced in 5aacc3e) are unsafe if we don't have a Chain interface from which to query block height at wallet creation (CreateWalletFromFile) but that's something to keep in mind if in the future you can run the wallet without a chain.
Yes, I think these cases would only be hit when running wallet code offline with the bitcoin-wallet tool or something similar. But if we add more offline features more code will have to change to be flexible about missing data
src/interfaces/wallet.cpp
Outdated
| } | ||
| num_blocks = m_wallet->GetLastBlockHeight(); | ||
| block_time = -1; | ||
| m_wallet->chain().findBlock(m_wallet->GetLastBlockHash(), nullptr, &block_time); |
There was a problem hiding this comment.
As noted in commit, we may have asynchronicity between node and wallet w.r.t block processing. So returned block time may not be the one of last block height. To avoid this we may cache block time to fetch it when needed with GetLastBlockTime. It would also remove some getBlockTime (but not all last time I looked on).
There was a problem hiding this comment.
As noted in commit, we may have asynchronicity between node and wallet w.r.t block processing. So returned block time may not be the one of last block height. To avoid this we may cache block time to fetch it when needed with
GetLastBlockTime. It would also remove somegetBlockTime(but not all last time I looked on).
Just to be clear, height and time here should be in sync due to cs_wallet being held above. Could still cache the time though. Commit description is saying how the GUI display should be more up to date after this commit, because the transaction data and num blocks value will be in sync, instead of a higher num blocks being returned with older transaction data
There was a problem hiding this comment.
Even further than cs_wallet, we are still holding cs_main there through the chain lock. When we're going to remove locked_chain we may have asynchronicity due to to height being based on BlockConnected locked by cs_wallet and findBlock locked by cs_main ?
There was a problem hiding this comment.
re: #17954 (comment)
(Relevant commit is cfc9373)
Even further than cs_wallet, we are still holding cs_main there through the chain lock. When we're going to remove locked_chain we may have asynchronicity due to to height being based on BlockConnected locked by cs_wallet and findBlock locked by cs_main ?
The GUI is asynchronous by design. It just needs to display internally consistent information within a transaction, and be able to determine if the information is fresh or out of date. The num_blocks height here returned to gui is used for that freshness check, so the new value set here should be better than the previous value for that. More ideally, though num_blocks will be replaced by a hash, which #17993 starts to do
src/wallet/rpcdump.cpp
Outdated
| file << strprintf("# mined on %s\n", tip_height ? FormatISO8601DateTime(locked_chain->getBlockTime(*tip_height)) : "(missing block time)"); | ||
| file << strprintf("# * Best block at time of backup was %i (%s),\n", pwallet->GetLastBlockHeight(), pwallet->GetLastBlockHash().ToString()); | ||
| int64_t block_time = 0; | ||
| pwallet->chain().findBlock(pwallet->GetLastBlockHash(), nullptr, &block_time); |
There was a problem hiding this comment.
Another candidate for GetLastBlockTime
src/wallet/rpcdump.cpp
Outdated
| } | ||
| Optional<int> tip_height = locked_chain->getHeight(); | ||
| nTimeBegin = tip_height ? locked_chain->getBlockTime(*tip_height) : 0; | ||
| pwallet->chain().findBlock(pwallet->GetLastBlockHash(), nullptr, &nTimeBegin); |
There was a problem hiding this comment.
I'm not sure about the commit message, IMO it's less accurate but on the whole make the rescan protection better by starting farther in the past. Anyway, being based on wallet tip or node tip should be safe given the range of TIMESTAMP_WINDOW
There was a problem hiding this comment.
I'm not sure about the commit message, IMO it's less accurate but on the whole make the rescan protection better by starting farther in the past. Anyway, being based on wallet tip or node tip should be safe given the range of TIMESTAMP_WINDOW
Hmm, I'm not sure when it would be less accurate. Are you thinking of a case?
There was a problem hiding this comment.
re: #17954 (comment)
I think I meaned because we rely now on wallet last block hash instead of main tip and we may be late from one block, so in my opinion we are less accurate from one block but we agree on rescan being safer. Nit interpretation, doesn't matter.
There was a problem hiding this comment.
re: #17954 (comment)
I think I meaned because we rely now on wallet last block hash instead of main tip and we may be late from one block, so in my opinion we are less accurate from one block but we agree on rescan being safer. Nit interpretation, doesn't matter.
I think in the case you are talking about the block height/hash/time values in the backup are now more accurate than before because cs_wallet is locked already. So the backup information make will be consistent with the wallet block tip, not the node block tip, in any cases where they are different
|
|
||
| // Verify all timestamps are present before importing any keys. | ||
| const Optional<int> tip_height = locked_chain->getHeight(); | ||
| now = tip_height ? locked_chain->getBlockMedianTimePast(*tip_height) : 0; |
There was a problem hiding this comment.
Agree that caching the last block time would make some of these commits easier.
ryanofsky
left a comment
There was a problem hiding this comment.
Thanks for the review! I pushed a few more commits I think finishing up the wallet rpc code. There are some more changes to come in wallet.cpp after this.
Updated 1f4b604 -> aeba8af (pr/unlock.1 -> pr/unlock.2, compare) with some tweaks to earlier commits and a few new commits extending the PR
re: #17954 (review)
What do you think about caching also last block time at connection ? I think that would make some changes here better by avoiding to uselessly lock the node, even if it's through the interface.
I think it's probably a good idea. It would use some more memory though, and it's unclear if it the change would simplify this PR or not overlap much. I think it's probably something to try out separately.
src/interfaces/wallet.cpp
Outdated
| } | ||
| num_blocks = m_wallet->GetLastBlockHeight(); | ||
| block_time = -1; | ||
| m_wallet->chain().findBlock(m_wallet->GetLastBlockHash(), nullptr, &block_time); |
There was a problem hiding this comment.
As noted in commit, we may have asynchronicity between node and wallet w.r.t block processing. So returned block time may not be the one of last block height. To avoid this we may cache block time to fetch it when needed with
GetLastBlockTime. It would also remove somegetBlockTime(but not all last time I looked on).
Just to be clear, height and time here should be in sync due to cs_wallet being held above. Could still cache the time though. Commit description is saying how the GUI display should be more up to date after this commit, because the transaction data and num blocks value will be in sync, instead of a higher num blocks being returned with older transaction data
| uint256 GetLastBlockHash() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) | ||
| { | ||
| AssertLockHeld(cs_wallet); | ||
| assert(m_last_block_processed_height >= 0); |
There was a problem hiding this comment.
It just occurs to me than this assert and the one in GetLastBlockHeight (than I introduced in 5aacc3e) are unsafe if we don't have a Chain interface from which to query block height at wallet creation (CreateWalletFromFile) but that's something to keep in mind if in the future you can run the wallet without a chain.
Yes, I think these cases would only be hit when running wallet code offline with the bitcoin-wallet tool or something similar. But if we add more offline features more code will have to change to be flexible about missing data
src/wallet/rpcdump.cpp
Outdated
| } | ||
| Optional<int> tip_height = locked_chain->getHeight(); | ||
| nTimeBegin = tip_height ? locked_chain->getBlockTime(*tip_height) : 0; | ||
| pwallet->chain().findBlock(pwallet->GetLastBlockHash(), nullptr, &nTimeBegin); |
There was a problem hiding this comment.
I'm not sure about the commit message, IMO it's less accurate but on the whole make the rescan protection better by starting farther in the past. Anyway, being based on wallet tip or node tip should be safe given the range of TIMESTAMP_WINDOW
Hmm, I'm not sure when it would be less accurate. Are you thinking of a case?
|
Added 3 commits aeba8af -> 60e6595 ( I think this is basically done. Combined with #17443 it should remove all calls to |
|
Thanks Russ, will review new changes soon.
I've a branch doing it, I can try to rebase it on top of this one and squeeze a last one before #16426. |
ryanofsky
left a comment
There was a problem hiding this comment.
Comments below are from last week (they were in pending status because I forgot to submit the github review)
src/interfaces/wallet.cpp
Outdated
| } | ||
| num_blocks = m_wallet->GetLastBlockHeight(); | ||
| block_time = -1; | ||
| m_wallet->chain().findBlock(m_wallet->GetLastBlockHash(), nullptr, &block_time); |
There was a problem hiding this comment.
re: #17954 (comment)
(Relevant commit is cfc9373)
Even further than cs_wallet, we are still holding cs_main there through the chain lock. When we're going to remove locked_chain we may have asynchronicity due to to height being based on BlockConnected locked by cs_wallet and findBlock locked by cs_main ?
The GUI is asynchronous by design. It just needs to display internally consistent information within a transaction, and be able to determine if the information is fresh or out of date. The num_blocks height here returned to gui is used for that freshness check, so the new value set here should be better than the previous value for that. More ideally, though num_blocks will be replaced by a hash, which #17993 starts to do
src/wallet/rpcdump.cpp
Outdated
| } | ||
| Optional<int> tip_height = locked_chain->getHeight(); | ||
| nTimeBegin = tip_height ? locked_chain->getBlockTime(*tip_height) : 0; | ||
| pwallet->chain().findBlock(pwallet->GetLastBlockHash(), nullptr, &nTimeBegin); |
There was a problem hiding this comment.
re: #17954 (comment)
I think I meaned because we rely now on wallet last block hash instead of main tip and we may be late from one block, so in my opinion we are less accurate from one block but we agree on rescan being safer. Nit interpretation, doesn't matter.
I think in the case you are talking about the block height/hash/time values in the backup are now more accurate than before because cs_wallet is locked already. So the backup information make will be consistent with the wallet block tip, not the node block tip, in any cases where they are different
0d788ae to
d2f92a9
Compare
Poll function was wrongly setting cached height to the current chain height instead of the chain height at the time of polling. This bug could cause balances to appear out of date, and was first introduced bitcoin@a0704a8#diff-2e3836af182cfb375329c3463ffd91f8L117. Before that commit, there wasn't a problem because cs_main was held during the poll update. Currently, the problem should be rare. But if 8937d99 from bitcoin#17954 were merged, the problem would get worse, because the wrong cachedNumBlocks value would be set if the wallet was polled in the interval between a block being connected and it processing the BlockConnected notification. Thanks to John Newbery <[email protected]> for catching this while reviewing bitcoin#17954.
|
|
||
| // Verify all timestamps are present before importing any keys. | ||
| const Optional<int> tip_height = locked_chain->getHeight(); | ||
| now = tip_height ? locked_chain->getBlockMedianTimePast(*tip_height) : 0; |
There was a problem hiding this comment.
Agree that caching the last block time would make some of these commits easier.
src/interfaces/chain.h
Outdated
| //! the specified block hash are verified. | ||
| virtual double guessVerificationProgress(const uint256& block_hash) = 0; | ||
|
|
||
| //! Return true if data is available for the specified blocks. |
There was a problem hiding this comment.
'specified blocks' is a bit vague. Can you be more precise about what block_hash min_height and max_height mean?
There was a problem hiding this comment.
re: #17954 (comment)
'specified blocks' is a bit vague. Can you be more precise about what
block_hashmin_heightandmax_heightmean?
Added description, also made min_height not Optional since nullopt was equivalent to 0
src/wallet/rpcwallet.cpp
Outdated
| if (!request.params[0].isNull()) { | ||
| start_height = request.params[0].get_int(); | ||
| if (start_height < 0 || !tip_height || start_height > *tip_height) { | ||
| if (start_height < 0 || tip_height < 0 || start_height > tip_height) { |
There was a problem hiding this comment.
GetLastBlockHeight() can't return a tip_height that's < 0, so I think you can just remove || tip_height < 0
There was a problem hiding this comment.
re: #17954 (comment)
GetLastBlockHeight()can't return atip_heightthat's < 0, so I think you can just remove|| tip_height < 0
Thanks updated
| { | ||
| auto locked_chain = pwallet->chain().lock(); | ||
| Optional<int> tip_height = locked_chain->getHeight(); | ||
| LOCK(pwallet->cs_wallet); |
There was a problem hiding this comment.
Do you need to hold the wallet lock for this entire block? Does it make sense to call:
WITH_LOCK(pwallet->cs_wallet, pwallet->GetLastBlockHeight());
There was a problem hiding this comment.
re: #17954 (comment)
Do you need to hold the wallet lock for this entire block? Does it make sense to call:
WITH_LOCK(pwallet->cs_wallet, pwallet->GetLastBlockHeight());
The lock is also needed for the GetLastBlockHash call in the findAncestorByHeight line below. This could do something cleverer to reduce locking, and I'm happy to make changes if there are suggestions, but moving the lock seemed like simplest change that would work.
src/wallet/rpcwallet.cpp
Outdated
| if (stop_height) { | ||
| stop_block = locked_chain->getBlockHash(*stop_height); | ||
| } | ||
| if (tip_height >= 0) { |
There was a problem hiding this comment.
Again, I think this is always true, so you can remove this conditional.
There was a problem hiding this comment.
re: #17954 (comment)
Again, I think this is always true, so you can remove this conditional.
Thanks, removed
bf36a3c gui: Fix race in WalletModel::pollBalanceChanged (Russell Yanofsky) Pull request description: Poll function was wrongly setting cached height to the current chain height instead of the chain height at the time of polling. This bug could cause balances to appear out of date, and was first introduced bitcoin@a0704a8#diff-2e3836af182cfb375329c3463ffd91f8L117. Before that commit, there wasn't a problem because cs_main was held during the poll update. Currently, the problem should be rare. But if 8937d99 from bitcoin#17954 were merged, the problem would get worse, because the wrong cachedNumBlocks value would be set if the wallet was polled in the interval between a block being connected and it processing the BlockConnected notification. MarcoFalke also points out that a0704a8 could lead to GUI hangs as well, because previously the pollBalanceChanged method, which runs on the GUI thread, would only make a nonblocking TRY_LOCK(cs_main) call, but after could make blocking LOCK(cs_main) calls, potentially locking up the GUI. Thanks to John Newbery for finding this bug this while reviewing bitcoin#17954. ACKs for top commit: Empact: utACK bitcoin@bf36a3c jonasschnelli: utACK bf36a3c Tree-SHA512: 1f4f229fa70a6d1fcf7be3806dca3252e86bc1755168fb421258389eb95aae67f863cb1216e6dc086b596c33560d1136215a4c87b5ff890abc8baaa3333b47f4
…k data Summary: FoundBlock class allows interfaces::Chain::findBlock to return more block information without having lots of optional output parameters. FoundBlock class is also used by other chain methods in upcoming commits. There is mostly no change in behavior. Only exception is CWallet::RescanFromTime now throwing NonFatalCheckError instead of std::logic_error. This is part [1/12] of Core [[bitcoin/bitcoin#17954 | PR17954]] : bitcoin/bitcoin@bf30cd4 Test Plan: ninja all check-all Reviewers: #bitcoin_abc, Fabien Reviewed By: #bitcoin_abc, Fabien Differential Revision: https://reviews.bitcoinabc.org/D7864
Summary: This is a step toward removing the Chain::Lock class and reducing cs_main locking. It also helps ensure the GUI display stays up to date in the case where the node chain height runs ahead of wallet last block processed height. This is part [2/12] of Core [[bitcoin/bitcoin#17954 | PR17954]] : bitcoin/bitcoin@f6da44c Depends on D7864 Test Plan: ninja all check-all Reviewers: #bitcoin_abc, Fabien Reviewed By: #bitcoin_abc, Fabien Differential Revision: https://reviews.bitcoinabc.org/D7865
This is a set of changes updating wallet code to make fewer calls to
Chain::Lockmethods, so theChain::Lockclass will be easier to remove in #16426 with fewer code changes and small changes to behavior.