/root/bitcoin/src/test/fuzz/txdownloadman.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) 2023 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 <consensus/validation.h> |
6 | | #include <node/context.h> |
7 | | #include <node/mempool_args.h> |
8 | | #include <node/miner.h> |
9 | | #include <node/txdownloadman.h> |
10 | | #include <node/txdownloadman_impl.h> |
11 | | #include <test/fuzz/FuzzedDataProvider.h> |
12 | | #include <test/fuzz/fuzz.h> |
13 | | #include <test/fuzz/util.h> |
14 | | #include <test/fuzz/util/mempool.h> |
15 | | #include <test/util/mining.h> |
16 | | #include <test/util/script.h> |
17 | | #include <test/util/setup_common.h> |
18 | | #include <test/util/txmempool.h> |
19 | | #include <util/hasher.h> |
20 | | #include <util/rbf.h> |
21 | | #include <txmempool.h> |
22 | | #include <validation.h> |
23 | | #include <validationinterface.h> |
24 | | |
25 | | namespace { |
26 | | |
27 | | const TestingSetup* g_setup; |
28 | | |
29 | | constexpr size_t NUM_COINS{50}; |
30 | | COutPoint COINS[NUM_COINS]; |
31 | | |
32 | | static TxValidationResult TESTED_TX_RESULTS[] = { |
33 | | // Skip TX_RESULT_UNSET |
34 | | TxValidationResult::TX_CONSENSUS, |
35 | | TxValidationResult::TX_RECENT_CONSENSUS_CHANGE, |
36 | | TxValidationResult::TX_INPUTS_NOT_STANDARD, |
37 | | TxValidationResult::TX_NOT_STANDARD, |
38 | | TxValidationResult::TX_MISSING_INPUTS, |
39 | | TxValidationResult::TX_PREMATURE_SPEND, |
40 | | TxValidationResult::TX_WITNESS_MUTATED, |
41 | | TxValidationResult::TX_WITNESS_STRIPPED, |
42 | | TxValidationResult::TX_CONFLICT, |
43 | | TxValidationResult::TX_MEMPOOL_POLICY, |
44 | | // Skip TX_NO_MEMPOOL |
45 | | TxValidationResult::TX_RECONSIDERABLE, |
46 | | TxValidationResult::TX_UNKNOWN, |
47 | | }; |
48 | | |
49 | | // Precomputed transactions. Some may conflict with each other. |
50 | | std::vector<CTransactionRef> TRANSACTIONS; |
51 | | |
52 | | // Limit the total number of peers because we don't expect coverage to change much with lots more peers. |
53 | | constexpr int NUM_PEERS = 16; |
54 | | |
55 | | // Precomputed random durations (positive and negative, each ~exponentially distributed). |
56 | | std::chrono::microseconds TIME_SKIPS[128]; |
57 | | |
58 | | static CTransactionRef MakeTransactionSpending(const std::vector<COutPoint>& outpoints, size_t num_outputs, bool add_witness) |
59 | 55.3k | { |
60 | 55.3k | CMutableTransaction tx; |
61 | | // If no outpoints are given, create a random one. |
62 | 206k | for (const auto& outpoint : outpoints) { |
63 | 206k | tx.vin.emplace_back(outpoint); |
64 | 206k | } |
65 | 55.3k | if (add_witness) { |
66 | 35.8k | tx.vin[0].scriptWitness.stack.push_back({1}); |
67 | 35.8k | } |
68 | 13.1M | for (size_t o = 0; o < num_outputs; ++o) tx.vout.emplace_back(CENT, P2WSH_OP_TRUE); |
69 | 55.3k | return MakeTransactionRef(tx); |
70 | 55.3k | } |
71 | | static std::vector<COutPoint> PickCoins(FuzzedDataProvider& fuzzed_data_provider) |
72 | 55.3k | { |
73 | 55.3k | std::vector<COutPoint> ret; |
74 | 55.3k | ret.push_back(fuzzed_data_provider.PickValueInArray(COINS)); |
75 | 150k | LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10) { |
76 | 150k | ret.push_back(fuzzed_data_provider.PickValueInArray(COINS)); |
77 | 150k | } |
78 | 55.3k | return ret; |
79 | 55.3k | } |
80 | | |
81 | | void initialize() |
82 | 0 | { |
83 | 0 | static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); |
84 | 0 | g_setup = testing_setup.get(); |
85 | 0 | for (uint32_t i = 0; i < uint32_t{NUM_COINS}; ++i) { |
86 | 0 | COINS[i] = COutPoint{Txid::FromUint256((HashWriter() << i).GetHash()), i}; |
87 | 0 | } |
88 | 0 | size_t outpoints_index = 0; |
89 | | // 2 transactions same txid different witness |
90 | 0 | { |
91 | 0 | auto tx1{MakeTransactionSpending({COINS[outpoints_index]}, /*num_outputs=*/5, /*add_witness=*/false)}; |
92 | 0 | auto tx2{MakeTransactionSpending({COINS[outpoints_index]}, /*num_outputs=*/5, /*add_witness=*/true)}; |
93 | 0 | Assert(tx1->GetHash() == tx2->GetHash()); |
94 | 0 | TRANSACTIONS.emplace_back(tx1); |
95 | 0 | TRANSACTIONS.emplace_back(tx2); |
96 | 0 | outpoints_index += 1; |
97 | 0 | } |
98 | | // 2 parents 1 child |
99 | 0 | { |
100 | 0 | auto tx_parent_1{MakeTransactionSpending({COINS[outpoints_index++]}, /*num_outputs=*/1, /*add_witness=*/true)}; |
101 | 0 | TRANSACTIONS.emplace_back(tx_parent_1); |
102 | 0 | auto tx_parent_2{MakeTransactionSpending({COINS[outpoints_index++]}, /*num_outputs=*/1, /*add_witness=*/false)}; |
103 | 0 | TRANSACTIONS.emplace_back(tx_parent_2); |
104 | 0 | TRANSACTIONS.emplace_back(MakeTransactionSpending({COutPoint{tx_parent_1->GetHash(), 0}, COutPoint{tx_parent_2->GetHash(), 0}}, |
105 | 0 | /*num_outputs=*/1, /*add_witness=*/true)); |
106 | 0 | } |
107 | | // 1 parent 2 children |
108 | 0 | { |
109 | 0 | auto tx_parent{MakeTransactionSpending({COINS[outpoints_index++]}, /*num_outputs=*/2, /*add_witness=*/true)}; |
110 | 0 | TRANSACTIONS.emplace_back(tx_parent); |
111 | 0 | TRANSACTIONS.emplace_back(MakeTransactionSpending({COutPoint{tx_parent->GetHash(), 0}}, |
112 | 0 | /*num_outputs=*/1, /*add_witness=*/true)); |
113 | 0 | TRANSACTIONS.emplace_back(MakeTransactionSpending({COutPoint{tx_parent->GetHash(), 1}}, |
114 | 0 | /*num_outputs=*/1, /*add_witness=*/true)); |
115 | 0 | } |
116 | | // chain of 5 segwit |
117 | 0 | { |
118 | 0 | COutPoint& last_outpoint = COINS[outpoints_index++]; |
119 | 0 | for (auto i{0}; i < 5; ++i) { |
120 | 0 | auto tx{MakeTransactionSpending({last_outpoint}, /*num_outputs=*/1, /*add_witness=*/true)}; |
121 | 0 | TRANSACTIONS.emplace_back(tx); |
122 | 0 | last_outpoint = COutPoint{tx->GetHash(), 0}; |
123 | 0 | } |
124 | 0 | } |
125 | | // chain of 5 non-segwit |
126 | 0 | { |
127 | 0 | COutPoint& last_outpoint = COINS[outpoints_index++]; |
128 | 0 | for (auto i{0}; i < 5; ++i) { |
129 | 0 | auto tx{MakeTransactionSpending({last_outpoint}, /*num_outputs=*/1, /*add_witness=*/false)}; |
130 | 0 | TRANSACTIONS.emplace_back(tx); |
131 | 0 | last_outpoint = COutPoint{tx->GetHash(), 0}; |
132 | 0 | } |
133 | 0 | } |
134 | | // Also create a loose tx for each outpoint. Some of these transactions conflict with the above |
135 | | // or have the same txid. |
136 | 0 | for (const auto& outpoint : COINS) { |
137 | 0 | TRANSACTIONS.emplace_back(MakeTransactionSpending({outpoint}, /*num_outputs=*/1, /*add_witness=*/true)); |
138 | 0 | } |
139 | | |
140 | | // Create random-looking time jumps |
141 | 0 | int i = 0; |
142 | | // TIME_SKIPS[N] for N=0..15 is just N microseconds. |
143 | 0 | for (; i < 16; ++i) { |
144 | 0 | TIME_SKIPS[i] = std::chrono::microseconds{i}; |
145 | 0 | } |
146 | | // TIME_SKIPS[N] for N=16..127 has randomly-looking but roughly exponentially increasing values up to |
147 | | // 198.416453 seconds. |
148 | 0 | for (; i < 128; ++i) { |
149 | 0 | int diff_bits = ((i - 10) * 2) / 9; |
150 | 0 | uint64_t diff = 1 + (CSipHasher(0, 0).Write(i).Finalize() >> (64 - diff_bits)); |
151 | 0 | TIME_SKIPS[i] = TIME_SKIPS[i - 1] + std::chrono::microseconds{diff}; |
152 | 0 | } |
153 | 0 | } |
154 | | |
155 | | void CheckPackageToValidate(const node::PackageToValidate& package_to_validate, NodeId peer) |
156 | 3.18k | { |
157 | 3.18k | Assert(package_to_validate.m_senders.size() == 2); |
158 | 3.18k | Assert(package_to_validate.m_senders.front() == peer); |
159 | 3.18k | Assert(package_to_validate.m_senders.back() < NUM_PEERS); |
160 | | |
161 | | // Package is a 1p1c |
162 | 3.18k | const auto& package = package_to_validate.m_txns; |
163 | 3.18k | Assert(IsChildWithParents(package)); |
164 | 3.18k | Assert(package.size() == 2); |
165 | 3.18k | } |
166 | | |
167 | | FUZZ_TARGET(txdownloadman, .init = initialize) |
168 | 3.78k | { |
169 | 3.78k | FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); |
170 | | |
171 | | // Initialize txdownloadman |
172 | 3.78k | bilingual_str error; |
173 | 3.78k | CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error}; |
174 | 3.78k | const auto max_orphan_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 300); |
175 | 3.78k | FastRandomContext det_rand{true}; |
176 | 3.78k | node::TxDownloadManager txdownloadman{node::TxDownloadOptions{pool, det_rand, max_orphan_count, true}}; |
177 | | |
178 | 3.78k | std::chrono::microseconds time{244466666}; |
179 | | |
180 | 3.78k | LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) |
181 | 89.9k | { |
182 | 89.9k | NodeId rand_peer = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, NUM_PEERS - 1); |
183 | | |
184 | | // Transaction can be one of the premade ones or a randomly generated one |
185 | 89.9k | auto rand_tx = fuzzed_data_provider.ConsumeBool() ? |
186 | 55.3k | MakeTransactionSpending(PickCoins(fuzzed_data_provider), |
187 | 55.3k | /*num_outputs=*/fuzzed_data_provider.ConsumeIntegralInRange(1, 500), |
188 | 55.3k | /*add_witness=*/fuzzed_data_provider.ConsumeBool()) : |
189 | 89.9k | TRANSACTIONS.at(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, TRANSACTIONS.size() - 1)); |
190 | | |
191 | 89.9k | CallOneOf( |
192 | 89.9k | fuzzed_data_provider, |
193 | 89.9k | [&] { |
194 | 9.34k | node::TxDownloadConnectionInfo info{ |
195 | 9.34k | .m_preferred = fuzzed_data_provider.ConsumeBool(), |
196 | 9.34k | .m_relay_permissions = fuzzed_data_provider.ConsumeBool(), |
197 | 9.34k | .m_wtxid_relay = fuzzed_data_provider.ConsumeBool() |
198 | 9.34k | }; |
199 | 9.34k | txdownloadman.ConnectedPeer(rand_peer, info); |
200 | 9.34k | }, |
201 | 89.9k | [&] { |
202 | 2.27k | txdownloadman.DisconnectedPeer(rand_peer); |
203 | 2.27k | txdownloadman.CheckIsEmpty(rand_peer); |
204 | 2.27k | }, |
205 | 89.9k | [&] { |
206 | 2.18k | txdownloadman.ActiveTipChange(); |
207 | 2.18k | }, |
208 | 89.9k | [&] { |
209 | 6.85k | CBlock block; |
210 | 6.85k | block.vtx.push_back(rand_tx); |
211 | 6.85k | txdownloadman.BlockConnected(std::make_shared<CBlock>(block)); |
212 | 6.85k | }, |
213 | 89.9k | [&] { |
214 | 3.14k | txdownloadman.BlockDisconnected(); |
215 | 3.14k | }, |
216 | 89.9k | [&] { |
217 | 2.47k | txdownloadman.MempoolAcceptedTx(rand_tx); |
218 | 2.47k | }, |
219 | 89.9k | [&] { |
220 | 22.5k | TxValidationState state; |
221 | 22.5k | state.Invalid(fuzzed_data_provider.PickValueInArray(TESTED_TX_RESULTS), ""); |
222 | 22.5k | bool first_time_failure{fuzzed_data_provider.ConsumeBool()}; |
223 | | |
224 | 22.5k | node::RejectedTxTodo todo = txdownloadman.MempoolRejectedTx(rand_tx, state, rand_peer, first_time_failure); |
225 | 22.5k | Assert(first_time_failure || !todo.m_should_add_extra_compact_tx); |
226 | 22.5k | }, |
227 | 89.9k | [&] { |
228 | 19.5k | GenTxid gtxid = fuzzed_data_provider.ConsumeBool() ? |
229 | 16.5k | GenTxid::Txid(rand_tx->GetHash()) : |
230 | 19.5k | GenTxid::Wtxid(rand_tx->GetWitnessHash()); |
231 | 19.5k | txdownloadman.AddTxAnnouncement(rand_peer, gtxid, time, /*p2p_inv=*/fuzzed_data_provider.ConsumeBool()); |
232 | 19.5k | }, |
233 | 89.9k | [&] { |
234 | 11.1k | txdownloadman.GetRequestsToSend(rand_peer, time); |
235 | 11.1k | }, |
236 | 89.9k | [&] { |
237 | 7.78k | txdownloadman.ReceivedTx(rand_peer, rand_tx); |
238 | 7.78k | const auto& [should_validate, maybe_package] = txdownloadman.ReceivedTx(rand_peer, rand_tx); |
239 | | // The only possible results should be: |
240 | | // - Don't validate the tx, no package. |
241 | | // - Don't validate the tx, package. |
242 | | // - Validate the tx, no package. |
243 | | // The only combination that doesn't make sense is validate both tx and package. |
244 | 7.78k | Assert(!(should_validate && maybe_package.has_value())); |
245 | 7.78k | if (maybe_package.has_value()) CheckPackageToValidate(*maybe_package, rand_peer); |
246 | 7.78k | }, |
247 | 89.9k | [&] { |
248 | 524 | txdownloadman.ReceivedNotFound(rand_peer, {rand_tx->GetWitnessHash()}); |
249 | 524 | }, |
250 | 89.9k | [&] { |
251 | 2.11k | const bool expect_work{txdownloadman.HaveMoreWork(rand_peer)}; |
252 | 2.11k | const auto ptx = txdownloadman.GetTxToReconsider(rand_peer); |
253 | | // expect_work=true doesn't necessarily mean the next item from the workset isn't a |
254 | | // nullptr, as the transaction could have been removed from orphanage without being |
255 | | // removed from the peer's workset. |
256 | 2.11k | if (ptx) { |
257 | | // However, if there was a non-null tx in the workset, HaveMoreWork should have |
258 | | // returned true. |
259 | 109 | Assert(expect_work); |
260 | 109 | } |
261 | 2.11k | } |
262 | 89.9k | ); |
263 | | // Jump forwards or backwards |
264 | 89.9k | auto time_skip = fuzzed_data_provider.PickValueInArray(TIME_SKIPS); |
265 | 89.9k | if (fuzzed_data_provider.ConsumeBool()) time_skip *= -1; |
266 | 89.9k | time += time_skip; |
267 | 89.9k | } |
268 | | // Disconnect everybody, check that all data structures are empty. |
269 | 64.3k | for (NodeId nodeid = 0; nodeid < NUM_PEERS; ++nodeid) { |
270 | 60.5k | txdownloadman.DisconnectedPeer(nodeid); |
271 | 60.5k | txdownloadman.CheckIsEmpty(nodeid); |
272 | 60.5k | } |
273 | 3.78k | txdownloadman.CheckIsEmpty(); |
274 | 3.78k | } |
275 | | |
276 | | // Give node 0 relay permissions, and nobody else. This helps us remember who is a RelayPermissions |
277 | | // peer without tracking anything (this is only for the txdownload_impl target). |
278 | 0 | static bool HasRelayPermissions(NodeId peer) { return peer == 0; } |
279 | | |
280 | | static void CheckInvariants(const node::TxDownloadManagerImpl& txdownload_impl, size_t max_orphan_count) |
281 | 0 | { |
282 | 0 | const TxOrphanage& orphanage = txdownload_impl.m_orphanage; |
283 | | |
284 | | // Orphanage usage should never exceed what is allowed |
285 | 0 | Assert(orphanage.Size() <= max_orphan_count); |
286 | | |
287 | | // We should never have more than the maximum in-flight requests out for a peer. |
288 | 0 | for (NodeId peer = 0; peer < NUM_PEERS; ++peer) { |
289 | 0 | if (!HasRelayPermissions(peer)) { |
290 | 0 | Assert(txdownload_impl.m_txrequest.CountInFlight(peer) <= node::MAX_PEER_TX_REQUEST_IN_FLIGHT); |
291 | 0 | } |
292 | 0 | } |
293 | 0 | txdownload_impl.m_txrequest.SanityCheck(); |
294 | 0 | } |
295 | | |
296 | | FUZZ_TARGET(txdownloadman_impl, .init = initialize) |
297 | 0 | { |
298 | 0 | FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); |
299 | | |
300 | | // Initialize a TxDownloadManagerImpl |
301 | 0 | bilingual_str error; |
302 | 0 | CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error}; |
303 | 0 | const auto max_orphan_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 300); |
304 | 0 | FastRandomContext det_rand{true}; |
305 | 0 | node::TxDownloadManagerImpl txdownload_impl{node::TxDownloadOptions{pool, det_rand, max_orphan_count, true}}; |
306 | |
|
307 | 0 | std::chrono::microseconds time{244466666}; |
308 | |
|
309 | 0 | LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) |
310 | 0 | { |
311 | 0 | NodeId rand_peer = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, NUM_PEERS - 1); |
312 | | |
313 | | // Transaction can be one of the premade ones or a randomly generated one |
314 | 0 | auto rand_tx = fuzzed_data_provider.ConsumeBool() ? |
315 | 0 | MakeTransactionSpending(PickCoins(fuzzed_data_provider), |
316 | 0 | /*num_outputs=*/fuzzed_data_provider.ConsumeIntegralInRange(1, 500), |
317 | 0 | /*add_witness=*/fuzzed_data_provider.ConsumeBool()) : |
318 | 0 | TRANSACTIONS.at(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, TRANSACTIONS.size() - 1)); |
319 | |
|
320 | 0 | CallOneOf( |
321 | 0 | fuzzed_data_provider, |
322 | 0 | [&] { |
323 | 0 | node::TxDownloadConnectionInfo info{ |
324 | 0 | .m_preferred = fuzzed_data_provider.ConsumeBool(), |
325 | 0 | .m_relay_permissions = HasRelayPermissions(rand_peer), |
326 | 0 | .m_wtxid_relay = fuzzed_data_provider.ConsumeBool() |
327 | 0 | }; |
328 | 0 | txdownload_impl.ConnectedPeer(rand_peer, info); |
329 | 0 | }, |
330 | 0 | [&] { |
331 | 0 | txdownload_impl.DisconnectedPeer(rand_peer); |
332 | 0 | txdownload_impl.CheckIsEmpty(rand_peer); |
333 | 0 | }, |
334 | 0 | [&] { |
335 | 0 | txdownload_impl.ActiveTipChange(); |
336 | | // After a block update, nothing should be in the rejection caches |
337 | 0 | for (const auto& tx : TRANSACTIONS) { |
338 | 0 | Assert(!txdownload_impl.RecentRejectsFilter().contains(tx->GetWitnessHash().ToUint256())); |
339 | 0 | Assert(!txdownload_impl.RecentRejectsFilter().contains(tx->GetHash().ToUint256())); |
340 | 0 | Assert(!txdownload_impl.RecentRejectsReconsiderableFilter().contains(tx->GetWitnessHash().ToUint256())); |
341 | 0 | Assert(!txdownload_impl.RecentRejectsReconsiderableFilter().contains(tx->GetHash().ToUint256())); |
342 | 0 | } |
343 | 0 | }, |
344 | 0 | [&] { |
345 | 0 | CBlock block; |
346 | 0 | block.vtx.push_back(rand_tx); |
347 | 0 | txdownload_impl.BlockConnected(std::make_shared<CBlock>(block)); |
348 | | // Block transactions must be removed from orphanage |
349 | 0 | Assert(!txdownload_impl.m_orphanage.HaveTx(rand_tx->GetWitnessHash())); |
350 | 0 | }, |
351 | 0 | [&] { |
352 | 0 | txdownload_impl.BlockDisconnected(); |
353 | 0 | Assert(!txdownload_impl.RecentConfirmedTransactionsFilter().contains(rand_tx->GetWitnessHash().ToUint256())); |
354 | 0 | Assert(!txdownload_impl.RecentConfirmedTransactionsFilter().contains(rand_tx->GetHash().ToUint256())); |
355 | 0 | }, |
356 | 0 | [&] { |
357 | 0 | txdownload_impl.MempoolAcceptedTx(rand_tx); |
358 | 0 | }, |
359 | 0 | [&] { |
360 | 0 | TxValidationState state; |
361 | 0 | state.Invalid(fuzzed_data_provider.PickValueInArray(TESTED_TX_RESULTS), ""); |
362 | 0 | bool first_time_failure{fuzzed_data_provider.ConsumeBool()}; |
363 | |
|
364 | 0 | bool reject_contains_wtxid{txdownload_impl.RecentRejectsFilter().contains(rand_tx->GetWitnessHash().ToUint256())}; |
365 | |
|
366 | 0 | node::RejectedTxTodo todo = txdownload_impl.MempoolRejectedTx(rand_tx, state, rand_peer, first_time_failure); |
367 | 0 | Assert(first_time_failure || !todo.m_should_add_extra_compact_tx); |
368 | 0 | if (!reject_contains_wtxid) Assert(todo.m_unique_parents.size() <= rand_tx->vin.size()); |
369 | 0 | }, |
370 | 0 | [&] { |
371 | 0 | GenTxid gtxid = fuzzed_data_provider.ConsumeBool() ? |
372 | 0 | GenTxid::Txid(rand_tx->GetHash()) : |
373 | 0 | GenTxid::Wtxid(rand_tx->GetWitnessHash()); |
374 | 0 | txdownload_impl.AddTxAnnouncement(rand_peer, gtxid, time, /*p2p_inv=*/fuzzed_data_provider.ConsumeBool()); |
375 | 0 | }, |
376 | 0 | [&] { |
377 | 0 | const auto getdata_requests = txdownload_impl.GetRequestsToSend(rand_peer, time); |
378 | | // TxDownloadManager should not be telling us to request things we already have. |
379 | | // Exclude m_lazy_recent_rejects_reconsiderable because it may request low-feerate parent of orphan. |
380 | 0 | for (const auto& gtxid : getdata_requests) { |
381 | 0 | Assert(!txdownload_impl.AlreadyHaveTx(gtxid, /*include_reconsiderable=*/false)); |
382 | 0 | } |
383 | 0 | }, |
384 | 0 | [&] { |
385 | 0 | const auto& [should_validate, maybe_package] = txdownload_impl.ReceivedTx(rand_peer, rand_tx); |
386 | | // The only possible results should be: |
387 | | // - Don't validate the tx, no package. |
388 | | // - Don't validate the tx, package. |
389 | | // - Validate the tx, no package. |
390 | | // The only combination that doesn't make sense is validate both tx and package. |
391 | 0 | Assert(!(should_validate && maybe_package.has_value())); |
392 | 0 | if (should_validate) { |
393 | 0 | Assert(!txdownload_impl.AlreadyHaveTx(GenTxid::Wtxid(rand_tx->GetWitnessHash()), /*include_reconsiderable=*/true)); |
394 | 0 | } |
395 | 0 | if (maybe_package.has_value()) { |
396 | 0 | CheckPackageToValidate(*maybe_package, rand_peer); |
397 | |
|
398 | 0 | const auto& package = maybe_package->m_txns; |
399 | | // Parent is in m_lazy_recent_rejects_reconsiderable and child is in m_orphanage |
400 | 0 | Assert(txdownload_impl.RecentRejectsReconsiderableFilter().contains(rand_tx->GetWitnessHash().ToUint256())); |
401 | 0 | Assert(txdownload_impl.m_orphanage.HaveTx(maybe_package->m_txns.back()->GetWitnessHash())); |
402 | | // Package has not been rejected |
403 | 0 | Assert(!txdownload_impl.RecentRejectsReconsiderableFilter().contains(GetPackageHash(package))); |
404 | | // Neither is in m_lazy_recent_rejects |
405 | 0 | Assert(!txdownload_impl.RecentRejectsFilter().contains(package.front()->GetWitnessHash().ToUint256())); |
406 | 0 | Assert(!txdownload_impl.RecentRejectsFilter().contains(package.back()->GetWitnessHash().ToUint256())); |
407 | 0 | } |
408 | 0 | }, |
409 | 0 | [&] { |
410 | 0 | txdownload_impl.ReceivedNotFound(rand_peer, {rand_tx->GetWitnessHash()}); |
411 | 0 | }, |
412 | 0 | [&] { |
413 | 0 | const bool expect_work{txdownload_impl.HaveMoreWork(rand_peer)}; |
414 | 0 | const auto ptx{txdownload_impl.GetTxToReconsider(rand_peer)}; |
415 | | // expect_work=true doesn't necessarily mean the next item from the workset isn't a |
416 | | // nullptr, as the transaction could have been removed from orphanage without being |
417 | | // removed from the peer's workset. |
418 | 0 | if (ptx) { |
419 | | // However, if there was a non-null tx in the workset, HaveMoreWork should have |
420 | | // returned true. |
421 | 0 | Assert(expect_work); |
422 | 0 | Assert(txdownload_impl.AlreadyHaveTx(GenTxid::Wtxid(ptx->GetWitnessHash()), /*include_reconsiderable=*/false)); |
423 | | // Presumably we have validated this tx. Use "missing inputs" to keep it in the |
424 | | // orphanage longer. Later iterations might call MempoolAcceptedTx or |
425 | | // MempoolRejectedTx with a different error. |
426 | 0 | TxValidationState state_missing_inputs; |
427 | 0 | state_missing_inputs.Invalid(TxValidationResult::TX_MISSING_INPUTS, ""); |
428 | 0 | txdownload_impl.MempoolRejectedTx(ptx, state_missing_inputs, rand_peer, fuzzed_data_provider.ConsumeBool()); |
429 | 0 | } |
430 | 0 | } |
431 | 0 | ); |
432 | | |
433 | | // Jump ahead in time |
434 | 0 | time += fuzzed_data_provider.PickValueInArray(TIME_SKIPS); |
435 | 0 | CheckInvariants(txdownload_impl, max_orphan_count); |
436 | 0 | } |
437 | | // Disconnect everybody, check that all data structures are empty. |
438 | 0 | for (NodeId nodeid = 0; nodeid < NUM_PEERS; ++nodeid) { |
439 | 0 | txdownload_impl.DisconnectedPeer(nodeid); |
440 | 0 | txdownload_impl.CheckIsEmpty(nodeid); |
441 | 0 | } |
442 | 0 | txdownload_impl.CheckIsEmpty(); |
443 | 0 | } |
444 | | |
445 | | } // namespace |