Skip to content

Commit 82ffd4d

Browse files
author
MarcoFalke
committed
Merge #14963: mempool, validation: Explain cs_main locking semantics
fa5e373 validation: Add cs_main locking annotations (MarcoFalke) fa5c346 doc: Add comment to cs_main and mempool::cs (MarcoFalke) fafe941 test: Add missing validation locks (MarcoFalke) fac4558 sync: Add RecursiveMutex type alias (MarcoFalke) Pull request description: Both the chain state and the transaction pool are validation specific, but access to them is protected by two locks. The two locks have the following semantics: * Writing to the chain state or adding transactions to the transaction pool -> Take both `cs_main` and `mempool::cs` * Reading either or removing transactions from the the transaction pool -> Take only the appropriate lock Tree-SHA512: 6f6e612ffc391904c6434a79a4f3f8de1b928bf0a3e3434b73561037b395e2b40a70a5a4bd8472dd230e9eacc8e5d5374c904a3c509910cf3971dd7ff59a626c
2 parents eb2aecf + fa5e373 commit 82ffd4d

File tree

8 files changed

+67
-22
lines changed

8 files changed

+67
-22
lines changed

src/bench/mempool_eviction.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
#include <list>
1010
#include <vector>
1111

12-
static void AddTx(const CTransactionRef& tx, const CAmount& nFee, CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(pool.cs)
12+
static void AddTx(const CTransactionRef& tx, const CAmount& nFee, CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs)
1313
{
1414
int64_t nTime = 0;
1515
unsigned int nHeight = 1;
@@ -108,7 +108,7 @@ static void MempoolEviction(benchmark::State& state)
108108
tx7.vout[1].nValue = 10 * COIN;
109109

110110
CTxMemPool pool;
111-
LOCK(pool.cs);
111+
LOCK2(cs_main, pool.cs);
112112
// Create transaction references outside the "hot loop"
113113
const CTransactionRef tx1_r{MakeTransactionRef(tx1)};
114114
const CTransactionRef tx2_r{MakeTransactionRef(tx2)};

src/sync.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
////////////////////////////////////////////////
2121

2222
/*
23-
CCriticalSection mutex;
23+
RecursiveMutex mutex;
2424
std::recursive_mutex mutex;
2525
2626
LOCK(mutex);
@@ -104,6 +104,7 @@ class LOCKABLE AnnotatedMixin : public PARENT
104104
* Wrapped mutex: supports recursive locking, but no waiting
105105
* TODO: We should move away from using the recursive lock by default.
106106
*/
107+
using RecursiveMutex = AnnotatedMixin<std::recursive_mutex>;
107108
typedef AnnotatedMixin<std::recursive_mutex> CCriticalSection;
108109

109110
/** Wrapped mutex: supports waiting but not recursive locking */

src/test/blockencodings_tests.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ BOOST_AUTO_TEST_CASE(SimpleRoundTripTest)
6262
TestMemPoolEntryHelper entry;
6363
CBlock block(BuildBlockTestCase());
6464

65-
LOCK(pool.cs);
65+
LOCK2(cs_main, pool.cs);
6666
pool.addUnchecked(entry.FromTx(block.vtx[2]));
6767
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0);
6868

@@ -162,7 +162,7 @@ BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest)
162162
TestMemPoolEntryHelper entry;
163163
CBlock block(BuildBlockTestCase());
164164

165-
LOCK(pool.cs);
165+
LOCK2(cs_main, pool.cs);
166166
pool.addUnchecked(entry.FromTx(block.vtx[2]));
167167
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0);
168168

@@ -232,7 +232,7 @@ BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest)
232232
TestMemPoolEntryHelper entry;
233233
CBlock block(BuildBlockTestCase());
234234

235-
LOCK(pool.cs);
235+
LOCK2(cs_main, pool.cs);
236236
pool.addUnchecked(entry.FromTx(block.vtx[1]));
237237
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[1]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0);
238238

src/test/mempool_tests.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest)
5555

5656

5757
CTxMemPool testPool;
58-
LOCK(testPool.cs);
58+
LOCK2(cs_main, testPool.cs);
5959

6060
// Nothing in pool, remove should do nothing:
6161
unsigned int poolSize = testPool.size();
@@ -120,7 +120,7 @@ static void CheckSort(CTxMemPool &pool, std::vector<std::string> &sortedOrder) E
120120
BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
121121
{
122122
CTxMemPool pool;
123-
LOCK(pool.cs);
123+
LOCK2(cs_main, pool.cs);
124124
TestMemPoolEntryHelper entry;
125125

126126
/* 3rd highest fee */
@@ -293,7 +293,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
293293
BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest)
294294
{
295295
CTxMemPool pool;
296-
LOCK(pool.cs);
296+
LOCK2(cs_main, pool.cs);
297297
TestMemPoolEntryHelper entry;
298298

299299
/* 3rd highest fee */
@@ -422,7 +422,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest)
422422
BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
423423
{
424424
CTxMemPool pool;
425-
LOCK(pool.cs);
425+
LOCK2(cs_main, pool.cs);
426426
TestMemPoolEntryHelper entry;
427427

428428
CMutableTransaction tx1 = CMutableTransaction();
@@ -595,7 +595,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests)
595595
size_t ancestors, descendants;
596596

597597
CTxMemPool pool;
598-
LOCK(pool.cs);
598+
LOCK2(cs_main, pool.cs);
599599
TestMemPoolEntryHelper entry;
600600

601601
/* Base transaction */

src/test/miner_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ static bool TestSequenceLocks(const CTransaction &tx, int flags) EXCLUSIVE_LOCKS
9999
// Test suite for ancestor feerate transaction selection.
100100
// Implemented as an additional function, rather than a separate test case,
101101
// to allow reusing the blockchain created in CreateNewBlock_validity.
102-
static void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::mempool.cs)
102+
static void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs)
103103
{
104104
// Test the ancestor feerate transaction selection.
105105
TestMemPoolEntryHelper entry;

src/test/policyestimator_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
1818
{
1919
CBlockPolicyEstimator feeEst;
2020
CTxMemPool mpool(&feeEst);
21-
LOCK(mpool.cs);
21+
LOCK2(cs_main, mpool.cs);
2222
TestMemPoolEntryHelper entry;
2323
CAmount basefee(2000);
2424
CAmount deltaFee(100);

src/txmempool.h

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,43 @@ class CTxMemPool
485485
>
486486
> indexed_transaction_set;
487487

488-
mutable CCriticalSection cs;
488+
/**
489+
* This mutex needs to be locked when accessing `mapTx` or other members
490+
* that are guarded by it.
491+
*
492+
* @par Consistency guarantees
493+
*
494+
* By design, it is guaranteed that:
495+
*
496+
* 1. Locking both `cs_main` and `mempool.cs` will give a view of mempool
497+
* that is consistent with current chain tip (`chainActive` and
498+
* `pcoinsTip`) and is fully populated. Fully populated means that if the
499+
* current active chain is missing transactions that were present in a
500+
* previously active chain, all the missing transactions will have been
501+
* re-added to the mempool and should be present if they meet size and
502+
* consistency constraints.
503+
*
504+
* 2. Locking `mempool.cs` without `cs_main` will give a view of a mempool
505+
* consistent with some chain that was active since `cs_main` was last
506+
* locked, and that is fully populated as described above. It is ok for
507+
* code that only needs to query or remove transactions from the mempool
508+
* to lock just `mempool.cs` without `cs_main`.
509+
*
510+
* To provide these guarantees, it is necessary to lock both `cs_main` and
511+
* `mempool.cs` whenever adding transactions to the mempool and whenever
512+
* changing the chain tip. It's necessary to keep both mutexes locked until
513+
* the mempool is consistent with the new chain tip and fully populated.
514+
*
515+
* @par Consistency bug
516+
*
517+
* The second guarantee above is not currently enforced, but
518+
* https://github.com/bitcoin/bitcoin/pull/14193 will fix it. No known code
519+
* in bitcoin currently depends on second guarantee, but it is important to
520+
* fix for third party code that needs be able to frequently poll the
521+
* mempool without locking `cs_main` and without encountering missing
522+
* transactions during reorgs.
523+
*/
524+
mutable RecursiveMutex cs;
489525
indexed_transaction_set mapTx GUARDED_BY(cs);
490526

491527
using txiter = indexed_transaction_set::nth_index<0>::type::const_iterator;
@@ -541,8 +577,8 @@ class CTxMemPool
541577
// Note that addUnchecked is ONLY called from ATMP outside of tests
542578
// and any other callers may break wallet's in-mempool tracking (due to
543579
// lack of CValidationInterface::TransactionAddedToMempool callbacks).
544-
void addUnchecked(const CTxMemPoolEntry& entry, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs);
545-
void addUnchecked(const CTxMemPoolEntry& entry, setEntries& setAncestors, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs);
580+
void addUnchecked(const CTxMemPoolEntry& entry, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
581+
void addUnchecked(const CTxMemPoolEntry& entry, setEntries& setAncestors, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
546582

547583
void removeRecursive(const CTransaction &tx, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN);
548584
void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@@ -594,7 +630,7 @@ class CTxMemPool
594630
* for). Note: vHashesToUpdate should be the set of transactions from the
595631
* disconnected block that have been accepted back into the mempool.
596632
*/
597-
void UpdateTransactionsFromBlock(const std::vector<uint256> &vHashesToUpdate);
633+
void UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
598634

599635
/** Try to calculate all in-mempool ancestors of entry.
600636
* (these are all calculated including the tx itself)
@@ -636,7 +672,7 @@ class CTxMemPool
636672
*/
637673
void GetTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) const;
638674

639-
unsigned long size()
675+
unsigned long size() const
640676
{
641677
LOCK(cs);
642678
return mapTx.size();

src/validation.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ class CChainState {
173173
CCoinsViewCache& view, const CChainParams& chainparams, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
174174

175175
// Block disconnection on our pcoinsTip:
176-
bool DisconnectTip(CValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions *disconnectpool);
176+
bool DisconnectTip(CValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
177177

178178
// Manual block validity manipulation:
179179
bool PreciousBlock(CValidationState& state, const CChainParams& params, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main);
@@ -210,9 +210,17 @@ class CChainState {
210210
bool RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs, const CChainParams& params) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
211211
} g_chainstate;
212212

213-
214-
215-
CCriticalSection cs_main;
213+
/**
214+
* Mutex to guard access to validation specific variables, such as reading
215+
* or changing the chainstate.
216+
*
217+
* This may also need to be locked when updating the transaction pool, e.g. on
218+
* AcceptToMemoryPool. See CTxMemPool::cs comment for details.
219+
*
220+
* The transaction pool has a separate lock to allow reading from it and the
221+
* chainstate at the same time.
222+
*/
223+
RecursiveMutex cs_main;
216224

217225
BlockMap& mapBlockIndex = g_chainstate.mapBlockIndex;
218226
CChain& chainActive = g_chainstate.chainActive;

0 commit comments

Comments
 (0)