Skip to content

Commit c6e42f1

Browse files
committed
Merge #14193: validation: Add missing mempool locks
fa2b083 [test] Add test to check mempool consistency in case of reorgs (MarcoFalke) fabeb1f validation: Add missing mempool locks (MarcoFalke) fa0c9db txpool: Make nTransactionsUpdated atomic (MarcoFalke) Pull request description: Take the mempool read lock during reorgs, so that we don't accidentally read an inconsistent mempool. ACKs for top commit: laanwj: code review ACK fa2b083 ryanofsky: utACK fa2b083 [EDIT: was ~e284e422e75189794e24fe482819d8b1407857c3~, from bad copy and paste]. Changes since last review: rebase after #15976, adding vTxHashes lock annotation, adding new commit dropping mempool lock for nTransactionsUpdated and making it atomic to avoid deadlock between mempool lock and g_best_block_mutex Tree-SHA512: cfe7777993589087753e000e3736d79d320dca412383fb77b56bef8946a04049722bf888c11b6f722adf677165185c7e58b4a269f7c5fa25e84dda375f6c8a7d
2 parents 6c21a80 + fa2b083 commit c6e42f1

File tree

6 files changed

+183
-45
lines changed

6 files changed

+183
-45
lines changed

src/rpc/mining.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request)
469469
nTransactionsUpdatedLastLP = nTransactionsUpdatedLast;
470470
}
471471

472-
// Release the wallet and main lock while waiting
472+
// Release lock while waiting
473473
LEAVE_CRITICAL_SECTION(cs_main);
474474
{
475475
checktxtime = std::chrono::steady_clock::now() + std::chrono::minutes(1);
@@ -480,6 +480,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request)
480480
if (g_best_block_cv.wait_until(lock, checktxtime) == std::cv_status::timeout)
481481
{
482482
// Timeout: Check transactions for update
483+
// without holding ::mempool.cs to avoid deadlocks
483484
if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLastLP)
484485
break;
485486
checktxtime += std::chrono::seconds(10);

src/test/validation_block_tests.cpp

Lines changed: 149 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <miner.h>
1111
#include <pow.h>
1212
#include <random.h>
13+
#include <script/standard.h>
1314
#include <test/setup_common.h>
1415
#include <util/time.h>
1516
#include <validation.h>
@@ -21,6 +22,8 @@ struct RegtestingSetup : public TestingSetup {
2122
RegtestingSetup() : TestingSetup(CBaseChainParams::REGTEST) {}
2223
};
2324

25+
static const std::vector<unsigned char> V_OP_TRUE{OP_TRUE};
26+
2427
BOOST_FIXTURE_TEST_SUITE(validation_block_tests, RegtestingSetup)
2528

2629
struct TestSubscriber : public CValidationInterface {
@@ -62,8 +65,21 @@ std::shared_ptr<CBlock> Block(const uint256& prev_hash)
6265
pblock->hashPrevBlock = prev_hash;
6366
pblock->nTime = ++time;
6467

68+
pubKey.clear();
69+
{
70+
WitnessV0ScriptHash witness_program;
71+
CSHA256().Write(&V_OP_TRUE[0], V_OP_TRUE.size()).Finalize(witness_program.begin());
72+
pubKey << OP_0 << ToByteVector(witness_program);
73+
}
74+
75+
// Make the coinbase transaction with two outputs:
76+
// One zero-value one that has a unique pubkey to make sure that blocks at the same height can have a different hash
77+
// Another one that has the coinbase reward in a P2WSH with OP_TRUE as witness program to make it easy to spend
6578
CMutableTransaction txCoinbase(*pblock->vtx[0]);
66-
txCoinbase.vout.resize(1);
79+
txCoinbase.vout.resize(2);
80+
txCoinbase.vout[1].scriptPubKey = pubKey;
81+
txCoinbase.vout[1].nValue = txCoinbase.vout[0].nValue;
82+
txCoinbase.vout[0].nValue = 0;
6783
txCoinbase.vin[0].scriptWitness.SetNull();
6884
pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
6985

@@ -72,6 +88,9 @@ std::shared_ptr<CBlock> Block(const uint256& prev_hash)
7288

7389
std::shared_ptr<CBlock> FinalizeBlock(std::shared_ptr<CBlock> pblock)
7490
{
91+
LOCK(cs_main); // For LookupBlockIndex
92+
GenerateCoinbaseCommitment(*pblock, LookupBlockIndex(pblock->hashPrevBlock), Params().GetConsensus());
93+
7594
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
7695

7796
while (!CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) {
@@ -82,13 +101,13 @@ std::shared_ptr<CBlock> FinalizeBlock(std::shared_ptr<CBlock> pblock)
82101
}
83102

84103
// construct a valid block
85-
const std::shared_ptr<const CBlock> GoodBlock(const uint256& prev_hash)
104+
std::shared_ptr<const CBlock> GoodBlock(const uint256& prev_hash)
86105
{
87106
return FinalizeBlock(Block(prev_hash));
88107
}
89108

90109
// construct an invalid block (but with a valid header)
91-
const std::shared_ptr<const CBlock> BadBlock(const uint256& prev_hash)
110+
std::shared_ptr<const CBlock> BadBlock(const uint256& prev_hash)
92111
{
93112
auto pblock = Block(prev_hash);
94113

@@ -188,4 +207,131 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
188207
BOOST_CHECK_EQUAL(sub.m_expected_tip, ::ChainActive().Tip()->GetBlockHash());
189208
}
190209

210+
/**
211+
* Test that mempool updates happen atomically with reorgs.
212+
*
213+
* This prevents RPC clients, among others, from retrieving immediately-out-of-date mempool data
214+
* during large reorgs.
215+
*
216+
* The test verifies this by creating a chain of `num_txs` blocks, matures their coinbases, and then
217+
* submits txns spending from their coinbase to the mempool. A fork chain is then processed,
218+
* invalidating the txns and evicting them from the mempool.
219+
*
220+
* We verify that the mempool updates atomically by polling it continuously
221+
* from another thread during the reorg and checking that its size only changes
222+
* once. The size changing exactly once indicates that the polling thread's
223+
* view of the mempool is either consistent with the chain state before reorg,
224+
* or consistent with the chain state after the reorg, and not just consistent
225+
* with some intermediate state during the reorg.
226+
*/
227+
BOOST_AUTO_TEST_CASE(mempool_locks_reorg)
228+
{
229+
bool ignored;
230+
auto ProcessBlock = [&ignored](std::shared_ptr<const CBlock> block) -> bool {
231+
return ProcessNewBlock(Params(), block, /* fForceProcessing */ true, /* fNewBlock */ &ignored);
232+
};
233+
234+
// Process all mined blocks
235+
BOOST_REQUIRE(ProcessBlock(std::make_shared<CBlock>(Params().GenesisBlock())));
236+
auto last_mined = GoodBlock(Params().GenesisBlock().GetHash());
237+
BOOST_REQUIRE(ProcessBlock(last_mined));
238+
239+
// Run the test multiple times
240+
for (int test_runs = 3; test_runs > 0; --test_runs) {
241+
BOOST_CHECK_EQUAL(last_mined->GetHash(), ::ChainActive().Tip()->GetBlockHash());
242+
243+
// Later on split from here
244+
const uint256 split_hash{last_mined->hashPrevBlock};
245+
246+
// Create a bunch of transactions to spend the miner rewards of the
247+
// most recent blocks
248+
std::vector<CTransactionRef> txs;
249+
for (int num_txs = 22; num_txs > 0; --num_txs) {
250+
CMutableTransaction mtx;
251+
mtx.vin.push_back(CTxIn{COutPoint{last_mined->vtx[0]->GetHash(), 1}, CScript{}});
252+
mtx.vin[0].scriptWitness.stack.push_back(V_OP_TRUE);
253+
mtx.vout.push_back(last_mined->vtx[0]->vout[1]);
254+
mtx.vout[0].nValue -= 1000;
255+
txs.push_back(MakeTransactionRef(mtx));
256+
257+
last_mined = GoodBlock(last_mined->GetHash());
258+
BOOST_REQUIRE(ProcessBlock(last_mined));
259+
}
260+
261+
// Mature the inputs of the txs
262+
for (int j = COINBASE_MATURITY; j > 0; --j) {
263+
last_mined = GoodBlock(last_mined->GetHash());
264+
BOOST_REQUIRE(ProcessBlock(last_mined));
265+
}
266+
267+
// Mine a reorg (and hold it back) before adding the txs to the mempool
268+
const uint256 tip_init{last_mined->GetHash()};
269+
270+
std::vector<std::shared_ptr<const CBlock>> reorg;
271+
last_mined = GoodBlock(split_hash);
272+
reorg.push_back(last_mined);
273+
for (size_t j = COINBASE_MATURITY + txs.size() + 1; j > 0; --j) {
274+
last_mined = GoodBlock(last_mined->GetHash());
275+
reorg.push_back(last_mined);
276+
}
277+
278+
// Add the txs to the tx pool
279+
{
280+
LOCK(cs_main);
281+
CValidationState state;
282+
std::list<CTransactionRef> plTxnReplaced;
283+
for (const auto& tx : txs) {
284+
BOOST_REQUIRE(AcceptToMemoryPool(
285+
::mempool,
286+
state,
287+
tx,
288+
/* pfMissingInputs */ &ignored,
289+
&plTxnReplaced,
290+
/* bypass_limits */ false,
291+
/* nAbsurdFee */ 0));
292+
}
293+
}
294+
295+
// Check that all txs are in the pool
296+
{
297+
LOCK(::mempool.cs);
298+
BOOST_CHECK_EQUAL(::mempool.mapTx.size(), txs.size());
299+
}
300+
301+
// Run a thread that simulates an RPC caller that is polling while
302+
// validation is doing a reorg
303+
std::thread rpc_thread{[&]() {
304+
// This thread is checking that the mempool either contains all of
305+
// the transactions invalidated by the reorg, or none of them, and
306+
// not some intermediate amount.
307+
while (true) {
308+
LOCK(::mempool.cs);
309+
if (::mempool.mapTx.size() == 0) {
310+
// We are done with the reorg
311+
break;
312+
}
313+
// Internally, we might be in the middle of the reorg, but
314+
// externally the reorg to the most-proof-of-work chain should
315+
// be atomic. So the caller assumes that the returned mempool
316+
// is consistent. That is, it has all txs that were there
317+
// before the reorg.
318+
assert(::mempool.mapTx.size() == txs.size());
319+
continue;
320+
}
321+
LOCK(cs_main);
322+
// We are done with the reorg, so the tip must have changed
323+
assert(tip_init != ::ChainActive().Tip()->GetBlockHash());
324+
}};
325+
326+
// Submit the reorg in this thread to invalidate and remove the txs from the tx pool
327+
for (const auto& b : reorg) {
328+
ProcessBlock(b);
329+
}
330+
// Check that the reorg was eventually successful
331+
BOOST_CHECK_EQUAL(last_mined->GetHash(), ::ChainActive().Tip()->GetBlockHash());
332+
333+
// We can join the other thread, which returns when the reorg was successful
334+
rpc_thread.join();
335+
}
336+
}
191337
BOOST_AUTO_TEST_SUITE_END()

src/txmempool.cpp

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap &cachedDescendan
104104
// for each such descendant, also update the ancestor state to include the parent.
105105
void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashesToUpdate)
106106
{
107-
LOCK(cs);
107+
AssertLockHeld(cs);
108108
// For each entry in vHashesToUpdate, store the set of in-mempool, but not
109109
// in-vHashesToUpdate transactions, so that we don't have to recalculate
110110
// descendants when we come across a previously seen entry.
@@ -322,8 +322,8 @@ void CTxMemPoolEntry::UpdateAncestorState(int64_t modifySize, CAmount modifyFee,
322322
assert(int(nSigOpCostWithAncestors) >= 0);
323323
}
324324

325-
CTxMemPool::CTxMemPool(CBlockPolicyEstimator* estimator) :
326-
nTransactionsUpdated(0), minerPolicyEstimator(estimator)
325+
CTxMemPool::CTxMemPool(CBlockPolicyEstimator* estimator)
326+
: nTransactionsUpdated(0), minerPolicyEstimator(estimator)
327327
{
328328
_clear(); //lock free clear
329329

@@ -341,13 +341,11 @@ bool CTxMemPool::isSpent(const COutPoint& outpoint) const
341341

342342
unsigned int CTxMemPool::GetTransactionsUpdated() const
343343
{
344-
LOCK(cs);
345344
return nTransactionsUpdated;
346345
}
347346

348347
void CTxMemPool::AddTransactionsUpdated(unsigned int n)
349348
{
350-
LOCK(cs);
351349
nTransactionsUpdated += n;
352350
}
353351

@@ -459,8 +457,7 @@ void CTxMemPool::CalculateDescendants(txiter entryit, setEntries& setDescendants
459457
void CTxMemPool::removeRecursive(const CTransaction &origTx, MemPoolRemovalReason reason)
460458
{
461459
// Remove transaction from memory pool
462-
{
463-
LOCK(cs);
460+
AssertLockHeld(cs);
464461
setEntries txToRemove;
465462
txiter origit = mapTx.find(origTx.GetHash());
466463
if (origit != mapTx.end()) {
@@ -485,13 +482,12 @@ void CTxMemPool::removeRecursive(const CTransaction &origTx, MemPoolRemovalReaso
485482
}
486483

487484
RemoveStaged(setAllRemoves, false, reason);
488-
}
489485
}
490486

491487
void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags)
492488
{
493489
// Remove transactions spending a coinbase which are now immature and no-longer-final transactions
494-
LOCK(cs);
490+
AssertLockHeld(cs);
495491
setEntries txToRemove;
496492
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
497493
const CTransaction& tx = it->GetTx();
@@ -547,7 +543,7 @@ void CTxMemPool::removeConflicts(const CTransaction &tx)
547543
*/
548544
void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight)
549545
{
550-
LOCK(cs);
546+
AssertLockHeld(cs);
551547
std::vector<const CTxMemPoolEntry*> entries;
552548
for (const auto& tx : vtx)
553549
{
@@ -922,7 +918,7 @@ void CTxMemPool::RemoveStaged(setEntries &stage, bool updateDescendants, MemPool
922918
}
923919

924920
int CTxMemPool::Expire(int64_t time) {
925-
LOCK(cs);
921+
AssertLockHeld(cs);
926922
indexed_transaction_set::index<entry_time>::type::iterator it = mapTx.get<entry_time>().begin();
927923
setEntries toremove;
928924
while (it != mapTx.get<entry_time>().end() && it->GetTime() < time) {
@@ -1015,7 +1011,7 @@ void CTxMemPool::trackPackageRemoved(const CFeeRate& rate) {
10151011
}
10161012

10171013
void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpendsRemaining) {
1018-
LOCK(cs);
1014+
AssertLockHeld(cs);
10191015

10201016
unsigned nTxnRemoved = 0;
10211017
CFeeRate maxFeeRateRemoved(0);

src/txmempool.h

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
#ifndef BITCOIN_TXMEMPOOL_H
77
#define BITCOIN_TXMEMPOOL_H
88

9+
#include <atomic>
10+
#include <map>
911
#include <memory>
1012
#include <set>
11-
#include <map>
12-
#include <vector>
13-
#include <utility>
1413
#include <string>
14+
#include <utility>
15+
#include <vector>
1516

1617
#include <amount.h>
1718
#include <coins.h>
@@ -443,7 +444,7 @@ class CTxMemPool
443444
{
444445
private:
445446
uint32_t nCheckFrequency GUARDED_BY(cs); //!< Value n means that n times in 2^32 we check.
446-
unsigned int nTransactionsUpdated; //!< Used by getblocktemplate to trigger CreateNewBlock() invocation
447+
std::atomic<unsigned int> nTransactionsUpdated; //!< Used by getblocktemplate to trigger CreateNewBlock() invocation
447448
CBlockPolicyEstimator* minerPolicyEstimator;
448449

449450
uint64_t totalTxSize; //!< sum of all mempool tx's virtual sizes. Differs from serialized tx size since witness data is discounted. Defined in BIP 141.
@@ -513,21 +514,12 @@ class CTxMemPool
513514
* `mempool.cs` whenever adding transactions to the mempool and whenever
514515
* changing the chain tip. It's necessary to keep both mutexes locked until
515516
* the mempool is consistent with the new chain tip and fully populated.
516-
*
517-
* @par Consistency bug
518-
*
519-
* The second guarantee above is not currently enforced, but
520-
* https://github.com/bitcoin/bitcoin/pull/14193 will fix it. No known code
521-
* in bitcoin currently depends on second guarantee, but it is important to
522-
* fix for third party code that needs be able to frequently poll the
523-
* mempool without locking `cs_main` and without encountering missing
524-
* transactions during reorgs.
525517
*/
526518
mutable RecursiveMutex cs;
527519
indexed_transaction_set mapTx GUARDED_BY(cs);
528520

529521
using txiter = indexed_transaction_set::nth_index<0>::type::const_iterator;
530-
std::vector<std::pair<uint256, txiter> > vTxHashes; //!< All tx witness hashes/entries in mapTx, in random order
522+
std::vector<std::pair<uint256, txiter>> vTxHashes GUARDED_BY(cs); //!< All tx witness hashes/entries in mapTx, in random order
531523

532524
struct CompareIteratorByHash {
533525
bool operator()(const txiter &a, const txiter &b) const {
@@ -582,10 +574,10 @@ class CTxMemPool
582574
void addUnchecked(const CTxMemPoolEntry& entry, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
583575
void addUnchecked(const CTxMemPoolEntry& entry, setEntries& setAncestors, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
584576

585-
void removeRecursive(const CTransaction &tx, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN);
586-
void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
587-
void removeConflicts(const CTransaction &tx) EXCLUSIVE_LOCKS_REQUIRED(cs);
588-
void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight);
577+
void removeRecursive(const CTransaction& tx, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN) EXCLUSIVE_LOCKS_REQUIRED(cs);
578+
void removeForReorg(const CCoinsViewCache* pcoins, unsigned int nMemPoolHeight, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
579+
void removeConflicts(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(cs);
580+
void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight) EXCLUSIVE_LOCKS_REQUIRED(cs);
589581

590582
void clear();
591583
void _clear() EXCLUSIVE_LOCKS_REQUIRED(cs); //lock free
@@ -598,7 +590,7 @@ class CTxMemPool
598590
* Check that none of this transactions inputs are in the mempool, and thus
599591
* the tx is not dependent on other mempool transactions to be included in a block.
600592
*/
601-
bool HasNoInputsOf(const CTransaction& tx) const;
593+
bool HasNoInputsOf(const CTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs);
602594

603595
/** Affect CreateNewBlock prioritisation of transactions */
604596
void PrioritiseTransaction(const uint256& hash, const CAmount& nFeeDelta);
@@ -632,7 +624,7 @@ class CTxMemPool
632624
* for). Note: vHashesToUpdate should be the set of transactions from the
633625
* disconnected block that have been accepted back into the mempool.
634626
*/
635-
void UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
627+
void UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
636628

637629
/** Try to calculate all in-mempool ancestors of entry.
638630
* (these are all calculated including the tx itself)
@@ -663,10 +655,10 @@ class CTxMemPool
663655
* pvNoSpendsRemaining, if set, will be populated with the list of outpoints
664656
* which are not in mempool which no longer have any spends in this mempool.
665657
*/
666-
void TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpendsRemaining=nullptr);
658+
void TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpendsRemaining = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs);
667659

668660
/** Expire all transaction (and their dependencies) in the mempool older than time. Return the number of removed transactions. */
669-
int Expire(int64_t time);
661+
int Expire(int64_t time) EXCLUSIVE_LOCKS_REQUIRED(cs);
670662

671663
/**
672664
* Calculate the ancestor and descendant count for the given transaction.

0 commit comments

Comments
 (0)