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