Remove GetDepthInMainChain dependency on locked chain interface#15931
Remove GetDepthInMainChain dependency on locked chain interface#15931meshcollider merged 9 commits intobitcoin:masterfrom
Conversation
|
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. |
5653f46 to
a0ef689
Compare
adf914a to
773da90
Compare
773da90 to
8b66249
Compare
8b66249 to
e284e42
Compare
|
Added to the "Chasing Concept ACK" board. @ryanofsky maybe you could give an initial Concept/Approach ACK/NACK ? |
|
Yes would be awesome to get a Concept ACK on the approach followed but if it's not the best one, I'm eager to rework the PR in depth! Going to rebase/fix tests failure soon |
ryanofsky
left a comment
There was a problem hiding this comment.
Concept ACK. I left a lot of suggestions, but overall this looks very good, and it's great to get rid of all these cs_main locks.
src/wallet/wallet.h
Outdated
There was a problem hiding this comment.
In commit "Use CWallet::m_last_block_processed_height in GetDepthInMainChain" (233ae92f2d51333f158810d85d13cf63a1144e6b)
Can we use -1 instead of 0 for this to be consistent with nIndex? Also would be good to note here that when a transaction is conflicted, hashBlock and block_height refer to the earliest block with a conflicting transaction instead of the block containing the transaction.
There was a problem hiding this comment.
The comment says An block_height == 0 in commit Add m_block_height field in CMerkleTx and then changes to A block_height == -1 in Use CWallet::m_last_block_processed_height in GetDepthInMainChain.
It would be better to not change this in the course of the PR and just set it to A block_height == -1 in the first PR.
src/wallet/wallet.h
Outdated
There was a problem hiding this comment.
In commit "Use CWallet::m_last_block_processed_height in GetDepthInMainChain" (233ae92f2d51333f158810d85d13cf63a1144e6b)
Isn't this going to break existing serialization of transactions in the wallet? I think it'd be best not to change the serialized representation and just make this a memory-only field that gets filled when the wallet is loaded.
There was a problem hiding this comment.
Hmmm filled at startup using getBlockHeight, given we already have hashBlock ? Do you envision the wallet to always query chain state to succeed its startup ?
src/wallet/wallet.cpp
Outdated
There was a problem hiding this comment.
In commit "Add m_block_height field in CMerkleTx" (8a1975586d3cc7f98961581859fd146a632bbd34)
This logic is getting hard to follow with three repetitive if(hashBlock...) blocks. Now that this is unconditionally updating hashBlock, I think the new logic is equivalent to the following and can be simplified:
assert(!wtxIn.isAbandoned()); // Incoming transactions should never have special abandoned block hash
if (wtxIn.hashBlock != wtx.hashBlock) {
wtx.hashBlock = wtxIn.hashBlock;
wtx.m_block_height = wtxIn.m_block_height;
fUpdated = true;
}There was a problem hiding this comment.
Updated AddToWallet, hope it's clearer
src/wallet/wallet.cpp
Outdated
There was a problem hiding this comment.
In commit "Add m_block_height field in CMerkleTx" (8a1975586d3cc7f98961581859fd146a632bbd34)
It looks like a remaining place where m_block_height is still unset is in importprunedfunds. It would probably be good to change that code to call wtx.SetMerkleBranch instead of setting members directly.
There was a problem hiding this comment.
Oooops, I forgot this one. I've added a call to SetMerkleBranch in importprunedfunds but had to change its argument to hask for transaction height too. Seems to me similar to the serialization issue, do we want wallet to ask chain the state of its stored transactions every time, even it should be able to infer it from the blocks already connected ?
src/wallet/wallet.cpp
Outdated
There was a problem hiding this comment.
In commit "Use CWallet::m_last_block_processed_height in GetDepthInMainChain" (233ae92f2d51333f158810d85d13cf63a1144e6b)
Can we assert(m_block_height > 0) after this point? I'm afraid of another bug like the importprunedfunds one mentioned earlier leaving m_block_height unset and this function returning bogus values.
There was a problem hiding this comment.
Hmmm, I introduced assert(m_block_height >= -1) as we may GetDepthInMainChain on fresh transactions, their hashBlock being unset, their depth should appear as zero.
cc9bd04 to
1458ded
Compare
|
Answered back to @ryanofsky comments on 1458ded, I'm on cleaning last Travis failures |
1697ee4 to
04b4683
Compare
src/wallet/rpcwallet.cpp
Outdated
There was a problem hiding this comment.
In commit "Remove locked_chain from GetDepthInMainChain and its callers" (dd3cb3804126abdba8684cb965d2e1dc3766b3fd)
Appears to be a mistake during rebase: lost abs() call
There was a problem hiding this comment.
Thanks to spot it, was introducted in 436ad43, corrected rebase mistake!
f1d66ce to
8a11ab3
Compare
ryanofsky
left a comment
There was a problem hiding this comment.
Code review ACK 8a11ab3ff4cba6456021e46813f530c22ada8ac1. No changes since previous review other than rebase
|
Sure Russell, I'll review. |
jnewbery
left a comment
There was a problem hiding this comment.
utACK 8a11ab3ff4cba6456021e46813f530c22ada8ac1
I have a couple of nits inline, which can be fixed up later in order to not invalidate review. There are a couple of changes that I would like fixed before merge, since they're to do with the git history, so can't be fixed after the fact (and they don't change the final code, so should be an easy re-ACK for other reviewers)
- Add the
block_heightmember in the Add block_height field in struct Confirmation to not potentially break a git bisect and make the history correct. - The wording for commit Restrain waitForNotificationsIfNewBlocksConnected check to strict tip is a little confusing. Can I suggest the following instead:
Only return early from BlockUntilSyncedToCurrentChain() if current tip is exact match
In the next commit, we start using BlockConnected/BlockDisconnected
callbacks to establish tx depth, rather than querying the chain
directly.
Currently, BlockUntilSyncedToCurrentChain() will return early if the
best block processed by the wallet is a descendant of the node's tip.
That means that in the case of a re-org, it won't wait for the
BlockDisconnected callbacks that have been enqueued during the re-org
but have not yet been triggered in the wallet.
Change BlockUntilSyncedToCurrentChain() to only return early if the
wallet's m_last_block_processed matches the tip exactly. This ensures
that there are no BlockDisconnected or BlockConnected callbacks
in-flight.
Great work with this PR. Thanks for persisting with it, and thanks to @ryanofsky for chasing reviewers!
src/wallet/wallet.h
Outdated
There was a problem hiding this comment.
I'm not a big fan of these default arguments, especially since the last two are both ints. It's too easy to accidentally call the constructor with the wrong number of arguments and set block_height when you meant to set nIndex (as this branch does in an intermediate commit)
There was a problem hiding this comment.
Rearrange members initialization order and add comments, tell me if you have a better measure.
jkczyz
left a comment
There was a problem hiding this comment.
utACK 8a11ab3 modulo a few comments.
src/wallet/wallet.h
Outdated
There was a problem hiding this comment.
Can these fields be const now?
There was a problem hiding this comment.
I think we can't due to setting them with method like setAbandoned?
There was a problem hiding this comment.
Ah, you're right. Could be better to assign a new object within setAbandoned instead, but feel free to leave as it is. Looks like there are other places where these are directly assigned.
src/wallet/wallet.h
Outdated
There was a problem hiding this comment.
The documentation needs to be updated for this new field.
There was a problem hiding this comment.
Shouldn't be an issue anymore as block_height has been switched to the right commit.
|
Updated at 36b68de :
Thanks @ryanofsky, @jnewbery, @jkczyz for reviews! Re-ACK should be minimal on diff. EDIT: forgot to update test too, travis should pass now. |
At wallet loading, we rely on chain state querying to retrieve height of txn, to do so we ensure that lock order is respected between cs_main and cs_wallet. If wallet loaded is the wallet-tool one, all wallet txn will show up with a height of zero. It doesn't matter as confirmation height is not used by wallet-tool. Reorder arguments and document Confirmation calls to avoid ambiguity. Fixes nits left from bitcoin#16624
Credit to jkczyz
is exact match In the next commit, we start using BlockConnected/BlockDisconnected callbacks to establish tx depth, rather than querying the chain directly. Currently, BlockUntilSyncedToCurrentChain will return early if the best block processed by the wallet is a descendant of the node'tip. That means that in the case of a re-org, it won't wait for the BlockDisconnected callbacks that have been enqueued during the re-org but have not yet been triggered in the wallet. Change BlockUntilSyncedToCurrentChain to only return early if the wallet's m_last_block_processed matches the tip exactly. This ensures that there are no BlockDisconnected or BlockConnected callbacks in-flight.
Avoid to lock chain to query state thanks to tracking last block height in CWallet.
We don't remove yet Chain locks as we need to preserve lock order with CWallet one until swapping at once to avoid deadlock failures (spotted by --enable-debug)
Pass conflicting height in CWallet::MarkConflicted
ryanofsky
left a comment
There was a problem hiding this comment.
Code review ACK 36b68de. Changes since last review: new jkczyz refactor importprunedfunds commit, changed BlockUntilSyncedToCurrentChainChanges commit title and description, changed Confirmation struct field order and line-wrapped comment
| int block_height; | ||
| uint256 hashBlock; | ||
| int nIndex; | ||
| Confirmation(Status s = UNCONFIRMED, int b = 0, uint256 h = uint256(), int i = 0) : status(s), block_height(b), hashBlock(h), nIndex(i) {} |
There was a problem hiding this comment.
very minor nit: using b for height and h for hash is a little confusing. Using a few extra characters and calling these status_in, height_in, hash_in and index_in would make this clearer.
|
utACK 769ff05 |
src/wallet/wallet.cpp
Outdated
There was a problem hiding this comment.
Yes it has to be before
Could leave a comment explaining why?
Moved it near to m_last_block_processed_height.
Why? If there's no reason to change then I'd it keep where it was.
src/wallet/wallet.h
Outdated
There was a problem hiding this comment.
nit, use default member initialize {-1} instead of = -1.
| return m_last_block_processed_height; | ||
| }; | ||
| /** Set last block processed height, currently only use in unit test */ | ||
| void SetLastBlockProcessed(int block_height, uint256 block_hash) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) |
There was a problem hiding this comment.
Use const uint256& block_hash?
| wallet->LoadWallet(firstRun); | ||
| { | ||
| auto spk_man = wallet->GetLegacyScriptPubKeyMan(); | ||
| auto locked_chain = wallet->chain().lock(); |
There was a problem hiding this comment.
Why not just lock ::cs_main if you don't even use the interface?
There was a problem hiding this comment.
Method chain here is the way to use the interfaces::chain, I think it doesn't matter that much here to use the Node or Chain one.
| } | ||
|
|
||
| void CZMQNotificationInterface::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) | ||
| void CZMQNotificationInterface::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexDisconnected) |
There was a problem hiding this comment.
nit, just pindex? Otherwise use snake case. Same in the header.
src/wallet/rpcdump.cpp
Outdated
There was a problem hiding this comment.
You can also drop Confirmation constructor and do
wtx.m_confirm = {CWalletTx::Status::CONFIRMED, merkleBlock.header.GetHash(), int(txnIndex)};and
SyncTransaction(ptx, {CWalletTx::Status::UNCONFIRMED, /* hash = */ {}, /* index = */ 0);| if (isUnconfirmed() || isAbandoned()) return 0; | ||
|
|
||
| return locked_chain.getBlockDepth(m_confirm.hashBlock) * (isConflicted() ? -1 : 1); | ||
| return (pwallet->GetLastBlockHeight() - m_confirm.block_height + 1) * (isConflicted() ? -1 : 1); |
There was a problem hiding this comment.
I don't think commit message is correct:
Avoid to lock chain to query state thanks to tracking last block
height in CWallet.
Should say something like?
Use wallet's last block height instead of the locked chain, which
will allow removing the chain lock.
There was a problem hiding this comment.
That's a better wording, I will use it if I have to rebase.
| LOCK(cs_wallet); | ||
|
|
||
| int conflictconfirms = -locked_chain->getBlockDepth(hashBlock); | ||
| int conflictconfirms = (m_last_block_processed_height - conflicting_height + 1) * -1; |
There was a problem hiding this comment.
What's the problem with -(m_last_block_processed_height - conflicting_height + 1)?
|
Can someome relaunch AppVeyor ? It got a linker error on bech32_tests.obj |
|
Looks good, thanks for keeping this maintained ariard. I agree with most of promag's comments but they aren't worth holding up merge for. utACK 36b68de |
|
It looks like this might have sped up all WalletBalance micro benchmarks: |
|
It appears like @MarcoFalke's link always displays the last 10 days, which already don't include the performance gains (which happened on Nov 8). I couldn't find a way to link to a specific date range in codespeed, so for future reference for people bumping into this thread, here's a screenshot instead (of the last 50 days). |
| if (isUnconfirmed() || isAbandoned()) return 0; | ||
|
|
||
| return locked_chain.getBlockDepth(m_confirm.hashBlock) * (isConflicted() ? -1 : 1); | ||
| return (pwallet->GetLastBlockHeight() - m_confirm.block_height + 1) * (isConflicted() ? -1 : 1); |
There was a problem hiding this comment.
In commit "Use CWallet::m_last_block_processed_height in GetDepthInMainChain" (0ff0387)
@ariard It seems like this line is written assuming that GetLastBlockHeight() is always >= m_confirm.block_height, but if I add asserts here:
assert(m_confirm.block_height >= 0);
assert(m_confirm.block_height <= pwallet->GetLastBlockHeight());The second assert fails in wallet_abandonconflict.py and wallet_bumpfee.py tests. Wondering if this expected or seems like a bug to you
| AssertLockHeld(pwallet->cs_wallet); | ||
| if (isUnconfirmed() || isAbandoned()) return 0; | ||
|
|
||
| return locked_chain.getBlockDepth(m_confirm.hashBlock) * (isConflicted() ? -1 : 1); |
There was a problem hiding this comment.
In commit "Use CWallet::m_last_block_processed_height in GetDepthInMainChain" (0ff0387)
Dropping the getBlockDepth call here introduces a bug because getBlockDepth had a safety check to return 0 if there was a reorg and hashBlock is no longer in the active chain, while the new code will just return a garbage depth. I think this is also the reason adding height asserts didn't work in some tests: #15931 (comment). A few fixes are possible and mentioned bug-reorg-depth. A fix shouldn't be hard, but a bigger challenge is adding better test coverage to prevent more bugs like this.
Work starter to remove Chain::Lock interface by adding m_last_block_processed_height in CWallet and m_block_height in CMerkleTx to avoid GetDepthInMainChain having to keep a lock . Once this one done, it should ease work to wipe out more cs_main locks from wallet code.
I think it's ready for a first round of review before to get further.
BlockUntilSyncedToCurrent: restrain isPotentialTip to isTip because we want to be sure that wallet see BlockDisconnected callbacks if its height differs from the Chain one. It means during a reorg, an RPC could return before the BlockDisconnected callback had been triggered. This could cause a tx that had been included in the disconnected block to be displayed as confirmed, for example.-After #16624, we instead rely on Confirmation.AbandonTransaction: in case of conflicted tx (nIndex = -1), we set its m_block_height to the one of conflicting blocks, but if this height is superior to CWallet::m_last_block_processed_height, that means tx isn't conflicted anymore so we return 0 as tx is again unconfirmed-Already done in #16624AddToWalletIfInvolvingMe: in case of block disconnected, transactions are added to mempool again, so we need to replace old txn inmapWalletwith a height set to zero so we remove check on block_hash.IsNull