Merge #14963: mempool, validation: Explain cs_main locking semantics
fa5e373365validation: Add cs_main locking annotations (MarcoFalke)fa5c346c5adoc: Add comment to cs_main and mempool::cs (MarcoFalke)fafe941bddtest: Add missing validation locks (MarcoFalke)fac4558462sync: 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
This commit is contained in:
8 files changed
+67
-22
No files matched your search
@@ -9,7 +9,7 @@
|
|||||||
#include <list>
|
#include <list>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
static void AddTx(const CTransactionRef& tx, const CAmount& nFee, CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(pool.cs)
|
static void AddTx(const CTransactionRef& tx, const CAmount& nFee, CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs)
|
||||||
{
|
{
|
||||||
int64_t nTime = 0;
|
int64_t nTime = 0;
|
||||||
unsigned int nHeight = 1;
|
unsigned int nHeight = 1;
|
||||||
@@ -108,7 +108,7 @@ static void MempoolEviction(benchmark::State& state)
|
|||||||
tx7.vout[1].nValue = 10 * COIN;
|
tx7.vout[1].nValue = 10 * COIN;
|
||||||
|
|
||||||
CTxMemPool pool;
|
CTxMemPool pool;
|
||||||
LOCK(pool.cs);
|
LOCK2(cs_main, pool.cs);
|
||||||
// Create transaction references outside the "hot loop"
|
// Create transaction references outside the "hot loop"
|
||||||
const CTransactionRef tx1_r{MakeTransactionRef(tx1)};
|
const CTransactionRef tx1_r{MakeTransactionRef(tx1)};
|
||||||
const CTransactionRef tx2_r{MakeTransactionRef(tx2)};
|
const CTransactionRef tx2_r{MakeTransactionRef(tx2)};
|
||||||
|
|||||||
+2
-1
@@ -20,7 +20,7 @@
|
|||||||
////////////////////////////////////////////////
|
////////////////////////////////////////////////
|
||||||
|
|
||||||
/*
|
/*
|
||||||
CCriticalSection mutex;
|
RecursiveMutex mutex;
|
||||||
std::recursive_mutex mutex;
|
std::recursive_mutex mutex;
|
||||||
|
|
||||||
LOCK(mutex);
|
LOCK(mutex);
|
||||||
@@ -104,6 +104,7 @@ public:
|
|||||||
* Wrapped mutex: supports recursive locking, but no waiting
|
* Wrapped mutex: supports recursive locking, but no waiting
|
||||||
* TODO: We should move away from using the recursive lock by default.
|
* TODO: We should move away from using the recursive lock by default.
|
||||||
*/
|
*/
|
||||||
|
using RecursiveMutex = AnnotatedMixin<std::recursive_mutex>;
|
||||||
typedef AnnotatedMixin<std::recursive_mutex> CCriticalSection;
|
typedef AnnotatedMixin<std::recursive_mutex> CCriticalSection;
|
||||||
|
|
||||||
/** Wrapped mutex: supports waiting but not recursive locking */
|
/** Wrapped mutex: supports waiting but not recursive locking */
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ BOOST_AUTO_TEST_CASE(SimpleRoundTripTest)
|
|||||||
TestMemPoolEntryHelper entry;
|
TestMemPoolEntryHelper entry;
|
||||||
CBlock block(BuildBlockTestCase());
|
CBlock block(BuildBlockTestCase());
|
||||||
|
|
||||||
LOCK(pool.cs);
|
LOCK2(cs_main, pool.cs);
|
||||||
pool.addUnchecked(entry.FromTx(block.vtx[2]));
|
pool.addUnchecked(entry.FromTx(block.vtx[2]));
|
||||||
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0);
|
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0);
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest)
|
|||||||
TestMemPoolEntryHelper entry;
|
TestMemPoolEntryHelper entry;
|
||||||
CBlock block(BuildBlockTestCase());
|
CBlock block(BuildBlockTestCase());
|
||||||
|
|
||||||
LOCK(pool.cs);
|
LOCK2(cs_main, pool.cs);
|
||||||
pool.addUnchecked(entry.FromTx(block.vtx[2]));
|
pool.addUnchecked(entry.FromTx(block.vtx[2]));
|
||||||
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0);
|
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0);
|
||||||
|
|
||||||
@@ -232,7 +232,7 @@ BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest)
|
|||||||
TestMemPoolEntryHelper entry;
|
TestMemPoolEntryHelper entry;
|
||||||
CBlock block(BuildBlockTestCase());
|
CBlock block(BuildBlockTestCase());
|
||||||
|
|
||||||
LOCK(pool.cs);
|
LOCK2(cs_main, pool.cs);
|
||||||
pool.addUnchecked(entry.FromTx(block.vtx[1]));
|
pool.addUnchecked(entry.FromTx(block.vtx[1]));
|
||||||
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[1]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0);
|
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[1]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0);
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest)
|
|||||||
|
|
||||||
|
|
||||||
CTxMemPool testPool;
|
CTxMemPool testPool;
|
||||||
LOCK(testPool.cs);
|
LOCK2(cs_main, testPool.cs);
|
||||||
|
|
||||||
// Nothing in pool, remove should do nothing:
|
// Nothing in pool, remove should do nothing:
|
||||||
unsigned int poolSize = testPool.size();
|
unsigned int poolSize = testPool.size();
|
||||||
@@ -120,7 +120,7 @@ static void CheckSort(CTxMemPool &pool, std::vector<std::string> &sortedOrder) E
|
|||||||
BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
||||||
{
|
{
|
||||||
CTxMemPool pool;
|
CTxMemPool pool;
|
||||||
LOCK(pool.cs);
|
LOCK2(cs_main, pool.cs);
|
||||||
TestMemPoolEntryHelper entry;
|
TestMemPoolEntryHelper entry;
|
||||||
|
|
||||||
/* 3rd highest fee */
|
/* 3rd highest fee */
|
||||||
@@ -293,7 +293,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
|||||||
BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest)
|
BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest)
|
||||||
{
|
{
|
||||||
CTxMemPool pool;
|
CTxMemPool pool;
|
||||||
LOCK(pool.cs);
|
LOCK2(cs_main, pool.cs);
|
||||||
TestMemPoolEntryHelper entry;
|
TestMemPoolEntryHelper entry;
|
||||||
|
|
||||||
/* 3rd highest fee */
|
/* 3rd highest fee */
|
||||||
@@ -422,7 +422,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest)
|
|||||||
BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
|
BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
|
||||||
{
|
{
|
||||||
CTxMemPool pool;
|
CTxMemPool pool;
|
||||||
LOCK(pool.cs);
|
LOCK2(cs_main, pool.cs);
|
||||||
TestMemPoolEntryHelper entry;
|
TestMemPoolEntryHelper entry;
|
||||||
|
|
||||||
CMutableTransaction tx1 = CMutableTransaction();
|
CMutableTransaction tx1 = CMutableTransaction();
|
||||||
@@ -595,7 +595,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests)
|
|||||||
size_t ancestors, descendants;
|
size_t ancestors, descendants;
|
||||||
|
|
||||||
CTxMemPool pool;
|
CTxMemPool pool;
|
||||||
LOCK(pool.cs);
|
LOCK2(cs_main, pool.cs);
|
||||||
TestMemPoolEntryHelper entry;
|
TestMemPoolEntryHelper entry;
|
||||||
|
|
||||||
/* Base transaction */
|
/* Base transaction */
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ static bool TestSequenceLocks(const CTransaction &tx, int flags) EXCLUSIVE_LOCKS
|
|||||||
// Test suite for ancestor feerate transaction selection.
|
// Test suite for ancestor feerate transaction selection.
|
||||||
// Implemented as an additional function, rather than a separate test case,
|
// Implemented as an additional function, rather than a separate test case,
|
||||||
// to allow reusing the blockchain created in CreateNewBlock_validity.
|
// to allow reusing the blockchain created in CreateNewBlock_validity.
|
||||||
static void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::mempool.cs)
|
static void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs)
|
||||||
{
|
{
|
||||||
// Test the ancestor feerate transaction selection.
|
// Test the ancestor feerate transaction selection.
|
||||||
TestMemPoolEntryHelper entry;
|
TestMemPoolEntryHelper entry;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
|
|||||||
{
|
{
|
||||||
CBlockPolicyEstimator feeEst;
|
CBlockPolicyEstimator feeEst;
|
||||||
CTxMemPool mpool(&feeEst);
|
CTxMemPool mpool(&feeEst);
|
||||||
LOCK(mpool.cs);
|
LOCK2(cs_main, mpool.cs);
|
||||||
TestMemPoolEntryHelper entry;
|
TestMemPoolEntryHelper entry;
|
||||||
CAmount basefee(2000);
|
CAmount basefee(2000);
|
||||||
CAmount deltaFee(100);
|
CAmount deltaFee(100);
|
||||||
|
|||||||
+41
-5
@@ -485,7 +485,43 @@ public:
|
|||||||
>
|
>
|
||||||
> indexed_transaction_set;
|
> indexed_transaction_set;
|
||||||
|
|
||||||
mutable CCriticalSection cs;
|
/**
|
||||||
|
* This mutex needs to be locked when accessing `mapTx` or other members
|
||||||
|
* that are guarded by it.
|
||||||
|
*
|
||||||
|
* @par Consistency guarantees
|
||||||
|
*
|
||||||
|
* By design, it is guaranteed that:
|
||||||
|
*
|
||||||
|
* 1. Locking both `cs_main` and `mempool.cs` will give a view of mempool
|
||||||
|
* that is consistent with current chain tip (`chainActive` and
|
||||||
|
* `pcoinsTip`) and is fully populated. Fully populated means that if the
|
||||||
|
* current active chain is missing transactions that were present in a
|
||||||
|
* previously active chain, all the missing transactions will have been
|
||||||
|
* re-added to the mempool and should be present if they meet size and
|
||||||
|
* consistency constraints.
|
||||||
|
*
|
||||||
|
* 2. Locking `mempool.cs` without `cs_main` will give a view of a mempool
|
||||||
|
* consistent with some chain that was active since `cs_main` was last
|
||||||
|
* locked, and that is fully populated as described above. It is ok for
|
||||||
|
* code that only needs to query or remove transactions from the mempool
|
||||||
|
* to lock just `mempool.cs` without `cs_main`.
|
||||||
|
*
|
||||||
|
* To provide these guarantees, it is necessary to lock both `cs_main` and
|
||||||
|
* `mempool.cs` whenever adding transactions to the mempool and whenever
|
||||||
|
* changing the chain tip. It's necessary to keep both mutexes locked until
|
||||||
|
* the mempool is consistent with the new chain tip and fully populated.
|
||||||
|
*
|
||||||
|
* @par Consistency bug
|
||||||
|
*
|
||||||
|
* The second guarantee above is not currently enforced, but
|
||||||
|
* https://github.com/bitcoin/bitcoin/pull/14193 will fix it. No known code
|
||||||
|
* in bitcoin currently depends on second guarantee, but it is important to
|
||||||
|
* fix for third party code that needs be able to frequently poll the
|
||||||
|
* mempool without locking `cs_main` and without encountering missing
|
||||||
|
* transactions during reorgs.
|
||||||
|
*/
|
||||||
|
mutable RecursiveMutex cs;
|
||||||
indexed_transaction_set mapTx GUARDED_BY(cs);
|
indexed_transaction_set mapTx GUARDED_BY(cs);
|
||||||
|
|
||||||
using txiter = indexed_transaction_set::nth_index<0>::type::const_iterator;
|
using txiter = indexed_transaction_set::nth_index<0>::type::const_iterator;
|
||||||
@@ -541,8 +577,8 @@ public:
|
|||||||
// Note that addUnchecked is ONLY called from ATMP outside of tests
|
// Note that addUnchecked is ONLY called from ATMP outside of tests
|
||||||
// and any other callers may break wallet's in-mempool tracking (due to
|
// and any other callers may break wallet's in-mempool tracking (due to
|
||||||
// lack of CValidationInterface::TransactionAddedToMempool callbacks).
|
// lack of CValidationInterface::TransactionAddedToMempool callbacks).
|
||||||
void addUnchecked(const CTxMemPoolEntry& entry, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs);
|
void addUnchecked(const CTxMemPoolEntry& entry, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
|
||||||
void addUnchecked(const CTxMemPoolEntry& entry, setEntries& setAncestors, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs);
|
void addUnchecked(const CTxMemPoolEntry& entry, setEntries& setAncestors, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
|
||||||
|
|
||||||
void removeRecursive(const CTransaction &tx, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN);
|
void removeRecursive(const CTransaction &tx, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN);
|
||||||
void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||||
@@ -594,7 +630,7 @@ public:
|
|||||||
* for). Note: vHashesToUpdate should be the set of transactions from the
|
* for). Note: vHashesToUpdate should be the set of transactions from the
|
||||||
* disconnected block that have been accepted back into the mempool.
|
* disconnected block that have been accepted back into the mempool.
|
||||||
*/
|
*/
|
||||||
void UpdateTransactionsFromBlock(const std::vector<uint256> &vHashesToUpdate);
|
void UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||||
|
|
||||||
/** Try to calculate all in-mempool ancestors of entry.
|
/** Try to calculate all in-mempool ancestors of entry.
|
||||||
* (these are all calculated including the tx itself)
|
* (these are all calculated including the tx itself)
|
||||||
@@ -636,7 +672,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
void GetTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) const;
|
void GetTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) const;
|
||||||
|
|
||||||
unsigned long size()
|
unsigned long size() const
|
||||||
{
|
{
|
||||||
LOCK(cs);
|
LOCK(cs);
|
||||||
return mapTx.size();
|
return mapTx.size();
|
||||||
|
|||||||
+12
-4
@@ -173,7 +173,7 @@ public:
|
|||||||
CCoinsViewCache& view, const CChainParams& chainparams, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
CCoinsViewCache& view, const CChainParams& chainparams, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||||
|
|
||||||
// Block disconnection on our pcoinsTip:
|
// Block disconnection on our pcoinsTip:
|
||||||
bool DisconnectTip(CValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions *disconnectpool);
|
bool DisconnectTip(CValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||||
|
|
||||||
// Manual block validity manipulation:
|
// Manual block validity manipulation:
|
||||||
bool PreciousBlock(CValidationState& state, const CChainParams& params, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main);
|
bool PreciousBlock(CValidationState& state, const CChainParams& params, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main);
|
||||||
@@ -210,9 +210,17 @@ private:
|
|||||||
bool RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs, const CChainParams& params) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
bool RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs, const CChainParams& params) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||||
} g_chainstate;
|
} g_chainstate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutex to guard access to validation specific variables, such as reading
|
||||||
CCriticalSection cs_main;
|
* or changing the chainstate.
|
||||||
|
*
|
||||||
|
* This may also need to be locked when updating the transaction pool, e.g. on
|
||||||
|
* AcceptToMemoryPool. See CTxMemPool::cs comment for details.
|
||||||
|
*
|
||||||
|
* The transaction pool has a separate lock to allow reading from it and the
|
||||||
|
* chainstate at the same time.
|
||||||
|
*/
|
||||||
|
RecursiveMutex cs_main;
|
||||||
|
|
||||||
BlockMap& mapBlockIndex = g_chainstate.mapBlockIndex;
|
BlockMap& mapBlockIndex = g_chainstate.mapBlockIndex;
|
||||||
CChain& chainActive = g_chainstate.chainActive;
|
CChain& chainActive = g_chainstate.chainActive;
|
||||||
|
|||||||
Reference in new issue
Block a user