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