[test] Add test to check mempool consistency in case of reorgs
This commit is contained in:
parent
fabeb1f613
commit
fa2b083c3f
1 changed files with 149 additions and 3 deletions
|
@ -10,6 +10,7 @@
|
||||||
#include <miner.h>
|
#include <miner.h>
|
||||||
#include <pow.h>
|
#include <pow.h>
|
||||||
#include <random.h>
|
#include <random.h>
|
||||||
|
#include <script/standard.h>
|
||||||
#include <test/setup_common.h>
|
#include <test/setup_common.h>
|
||||||
#include <validation.h>
|
#include <validation.h>
|
||||||
#include <validationinterface.h>
|
#include <validationinterface.h>
|
||||||
|
@ -18,6 +19,8 @@ struct RegtestingSetup : public TestingSetup {
|
||||||
RegtestingSetup() : TestingSetup(CBaseChainParams::REGTEST) {}
|
RegtestingSetup() : TestingSetup(CBaseChainParams::REGTEST) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const std::vector<unsigned char> V_OP_TRUE{OP_TRUE};
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_SUITE(validation_block_tests, RegtestingSetup)
|
BOOST_FIXTURE_TEST_SUITE(validation_block_tests, RegtestingSetup)
|
||||||
|
|
||||||
struct TestSubscriber : public CValidationInterface {
|
struct TestSubscriber : public CValidationInterface {
|
||||||
|
@ -59,8 +62,21 @@ std::shared_ptr<CBlock> Block(const uint256& prev_hash)
|
||||||
pblock->hashPrevBlock = prev_hash;
|
pblock->hashPrevBlock = prev_hash;
|
||||||
pblock->nTime = ++time;
|
pblock->nTime = ++time;
|
||||||
|
|
||||||
|
pubKey.clear();
|
||||||
|
{
|
||||||
|
WitnessV0ScriptHash witness_program;
|
||||||
|
CSHA256().Write(&V_OP_TRUE[0], V_OP_TRUE.size()).Finalize(witness_program.begin());
|
||||||
|
pubKey << OP_0 << ToByteVector(witness_program);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the coinbase transaction with two outputs:
|
||||||
|
// One zero-value one that has a unique pubkey to make sure that blocks at the same height can have a different hash
|
||||||
|
// Another one that has the coinbase reward in a P2WSH with OP_TRUE as witness program to make it easy to spend
|
||||||
CMutableTransaction txCoinbase(*pblock->vtx[0]);
|
CMutableTransaction txCoinbase(*pblock->vtx[0]);
|
||||||
txCoinbase.vout.resize(1);
|
txCoinbase.vout.resize(2);
|
||||||
|
txCoinbase.vout[1].scriptPubKey = pubKey;
|
||||||
|
txCoinbase.vout[1].nValue = txCoinbase.vout[0].nValue;
|
||||||
|
txCoinbase.vout[0].nValue = 0;
|
||||||
txCoinbase.vin[0].scriptWitness.SetNull();
|
txCoinbase.vin[0].scriptWitness.SetNull();
|
||||||
pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
|
pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
|
||||||
|
|
||||||
|
@ -69,6 +85,9 @@ std::shared_ptr<CBlock> Block(const uint256& prev_hash)
|
||||||
|
|
||||||
std::shared_ptr<CBlock> FinalizeBlock(std::shared_ptr<CBlock> pblock)
|
std::shared_ptr<CBlock> FinalizeBlock(std::shared_ptr<CBlock> pblock)
|
||||||
{
|
{
|
||||||
|
LOCK(cs_main); // For LookupBlockIndex
|
||||||
|
GenerateCoinbaseCommitment(*pblock, LookupBlockIndex(pblock->hashPrevBlock), Params().GetConsensus());
|
||||||
|
|
||||||
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
|
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
|
||||||
|
|
||||||
while (!CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) {
|
while (!CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) {
|
||||||
|
@ -79,13 +98,13 @@ std::shared_ptr<CBlock> FinalizeBlock(std::shared_ptr<CBlock> pblock)
|
||||||
}
|
}
|
||||||
|
|
||||||
// construct a valid block
|
// construct a valid block
|
||||||
const std::shared_ptr<const CBlock> GoodBlock(const uint256& prev_hash)
|
std::shared_ptr<const CBlock> GoodBlock(const uint256& prev_hash)
|
||||||
{
|
{
|
||||||
return FinalizeBlock(Block(prev_hash));
|
return FinalizeBlock(Block(prev_hash));
|
||||||
}
|
}
|
||||||
|
|
||||||
// construct an invalid block (but with a valid header)
|
// construct an invalid block (but with a valid header)
|
||||||
const std::shared_ptr<const CBlock> BadBlock(const uint256& prev_hash)
|
std::shared_ptr<const CBlock> BadBlock(const uint256& prev_hash)
|
||||||
{
|
{
|
||||||
auto pblock = Block(prev_hash);
|
auto pblock = Block(prev_hash);
|
||||||
|
|
||||||
|
@ -185,4 +204,131 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
|
||||||
BOOST_CHECK_EQUAL(sub.m_expected_tip, ::ChainActive().Tip()->GetBlockHash());
|
BOOST_CHECK_EQUAL(sub.m_expected_tip, ::ChainActive().Tip()->GetBlockHash());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that mempool updates happen atomically with reorgs.
|
||||||
|
*
|
||||||
|
* This prevents RPC clients, among others, from retrieving immediately-out-of-date mempool data
|
||||||
|
* during large reorgs.
|
||||||
|
*
|
||||||
|
* The test verifies this by creating a chain of `num_txs` blocks, matures their coinbases, and then
|
||||||
|
* submits txns spending from their coinbase to the mempool. A fork chain is then processed,
|
||||||
|
* invalidating the txns and evicting them from the mempool.
|
||||||
|
*
|
||||||
|
* We verify that the mempool updates atomically by polling it continuously
|
||||||
|
* from another thread during the reorg and checking that its size only changes
|
||||||
|
* once. The size changing exactly once indicates that the polling thread's
|
||||||
|
* view of the mempool is either consistent with the chain state before reorg,
|
||||||
|
* or consistent with the chain state after the reorg, and not just consistent
|
||||||
|
* with some intermediate state during the reorg.
|
||||||
|
*/
|
||||||
|
BOOST_AUTO_TEST_CASE(mempool_locks_reorg)
|
||||||
|
{
|
||||||
|
bool ignored;
|
||||||
|
auto ProcessBlock = [&ignored](std::shared_ptr<const CBlock> block) -> bool {
|
||||||
|
return ProcessNewBlock(Params(), block, /* fForceProcessing */ true, /* fNewBlock */ &ignored);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Process all mined blocks
|
||||||
|
BOOST_REQUIRE(ProcessBlock(std::make_shared<CBlock>(Params().GenesisBlock())));
|
||||||
|
auto last_mined = GoodBlock(Params().GenesisBlock().GetHash());
|
||||||
|
BOOST_REQUIRE(ProcessBlock(last_mined));
|
||||||
|
|
||||||
|
// Run the test multiple times
|
||||||
|
for (int test_runs = 3; test_runs > 0; --test_runs) {
|
||||||
|
BOOST_CHECK_EQUAL(last_mined->GetHash(), ::ChainActive().Tip()->GetBlockHash());
|
||||||
|
|
||||||
|
// Later on split from here
|
||||||
|
const uint256 split_hash{last_mined->hashPrevBlock};
|
||||||
|
|
||||||
|
// Create a bunch of transactions to spend the miner rewards of the
|
||||||
|
// most recent blocks
|
||||||
|
std::vector<CTransactionRef> txs;
|
||||||
|
for (int num_txs = 22; num_txs > 0; --num_txs) {
|
||||||
|
CMutableTransaction mtx;
|
||||||
|
mtx.vin.push_back(CTxIn{COutPoint{last_mined->vtx[0]->GetHash(), 1}, CScript{}});
|
||||||
|
mtx.vin[0].scriptWitness.stack.push_back(V_OP_TRUE);
|
||||||
|
mtx.vout.push_back(last_mined->vtx[0]->vout[1]);
|
||||||
|
mtx.vout[0].nValue -= 1000;
|
||||||
|
txs.push_back(MakeTransactionRef(mtx));
|
||||||
|
|
||||||
|
last_mined = GoodBlock(last_mined->GetHash());
|
||||||
|
BOOST_REQUIRE(ProcessBlock(last_mined));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mature the inputs of the txs
|
||||||
|
for (int j = COINBASE_MATURITY; j > 0; --j) {
|
||||||
|
last_mined = GoodBlock(last_mined->GetHash());
|
||||||
|
BOOST_REQUIRE(ProcessBlock(last_mined));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mine a reorg (and hold it back) before adding the txs to the mempool
|
||||||
|
const uint256 tip_init{last_mined->GetHash()};
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<const CBlock>> reorg;
|
||||||
|
last_mined = GoodBlock(split_hash);
|
||||||
|
reorg.push_back(last_mined);
|
||||||
|
for (size_t j = COINBASE_MATURITY + txs.size() + 1; j > 0; --j) {
|
||||||
|
last_mined = GoodBlock(last_mined->GetHash());
|
||||||
|
reorg.push_back(last_mined);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the txs to the tx pool
|
||||||
|
{
|
||||||
|
LOCK(cs_main);
|
||||||
|
CValidationState state;
|
||||||
|
std::list<CTransactionRef> plTxnReplaced;
|
||||||
|
for (const auto& tx : txs) {
|
||||||
|
BOOST_REQUIRE(AcceptToMemoryPool(
|
||||||
|
::mempool,
|
||||||
|
state,
|
||||||
|
tx,
|
||||||
|
/* pfMissingInputs */ &ignored,
|
||||||
|
&plTxnReplaced,
|
||||||
|
/* bypass_limits */ false,
|
||||||
|
/* nAbsurdFee */ 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that all txs are in the pool
|
||||||
|
{
|
||||||
|
LOCK(::mempool.cs);
|
||||||
|
BOOST_CHECK_EQUAL(::mempool.mapTx.size(), txs.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run a thread that simulates an RPC caller that is polling while
|
||||||
|
// validation is doing a reorg
|
||||||
|
std::thread rpc_thread{[&]() {
|
||||||
|
// This thread is checking that the mempool either contains all of
|
||||||
|
// the transactions invalidated by the reorg, or none of them, and
|
||||||
|
// not some intermediate amount.
|
||||||
|
while (true) {
|
||||||
|
LOCK(::mempool.cs);
|
||||||
|
if (::mempool.mapTx.size() == 0) {
|
||||||
|
// We are done with the reorg
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Internally, we might be in the middle of the reorg, but
|
||||||
|
// externally the reorg to the most-proof-of-work chain should
|
||||||
|
// be atomic. So the caller assumes that the returned mempool
|
||||||
|
// is consistent. That is, it has all txs that were there
|
||||||
|
// before the reorg.
|
||||||
|
assert(::mempool.mapTx.size() == txs.size());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LOCK(cs_main);
|
||||||
|
// We are done with the reorg, so the tip must have changed
|
||||||
|
assert(tip_init != ::ChainActive().Tip()->GetBlockHash());
|
||||||
|
}};
|
||||||
|
|
||||||
|
// Submit the reorg in this thread to invalidate and remove the txs from the tx pool
|
||||||
|
for (const auto& b : reorg) {
|
||||||
|
ProcessBlock(b);
|
||||||
|
}
|
||||||
|
// Check that the reorg was eventually successful
|
||||||
|
BOOST_CHECK_EQUAL(last_mined->GetHash(), ::ChainActive().Tip()->GetBlockHash());
|
||||||
|
|
||||||
|
// We can join the other thread, which returns when the reorg was successful
|
||||||
|
rpc_thread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
Loading…
Reference in a new issue