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