Skip to content

Commit

Permalink
Merge #11824: Block ActivateBestChain to empty validationinterface queue
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sipa committed Dec 29, 2017
2 parents 5180a86 + 97d2b09 commit d9fdac1
Show file tree
Hide file tree
Showing 11 changed files with 292 additions and 213 deletions.
368 changes: 197 additions & 171 deletions src/net_processing.cpp

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions src/scheduler.cpp
Expand Up @@ -206,3 +206,8 @@ void SingleThreadedSchedulerClient::EmptyQueue() {
should_continue = !m_callbacks_pending.empty();
}
}

size_t SingleThreadedSchedulerClient::CallbacksPending() {
LOCK(m_cs_callbacks_pending);
return m_callbacks_pending.size();
}
2 changes: 2 additions & 0 deletions src/scheduler.h
Expand Up @@ -108,6 +108,8 @@ class SingleThreadedSchedulerClient {
// Processes all remaining queue members on the calling thread, blocking until queue is empty
// Must be called after the CScheduler has no remaining processing threads!
void EmptyQueue();

size_t CallbacksPending();
};

#endif
38 changes: 21 additions & 17 deletions src/test/miner_tests.cpp
Expand Up @@ -216,7 +216,6 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
entry.nFee = 11;
entry.nHeight = 11;

LOCK(cs_main);
fCheckpointsEnabled = false;

// Simple block creation, nothing special yet:
Expand All @@ -229,27 +228,32 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
for (unsigned int i = 0; i < sizeof(blockinfo)/sizeof(*blockinfo); ++i)
{
CBlock *pblock = &pblocktemplate->block; // pointer for convenience
pblock->nVersion = 1;
pblock->nTime = chainActive.Tip()->GetMedianTimePast()+1;
CMutableTransaction txCoinbase(*pblock->vtx[0]);
txCoinbase.nVersion = 1;
txCoinbase.vin[0].scriptSig = CScript();
txCoinbase.vin[0].scriptSig.push_back(blockinfo[i].extranonce);
txCoinbase.vin[0].scriptSig.push_back(chainActive.Height());
txCoinbase.vout.resize(1); // Ignore the (optional) segwit commitment added by CreateNewBlock (as the hardcoded nonces don't account for this)
txCoinbase.vout[0].scriptPubKey = CScript();
pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
if (txFirst.size() == 0)
baseheight = chainActive.Height();
if (txFirst.size() < 4)
txFirst.push_back(pblock->vtx[0]);
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
pblock->nNonce = blockinfo[i].nonce;
{
LOCK(cs_main);
pblock->nVersion = 1;
pblock->nTime = chainActive.Tip()->GetMedianTimePast()+1;
CMutableTransaction txCoinbase(*pblock->vtx[0]);
txCoinbase.nVersion = 1;
txCoinbase.vin[0].scriptSig = CScript();
txCoinbase.vin[0].scriptSig.push_back(blockinfo[i].extranonce);
txCoinbase.vin[0].scriptSig.push_back(chainActive.Height());
txCoinbase.vout.resize(1); // Ignore the (optional) segwit commitment added by CreateNewBlock (as the hardcoded nonces don't account for this)
txCoinbase.vout[0].scriptPubKey = CScript();
pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
if (txFirst.size() == 0)
baseheight = chainActive.Height();
if (txFirst.size() < 4)
txFirst.push_back(pblock->vtx[0]);
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
pblock->nNonce = blockinfo[i].nonce;
}
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
BOOST_CHECK(ProcessNewBlock(chainparams, shared_pblock, true, nullptr));
pblock->hashPrevBlock = pblock->GetHash();
}

LOCK(cs_main);

// Just to make sure we can still make simple blocks
BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));

Expand Down
6 changes: 3 additions & 3 deletions src/test/test_bitcoin.cpp
Expand Up @@ -69,9 +69,9 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha
fs::create_directories(pathTemp);
gArgs.ForceSetArg("-datadir", pathTemp.string());

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

mempool.setSanityCheck(1.0);
Expand Down
21 changes: 11 additions & 10 deletions src/test/txvalidationcache_tests.cpp
Expand Up @@ -66,7 +66,6 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup)

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

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

LOCK(cs_main);

// Test that invalidity under a set of flags doesn't preclude validity
// under other (eg consensus) flags.
// spend_tx is invalid according to DERSIG
{
LOCK(cs_main);

CValidationState state;
PrecomputedTransactionData ptd_spend_tx(spend_tx);

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

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

block = CreateAndProcessBlock({spend_tx}, p2pk_scriptPubKey);
BOOST_CHECK(chainActive.Tip()->GetBlockHash() == block.GetHash());
BOOST_CHECK(pcoinsTip->GetBestBlock() == block.GetHash());
}
block = CreateAndProcessBlock({spend_tx}, p2pk_scriptPubKey);
BOOST_CHECK(chainActive.Tip()->GetBlockHash() == block.GetHash());
BOOST_CHECK(pcoinsTip->GetBestBlock() == block.GetHash());

LOCK(cs_main);

// Test P2SH: construct a transaction that is valid without P2SH, and
// then test validity with P2SH.
Expand Down
12 changes: 12 additions & 0 deletions src/validation.cpp
Expand Up @@ -40,6 +40,7 @@
#include <validationinterface.h>
#include <warnings.h>

#include <future>
#include <sstream>

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

CBlockIndex *pindexMostWork = nullptr;
CBlockIndex *pindexNewTip = nullptr;
int nStopAtHeight = gArgs.GetArg("-stopatheight", DEFAULT_STOPATHEIGHT);
do {
boost::this_thread::interruption_point();

if (GetMainSignals().CallbacksPending() > 10) {
// Block until the validation queue drains. This should largely
// never happen in normal operation, however may happen during
// reindex, causing memory blowup if we run too far ahead.
SyncWithValidationInterfaceQueue();
}

if (ShutdownRequested())
break;

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

bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock> pblock, bool fForceProcessing, bool *fNewBlock)
{
AssertLockNotHeld(cs_main);

{
CBlockIndex *pindex = nullptr;
if (fNewBlock) *fNewBlock = false;
Expand Down
17 changes: 17 additions & 0 deletions src/validationinterface.cpp
Expand Up @@ -11,9 +11,11 @@
#include <sync.h>
#include <txmempool.h>
#include <util.h>
#include <validation.h>

#include <list>
#include <atomic>
#include <future>

#include <boost/signals2/signal.hpp>

Expand Down Expand Up @@ -54,6 +56,11 @@ void CMainSignals::FlushBackgroundCallbacks() {
}
}

size_t CMainSignals::CallbacksPending() {
if (!m_internals) return 0;
return m_internals->m_schedulerClient.CallbacksPending();
}

void CMainSignals::RegisterWithMempoolSignals(CTxMemPool& pool) {
pool.NotifyEntryRemoved.connect(boost::bind(&CMainSignals::MempoolEntryRemoved, this, _1, _2));
}
Expand Down Expand Up @@ -113,6 +120,16 @@ void CallFunctionInValidationInterfaceQueue(std::function<void ()> func) {
g_signals.m_internals->m_schedulerClient.AddToProcessQueue(std::move(func));
}

void SyncWithValidationInterfaceQueue() {
AssertLockNotHeld(cs_main);
// Block until the validation queue drains
std::promise<void> promise;
CallFunctionInValidationInterfaceQueue([&promise] {
promise.set_value();
});
promise.get_future().wait();
}

void CMainSignals::MempoolEntryRemoved(CTransactionRef ptx, MemPoolRemovalReason reason) {
if (reason != MemPoolRemovalReason::BLOCK && reason != MemPoolRemovalReason::CONFLICT) {
m_internals->m_schedulerClient.AddToProcessQueue([ptx, this] {
Expand Down
12 changes: 12 additions & 0 deletions src/validationinterface.h
Expand Up @@ -42,6 +42,16 @@ void UnregisterAllValidationInterfaces();
* will result in a deadlock (that DEBUG_LOCKORDER will miss).
*/
void CallFunctionInValidationInterfaceQueue(std::function<void ()> func);
/**
* This is a synonym for the following, which asserts certain locks are not
* held:
* std::promise<void> promise;
* CallFunctionInValidationInterfaceQueue([&promise] {
* promise.set_value();
* });
* promise.get_future().wait();
*/
void SyncWithValidationInterfaceQueue();

class CValidationInterface {
protected:
Expand Down Expand Up @@ -131,6 +141,8 @@ class CMainSignals {
/** Call any remaining callbacks on the calling thread */
void FlushBackgroundCallbacks();

size_t CallbacksPending();

/** Register with mempool to call TransactionRemovedFromMempool callbacks */
void RegisterWithMempoolSignals(CTxMemPool& pool);
/** Unregister with mempool */
Expand Down
17 changes: 11 additions & 6 deletions src/wallet/test/wallet_tests.cpp
Expand Up @@ -370,15 +370,15 @@ static void AddKey(CWallet& wallet, const CKey& key)

BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
{
LOCK(cs_main);

// Cap last block file size, and mine new block in a new block file.
CBlockIndex* const nullBlock = nullptr;
CBlockIndex* oldTip = chainActive.Tip();
GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE;
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
CBlockIndex* newTip = chainActive.Tip();

LOCK(cs_main);

// Verify ScanForWalletTransactions picks up transactions in both the old
// and new block files.
{
Expand Down Expand Up @@ -447,8 +447,6 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
// than or equal to key birthday.
BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
{
LOCK(cs_main);

// Create two blocks with same timestamp to verify that importwallet rescan
// will pick up both blocks, not just the first.
const int64_t BLOCK_TIME = chainActive.Tip()->GetBlockTimeMax() + 5;
Expand All @@ -462,6 +460,8 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
SetMockTime(KEY_TIME);
coinbaseTxns.emplace_back(*CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);

LOCK(cs_main);

// Import key into wallet and call dumpwallet to create backup file.
{
CWallet wallet;
Expand Down Expand Up @@ -627,10 +627,15 @@ class ListCoinsTestingSetup : public TestChain100Setup
BOOST_CHECK(wallet->CreateTransaction({recipient}, wtx, reservekey, fee, changePos, error, dummy));
CValidationState state;
BOOST_CHECK(wallet->CommitTransaction(wtx, reservekey, nullptr, state));
CMutableTransaction blocktx;
{
LOCK(wallet->cs_wallet);
blocktx = CMutableTransaction(*wallet->mapWallet.at(wtx.GetHash()).tx);
}
CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
LOCK(wallet->cs_wallet);
auto it = wallet->mapWallet.find(wtx.GetHash());
BOOST_CHECK(it != wallet->mapWallet.end());
CreateAndProcessBlock({CMutableTransaction(*it->second.tx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
it->second.SetMerkleBranch(chainActive.Tip(), 1);
return it->second;
}
Expand All @@ -641,7 +646,6 @@ class ListCoinsTestingSetup : public TestChain100Setup
BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
{
std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString();
LOCK2(cs_main, wallet->cs_wallet);

// Confirm ListCoins initially returns 1 coin grouped under coinbaseKey
// address.
Expand Down Expand Up @@ -669,6 +673,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
BOOST_CHECK_EQUAL(available.size(), 2);
for (const auto& group : list) {
for (const auto& coin : group.second) {
LOCK(wallet->cs_wallet);
wallet->LockCoin(COutPoint(coin.tx->GetHash(), coin.i));
}
}
Expand Down
7 changes: 1 addition & 6 deletions src/wallet/wallet.cpp
Expand Up @@ -1292,12 +1292,7 @@ void CWallet::BlockUntilSyncedToCurrentChain() {
// ...otherwise put a callback in the validation interface queue and wait
// for the queue to drain enough to execute it (indicating we are caught up
// at least with the time we entered this function).

std::promise<void> promise;
CallFunctionInValidationInterfaceQueue([&promise] {
promise.set_value();
});
promise.get_future().wait();
SyncWithValidationInterfaceQueue();
}


Expand Down

0 comments on commit d9fdac1

Please sign in to comment.