LCOV - code coverage report
Current view: top level - src/test/fuzz - block_index_tree.cpp (source / functions) Coverage Total Hit
Test: fuzz_coverage.info Lines: 97.5 % 158 154
Test Date: 2025-12-28 04:16:35 Functions: 100.0 % 9 9
Branches: 60.1 % 218 131

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) 2020-2022 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                 :             : 
       6                 :             : #include <chain.h>
       7                 :             : #include <chainparams.h>
       8                 :             : #include <flatfile.h>
       9                 :             : #include <primitives/block.h>
      10                 :             : #include <primitives/transaction.h>
      11                 :             : #include <test/fuzz/FuzzedDataProvider.h>
      12                 :             : #include <test/fuzz/fuzz.h>
      13                 :             : #include <test/fuzz/util.h>
      14                 :             : #include <test/util/setup_common.h>
      15                 :             : #include <test/util/validation.h>
      16                 :             : #include <validation.h>
      17                 :             : 
      18                 :             : #include <ranges>
      19                 :             : #include <vector>
      20                 :             : 
      21                 :             : const TestingSetup* g_setup;
      22                 :             : 
      23                 :       25512 : CBlockHeader ConsumeBlockHeader(FuzzedDataProvider& provider, uint256 prev_hash, int& nonce_counter)
      24                 :             : {
      25                 :       25512 :     CBlockHeader header;
      26                 :       25512 :     header.nVersion = provider.ConsumeIntegral<decltype(header.nVersion)>();
      27                 :       25512 :     header.hashPrevBlock = prev_hash;
      28                 :       25512 :     header.hashMerkleRoot = uint256{}; // never used
      29                 :       25512 :     header.nTime = provider.ConsumeIntegral<decltype(header.nTime)>();
      30                 :       25512 :     header.nBits = Params().GenesisBlock().nBits; // not fuzzed because not used (validation is mocked).
      31                 :       25512 :     header.nNonce = nonce_counter++;              // prevent creating multiple block headers with the same hash
      32                 :       25512 :     return header;
      33                 :             : }
      34                 :             : 
      35                 :           1 : void initialize_block_index_tree()
      36                 :             : {
      37   [ +  -  +  -  :           1 :     static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
                   +  - ]
      38                 :           1 :     g_setup = testing_setup.get();
      39                 :           1 : }
      40                 :             : 
      41         [ +  - ]:         616 : FUZZ_TARGET(block_index_tree, .init = initialize_block_index_tree)
      42                 :             : {
      43                 :         164 :     FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
      44                 :         164 :     SetMockTime(ConsumeTime(fuzzed_data_provider));
      45                 :         164 :     auto& chainman = static_cast<TestChainstateManager&>(*g_setup->m_node.chainman);
      46                 :         164 :     auto& blockman = static_cast<TestBlockManager&>(chainman.m_blockman);
      47         [ -  + ]:         164 :     CBlockIndex* genesis = chainman.ActiveChainstate().m_chain[0];
      48                 :         164 :     int nonce_counter = 0;
      49                 :         164 :     std::vector<CBlockIndex*> blocks;
      50         [ +  - ]:         164 :     blocks.push_back(genesis);
      51                 :         164 :     bool abort_run{false};
      52                 :             : 
      53                 :         164 :     std::vector<CBlockIndex*> pruned_blocks;
      54                 :             : 
      55   [ +  +  +  + ]:       46755 :     LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 1000)
      56                 :             :     {
      57         [ +  + ]:       46592 :         if (abort_run) break;
      58         [ +  - ]:       46591 :         CallOneOf(
      59                 :             :             fuzzed_data_provider,
      60                 :       31879 :             [&] {
      61                 :             :                 // Receive a header building on an existing valid one. This assumes headers are valid, so PoW is not relevant here.
      62                 :       31879 :                 LOCK(cs_main);
      63                 :       31879 :                 CBlockIndex* prev_block = PickValue(fuzzed_data_provider, blocks);
      64         [ +  + ]:       31879 :                 if (!(prev_block->nStatus & BLOCK_FAILED_MASK)) {
      65         [ +  - ]:       25512 :                     CBlockHeader header = ConsumeBlockHeader(fuzzed_data_provider, prev_block->GetBlockHash(), nonce_counter);
      66         [ +  - ]:       25512 :                     CBlockIndex* index = blockman.AddToBlockIndex(header, chainman.m_best_header);
      67         [ -  + ]:       25512 :                     assert(index->nStatus & BLOCK_VALID_TREE);
      68         [ -  + ]:       25512 :                     assert(index->pprev == prev_block);
      69         [ +  - ]:       25512 :                     blocks.push_back(index);
      70                 :             :                 }
      71                 :       31879 :             },
      72                 :        6210 :             [&] {
      73                 :             :                 // Receive a full block (valid or invalid) for an existing header, but don't attempt to connect it yet
      74                 :        6210 :                 LOCK(cs_main);
      75                 :        6210 :                 CBlockIndex* index = PickValue(fuzzed_data_provider, blocks);
      76                 :             :                 // Must be new to us and not known to be invalid (e.g. because of an invalid ancestor).
      77   [ +  +  +  + ]:        6210 :                 if (index->nTx == 0 && !(index->nStatus & BLOCK_FAILED_MASK)) {
      78         [ +  + ]:        3407 :                     if (fuzzed_data_provider.ConsumeBool()) { // Invalid
      79         [ +  - ]:        1672 :                         BlockValidationState state;
      80   [ +  -  +  -  :        3344 :                         state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid");
                   +  - ]
      81         [ +  - ]:        1672 :                         chainman.InvalidBlockFound(index, state);
      82                 :        1672 :                     } else {
      83                 :        1735 :                         size_t nTx = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 1000);
      84                 :        1735 :                         CBlock block; // Dummy block, so that ReceivedBlockTransactions can infer a nTx value.
      85         [ +  - ]:        3470 :                         block.vtx = std::vector<CTransactionRef>(nTx);
      86         [ +  - ]:        1735 :                         FlatFilePos pos(0, fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 1000));
      87         [ +  - ]:        1735 :                         chainman.ReceivedBlockTransactions(block, index, pos);
      88         [ -  + ]:        1735 :                         assert(index->nStatus & BLOCK_VALID_TRANSACTIONS);
      89         [ -  + ]:        1735 :                         assert(index->nStatus & BLOCK_HAVE_DATA);
      90                 :        1735 :                     }
      91                 :             :                 }
      92                 :        6210 :             },
      93                 :        3401 :             [&] {
      94                 :             :                 // Simplified ActivateBestChain(): Try to move to a chain with more work - with the possibility of finding blocks to be invalid on the way
      95                 :        3401 :                 LOCK(cs_main);
      96         [ +  - ]:        3401 :                 auto& chain = chainman.ActiveChain();
      97         [ -  + ]:        3401 :                 CBlockIndex* old_tip = chain.Tip();
      98         [ -  + ]:        3401 :                 assert(old_tip);
      99                 :        3779 :                 do {
     100         [ +  - ]:        3779 :                     CBlockIndex* best_tip = chainman.FindMostWorkChain();
     101         [ -  + ]:        3779 :                     assert(best_tip);                   // Should at least return current tip
     102   [ -  +  +  + ]:        7558 :                     if (best_tip == chain.Tip()) break; // Nothing to do
     103                 :             :                     // Rewind chain to forking point
     104         [ +  - ]:         900 :                     const CBlockIndex* fork = chain.FindFork(best_tip);
     105                 :             :                     // If we can't go back to the fork point due to pruned data, abort this run. In reality, a pruned node would also currently just crash in this scenario.
     106                 :             :                     // This is very unlikely to happen due to the minimum pruning threshold of 550MiB.
     107         [ -  + ]:         900 :                     CBlockIndex* it = chain.Tip();
     108   [ +  -  +  + ]:        1791 :                     while (it && it->nHeight != fork->nHeight) {
     109         [ +  + ]:         892 :                         if (!(it->nStatus & BLOCK_HAVE_UNDO)) {
     110         [ -  + ]:           1 :                             assert(blockman.m_have_pruned);
     111                 :           1 :                             abort_run = true;
     112         [ +  - ]:           1 :                             return;
     113                 :             :                         }
     114                 :         891 :                         it = it->pprev;
     115                 :             :                     }
     116   [ +  -  +  - ]:        1798 :                     chain.SetTip(*chain[fork->nHeight]);
     117                 :             : 
     118                 :             :                     // Prepare new blocks to connect
     119                 :         899 :                     std::vector<CBlockIndex*> to_connect;
     120                 :         899 :                     it = best_tip;
     121   [ +  -  +  + ]:        2876 :                     while (it && it->nHeight != fork->nHeight) {
     122         [ +  - ]:        1977 :                         to_connect.push_back(it);
     123                 :        1977 :                         it = it->pprev;
     124                 :             :                     }
     125                 :             :                     // Connect blocks, possibly fail
     126         [ +  + ]:        1911 :                     for (CBlockIndex* block : to_connect | std::views::reverse) {
     127         [ -  + ]:        1625 :                         assert(!(block->nStatus & BLOCK_FAILED_MASK));
     128         [ -  + ]:        1625 :                         assert(block->nStatus & BLOCK_HAVE_DATA);
     129         [ +  + ]:        1625 :                         if (!block->IsValid(BLOCK_VALID_SCRIPTS)) {
     130         [ +  + ]:         827 :                             if (fuzzed_data_provider.ConsumeBool()) { // Invalid
     131         [ +  - ]:         491 :                                 BlockValidationState state;
     132   [ +  -  +  -  :         982 :                                 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid");
                   +  - ]
     133         [ +  - ]:         491 :                                 chainman.InvalidBlockFound(block, state);
     134                 :             :                                 // This results in duplicate calls to InvalidChainFound, but mirrors the behavior in validation
     135         [ +  - ]:         491 :                                 chainman.InvalidChainFound(to_connect.front());
     136                 :         491 :                                 break;
     137                 :         491 :                             } else {
     138                 :         336 :                                 block->RaiseValidity(BLOCK_VALID_SCRIPTS);
     139                 :         336 :                                 block->nStatus |= BLOCK_HAVE_UNDO;
     140                 :             :                             }
     141                 :             :                         }
     142         [ +  - ]:        1134 :                         chain.SetTip(*block);
     143   [ +  -  +  - ]:        1134 :                         chainman.ActiveChainstate().PruneBlockIndexCandidates();
     144                 :             :                         // ActivateBestChainStep may release cs_main / not connect all blocks in one go - but only if we have at least as much chain work as we had at the start.
     145   [ +  -  +  +  :        1134 :                         if (block->nChainWork > old_tip->nChainWork && fuzzed_data_provider.ConsumeBool()) {
                   +  + ]
     146                 :             :                             break;
     147                 :             :                         }
     148                 :             :                     }
     149   [ -  +  +  -  :        1798 :                 } while (node::CBlockIndexWorkComparator()(chain.Tip(), old_tip));
                   +  + ]
     150   [ -  +  +  -  :        6800 :                 assert(chain.Tip()->nChainWork >= old_tip->nChainWork);
                   -  + ]
     151                 :        3401 :             },
     152                 :        3118 :             [&] {
     153                 :             :                 // Prune chain - dealing with block files is beyond the scope of this test, so just prune random blocks, making no assumptions
     154                 :             :                 // about what blocks are pruned together because they are in the same block file.
     155                 :             :                 // Also don't prune blocks outside of the chain for now - this would make the fuzzer crash because of the problem described in
     156                 :             :                 // https://github.com/bitcoin/bitcoin/issues/31512
     157                 :        3118 :                 LOCK(cs_main);
     158         [ +  - ]:        3118 :                 auto& chain = chainman.ActiveChain();
     159         [ -  + ]:        3118 :                 int prune_height = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, chain.Height());
     160         [ +  - ]:        3118 :                 CBlockIndex* prune_block{chain[prune_height]};
     161   [ -  +  +  +  :        6236 :                 if (prune_block != chain.Tip() && (prune_block->nStatus & BLOCK_HAVE_DATA)) {
                   +  + ]
     162                 :         834 :                     blockman.m_have_pruned = true;
     163                 :         834 :                     prune_block->nStatus &= ~BLOCK_HAVE_DATA;
     164                 :         834 :                     prune_block->nStatus &= ~BLOCK_HAVE_UNDO;
     165                 :         834 :                     prune_block->nFile = 0;
     166                 :         834 :                     prune_block->nDataPos = 0;
     167                 :         834 :                     prune_block->nUndoPos = 0;
     168                 :         834 :                     auto range = blockman.m_blocks_unlinked.equal_range(prune_block->pprev);
     169         [ -  + ]:         834 :                     while (range.first != range.second) {
     170                 :           0 :                         std::multimap<CBlockIndex*, CBlockIndex*>::iterator _it = range.first;
     171                 :           0 :                         range.first++;
     172         [ #  # ]:           0 :                         if (_it->second == prune_block) {
     173                 :           0 :                             blockman.m_blocks_unlinked.erase(_it);
     174                 :             :                         }
     175                 :             :                     }
     176         [ +  - ]:         834 :                     pruned_blocks.push_back(prune_block);
     177                 :             :                 }
     178                 :        3118 :             },
     179                 :        1983 :             [&] {
     180                 :             :                 // Download a previously pruned block
     181                 :        1983 :                 LOCK(cs_main);
     182         [ -  + ]:        1983 :                 size_t num_pruned = pruned_blocks.size();
     183   [ +  +  +  - ]:        1983 :                 if (num_pruned == 0) return;
     184                 :         763 :                 size_t i = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, num_pruned - 1);
     185         [ -  + ]:         763 :                 CBlockIndex* index = pruned_blocks[i];
     186         [ -  + ]:         763 :                 assert(!(index->nStatus & BLOCK_HAVE_DATA));
     187                 :         763 :                 CBlock block;
     188         [ +  - ]:        1526 :                 block.vtx = std::vector<CTransactionRef>(index->nTx); // Set the number of tx to the prior value.
     189         [ +  - ]:         763 :                 FlatFilePos pos(0, fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 1000));
     190         [ +  - ]:         763 :                 chainman.ReceivedBlockTransactions(block, index, pos);
     191         [ -  + ]:         763 :                 assert(index->nStatus & BLOCK_VALID_TRANSACTIONS);
     192         [ -  + ]:         763 :                 assert(index->nStatus & BLOCK_HAVE_DATA);
     193                 :         763 :                 pruned_blocks.erase(pruned_blocks.begin() + i);
     194         [ +  - ]:        2746 :             });
     195                 :             :     }
     196         [ +  + ]:         164 :     if (!abort_run) {
     197         [ +  - ]:         163 :         chainman.CheckBlockIndex();
     198                 :             :     }
     199                 :             : 
     200                 :             :     // clean up global state changed by last iteration and prepare for next iteration
     201                 :         164 :     {
     202         [ +  - ]:         164 :         LOCK(cs_main);
     203                 :         164 :         genesis->nStatus |= BLOCK_HAVE_DATA;
     204                 :         164 :         genesis->nStatus |= BLOCK_HAVE_UNDO;
     205                 :         164 :         chainman.m_best_header = genesis;
     206         [ +  - ]:         164 :         chainman.ResetBestInvalid();
     207                 :         164 :         chainman.nBlockSequenceId = 2;
     208   [ +  -  +  - ]:         164 :         chainman.ActiveChain().SetTip(*genesis);
     209         [ +  - ]:         164 :         chainman.ActiveChainstate().setBlockIndexCandidates.clear();
     210                 :         164 :         chainman.m_cached_finished_ibd = false;
     211                 :         164 :         blockman.m_blocks_unlinked.clear();
     212                 :         164 :         blockman.m_have_pruned = false;
     213         [ +  - ]:         164 :         blockman.CleanupForFuzzing();
     214                 :             :         // Delete all blocks but Genesis from block index
     215                 :         164 :         uint256 genesis_hash = genesis->GetBlockHash();
     216         [ +  + ]:       25840 :         for (auto it = blockman.m_block_index.begin(); it != blockman.m_block_index.end();) {
     217         [ +  + ]:       25676 :             if (it->first != genesis_hash) {
     218                 :       25512 :                 it = blockman.m_block_index.erase(it);
     219                 :             :             } else {
     220                 :         164 :                 ++it;
     221                 :             :             }
     222                 :             :         }
     223   [ +  -  +  - ]:         164 :         chainman.ActiveChainstate().TryAddBlockIndexCandidate(genesis);
     224         [ -  + ]:         164 :         assert(blockman.m_block_index.size() == 1);
     225   [ +  -  -  + ]:         164 :         assert(chainman.ActiveChainstate().setBlockIndexCandidates.size() == 1);
     226   [ +  -  -  +  :         164 :         assert(chainman.ActiveChain().Height() == 0);
                   -  + ]
     227                 :         164 :     }
     228                 :         164 : }
        

Generated by: LCOV version 2.0-1