/ src / test / fuzz / block_index_tree.cpp
block_index_tree.cpp
  1  // Copyright (c) 2020-present 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  CBlockHeader ConsumeBlockHeader(FuzzedDataProvider& provider, uint256 prev_hash, int& nonce_counter)
 24  {
 25      CBlockHeader header;
 26      header.nVersion = provider.ConsumeIntegral<decltype(header.nVersion)>();
 27      header.hashPrevBlock = prev_hash;
 28      header.hashMerkleRoot = uint256{}; // never used
 29      header.nTime = provider.ConsumeIntegral<decltype(header.nTime)>();
 30      header.nBits = Params().GenesisBlock().nBits; // not fuzzed because not used (validation is mocked).
 31      header.nNonce = nonce_counter++;              // prevent creating multiple block headers with the same hash
 32      return header;
 33  }
 34  
 35  void initialize_block_index_tree()
 36  {
 37      static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
 38      g_setup = testing_setup.get();
 39  }
 40  
 41  FUZZ_TARGET(block_index_tree, .init = initialize_block_index_tree)
 42  {
 43      FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
 44      SetMockTime(ConsumeTime(fuzzed_data_provider));
 45      auto& chainman = static_cast<TestChainstateManager&>(*g_setup->m_node.chainman);
 46      auto& blockman = static_cast<TestBlockManager&>(chainman.m_blockman);
 47      CBlockIndex* genesis = chainman.ActiveChainstate().m_chain[0];
 48      int nonce_counter = 0;
 49      std::vector<CBlockIndex*> blocks;
 50      blocks.push_back(genesis);
 51      bool abort_run{false};
 52  
 53      std::vector<CBlockIndex*> pruned_blocks;
 54  
 55      LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 1000)
 56      {
 57          if (abort_run) break;
 58          CallOneOf(
 59              fuzzed_data_provider,
 60              [&] {
 61                  // Receive a header building on an existing valid one. This assumes headers are valid, so PoW is not relevant here.
 62                  LOCK(cs_main);
 63                  CBlockIndex* prev_block = PickValue(fuzzed_data_provider, blocks);
 64                  if (!(prev_block->nStatus & BLOCK_FAILED_VALID)) {
 65                      CBlockHeader header = ConsumeBlockHeader(fuzzed_data_provider, prev_block->GetBlockHash(), nonce_counter);
 66                      CBlockIndex* index = blockman.AddToBlockIndex(header, chainman.m_best_header);
 67                      assert(index->nStatus & BLOCK_VALID_TREE);
 68                      assert(index->pprev == prev_block);
 69                      blocks.push_back(index);
 70                  }
 71              },
 72              [&] {
 73                  // Receive a full block (valid or invalid) for an existing header, but don't attempt to connect it yet
 74                  LOCK(cs_main);
 75                  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                  if (index->nTx == 0 && !(index->nStatus & BLOCK_FAILED_VALID)) {
 78                      if (fuzzed_data_provider.ConsumeBool()) { // Invalid
 79                          BlockValidationState state;
 80                          state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid");
 81                          chainman.InvalidBlockFound(index, state);
 82                      } else {
 83                          size_t nTx = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 1000);
 84                          CBlock block; // Dummy block, so that ReceivedBlockTransactions can infer a nTx value.
 85                          block.vtx = std::vector<CTransactionRef>(nTx);
 86                          FlatFilePos pos(0, fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 1000));
 87                          chainman.ReceivedBlockTransactions(block, index, pos);
 88                          assert(index->nStatus & BLOCK_VALID_TRANSACTIONS);
 89                          assert(index->nStatus & BLOCK_HAVE_DATA);
 90                      }
 91                  }
 92              },
 93              [&] {
 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                  LOCK(cs_main);
 96                  auto& chain = chainman.ActiveChain();
 97                  CBlockIndex* old_tip = chain.Tip();
 98                  assert(old_tip);
 99                  do {
100                      CBlockIndex* best_tip = chainman.FindMostWorkChain();
101                      assert(best_tip);                   // Should at least return current tip
102                      if (best_tip == chain.Tip()) break; // Nothing to do
103                      // Rewind chain to forking point
104                      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                      CBlockIndex* it = chain.Tip();
108                      while (it && it->nHeight != fork->nHeight) {
109                          if (!(it->nStatus & BLOCK_HAVE_UNDO)) {
110                              assert(blockman.m_have_pruned);
111                              abort_run = true;
112                              return;
113                          }
114                          it = it->pprev;
115                      }
116                      chain.SetTip(*chain[fork->nHeight]);
117  
118                      // Prepare new blocks to connect
119                      std::vector<CBlockIndex*> to_connect;
120                      it = best_tip;
121                      while (it && it->nHeight != fork->nHeight) {
122                          to_connect.push_back(it);
123                          it = it->pprev;
124                      }
125                      // Connect blocks, possibly fail
126                      for (CBlockIndex* block : to_connect | std::views::reverse) {
127                          assert(!(block->nStatus & BLOCK_FAILED_VALID));
128                          assert(block->nStatus & BLOCK_HAVE_DATA);
129                          if (!block->IsValid(BLOCK_VALID_SCRIPTS)) {
130                              if (fuzzed_data_provider.ConsumeBool()) { // Invalid
131                                  BlockValidationState state;
132                                  state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid");
133                                  chainman.InvalidBlockFound(block, state);
134                                  // This results in duplicate calls to InvalidChainFound, but mirrors the behavior in validation
135                                  chainman.InvalidChainFound(to_connect.front());
136                                  break;
137                              } else {
138                                  block->RaiseValidity(BLOCK_VALID_SCRIPTS);
139                                  block->nStatus |= BLOCK_HAVE_UNDO;
140                              }
141                          }
142                          chain.SetTip(*block);
143                          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                          if (block->nChainWork > old_tip->nChainWork && fuzzed_data_provider.ConsumeBool()) {
146                              break;
147                          }
148                      }
149                  } while (node::CBlockIndexWorkComparator()(chain.Tip(), old_tip));
150                  assert(chain.Tip()->nChainWork >= old_tip->nChainWork);
151              },
152              [&] {
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                  LOCK(cs_main);
158                  auto& chain = chainman.ActiveChain();
159                  int prune_height = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, chain.Height());
160                  CBlockIndex* prune_block{chain[prune_height]};
161                  if (prune_block != chain.Tip() && (prune_block->nStatus & BLOCK_HAVE_DATA)) {
162                      blockman.m_have_pruned = true;
163                      prune_block->nStatus &= ~BLOCK_HAVE_DATA;
164                      prune_block->nStatus &= ~BLOCK_HAVE_UNDO;
165                      prune_block->nFile = 0;
166                      prune_block->nDataPos = 0;
167                      prune_block->nUndoPos = 0;
168                      auto range = blockman.m_blocks_unlinked.equal_range(prune_block->pprev);
169                      while (range.first != range.second) {
170                          std::multimap<CBlockIndex*, CBlockIndex*>::iterator _it = range.first;
171                          range.first++;
172                          if (_it->second == prune_block) {
173                              blockman.m_blocks_unlinked.erase(_it);
174                          }
175                      }
176                      pruned_blocks.push_back(prune_block);
177                  }
178              },
179              [&] {
180                  // Download a previously pruned block
181                  LOCK(cs_main);
182                  size_t num_pruned = pruned_blocks.size();
183                  if (num_pruned == 0) return;
184                  size_t i = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, num_pruned - 1);
185                  CBlockIndex* index = pruned_blocks[i];
186                  assert(!(index->nStatus & BLOCK_HAVE_DATA));
187                  CBlock block;
188                  block.vtx = std::vector<CTransactionRef>(index->nTx); // Set the number of tx to the prior value.
189                  FlatFilePos pos(0, fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 1000));
190                  chainman.ReceivedBlockTransactions(block, index, pos);
191                  assert(index->nStatus & BLOCK_VALID_TRANSACTIONS);
192                  assert(index->nStatus & BLOCK_HAVE_DATA);
193                  pruned_blocks.erase(pruned_blocks.begin() + i);
194              });
195      }
196      if (!abort_run) {
197          chainman.CheckBlockIndex();
198      }
199  
200      // clean up global state changed by last iteration and prepare for next iteration
201      {
202          LOCK(cs_main);
203          genesis->nStatus |= BLOCK_HAVE_DATA;
204          genesis->nStatus |= BLOCK_HAVE_UNDO;
205          chainman.m_best_header = genesis;
206          chainman.ResetBestInvalid();
207          chainman.nBlockSequenceId = 2;
208          chainman.ActiveChain().SetTip(*genesis);
209          chainman.ActiveChainstate().setBlockIndexCandidates.clear();
210          chainman.m_cached_is_ibd = true;
211          blockman.m_blocks_unlinked.clear();
212          blockman.m_have_pruned = false;
213          blockman.CleanupForFuzzing();
214          // Delete all blocks but Genesis from block index
215          uint256 genesis_hash = genesis->GetBlockHash();
216          for (auto it = blockman.m_block_index.begin(); it != blockman.m_block_index.end();) {
217              if (it->first != genesis_hash) {
218                  it = blockman.m_block_index.erase(it);
219              } else {
220                  ++it;
221              }
222          }
223          chainman.ActiveChainstate().TryAddBlockIndexCandidate(genesis);
224          assert(blockman.m_block_index.size() == 1);
225          assert(chainman.ActiveChainstate().setBlockIndexCandidates.size() == 1);
226          assert(chainman.ActiveChain().Height() == 0);
227      }
228  }