/ src / test / fuzz / headerssync.cpp
headerssync.cpp
  1  // Copyright (c) 2022-present The Bitcoin Core developers
  2  // Distributed under the MIT software license, see the accompanying
  3  // file COPYING or https://opensource.org/license/mit.
  4  
  5  #include <arith_uint256.h>
  6  #include <chain.h>
  7  #include <chainparams.h>
  8  #include <headerssync.h>
  9  #include <test/fuzz/fuzz.h>
 10  #include <test/fuzz/util.h>
 11  #include <test/util/setup_common.h>
 12  #include <uint256.h>
 13  #include <util/chaintype.h>
 14  #include <util/time.h>
 15  #include <validation.h>
 16  
 17  #include <iterator>
 18  #include <vector>
 19  
 20  static void initialize_headers_sync_state_fuzz()
 21  {
 22      static const auto testing_setup = MakeNoLogFileContext<>(
 23          /*chain_type=*/ChainType::MAIN);
 24  }
 25  
 26  void MakeHeadersContinuous(
 27      const CBlockHeader& genesis_header,
 28      const std::vector<CBlockHeader>& all_headers,
 29      std::vector<CBlockHeader>& new_headers)
 30  {
 31      Assume(!new_headers.empty());
 32  
 33      const CBlockHeader* prev_header{
 34          all_headers.empty() ? &genesis_header : &all_headers.back()};
 35  
 36      for (auto& header : new_headers) {
 37          header.hashPrevBlock = prev_header->GetHash();
 38  
 39          prev_header = &header;
 40      }
 41  }
 42  
 43  class FuzzedHeadersSyncState : public HeadersSyncState
 44  {
 45  public:
 46      FuzzedHeadersSyncState(const HeadersSyncParams& sync_params, const size_t commit_offset,
 47                             const CBlockIndex& chain_start, const arith_uint256& minimum_required_work)
 48          : HeadersSyncState(/*id=*/0, Params().GetConsensus(), sync_params, chain_start, minimum_required_work)
 49      {
 50          const_cast<size_t&>(m_commit_offset) = commit_offset;
 51      }
 52  };
 53  
 54  FUZZ_TARGET(headers_sync_state, .init = initialize_headers_sync_state_fuzz)
 55  {
 56      SeedRandomStateForTest(SeedRand::ZEROS);
 57      FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
 58  
 59      CBlockHeader genesis_header{Params().GenesisBlock()};
 60      CBlockIndex start_index(genesis_header);
 61  
 62      SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/start_index.GetMedianTimePast()));
 63  
 64      const uint256 genesis_hash = genesis_header.GetHash();
 65      start_index.phashBlock = &genesis_hash;
 66  
 67      const HeadersSyncParams params{
 68          .commitment_period = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, Params().HeadersSync().commitment_period * 2),
 69          .redownload_buffer_size = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, Params().HeadersSync().redownload_buffer_size * 2),
 70      };
 71      arith_uint256 min_work{UintToArith256(ConsumeUInt256(fuzzed_data_provider))};
 72      FuzzedHeadersSyncState headers_sync(
 73          params,
 74          /*commit_offset=*/fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, params.commitment_period - 1),
 75          /*chain_start=*/start_index,
 76          /*minimum_required_work=*/min_work);
 77  
 78      // Store headers for potential redownload phase.
 79      std::vector<CBlockHeader> all_headers;
 80      std::vector<CBlockHeader>::const_iterator redownloaded_it;
 81      bool presync{true};
 82      bool requested_more{true};
 83  
 84      while (requested_more) {
 85          std::vector<CBlockHeader> headers;
 86  
 87          // Consume headers from fuzzer or maybe replay headers if we got to the
 88          // redownload phase.
 89          if (presync || fuzzed_data_provider.ConsumeBool()) {
 90              auto deser_headers = ConsumeDeserializable<std::vector<CBlockHeader>>(fuzzed_data_provider);
 91              if (!deser_headers || deser_headers->empty()) return;
 92  
 93              if (fuzzed_data_provider.ConsumeBool()) {
 94                  MakeHeadersContinuous(genesis_header, all_headers, *deser_headers);
 95              }
 96  
 97              headers.swap(*deser_headers);
 98          } else if (auto num_headers_left{std::distance(redownloaded_it, all_headers.cend())}; num_headers_left > 0) {
 99              // Consume some headers from the redownload buffer (At least one
100              // header is consumed).
101              auto begin_it{redownloaded_it};
102              std::advance(redownloaded_it, fuzzed_data_provider.ConsumeIntegralInRange<int>(1, num_headers_left));
103              headers.insert(headers.cend(), begin_it, redownloaded_it);
104          }
105  
106          if (headers.empty()) return;
107          auto result = headers_sync.ProcessNextHeaders(headers, fuzzed_data_provider.ConsumeBool());
108          requested_more = result.request_more;
109  
110          if (result.request_more) {
111              if (presync) {
112                  all_headers.insert(all_headers.cend(), headers.cbegin(), headers.cend());
113  
114                  if (headers_sync.GetState() == HeadersSyncState::State::REDOWNLOAD) {
115                      presync = false;
116                      redownloaded_it = all_headers.cbegin();
117  
118                      // If we get to redownloading, the presynced headers need
119                      // to have the min amount of work on them.
120                      assert(CalculateClaimedHeadersWork(all_headers) >= min_work);
121                  }
122              }
123  
124              (void)headers_sync.NextHeadersRequestLocator();
125          }
126      }
127  }