/root/bitcoin/src/test/fuzz/p2p_headers_presync.cpp
Line | Count | Source |
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 | 0 | HeadersSyncSetup(const ChainType chain_type, TestOpts opts) : TestingSetup(chain_type, opts) |
31 | 0 | { |
32 | 0 | PeerManager::Options peerman_opts; |
33 | 0 | node::ApplyArgsManOptions(*m_node.args, peerman_opts); |
34 | 0 | peerman_opts.max_headers_result = FUZZ_MAX_HEADERS_RESULTS; |
35 | | // The peerman's rng is a global that is re-used, so it will be re-used |
36 | | // and may cause non-determinism between runs. This may even influence |
37 | | // the global RNG, because seeding may be done from the gloabl one. For |
38 | | // now, avoid it influencing the global RNG, and initialize it with a |
39 | | // constant instead. |
40 | 0 | peerman_opts.deterministic_rng = true; |
41 | | // No txs are relayed. Disable irrelevant and possibly |
42 | | // non-deterministic code paths. |
43 | 0 | peerman_opts.ignore_incoming_txs = true; |
44 | 0 | m_node.peerman = PeerManager::make(*m_node.connman, *m_node.addrman, |
45 | 0 | m_node.banman.get(), *m_node.chainman, |
46 | 0 | *m_node.mempool, *m_node.warnings, peerman_opts); |
47 | |
|
48 | 0 | CConnman::Options options; |
49 | 0 | options.m_msgproc = m_node.peerman.get(); |
50 | 0 | m_node.connman->Init(options); |
51 | 0 | } |
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 | 0 | { |
60 | 0 | m_connections.clear(); |
61 | 0 | auto& connman = static_cast<ConnmanTestMsg&>(*m_node.connman); |
62 | 0 | connman.StopNodes(); |
63 | |
|
64 | 0 | static NodeId id{0}; |
65 | 0 | std::vector<ConnectionType> conn_types = { |
66 | 0 | ConnectionType::OUTBOUND_FULL_RELAY, |
67 | 0 | ConnectionType::BLOCK_RELAY, |
68 | 0 | ConnectionType::INBOUND |
69 | 0 | }; |
70 | |
|
71 | 0 | for (auto conn_type : conn_types) { |
72 | 0 | CAddress addr{}; |
73 | 0 | m_connections.push_back(new CNode(id++, nullptr, addr, 0, 0, addr, "", conn_type, false)); |
74 | 0 | CNode& p2p_node = *m_connections.back(); |
75 | |
|
76 | 0 | connman.Handshake( |
77 | 0 | /*node=*/p2p_node, |
78 | 0 | /*successfully_connected=*/true, |
79 | 0 | /*remote_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS), |
80 | 0 | /*local_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS), |
81 | 0 | /*version=*/PROTOCOL_VERSION, |
82 | 0 | /*relay_txs=*/true); |
83 | |
|
84 | 0 | connman.AddTestNode(p2p_node); |
85 | 0 | } |
86 | 0 | } |
87 | | |
88 | | void HeadersSyncSetup::SendMessage(FuzzedDataProvider& fuzzed_data_provider, CSerializedNetMsg&& msg) |
89 | 0 | { |
90 | 0 | auto& connman = static_cast<ConnmanTestMsg&>(*m_node.connman); |
91 | 0 | CNode& connection = *PickValue(fuzzed_data_provider, m_connections); |
92 | |
|
93 | 0 | connman.FlushSendBuffer(connection); |
94 | 0 | (void)connman.ReceiveMsgFrom(connection, std::move(msg)); |
95 | 0 | connection.fPauseSend = false; |
96 | 0 | try { |
97 | 0 | connman.ProcessMessagesOnce(connection); |
98 | 0 | } catch (const std::ios_base::failure&) { |
99 | 0 | } |
100 | 0 | m_node.peerman->SendMessages(&connection); |
101 | 0 | } |
102 | | |
103 | | CBlockHeader ConsumeHeader(FuzzedDataProvider& fuzzed_data_provider, const uint256& prev_hash, uint32_t prev_nbits) |
104 | 0 | { |
105 | 0 | CBlockHeader header; |
106 | 0 | 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 | 0 | if (fuzzed_data_provider.ConsumeBool()) { |
117 | 0 | header.nBits = prev_nbits; |
118 | 0 | } else { |
119 | 0 | arith_uint256 lower_target = UintToArith256(uint256{"0000000000000000000342190000000000000000000000000000000000000000"}); |
120 | 0 | arith_uint256 upper_target = UintToArith256(uint256{"00000000ffff0000000000000000000000000000000000000000000000000000"}); |
121 | 0 | arith_uint256 target = ConsumeArithUInt256InRange(fuzzed_data_provider, lower_target, upper_target); |
122 | 0 | header.nBits = target.GetCompact(); |
123 | 0 | } |
124 | 0 | header.nTime = ConsumeTime(fuzzed_data_provider); |
125 | 0 | header.hashPrevBlock = prev_hash; |
126 | 0 | header.nVersion = fuzzed_data_provider.ConsumeIntegral<int32_t>(); |
127 | 0 | return header; |
128 | 0 | } |
129 | | |
130 | | CBlock ConsumeBlock(FuzzedDataProvider& fuzzed_data_provider, const uint256& prev_hash, uint32_t prev_nbits) |
131 | 0 | { |
132 | 0 | 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 | 0 | CBlock block{header}; |
136 | 0 | CMutableTransaction tx; |
137 | 0 | tx.vin.resize(1); |
138 | 0 | tx.vout.resize(1); |
139 | 0 | tx.vout[0].nValue = 0; |
140 | 0 | tx.vin[0].scriptSig.resize(2); |
141 | 0 | block.vtx.push_back(MakeTransactionRef(tx)); |
142 | 0 | block.hashMerkleRoot = block.vtx[0]->GetHash(); |
143 | 0 | return block; |
144 | 0 | } |
145 | | |
146 | | void FinalizeHeader(CBlockHeader& header, const ChainstateManager& chainman) |
147 | 0 | { |
148 | 0 | while (!CheckProofOfWork(header.GetHash(), header.nBits, chainman.GetParams().GetConsensus())) { |
149 | 0 | ++(header.nNonce); |
150 | 0 | } |
151 | 0 | } |
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 | 0 | { |
159 | 0 | static auto setup{ |
160 | 0 | MakeNoLogFileContext<HeadersSyncSetup>(ChainType::MAIN, |
161 | 0 | { |
162 | 0 | .setup_validation_interface = false, |
163 | 0 | }), |
164 | 0 | }; |
165 | 0 | g_testing_setup = setup.get(); |
166 | 0 | } |
167 | | } // namespace |
168 | | |
169 | | FUZZ_TARGET(p2p_headers_presync, .init = initialize) |
170 | 0 | { |
171 | 0 | SeedRandomStateForTest(SeedRand::ZEROS); |
172 | 0 | 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 | 0 | ElapseSteady elapse_steady{}; |
176 | |
|
177 | 0 | ChainstateManager& chainman = *g_testing_setup->m_node.chainman; |
178 | 0 | CBlockHeader base{chainman.GetParams().GenesisBlock()}; |
179 | 0 | SetMockTime(base.nTime); |
180 | |
|
181 | 0 | LOCK(NetEventsInterface::g_msgproc_mutex); |
182 | |
|
183 | 0 | g_testing_setup->ResetAndInitialize(); |
184 | | |
185 | | // The chain is just a single block, so this is equal to 1 |
186 | 0 | size_t original_index_size{WITH_LOCK(cs_main, return chainman.m_blockman.m_block_index.size())}; |
187 | 0 | arith_uint256 total_work{WITH_LOCK(cs_main, return chainman.m_best_header->nChainWork)}; |
188 | |
|
189 | 0 | std::vector<CBlockHeader> all_headers; |
190 | |
|
191 | 0 | LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100) |
192 | 0 | { |
193 | 0 | auto finalized_block = [&]() { |
194 | 0 | CBlock block = ConsumeBlock(fuzzed_data_provider, base.GetHash(), base.nBits); |
195 | 0 | FinalizeHeader(block, chainman); |
196 | 0 | return block; |
197 | 0 | }; |
198 | | |
199 | | // Send low-work headers, compact blocks, and blocks |
200 | 0 | CallOneOf( |
201 | 0 | fuzzed_data_provider, |
202 | 0 | [&]() NO_THREAD_SAFETY_ANALYSIS { |
203 | | // Send FUZZ_MAX_HEADERS_RESULTS headers |
204 | 0 | std::vector<CBlock> headers; |
205 | 0 | headers.resize(FUZZ_MAX_HEADERS_RESULTS); |
206 | 0 | for (CBlock& header : headers) { |
207 | 0 | header = ConsumeHeader(fuzzed_data_provider, base.GetHash(), base.nBits); |
208 | 0 | FinalizeHeader(header, chainman); |
209 | 0 | base = header; |
210 | 0 | } |
211 | |
|
212 | 0 | all_headers.insert(all_headers.end(), headers.begin(), headers.end()); |
213 | |
|
214 | 0 | auto headers_msg = NetMsg::Make(NetMsgType::HEADERS, TX_WITH_WITNESS(headers)); |
215 | 0 | g_testing_setup->SendMessage(fuzzed_data_provider, std::move(headers_msg)); |
216 | 0 | }, |
217 | 0 | [&]() NO_THREAD_SAFETY_ANALYSIS { |
218 | | // Send a compact block |
219 | 0 | auto block = finalized_block(); |
220 | 0 | CBlockHeaderAndShortTxIDs cmpct_block{block, fuzzed_data_provider.ConsumeIntegral<uint64_t>()}; |
221 | |
|
222 | 0 | all_headers.push_back(block); |
223 | |
|
224 | 0 | auto headers_msg = NetMsg::Make(NetMsgType::CMPCTBLOCK, TX_WITH_WITNESS(cmpct_block)); |
225 | 0 | g_testing_setup->SendMessage(fuzzed_data_provider, std::move(headers_msg)); |
226 | 0 | }, |
227 | 0 | [&]() NO_THREAD_SAFETY_ANALYSIS { |
228 | | // Send a block |
229 | 0 | auto block = finalized_block(); |
230 | |
|
231 | 0 | all_headers.push_back(block); |
232 | |
|
233 | 0 | auto headers_msg = NetMsg::Make(NetMsgType::BLOCK, TX_WITH_WITNESS(block)); |
234 | 0 | g_testing_setup->SendMessage(fuzzed_data_provider, std::move(headers_msg)); |
235 | 0 | }); |
236 | 0 | } |
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 | 0 | total_work += CalculateClaimedHeadersWork(all_headers); |
242 | | |
243 | | // This test should never create a chain with more work than MinimumChainWork. |
244 | 0 | 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 | 0 | assert(WITH_LOCK(cs_main, return chainman.m_blockman.m_block_index.size()) == original_index_size); |
250 | 0 | } |