p2p_headers_presync.cpp raw

   1  // Copyright (c) 2024-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  #include <arith_uint256.h>
   6  #include <blockencodings.h>
   7  #include <net.h>
   8  #include <net_processing.h>
   9  #include <netmessagemaker.h>
  10  #include <node/peerman_args.h>
  11  #include <test/fuzz/FuzzedDataProvider.h>
  12  #include <test/fuzz/fuzz.h>
  13  #include <test/fuzz/util.h>
  14  #include <test/util/net.h>
  15  #include <test/util/script.h>
  16  #include <test/util/setup_common.h>
  17  #include <test/util/time.h>
  18  #include <uint256.h>
  19  #include <validation.h>
  20  
  21  namespace {
  22  constexpr uint32_t FUZZ_MAX_HEADERS_RESULTS{16};
  23  
  24  class HeadersSyncSetup : public TestingSetup
  25  {
  26      std::vector<CNode*> m_connections;
  27  
  28  public:
  29      HeadersSyncSetup(const ChainType chain_type, TestOpts opts) : TestingSetup(chain_type, opts)
  30      {
  31          PeerManager::Options peerman_opts;
  32          node::ApplyArgsManOptions(*m_node.args, peerman_opts);
  33          peerman_opts.max_headers_result = FUZZ_MAX_HEADERS_RESULTS;
  34          // The peerman's rng is a global that is reused, so it will be reused
  35          // and may cause non-determinism between runs. This may even influence
  36          // the global RNG, because seeding may be done from the global one. For
  37          // now, avoid it influencing the global RNG, and initialize it with a
  38          // constant instead.
  39          peerman_opts.deterministic_rng = true;
  40          // No txs are relayed. Disable irrelevant and possibly
  41          // non-deterministic code paths.
  42          peerman_opts.ignore_incoming_txs = true;
  43          m_node.peerman = PeerManager::make(*m_node.connman, *m_node.addrman,
  44                                             m_node.banman.get(), *m_node.chainman,
  45                                             *m_node.mempool, *m_node.warnings, peerman_opts);
  46  
  47          CConnman::Options options;
  48          options.m_msgproc = m_node.peerman.get();
  49          m_node.connman->Init(options);
  50      }
  51  
  52      void ResetAndInitialize() EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex);
  53      void SendMessage(FuzzedDataProvider& fuzzed_data_provider, CSerializedNetMsg&& msg)
  54          EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex);
  55  };
  56  
  57  void HeadersSyncSetup::ResetAndInitialize()
  58  {
  59      m_connections.clear();
  60      auto& connman = static_cast<ConnmanTestMsg&>(*m_node.connman);
  61      connman.StopNodes();
  62  
  63      static NodeId id{0};
  64      std::vector<ConnectionType> conn_types = {
  65          ConnectionType::OUTBOUND_FULL_RELAY,
  66          ConnectionType::BLOCK_RELAY,
  67          ConnectionType::INBOUND
  68      };
  69  
  70      for (auto conn_type : conn_types) {
  71          CAddress addr{};
  72          m_connections.push_back(new CNode(id++, nullptr, addr, 0, 0, addr, "", conn_type, false, 0));
  73          CNode& p2p_node = *m_connections.back();
  74  
  75          connman.Handshake(
  76              /*node=*/p2p_node,
  77              /*successfully_connected=*/true,
  78              /*remote_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS),
  79              /*local_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS),
  80              /*version=*/PROTOCOL_VERSION,
  81              /*relay_txs=*/true);
  82  
  83          connman.AddTestNode(p2p_node);
  84      }
  85  }
  86  
  87  void HeadersSyncSetup::SendMessage(FuzzedDataProvider& fuzzed_data_provider, CSerializedNetMsg&& msg)
  88  {
  89      auto& connman = static_cast<ConnmanTestMsg&>(*m_node.connman);
  90      CNode& connection = *PickValue(fuzzed_data_provider, m_connections);
  91  
  92      connman.FlushSendBuffer(connection);
  93      (void)connman.ReceiveMsgFrom(connection, std::move(msg));
  94      connection.fPauseSend = false;
  95      try {
  96          connman.ProcessMessagesOnce(connection);
  97      } catch (const std::ios_base::failure&) {
  98      }
  99      m_node.peerman->SendMessages(connection);
 100  }
 101  
 102  CBlockHeader ConsumeHeader(FuzzedDataProvider& fuzzed_data_provider, const uint256& prev_hash, uint32_t prev_nbits)
 103  {
 104      CBlockHeader header;
 105      header.nNonce = 0;
 106      // Either use the previous difficulty or let the fuzzer choose. The upper target in the
 107      // range comes from the bits value of the genesis block, which is 0x1d00ffff. The lower
 108      // target comes from the bits value of mainnet block 840000, which is 0x17034219.
 109      // Calling lower_target.SetCompact(0x17034219) and upper_target.SetCompact(0x1d00ffff)
 110      // should return the values below.
 111      //
 112      // RPC commands to verify:
 113      // getblockheader 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
 114      // getblockheader 0000000000000000000320283a032748cef8227873ff4872689bf23f1cda83a5
 115      if (fuzzed_data_provider.ConsumeBool()) {
 116          header.nBits = prev_nbits;
 117      } else {
 118          arith_uint256 lower_target = UintToArith256(uint256{"0000000000000000000342190000000000000000000000000000000000000000"});
 119          arith_uint256 upper_target = UintToArith256(uint256{"00000000ffff0000000000000000000000000000000000000000000000000000"});
 120          arith_uint256 target = ConsumeArithUInt256InRange(fuzzed_data_provider, lower_target, upper_target);
 121          header.nBits = target.GetCompact();
 122      }
 123      header.nTime = TicksSinceEpoch<std::chrono::seconds>(ConsumeTime(fuzzed_data_provider));
 124      header.hashPrevBlock = prev_hash;
 125      header.nVersion = fuzzed_data_provider.ConsumeIntegral<int32_t>();
 126      return header;
 127  }
 128  
 129  CBlock ConsumeBlock(FuzzedDataProvider& fuzzed_data_provider, const uint256& prev_hash, uint32_t prev_nbits)
 130  {
 131      auto header = ConsumeHeader(fuzzed_data_provider, prev_hash, prev_nbits);
 132      // In order to reach the headers acceptance logic, the block is
 133      // constructed in a way that will pass the mutation checks.
 134      CBlock block{header};
 135      CMutableTransaction tx;
 136      tx.vin.resize(1);
 137      tx.vout.resize(1);
 138      tx.vout[0].nValue = 0;
 139      tx.vin[0].scriptSig.resize(2);
 140      block.vtx.push_back(MakeTransactionRef(tx));
 141      block.hashMerkleRoot = block.vtx[0]->GetHash().ToUint256();
 142      return block;
 143  }
 144  
 145  // Global setup works for this test as state modification (specifically in the
 146  // block index) would indicate a bug.
 147  HeadersSyncSetup* g_testing_setup;
 148  
 149  void initialize()
 150  {
 151      static auto setup{
 152          MakeNoLogFileContext<HeadersSyncSetup>(ChainType::MAIN,
 153                                                 {
 154                                                     .setup_validation_interface = false,
 155                                                 }),
 156      };
 157      g_testing_setup = setup.get();
 158  }
 159  } // namespace
 160  
 161  FUZZ_TARGET(p2p_headers_presync, .init = initialize)
 162  {
 163      SeedRandomStateForTest(SeedRand::ZEROS);
 164      FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
 165      // The steady clock is currently only used for logging, so a constant
 166      // time-point seems acceptable for now.
 167      FakeSteadyClock steady_ctx{};
 168  
 169      ChainstateManager& chainman = *g_testing_setup->m_node.chainman;
 170      CBlockHeader base{chainman.GetParams().GenesisBlock()};
 171      const FakeNodeClock clock{base.Time()};
 172  
 173      LOCK(NetEventsInterface::g_msgproc_mutex);
 174  
 175      g_testing_setup->ResetAndInitialize();
 176  
 177      // The chain is just a single block, so this is equal to 1
 178      size_t original_index_size{WITH_LOCK(cs_main, return chainman.m_blockman.m_block_index.size())};
 179      arith_uint256 total_work{WITH_LOCK(cs_main, return chainman.m_best_header->nChainWork)};
 180  
 181      std::vector<CBlockHeader> all_headers;
 182  
 183      LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100)
 184      {
 185          auto finalized_block = [&]() {
 186              CBlock block = ConsumeBlock(fuzzed_data_provider, base.GetHash(), base.nBits);
 187              FinalizeHeader(block, chainman);
 188              return block;
 189          };
 190  
 191          // Send low-work headers, compact blocks, and blocks
 192          CallOneOf(
 193              fuzzed_data_provider,
 194              [&]() NO_THREAD_SAFETY_ANALYSIS {
 195                  // Send FUZZ_MAX_HEADERS_RESULTS headers
 196                  std::vector<CBlock> headers;
 197                  headers.resize(FUZZ_MAX_HEADERS_RESULTS);
 198                  for (CBlock& header : headers) {
 199                      header = ConsumeHeader(fuzzed_data_provider, base.GetHash(), base.nBits);
 200                      FinalizeHeader(header, chainman);
 201                      base = header;
 202                  }
 203  
 204                  all_headers.insert(all_headers.end(), headers.begin(), headers.end());
 205  
 206                  auto headers_msg = NetMsg::Make(NetMsgType::HEADERS, TX_WITH_WITNESS(headers));
 207                  g_testing_setup->SendMessage(fuzzed_data_provider, std::move(headers_msg));
 208              },
 209              [&]() NO_THREAD_SAFETY_ANALYSIS {
 210                  // Send a compact block
 211                  auto block = finalized_block();
 212                  CBlockHeaderAndShortTxIDs cmpct_block{block, fuzzed_data_provider.ConsumeIntegral<uint64_t>()};
 213  
 214                  all_headers.push_back(block);
 215  
 216                  auto headers_msg = NetMsg::Make(NetMsgType::CMPCTBLOCK, TX_WITH_WITNESS(cmpct_block));
 217                  g_testing_setup->SendMessage(fuzzed_data_provider, std::move(headers_msg));
 218              },
 219              [&]() NO_THREAD_SAFETY_ANALYSIS {
 220                  // Send a block
 221                  auto block = finalized_block();
 222  
 223                  all_headers.push_back(block);
 224  
 225                  auto headers_msg = NetMsg::Make(NetMsgType::BLOCK, TX_WITH_WITNESS(block));
 226                  g_testing_setup->SendMessage(fuzzed_data_provider, std::move(headers_msg));
 227              });
 228      }
 229  
 230      // This is a conservative overestimate, as base is only moved forward when sending headers. In theory,
 231      // the longest chain generated by this test is 1600 (FUZZ_MAX_HEADERS_RESULTS * 100) headers. In that case,
 232      // this variable will accurately reflect the chain's total work.
 233      total_work += CalculateClaimedHeadersWork(all_headers);
 234  
 235      // This test should never create a chain with more work than MinimumChainWork.
 236      assert(total_work < chainman.MinimumChainWork());
 237  
 238      // The headers/blocks sent in this test should never be stored, as the chains don't have the work required
 239      // to meet the anti-DoS work threshold. So, if at any point the block index grew in size, then there's a bug
 240      // in the headers pre-sync logic.
 241      assert(WITH_LOCK(cs_main, return chainman.m_blockman.m_block_index.size()) == original_index_size);
 242  }
 243