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 }