-
Notifications
You must be signed in to change notification settings - Fork 37.7k
locks: Annotate CTxMemPool::check to require cs_main #20972
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
locks: Annotate CTxMemPool::check to require cs_main #20972
Conversation
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.
Concept ACK.
src/txmempool.h
Outdated
@@ -602,7 +602,7 @@ class CTxMemPool | |||
* all inputs are in the mapNextTx array). If sanity-checking is turned off, | |||
* check does nothing. | |||
*/ | |||
void check(const CCoinsViewCache *pcoins) const; | |||
void check(const CCoinsViewCache *pcoins) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); |
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.
Maybe use explicit global namespace
void check(const CCoinsViewCache *pcoins) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); | |
void check(const CCoinsViewCache *pcoins) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main); |
?
It seems reasonable to add a lock assertion into the |
Not sure what you mean by this? Do you mean adding an |
Both. Annotation for compile-time check, and assertion for run-time check. EDIT: https://github.com/bitcoin/bitcoin/blob/master/doc/developer-notes.md#threads-and-synchronization |
Currently, CTxMemPool::check locks CTxMemPool's own cs member, then calls GetSpendHeight which locks cs_main. This can potentially cause an undesirable lock invesion since CTxMemPool's cs is supposed to be locked after cs_main. This does not cause us any problems right now because all callers of CTxMemPool already lock cs_main before calling CTxMemPool::check, which means that the LOCK(cs_main) in GetSpendHeight becomes benign. However, it is currently possible for new code to be added which calls CTxMemPool::check without locking cs_main (which would be dangerous). Therefore we should make it explicit that cs_main needs to be held before calling CTxMemPool::check. NOTE: After all review-only assertions are removed in "bitcoin#20158 | tree-wide: De-globalize ChainstateManager", and assuming that we keep the changes in "validation: Pass in spendheight to CTxMemPool::check", we can re-evaluate to see if this annotation is still necessary.
b8ac27c
to
b396467
Compare
Thanks @hebasto, made the changes! |
Code review ACK b396467 Verified that the three call sites (two in net_processing.cpp and one in validation.cpp) all hold cs_main before calling mempool.check(). |
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 b396467
@@ -618,6 +618,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const | |||
|
|||
if (GetRand(m_check_ratio) >= 1) return; | |||
|
|||
AssertLockHeld(::cs_main); |
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.
AssertLockHeld(::cs_main); | |
AssertLockHeld(::cs_main); // for GetSpendHeight |
nit: Could mention for which function this is needed?
nit: Since all callers of GetSpendHeight
already have cs_main, would it make sense to remove the recursive lock from GetSpendHeight
itself and replace it with a debug-only/compile-only AssertLockHeld/EXCLUSIVE_LOCKS_REQUIRED?
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.
Could mention for which function this is needed?
I've dug into this function a bit more, and I think it's more than that. check()
takes a copy of the CoinsTip:
Line 627 in 3734adb
CCoinsViewCache mempoolDuplicate(const_cast<CCoinsViewCache*>(pcoins)); |
and is then fetching coins from that CCoinsViewCache
via CheckTxInputs()
:
Line 610 in 3734adb
bool fCheckResult = tx.IsCoinBase() || Consensus::CheckTxInputs(tx, dummy_state, mempoolDuplicate, spendheight, txfee); |
If the coins in that CCoinsView
were to be updated by a different thread while check()
is running, then we could hit an assert. There's an implicit assumption that the CCoinsView
won't change while this function is running, so I think we need cs_main
throughout this function.
It's a shame that mempool calls back into validation in this way and has a circular dependency, but that's how it is for now.
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 b396467 review and debug built, verified that cs_main
is held by callers of CTxMemPool::check()
in PeerManagerImpl::ProcessOrphanTx()
, PeerManagerImpl::ProcessMessage()
, and CChainState::ActivateBestChainStep()
Happy to re-ACK if you add documentation or update the commit message with the info in #20972 (comment).
If this is changed, I'd prefer to add the lock annotations (to GetSpendHeight and maybe others), so that the code is self-documenting. As in: Accidentally removing a lock(annotation) will fail to compile with a verbose reason. |
I wasn't really suggesting that we add documentation, just that we don't add misleading documentation that cs_main is only needed for GetSpendHeight. I can see the benefit to documenting the reasoning in the commit log, and would be happy to reACK a push that adds that commit message.
I'll commit to reviewing this (here or in a follow-up PR). |
…main b396467 locks: Annotate CTxMemPool::check to require cs_main (Carl Dong) Pull request description: ``` Currently, CTxMemPool::check locks CTxMemPool's own cs member, then calls GetSpendHeight which locks cs_main. This can potentially cause an undesirable lock invesion since CTxMemPool's cs is supposed to be locked after cs_main. This does not cause us any problems right now because all callers of CTxMemPool already lock cs_main before calling CTxMemPool::check, which means that the LOCK(cs_main) in GetSpendHeight becomes benign. However, it is currently possible for new code to be added which calls CTxMemPool::check without locking cs_main (which would be dangerous). Therefore we should make it explicit that cs_main needs to be held before calling CTxMemPool::check. NOTE: After all review-only assertions are removed in "bitcoin#20158 | tree-wide: De-globalize ChainstateManager", and assuming that we keep the changes in "validation: Pass in spendheight to CTxMemPool::check", we can re-evaluate to see if this annotation is still necessary. ``` ----- Previous discussions: 1. bitcoin#20158 (comment) 2. bitcoin#20158 (review) 3. bitcoin#20749 (comment) ACKs for top commit: jnewbery: Code review ACK b396467 MarcoFalke: ACK b396467 jonatack: ACK b396467 review and debug built, verified that `cs_main` is held by callers of `CTxMemPool::check()` in `PeerManagerImpl::ProcessOrphanTx()`, `PeerManagerImpl::ProcessMessage()`, and `CChainState::ActivateBestChainStep()` Tree-SHA512: 4635cddb4aa1af9532bb657b2f9c4deec4568d16ba28c574eae91bb77368cd40e23c3c720a9de11cec78e7ad678a44a5e25af67f13214b86b56e777e0c35a026
Summary: Currently, CTxMemPool::check locks CTxMemPool's own cs member, then calls GetSpendHeight which locks cs_main. This can potentially cause an undesirable lock invesion since CTxMemPool's cs is supposed to be locked after cs_main. This does not cause us any problems right now because all callers of CTxMemPool already lock cs_main before calling CTxMemPool::check, which means that the LOCK(cs_main) in GetSpendHeight becomes benign. However, it is currently possible for new code to be added which calls CTxMemPool::check without locking cs_main (which would be dangerous). Therefore we should make it explicit that cs_main needs to be held before calling CTxMemPool::check. NOTE: After all review-only assertions are removed in "[[bitcoin/bitcoin#20158 | core#20158]] | tree-wide: De-globalize ChainstateManager", and assuming that we keep the changes in "validation: Pass in spendheight to CTxMemPool::check", we can re-evaluate to see if this annotation is still necessary. This is a backport of [[bitcoin/bitcoin#20972 | core#20972]] Depends on D10842 Test Plan: With TSAN: `ninja all check-all` Reviewers: #bitcoin_abc, Fabien Reviewed By: #bitcoin_abc, Fabien Differential Revision: https://reviews.bitcoinabc.org/D10844
Previous discussions: