Skip to content

Commit d9fdac1

Browse files
committed
Merge #11824: Block ActivateBestChain to empty validationinterface queue
97d2b09 Add helper to wait for validation interface queue to catch up (Matt Corallo) 3613749 Block ActivateBestChain to empty validationinterface queue (Matt Corallo) 5a933ce Add an interface to get the queue depth out of CValidationInterface (Matt Corallo) a99b76f Require no cs_main lock for ProcessNewBlock/ActivateBestChain (Matt Corallo) a734896 Avoid cs_main in net_processing ActivateBestChain calls (Matt Corallo) 66aa1d5 Refactor ProcessGetData in anticipation of avoiding cs_main for ABC (Matt Corallo) 818075a Create new mutex for orphans, no cs_main in PLV::BlockConnected (Matt Corallo) Pull request description: This should fix #11822. It ended up bigger than I hoped for, but its not too gnarly. Note that " Require no cs_main lock for ProcessNewBlock/ActivateBestChain" is mostly pure code-movement. Tree-SHA512: 1127688545926f6099449dca6a4e6609eefc3abbd72f1c66e03d32bd8c7b31e82097d8307822cfd1dec0321703579cfdd82069cab6e17b1024e75eac694122cb
2 parents 5180a86 + 97d2b09 commit d9fdac1

11 files changed

+292
-213
lines changed

src/net_processing.cpp

Lines changed: 197 additions & 171 deletions
Large diffs are not rendered by default.

src/scheduler.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,3 +206,8 @@ void SingleThreadedSchedulerClient::EmptyQueue() {
206206
should_continue = !m_callbacks_pending.empty();
207207
}
208208
}
209+
210+
size_t SingleThreadedSchedulerClient::CallbacksPending() {
211+
LOCK(m_cs_callbacks_pending);
212+
return m_callbacks_pending.size();
213+
}

src/scheduler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ class SingleThreadedSchedulerClient {
108108
// Processes all remaining queue members on the calling thread, blocking until queue is empty
109109
// Must be called after the CScheduler has no remaining processing threads!
110110
void EmptyQueue();
111+
112+
size_t CallbacksPending();
111113
};
112114

113115
#endif

src/test/miner_tests.cpp

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,6 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
216216
entry.nFee = 11;
217217
entry.nHeight = 11;
218218

219-
LOCK(cs_main);
220219
fCheckpointsEnabled = false;
221220

222221
// Simple block creation, nothing special yet:
@@ -229,27 +228,32 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
229228
for (unsigned int i = 0; i < sizeof(blockinfo)/sizeof(*blockinfo); ++i)
230229
{
231230
CBlock *pblock = &pblocktemplate->block; // pointer for convenience
232-
pblock->nVersion = 1;
233-
pblock->nTime = chainActive.Tip()->GetMedianTimePast()+1;
234-
CMutableTransaction txCoinbase(*pblock->vtx[0]);
235-
txCoinbase.nVersion = 1;
236-
txCoinbase.vin[0].scriptSig = CScript();
237-
txCoinbase.vin[0].scriptSig.push_back(blockinfo[i].extranonce);
238-
txCoinbase.vin[0].scriptSig.push_back(chainActive.Height());
239-
txCoinbase.vout.resize(1); // Ignore the (optional) segwit commitment added by CreateNewBlock (as the hardcoded nonces don't account for this)
240-
txCoinbase.vout[0].scriptPubKey = CScript();
241-
pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
242-
if (txFirst.size() == 0)
243-
baseheight = chainActive.Height();
244-
if (txFirst.size() < 4)
245-
txFirst.push_back(pblock->vtx[0]);
246-
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
247-
pblock->nNonce = blockinfo[i].nonce;
231+
{
232+
LOCK(cs_main);
233+
pblock->nVersion = 1;
234+
pblock->nTime = chainActive.Tip()->GetMedianTimePast()+1;
235+
CMutableTransaction txCoinbase(*pblock->vtx[0]);
236+
txCoinbase.nVersion = 1;
237+
txCoinbase.vin[0].scriptSig = CScript();
238+
txCoinbase.vin[0].scriptSig.push_back(blockinfo[i].extranonce);
239+
txCoinbase.vin[0].scriptSig.push_back(chainActive.Height());
240+
txCoinbase.vout.resize(1); // Ignore the (optional) segwit commitment added by CreateNewBlock (as the hardcoded nonces don't account for this)
241+
txCoinbase.vout[0].scriptPubKey = CScript();
242+
pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
243+
if (txFirst.size() == 0)
244+
baseheight = chainActive.Height();
245+
if (txFirst.size() < 4)
246+
txFirst.push_back(pblock->vtx[0]);
247+
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
248+
pblock->nNonce = blockinfo[i].nonce;
249+
}
248250
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
249251
BOOST_CHECK(ProcessNewBlock(chainparams, shared_pblock, true, nullptr));
250252
pblock->hashPrevBlock = pblock->GetHash();
251253
}
252254

255+
LOCK(cs_main);
256+
253257
// Just to make sure we can still make simple blocks
254258
BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));
255259

src/test/test_bitcoin.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha
6969
fs::create_directories(pathTemp);
7070
gArgs.ForceSetArg("-datadir", pathTemp.string());
7171

72-
// Note that because we don't bother running a scheduler thread here,
73-
// callbacks via CValidationInterface are unreliable, but that's OK,
74-
// our unit tests aren't testing multiple parts of the code at once.
72+
// We have to run a scheduler thread to prevent ActivateBestChain
73+
// from blocking due to queue overrun.
74+
threadGroup.create_thread(boost::bind(&CScheduler::serviceQueue, &scheduler));
7575
GetMainSignals().RegisterBackgroundSignalScheduler(scheduler);
7676

7777
mempool.setSanityCheck(1.0);

src/test/txvalidationcache_tests.cpp

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup)
6666

6767
// Test 1: block with both of those transactions should be rejected.
6868
block = CreateAndProcessBlock(spends, scriptPubKey);
69-
LOCK(cs_main);
7069
BOOST_CHECK(chainActive.Tip()->GetBlockHash() != block.GetHash());
7170

7271
// Test 2: ... and should be rejected if spend1 is in the memory pool
@@ -190,12 +189,12 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup)
190189
spend_tx.vin[0].scriptSig << vchSig;
191190
}
192191

193-
LOCK(cs_main);
194-
195192
// Test that invalidity under a set of flags doesn't preclude validity
196193
// under other (eg consensus) flags.
197194
// spend_tx is invalid according to DERSIG
198195
{
196+
LOCK(cs_main);
197+
199198
CValidationState state;
200199
PrecomputedTransactionData ptd_spend_tx(spend_tx);
201200

@@ -213,15 +212,17 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup)
213212
// test later that block validation works fine in the absence of cached
214213
// successes.
215214
ValidateCheckInputsForAllFlags(spend_tx, SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_LOW_S | SCRIPT_VERIFY_STRICTENC, false);
215+
}
216216

217-
// And if we produce a block with this tx, it should be valid (DERSIG not
218-
// enabled yet), even though there's no cache entry.
219-
CBlock block;
217+
// And if we produce a block with this tx, it should be valid (DERSIG not
218+
// enabled yet), even though there's no cache entry.
219+
CBlock block;
220220

221-
block = CreateAndProcessBlock({spend_tx}, p2pk_scriptPubKey);
222-
BOOST_CHECK(chainActive.Tip()->GetBlockHash() == block.GetHash());
223-
BOOST_CHECK(pcoinsTip->GetBestBlock() == block.GetHash());
224-
}
221+
block = CreateAndProcessBlock({spend_tx}, p2pk_scriptPubKey);
222+
BOOST_CHECK(chainActive.Tip()->GetBlockHash() == block.GetHash());
223+
BOOST_CHECK(pcoinsTip->GetBestBlock() == block.GetHash());
224+
225+
LOCK(cs_main);
225226

226227
// Test P2SH: construct a transaction that is valid without P2SH, and
227228
// then test validity with P2SH.

src/validation.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include <validationinterface.h>
4141
#include <warnings.h>
4242

43+
#include <future>
4344
#include <sstream>
4445

4546
#include <boost/algorithm/string/replace.hpp>
@@ -2559,12 +2560,21 @@ bool CChainState::ActivateBestChain(CValidationState &state, const CChainParams&
25592560
// far from a guarantee. Things in the P2P/RPC will often end up calling
25602561
// us in the middle of ProcessNewBlock - do not assume pblock is set
25612562
// sanely for performance or correctness!
2563+
AssertLockNotHeld(cs_main);
25622564

25632565
CBlockIndex *pindexMostWork = nullptr;
25642566
CBlockIndex *pindexNewTip = nullptr;
25652567
int nStopAtHeight = gArgs.GetArg("-stopatheight", DEFAULT_STOPATHEIGHT);
25662568
do {
25672569
boost::this_thread::interruption_point();
2570+
2571+
if (GetMainSignals().CallbacksPending() > 10) {
2572+
// Block until the validation queue drains. This should largely
2573+
// never happen in normal operation, however may happen during
2574+
// reindex, causing memory blowup if we run too far ahead.
2575+
SyncWithValidationInterfaceQueue();
2576+
}
2577+
25682578
if (ShutdownRequested())
25692579
break;
25702580

@@ -3383,6 +3393,8 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CVali
33833393

33843394
bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock> pblock, bool fForceProcessing, bool *fNewBlock)
33853395
{
3396+
AssertLockNotHeld(cs_main);
3397+
33863398
{
33873399
CBlockIndex *pindex = nullptr;
33883400
if (fNewBlock) *fNewBlock = false;

src/validationinterface.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
#include <sync.h>
1212
#include <txmempool.h>
1313
#include <util.h>
14+
#include <validation.h>
1415

1516
#include <list>
1617
#include <atomic>
18+
#include <future>
1719

1820
#include <boost/signals2/signal.hpp>
1921

@@ -54,6 +56,11 @@ void CMainSignals::FlushBackgroundCallbacks() {
5456
}
5557
}
5658

59+
size_t CMainSignals::CallbacksPending() {
60+
if (!m_internals) return 0;
61+
return m_internals->m_schedulerClient.CallbacksPending();
62+
}
63+
5764
void CMainSignals::RegisterWithMempoolSignals(CTxMemPool& pool) {
5865
pool.NotifyEntryRemoved.connect(boost::bind(&CMainSignals::MempoolEntryRemoved, this, _1, _2));
5966
}
@@ -113,6 +120,16 @@ void CallFunctionInValidationInterfaceQueue(std::function<void ()> func) {
113120
g_signals.m_internals->m_schedulerClient.AddToProcessQueue(std::move(func));
114121
}
115122

123+
void SyncWithValidationInterfaceQueue() {
124+
AssertLockNotHeld(cs_main);
125+
// Block until the validation queue drains
126+
std::promise<void> promise;
127+
CallFunctionInValidationInterfaceQueue([&promise] {
128+
promise.set_value();
129+
});
130+
promise.get_future().wait();
131+
}
132+
116133
void CMainSignals::MempoolEntryRemoved(CTransactionRef ptx, MemPoolRemovalReason reason) {
117134
if (reason != MemPoolRemovalReason::BLOCK && reason != MemPoolRemovalReason::CONFLICT) {
118135
m_internals->m_schedulerClient.AddToProcessQueue([ptx, this] {

src/validationinterface.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ void UnregisterAllValidationInterfaces();
4242
* will result in a deadlock (that DEBUG_LOCKORDER will miss).
4343
*/
4444
void CallFunctionInValidationInterfaceQueue(std::function<void ()> func);
45+
/**
46+
* This is a synonym for the following, which asserts certain locks are not
47+
* held:
48+
* std::promise<void> promise;
49+
* CallFunctionInValidationInterfaceQueue([&promise] {
50+
* promise.set_value();
51+
* });
52+
* promise.get_future().wait();
53+
*/
54+
void SyncWithValidationInterfaceQueue();
4555

4656
class CValidationInterface {
4757
protected:
@@ -131,6 +141,8 @@ class CMainSignals {
131141
/** Call any remaining callbacks on the calling thread */
132142
void FlushBackgroundCallbacks();
133143

144+
size_t CallbacksPending();
145+
134146
/** Register with mempool to call TransactionRemovedFromMempool callbacks */
135147
void RegisterWithMempoolSignals(CTxMemPool& pool);
136148
/** Unregister with mempool */

src/wallet/test/wallet_tests.cpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -370,15 +370,15 @@ static void AddKey(CWallet& wallet, const CKey& key)
370370

371371
BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
372372
{
373-
LOCK(cs_main);
374-
375373
// Cap last block file size, and mine new block in a new block file.
376374
CBlockIndex* const nullBlock = nullptr;
377375
CBlockIndex* oldTip = chainActive.Tip();
378376
GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE;
379377
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
380378
CBlockIndex* newTip = chainActive.Tip();
381379

380+
LOCK(cs_main);
381+
382382
// Verify ScanForWalletTransactions picks up transactions in both the old
383383
// and new block files.
384384
{
@@ -447,8 +447,6 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
447447
// than or equal to key birthday.
448448
BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
449449
{
450-
LOCK(cs_main);
451-
452450
// Create two blocks with same timestamp to verify that importwallet rescan
453451
// will pick up both blocks, not just the first.
454452
const int64_t BLOCK_TIME = chainActive.Tip()->GetBlockTimeMax() + 5;
@@ -462,6 +460,8 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
462460
SetMockTime(KEY_TIME);
463461
coinbaseTxns.emplace_back(*CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
464462

463+
LOCK(cs_main);
464+
465465
// Import key into wallet and call dumpwallet to create backup file.
466466
{
467467
CWallet wallet;
@@ -627,10 +627,15 @@ class ListCoinsTestingSetup : public TestChain100Setup
627627
BOOST_CHECK(wallet->CreateTransaction({recipient}, wtx, reservekey, fee, changePos, error, dummy));
628628
CValidationState state;
629629
BOOST_CHECK(wallet->CommitTransaction(wtx, reservekey, nullptr, state));
630+
CMutableTransaction blocktx;
631+
{
632+
LOCK(wallet->cs_wallet);
633+
blocktx = CMutableTransaction(*wallet->mapWallet.at(wtx.GetHash()).tx);
634+
}
635+
CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
630636
LOCK(wallet->cs_wallet);
631637
auto it = wallet->mapWallet.find(wtx.GetHash());
632638
BOOST_CHECK(it != wallet->mapWallet.end());
633-
CreateAndProcessBlock({CMutableTransaction(*it->second.tx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
634639
it->second.SetMerkleBranch(chainActive.Tip(), 1);
635640
return it->second;
636641
}
@@ -641,7 +646,6 @@ class ListCoinsTestingSetup : public TestChain100Setup
641646
BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
642647
{
643648
std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString();
644-
LOCK2(cs_main, wallet->cs_wallet);
645649

646650
// Confirm ListCoins initially returns 1 coin grouped under coinbaseKey
647651
// address.
@@ -669,6 +673,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
669673
BOOST_CHECK_EQUAL(available.size(), 2);
670674
for (const auto& group : list) {
671675
for (const auto& coin : group.second) {
676+
LOCK(wallet->cs_wallet);
672677
wallet->LockCoin(COutPoint(coin.tx->GetHash(), coin.i));
673678
}
674679
}

0 commit comments

Comments
 (0)