LCOV - code coverage report
Current view: top level - src/test - validation_block_tests.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 180 181 99.4 %
Date: 2026-06-25 07:23:43 Functions: 42 42 100.0 %

          Line data    Source code
       1             : // Copyright (c) 2018-2021 The Bitcoin Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <boost/test/unit_test.hpp>
       6             : 
       7             : #include <chainparams.h>
       8             : #include <consensus/consensus.h>
       9             : #include <consensus/merkle.h>
      10             : #include <consensus/validation.h>
      11             : #include <node/miner.h>
      12             : #include <pow.h>
      13             : #include <random.h>
      14             : #include <script/standard.h>
      15             : #include <test/util/random.h>
      16             : #include <test/util/script.h>
      17             : #include <test/util/setup_common.h>
      18             : #include <util/time.h>
      19             : #include <validation.h>
      20             : #include <validationinterface.h>
      21             : 
      22             : #include <thread>
      23             : 
      24             : using node::BlockAssembler;
      25             : 
      26             : namespace validation_block_tests {
      27           3 : struct MinerTestingSetup : public RegTestingSetup {
      28             :     std::shared_ptr<CBlock> Block(const uint256& prev_hash);
      29             :     std::shared_ptr<const CBlock> GoodBlock(const uint256& prev_hash);
      30             :     std::shared_ptr<const CBlock> BadBlock(const uint256& prev_hash);
      31             :     std::shared_ptr<CBlock> FinalizeBlock(std::shared_ptr<CBlock> pblock);
      32             :     void BuildChain(const uint256& root, int height, const unsigned int invalid_rate, const unsigned int branch_rate, const unsigned int max_size, std::vector<std::shared_ptr<const CBlock>>& blocks);
      33             : };
      34             : } // namespace validation_block_tests
      35             : 
      36         146 : BOOST_FIXTURE_TEST_SUITE(validation_block_tests, MinerTestingSetup)
      37             : 
      38             : struct TestSubscriber final : public CValidationInterface {
      39             :     uint256 m_expected_tip;
      40             : 
      41           2 :     explicit TestSubscriber(uint256 tip) : m_expected_tip(tip) {}
      42             : 
      43          38 :     void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) override
      44             :     {
      45          38 :         BOOST_CHECK_EQUAL(m_expected_tip, pindexNew->GetBlockHash());
      46          38 :     }
      47             : 
      48          40 :     void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) override
      49             :     {
      50          40 :         BOOST_CHECK_EQUAL(m_expected_tip, block->hashPrevBlock);
      51          40 :         BOOST_CHECK_EQUAL(m_expected_tip, pindex->pprev->GetBlockHash());
      52             : 
      53          40 :         m_expected_tip = block->GetHash();
      54          40 :     }
      55             : 
      56           2 :     void BlockDisconnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) override
      57             :     {
      58           2 :         BOOST_CHECK_EQUAL(m_expected_tip, block->GetHash());
      59           2 :         BOOST_CHECK_EQUAL(m_expected_tip, pindex->GetBlockHash());
      60             : 
      61           2 :         m_expected_tip = block->hashPrevBlock;
      62           2 :     }
      63             : };
      64             : 
      65         804 : std::shared_ptr<CBlock> MinerTestingSetup::Block(const uint256& prev_hash)
      66             : {
      67             :     static int i = 0;
      68         804 :     static uint64_t time = Params().GenesisBlock().nTime;
      69             : 
      70         804 :     auto ptemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(CScript{} << i++ << OP_TRUE);
      71         804 :     auto pblock = std::make_shared<CBlock>(ptemplate->block);
      72         804 :     pblock->hashPrevBlock = prev_hash;
      73         804 :     pblock->nTime = ++time;
      74             : 
      75             :     // Make the coinbase transaction with two outputs:
      76             :     // One zero-value one that has a unique pubkey to make sure that blocks at
      77             :     // the same height can have a different hash. Another one that has the
      78             :     // coinbase reward in a P2SH with OP_TRUE as scriptPubKey to make it easy to
      79             :     // spend
      80         804 :     CMutableTransaction txCoinbase(*pblock->vtx[0]);
      81         804 :     txCoinbase.vout.resize(2);
      82         804 :     txCoinbase.vout[1].scriptPubKey = P2SH_OP_TRUE;
      83         804 :     txCoinbase.vout[1].nValue = txCoinbase.vout[0].nValue;
      84         804 :     txCoinbase.vout[0].nValue = 0;
      85             :     // Always pad with OP_0 at the end to avoid bad-cb-length error
      86        1608 :     txCoinbase.vin[0].scriptSig = CScript{} << WITH_LOCK(::cs_main, return m_node.chainman->m_blockman.LookupBlockIndex(prev_hash)->nHeight + 1) << OP_0;
      87         804 :     pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
      88             : 
      89         804 :     return pblock;
      90         804 : }
      91             : 
      92         804 : std::shared_ptr<CBlock> MinerTestingSetup::FinalizeBlock(std::shared_ptr<CBlock> pblock)
      93             : {
      94         804 :     pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
      95             : 
      96        1659 :     while (!CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) {
      97         855 :         ++(pblock->nNonce);
      98             :     }
      99             : 
     100             :     // submit block header, so that miner can get the block height from the
     101             :     // global state and the node has the topology of the chain
     102         804 :     BlockValidationState ignored;
     103         804 :     BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlockHeaders({pblock->GetBlockHeader()}, ignored));
     104             : 
     105         804 :     return pblock;
     106         804 : }
     107             : 
     108             : // construct a valid block
     109         797 : std::shared_ptr<const CBlock> MinerTestingSetup::GoodBlock(const uint256& prev_hash)
     110             : {
     111         797 :     return FinalizeBlock(Block(prev_hash));
     112           0 : }
     113             : 
     114             : // construct an invalid block (but with a valid header)
     115           7 : std::shared_ptr<const CBlock> MinerTestingSetup::BadBlock(const uint256& prev_hash)
     116             : {
     117           7 :     auto pblock = Block(prev_hash);
     118             : 
     119           7 :     CMutableTransaction coinbase_spend;
     120           7 :     coinbase_spend.vin.emplace_back(COutPoint(pblock->vtx[0]->GetHash(), 0), CScript(), 0);
     121           7 :     coinbase_spend.vout.push_back(pblock->vtx[0]->vout[0]);
     122             : 
     123           7 :     CTransactionRef tx = MakeTransactionRef(coinbase_spend);
     124           7 :     pblock->vtx.push_back(tx);
     125             : 
     126           7 :     auto ret = FinalizeBlock(pblock);
     127           7 :     return ret;
     128           7 : }
     129             : 
     130          58 : void MinerTestingSetup::BuildChain(const uint256& root, int height, const unsigned int invalid_rate, const unsigned int branch_rate, const unsigned int max_size, std::vector<std::shared_ptr<const CBlock>>& blocks)
     131             : {
     132          58 :     if (height <= 0 || blocks.size() >= max_size) return;
     133             : 
     134          58 :     bool gen_invalid = InsecureRandRange(100) < invalid_rate;
     135          58 :     bool gen_fork = InsecureRandRange(100) < branch_rate;
     136             : 
     137          58 :     const std::shared_ptr<const CBlock> pblock = gen_invalid ? BadBlock(root) : GoodBlock(root);
     138          58 :     blocks.push_back(pblock);
     139          58 :     if (!gen_invalid) {
     140          51 :         BuildChain(pblock->GetHash(), height - 1, invalid_rate, branch_rate, max_size, blocks);
     141          51 :     }
     142             : 
     143          58 :     if (gen_fork) {
     144           6 :         blocks.push_back(GoodBlock(root));
     145           6 :         BuildChain(blocks.back()->GetHash(), height - 1, invalid_rate, branch_rate, max_size, blocks);
     146           6 :     }
     147          58 : }
     148             : 
     149         148 : BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
     150             : {
     151             :     // build a large-ish chain that's likely to have some forks
     152           1 :     std::vector<std::shared_ptr<const CBlock>> blocks;
     153           2 :     while (blocks.size() < 50) {
     154           1 :         blocks.clear();
     155           1 :         BuildChain(Params().GenesisBlock().GetHash(), 100, 15, 10, 500, blocks);
     156             :     }
     157             : 
     158             :     bool ignored;
     159             :     // Connect the genesis block and drain any outstanding events
     160           1 :     BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(std::make_shared<CBlock>(Params().GenesisBlock()), true, &ignored));
     161           1 :     SyncWithValidationInterfaceQueue();
     162             : 
     163             :     // subscribe to events (this subscriber will validate event ordering)
     164           1 :     const CBlockIndex* initial_tip = nullptr;
     165             :     {
     166           1 :         LOCK(cs_main);
     167           1 :         initial_tip = m_node.chainman->ActiveChain().Tip();
     168           1 :     }
     169           1 :     auto sub = std::make_shared<TestSubscriber>(initial_tip->GetBlockHash());
     170           1 :     RegisterSharedValidationInterface(sub);
     171             : 
     172             :     // create a bunch of threads that repeatedly process a block generated above at random
     173             :     // this will create parallelism and randomness inside validation - the ValidationInterface
     174             :     // will subscribe to events generated during block validation and assert on ordering invariance
     175           1 :     std::vector<std::thread> threads;
     176          11 :     for (int i = 0; i < 10; i++) {
     177          20 :         threads.emplace_back([&]() {
     178             :             bool ignored;
     179          10 :             FastRandomContext insecure;
     180       10010 :             for (int i = 0; i < 1000; i++) {
     181       10000 :                 const auto& block = blocks[insecure.randrange(blocks.size() - 1)];
     182       10000 :                 Assert(m_node.chainman)->ProcessNewBlock(block, true, &ignored);
     183       10000 :             }
     184             : 
     185             :             // to make sure that eventually we process the full chain - do it here
     186         650 :             for (const auto& block : blocks) {
     187         640 :                 if (block->vtx.size() == 1) {
     188         570 :                     bool processed = Assert(m_node.chainman)->ProcessNewBlock(block, true, &ignored);
     189         570 :                     assert(processed);
     190         570 :                 }
     191             :             }
     192          10 :         });
     193          10 :     }
     194             : 
     195          11 :     for (auto& t : threads) {
     196          10 :         t.join();
     197             :     }
     198           1 :     SyncWithValidationInterfaceQueue();
     199             : 
     200           1 :     UnregisterSharedValidationInterface(sub);
     201             : 
     202           1 :     LOCK(cs_main);
     203           1 :     BOOST_CHECK_EQUAL(sub->m_expected_tip, m_node.chainman->ActiveChain().Tip()->GetBlockHash());
     204           1 : }
     205             : 
     206             : /**
     207             :  * Test that AcceptBlock works correctly when a pre-computed known_hash is
     208             :  * supplied, exercising the reindex optimization path through AcceptBlockHeader
     209             :  * and CheckBlock.
     210             :  */
     211         148 : BOOST_AUTO_TEST_CASE(checkblock_accept_known_hash)
     212             : {
     213             :     bool ignored;
     214           1 :     BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(
     215             :         std::make_shared<CBlock>(Params().GenesisBlock()), true, &ignored));
     216             : 
     217           1 :     auto good = GoodBlock(Params().GenesisBlock().GetHash());
     218           1 :     const uint256 hash{good->GetHash()};
     219             : 
     220             :     // AcceptBlock with correct known_hash should succeed.
     221             :     // Do not call CheckBlock beforehand: that would set fChecked=true,
     222             :     // causing AcceptBlock's internal CheckBlock to short-circuit and skip
     223             :     // the known_hash path. Keeping fChecked=false mirrors the reindex flow.
     224             :     {
     225           1 :         LOCK(::cs_main);
     226           1 :         BlockValidationState state;
     227           1 :         CBlockIndex* pindex = nullptr;
     228           1 :         bool newblock = false;
     229           1 :         BOOST_REQUIRE(m_node.chainman->ActiveChainstate().AcceptBlock(
     230             :             good, state, &pindex, /*fRequested=*/true,
     231             :             /*dbp=*/nullptr, &newblock, &hash));
     232           1 :         BOOST_REQUIRE(state.IsValid());
     233           1 :         BOOST_REQUIRE(newblock);
     234           1 :         BOOST_REQUIRE(pindex != nullptr);
     235           1 :         BOOST_CHECK_EQUAL(pindex->GetBlockHash(), hash);
     236           1 :     }
     237           1 : }
     238             : 
     239             : /**
     240             :  * Test that mempool updates happen atomically with reorgs.
     241             :  *
     242             :  * This prevents RPC clients, among others, from retrieving immediately-out-of-date mempool data
     243             :  * during large reorgs.
     244             :  *
     245             :  * The test verifies this by creating a chain of `num_txs` blocks, matures their coinbases, and then
     246             :  * submits txns spending from their coinbase to the mempool. A fork chain is then processed,
     247             :  * invalidating the txns and evicting them from the mempool.
     248             :  *
     249             :  * We verify that the mempool updates atomically by polling it continuously
     250             :  * from another thread during the reorg and checking that its size only changes
     251             :  * once. The size changing exactly once indicates that the polling thread's
     252             :  * view of the mempool is either consistent with the chain state before reorg,
     253             :  * or consistent with the chain state after the reorg, and not just consistent
     254             :  * with some intermediate state during the reorg.
     255             :  */
     256         148 : BOOST_AUTO_TEST_CASE(mempool_locks_reorg)
     257             : {
     258             :     bool ignored;
     259         741 :     auto ProcessBlock = [&](std::shared_ptr<const CBlock> block) -> bool {
     260         740 :         return Assert(m_node.chainman)->ProcessNewBlock(block, /*force_processing=*/true, /*new_block=*/&ignored);
     261             :     };
     262             : 
     263             :     // Process all mined blocks
     264           1 :     BOOST_REQUIRE(ProcessBlock(std::make_shared<CBlock>(Params().GenesisBlock())));
     265           1 :     auto last_mined = GoodBlock(Params().GenesisBlock().GetHash());
     266           1 :     BOOST_REQUIRE(ProcessBlock(last_mined));
     267             : 
     268             :     // Run the test multiple times
     269           4 :     for (int test_runs = 3; test_runs > 0; --test_runs) {
     270           3 :         BOOST_CHECK_EQUAL(last_mined->GetHash(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
     271             : 
     272             :         // Later on split from here
     273           3 :         const uint256 split_hash{last_mined->hashPrevBlock};
     274             : 
     275             :         // Create a bunch of transactions to spend the miner rewards of the
     276             :         // most recent blocks
     277           3 :         std::vector<CTransactionRef> txs;
     278          69 :         for (int num_txs = 22; num_txs > 0; --num_txs) {
     279          66 :             CMutableTransaction mtx;
     280         132 :             mtx.vin.push_back(
     281         132 :                 CTxIn(COutPoint(last_mined->vtx[0]->GetHash(), 1),
     282          66 :                       CScript() << ToByteVector(CScript() << OP_TRUE)));
     283             :             // Two outputs to make sure the transaction is larger than 100 bytes
     284         198 :             for (int i = 1; i < 3; ++i) {
     285         264 :                 mtx.vout.emplace_back(
     286         264 :                     CTxOut(50000,
     287         264 :                            CScript() << OP_DUP << OP_HASH160
     288         132 :                                      << ToByteVector(CScriptID(CScript() << i))
     289         132 :                                      << OP_EQUALVERIFY << OP_CHECKSIG));
     290         132 :             }
     291          66 :             txs.push_back(MakeTransactionRef(mtx));
     292             : 
     293          66 :             last_mined = GoodBlock(last_mined->GetHash());
     294          66 :             BOOST_REQUIRE(ProcessBlock(last_mined));
     295          66 :         }
     296             : 
     297             :         // Mature the inputs of the txs
     298         303 :         for (int j = COINBASE_MATURITY; j > 0; --j) {
     299         300 :             last_mined = GoodBlock(last_mined->GetHash());
     300         300 :             BOOST_REQUIRE(ProcessBlock(last_mined));
     301         300 :         }
     302             : 
     303             :         // Mine a reorg (and hold it back) before adding the txs to the mempool
     304           3 :         const uint256 tip_init{last_mined->GetHash()};
     305             : 
     306           3 :         std::vector<std::shared_ptr<const CBlock>> reorg;
     307           3 :         last_mined = GoodBlock(split_hash);
     308           3 :         reorg.push_back(last_mined);
     309         372 :         for (size_t j = COINBASE_MATURITY + txs.size() + 1; j > 0; --j) {
     310         369 :             last_mined = GoodBlock(last_mined->GetHash());
     311         369 :             reorg.push_back(last_mined);
     312         369 :         }
     313             : 
     314             :         // Add the txs to the tx pool
     315             :         {
     316           3 :             LOCK(cs_main);
     317          69 :             for (const auto& tx : txs) {
     318          66 :                 const MempoolAcceptResult result = m_node.chainman->ProcessTransaction(tx);
     319          66 :                 BOOST_REQUIRE(result.m_result_type == MempoolAcceptResult::ResultType::VALID);
     320          66 :             }
     321           3 :         }
     322             : 
     323             :         // Check that all txs are in the pool
     324             :         {
     325           3 :             LOCK(m_node.mempool->cs);
     326           3 :             BOOST_CHECK_EQUAL(m_node.mempool->mapTx.size(), txs.size());
     327           3 :         }
     328             : 
     329             :         // Run a thread that simulates an RPC caller that is polling while
     330             :         // validation is doing a reorg
     331           6 :         std::thread rpc_thread{[&]() {
     332             :             // This thread is checking that the mempool either contains all of
     333             :             // the transactions invalidated by the reorg, or none of them, and
     334             :             // not some intermediate amount.
     335           3 :             while (true) {
     336     7501216 :                 LOCK(m_node.mempool->cs);
     337     7501216 :                 if (m_node.mempool->mapTx.size() == 0) {
     338             :                     // We are done with the reorg
     339           3 :                     break;
     340             :                 }
     341             :                 // Internally, we might be in the middle of the reorg, but
     342             :                 // externally the reorg to the most-proof-of-work chain should
     343             :                 // be atomic. So the caller assumes that the returned mempool
     344             :                 // is consistent. That is, it has all txs that were there
     345             :                 // before the reorg.
     346     7501213 :                 assert(m_node.mempool->mapTx.size() == txs.size());
     347     7501213 :                 continue;
     348     7501216 :             }
     349           3 :             LOCK(cs_main);
     350             :             // We are done with the reorg, so the tip must have changed
     351           3 :             assert(tip_init != m_node.chainman->ActiveChain().Tip()->GetBlockHash());
     352           3 :         }};
     353             : 
     354             :         // Submit the reorg in this thread to invalidate and remove the txs from the tx pool
     355         375 :         for (const auto& b : reorg) {
     356         372 :             ProcessBlock(b);
     357             :         }
     358             :         // Check that the reorg was eventually successful
     359           3 :         BOOST_CHECK_EQUAL(last_mined->GetHash(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
     360             : 
     361             :         // We can join the other thread, which returns when the reorg was successful
     362           3 :         rpc_thread.join();
     363           3 :     }
     364           1 : }
     365         146 : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.16