10
10
#include < miner.h>
11
11
#include < pow.h>
12
12
#include < random.h>
13
+ #include < script/standard.h>
13
14
#include < test/setup_common.h>
14
15
#include < validation.h>
15
16
#include < validationinterface.h>
@@ -18,6 +19,8 @@ struct RegtestingSetup : public TestingSetup {
18
19
RegtestingSetup () : TestingSetup(CBaseChainParams::REGTEST) {}
19
20
};
20
21
22
+ static const std::vector<unsigned char > V_OP_TRUE{OP_TRUE};
23
+
21
24
BOOST_FIXTURE_TEST_SUITE (validation_block_tests, RegtestingSetup)
22
25
23
26
struct TestSubscriber : public CValidationInterface {
@@ -59,8 +62,21 @@ std::shared_ptr<CBlock> Block(const uint256& prev_hash)
59
62
pblock->hashPrevBlock = prev_hash;
60
63
pblock->nTime = ++time;
61
64
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
62
75
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 ;
64
80
txCoinbase.vin [0 ].scriptWitness .SetNull ();
65
81
pblock->vtx [0 ] = MakeTransactionRef (std::move (txCoinbase));
66
82
@@ -69,6 +85,9 @@ std::shared_ptr<CBlock> Block(const uint256& prev_hash)
69
85
70
86
std::shared_ptr<CBlock> FinalizeBlock (std::shared_ptr<CBlock> pblock)
71
87
{
88
+ LOCK (cs_main); // For LookupBlockIndex
89
+ GenerateCoinbaseCommitment (*pblock, LookupBlockIndex (pblock->hashPrevBlock ), Params ().GetConsensus ());
90
+
72
91
pblock->hashMerkleRoot = BlockMerkleRoot (*pblock);
73
92
74
93
while (!CheckProofOfWork (pblock->GetHash (), pblock->nBits , Params ().GetConsensus ())) {
@@ -79,13 +98,13 @@ std::shared_ptr<CBlock> FinalizeBlock(std::shared_ptr<CBlock> pblock)
79
98
}
80
99
81
100
// 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)
83
102
{
84
103
return FinalizeBlock (Block (prev_hash));
85
104
}
86
105
87
106
// 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)
89
108
{
90
109
auto pblock = Block (prev_hash);
91
110
@@ -185,4 +204,131 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
185
204
BOOST_CHECK_EQUAL (sub.m_expected_tip , ::ChainActive ().Tip ()->GetBlockHash ());
186
205
}
187
206
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
+ }
188
334
BOOST_AUTO_TEST_SUITE_END ()
0 commit comments