Skip to content

Commit fa2b083

Browse files
author
MarcoFalke
committed
[test] Add test to check mempool consistency in case of reorgs
1 parent fabeb1f commit fa2b083

File tree

1 file changed

+149
-3
lines changed

1 file changed

+149
-3
lines changed

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 <validation.h>
1516
#include <validationinterface.h>
@@ -18,6 +19,8 @@ struct RegtestingSetup : public TestingSetup {
1819
RegtestingSetup() : TestingSetup(CBaseChainParams::REGTEST) {}
1920
};
2021

22+
static const std::vector<unsigned char> V_OP_TRUE{OP_TRUE};
23+
2124
BOOST_FIXTURE_TEST_SUITE(validation_block_tests, RegtestingSetup)
2225

2326
struct TestSubscriber : public CValidationInterface {
@@ -59,8 +62,21 @@ std::shared_ptr<CBlock> Block(const uint256& prev_hash)
5962
pblock->hashPrevBlock = prev_hash;
6063
pblock->nTime = ++time;
6164

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

@@ -69,6 +85,9 @@ std::shared_ptr<CBlock> Block(const uint256& prev_hash)
6985

7086
std::shared_ptr<CBlock> FinalizeBlock(std::shared_ptr<CBlock> pblock)
7187
{
88+
LOCK(cs_main); // For LookupBlockIndex
89+
GenerateCoinbaseCommitment(*pblock, LookupBlockIndex(pblock->hashPrevBlock), Params().GetConsensus());
90+
7291
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
7392

7493
while (!CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) {
@@ -79,13 +98,13 @@ std::shared_ptr<CBlock> FinalizeBlock(std::shared_ptr<CBlock> pblock)
7998
}
8099

81100
// construct a valid block
82-
const std::shared_ptr<const CBlock> GoodBlock(const uint256& prev_hash)
101+
std::shared_ptr<const CBlock> GoodBlock(const uint256& prev_hash)
83102
{
84103
return FinalizeBlock(Block(prev_hash));
85104
}
86105

87106
// construct an invalid block (but with a valid header)
88-
const std::shared_ptr<const CBlock> BadBlock(const uint256& prev_hash)
107+
std::shared_ptr<const CBlock> BadBlock(const uint256& prev_hash)
89108
{
90109
auto pblock = Block(prev_hash);
91110

@@ -185,4 +204,131 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
185204
BOOST_CHECK_EQUAL(sub.m_expected_tip, ::ChainActive().Tip()->GetBlockHash());
186205
}
187206

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

0 commit comments

Comments
 (0)