p2p_headers_presync.cpp raw

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