/root/bitcoin/src/test/fuzz/txdownloadman.cpp
Line | Count | Source |
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 <util/time.h> |
22 | | #include <txmempool.h> |
23 | | #include <validation.h> |
24 | | #include <validationinterface.h> |
25 | | |
26 | | namespace { |
27 | | |
28 | | const TestingSetup* g_setup; |
29 | | |
30 | | constexpr size_t NUM_COINS{50}; |
31 | | COutPoint COINS[NUM_COINS]; |
32 | | |
33 | | static TxValidationResult TESTED_TX_RESULTS[] = { |
34 | | // Skip TX_RESULT_UNSET |
35 | | TxValidationResult::TX_CONSENSUS, |
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 | 120k | { |
60 | 120k | CMutableTransaction tx; |
61 | | // If no outpoints are given, create a random one. |
62 | 536k | for (const auto& outpoint : outpoints) { |
63 | 536k | tx.vin.emplace_back(outpoint); |
64 | 536k | } |
65 | 120k | if (add_witness) { |
66 | 77.1k | tx.vin[0].scriptWitness.stack.push_back({1}); |
67 | 77.1k | } |
68 | 23.1M | for (size_t o = 0; o < num_outputs; ++o) tx.vout.emplace_back(CENT, P2WSH_OP_TRUE); |
69 | 120k | return MakeTransactionRef(tx); |
70 | 120k | } |
71 | | static std::vector<COutPoint> PickCoins(FuzzedDataProvider& fuzzed_data_provider) |
72 | 120k | { |
73 | 120k | std::vector<COutPoint> ret; |
74 | 120k | ret.push_back(fuzzed_data_provider.PickValueInArray(COINS)); |
75 | 416k | LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10) { |
76 | 416k | ret.push_back(fuzzed_data_provider.PickValueInArray(COINS)); |
77 | 416k | } |
78 | 120k | return ret; |
79 | 120k | } |
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.90k | { |
157 | 3.90k | Assert(package_to_validate.m_senders.size() == 2); |
158 | 3.90k | Assert(package_to_validate.m_senders.front() == peer); |
159 | 3.90k | Assert(package_to_validate.m_senders.back() < NUM_PEERS); |
160 | | |
161 | | // Package is a 1p1c |
162 | 3.90k | const auto& package = package_to_validate.m_txns; |
163 | 3.90k | Assert(IsChildWithParents(package)); |
164 | 3.90k | Assert(package.size() == 2); |
165 | 3.90k | } |
166 | | |
167 | | FUZZ_TARGET(txdownloadman, .init = initialize) |
168 | 559 | { |
169 | 559 | SeedRandomStateForTest(SeedRand::ZEROS); |
170 | 559 | FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); |
171 | 559 | SetMockTime(ConsumeTime(fuzzed_data_provider)); |
172 | | |
173 | | // Initialize txdownloadman |
174 | 559 | bilingual_str error; |
175 | 559 | CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error}; |
176 | 559 | FastRandomContext det_rand{true}; |
177 | 559 | node::TxDownloadManager txdownloadman{node::TxDownloadOptions{pool, det_rand, true}}; |
178 | | |
179 | 559 | std::chrono::microseconds time{244466666}; |
180 | | |
181 | 559 | LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 500) |
182 | 163k | { |
183 | 163k | NodeId rand_peer = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, NUM_PEERS - 1); |
184 | | |
185 | | // Transaction can be one of the premade ones or a randomly generated one |
186 | 163k | auto rand_tx = fuzzed_data_provider.ConsumeBool() ? |
187 | 120k | MakeTransactionSpending(PickCoins(fuzzed_data_provider), |
188 | 120k | /*num_outputs=*/fuzzed_data_provider.ConsumeIntegralInRange(1, 500), |
189 | 120k | /*add_witness=*/fuzzed_data_provider.ConsumeBool()) : |
190 | 163k | TRANSACTIONS.at(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, TRANSACTIONS.size() - 1)); |
191 | | |
192 | 163k | CallOneOf( |
193 | 163k | fuzzed_data_provider, |
194 | 163k | [&] { |
195 | 18.1k | node::TxDownloadConnectionInfo info{ |
196 | 18.1k | .m_preferred = fuzzed_data_provider.ConsumeBool(), |
197 | 18.1k | .m_relay_permissions = fuzzed_data_provider.ConsumeBool(), |
198 | 18.1k | .m_wtxid_relay = fuzzed_data_provider.ConsumeBool() |
199 | 18.1k | }; |
200 | 18.1k | txdownloadman.ConnectedPeer(rand_peer, info); |
201 | 18.1k | }, |
202 | 163k | [&] { |
203 | 9.26k | txdownloadman.DisconnectedPeer(rand_peer); |
204 | 9.26k | txdownloadman.CheckIsEmpty(rand_peer); |
205 | 9.26k | }, |
206 | 163k | [&] { |
207 | 932 | txdownloadman.ActiveTipChange(); |
208 | 932 | }, |
209 | 163k | [&] { |
210 | 10.4k | CBlock block; |
211 | 10.4k | block.vtx.push_back(rand_tx); |
212 | 10.4k | txdownloadman.BlockConnected(std::make_shared<CBlock>(block)); |
213 | 10.4k | }, |
214 | 163k | [&] { |
215 | 5.71k | txdownloadman.BlockDisconnected(); |
216 | 5.71k | }, |
217 | 163k | [&] { |
218 | 12.5k | txdownloadman.MempoolAcceptedTx(rand_tx); |
219 | 12.5k | }, |
220 | 163k | [&] { |
221 | 47.6k | TxValidationState state; |
222 | 47.6k | state.Invalid(fuzzed_data_provider.PickValueInArray(TESTED_TX_RESULTS), ""); |
223 | 47.6k | bool first_time_failure{fuzzed_data_provider.ConsumeBool()}; |
224 | | |
225 | 47.6k | node::RejectedTxTodo todo = txdownloadman.MempoolRejectedTx(rand_tx, state, rand_peer, first_time_failure); |
226 | 47.6k | Assert(first_time_failure || !todo.m_should_add_extra_compact_tx); |
227 | 47.6k | }, |
228 | 163k | [&] { |
229 | 29.8k | auto gtxid = fuzzed_data_provider.ConsumeBool() ? |
230 | 23.6k | GenTxid{rand_tx->GetHash()} : |
231 | 29.8k | GenTxid{rand_tx->GetWitnessHash()}; |
232 | 29.8k | txdownloadman.AddTxAnnouncement(rand_peer, gtxid, time); |
233 | 29.8k | }, |
234 | 163k | [&] { |
235 | 8.55k | txdownloadman.GetRequestsToSend(rand_peer, time); |
236 | 8.55k | }, |
237 | 163k | [&] { |
238 | 11.0k | txdownloadman.ReceivedTx(rand_peer, rand_tx); |
239 | 11.0k | const auto& [should_validate, maybe_package] = txdownloadman.ReceivedTx(rand_peer, rand_tx); |
240 | | // The only possible results should be: |
241 | | // - Don't validate the tx, no package. |
242 | | // - Don't validate the tx, package. |
243 | | // - Validate the tx, no package. |
244 | | // The only combination that doesn't make sense is validate both tx and package. |
245 | 11.0k | Assert(!(should_validate && maybe_package.has_value())); |
246 | 11.0k | if (maybe_package.has_value()) CheckPackageToValidate(*maybe_package, rand_peer); |
247 | 11.0k | }, |
248 | 163k | [&] { |
249 | 2.03k | txdownloadman.ReceivedNotFound(rand_peer, {rand_tx->GetWitnessHash()}); |
250 | 2.03k | }, |
251 | 163k | [&] { |
252 | 7.16k | const bool expect_work{txdownloadman.HaveMoreWork(rand_peer)}; |
253 | 7.16k | const auto ptx = txdownloadman.GetTxToReconsider(rand_peer); |
254 | | // expect_work=true doesn't necessarily mean the next item from the workset isn't a |
255 | | // nullptr, as the transaction could have been removed from orphanage without being |
256 | | // removed from the peer's workset. |
257 | 7.16k | if (ptx) { |
258 | | // However, if there was a non-null tx in the workset, HaveMoreWork should have |
259 | | // returned true. |
260 | 1.49k | Assert(expect_work); |
261 | 1.49k | } |
262 | 7.16k | }); |
263 | | // Jump forwards or backwards |
264 | 163k | auto time_skip = fuzzed_data_provider.PickValueInArray(TIME_SKIPS); |
265 | 163k | if (fuzzed_data_provider.ConsumeBool()) time_skip *= -1; |
266 | 163k | time += time_skip; |
267 | 163k | } |
268 | | // Disconnect everybody, check that all data structures are empty. |
269 | 9.50k | for (NodeId nodeid = 0; nodeid < NUM_PEERS; ++nodeid) { |
270 | 8.94k | txdownloadman.DisconnectedPeer(nodeid); |
271 | 8.94k | txdownloadman.CheckIsEmpty(nodeid); |
272 | 8.94k | } |
273 | 559 | txdownloadman.CheckIsEmpty(); |
274 | 559 | } |
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) |
281 | 0 | { |
282 | 0 | txdownload_impl.m_orphanage->SanityCheck(); |
283 | | // We should never have more than the maximum in-flight requests out for a peer. |
284 | 0 | for (NodeId peer = 0; peer < NUM_PEERS; ++peer) { |
285 | 0 | if (!HasRelayPermissions(peer)) { |
286 | 0 | Assert(txdownload_impl.m_txrequest.Count(peer) <= node::MAX_PEER_TX_ANNOUNCEMENTS); |
287 | 0 | } |
288 | 0 | } |
289 | 0 | txdownload_impl.m_txrequest.SanityCheck(); |
290 | 0 | } |
291 | | |
292 | | FUZZ_TARGET(txdownloadman_impl, .init = initialize) |
293 | 0 | { |
294 | 0 | SeedRandomStateForTest(SeedRand::ZEROS); |
295 | 0 | FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); |
296 | 0 | SetMockTime(ConsumeTime(fuzzed_data_provider)); |
297 | | |
298 | | // Initialize a TxDownloadManagerImpl |
299 | 0 | bilingual_str error; |
300 | 0 | CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error}; |
301 | 0 | FastRandomContext det_rand{true}; |
302 | 0 | node::TxDownloadManagerImpl txdownload_impl{node::TxDownloadOptions{pool, det_rand, true}}; |
303 | |
|
304 | 0 | std::chrono::microseconds time{244466666}; |
305 | |
|
306 | 0 | LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 500) |
307 | 0 | { |
308 | 0 | NodeId rand_peer = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, NUM_PEERS - 1); |
309 | | |
310 | | // Transaction can be one of the premade ones or a randomly generated one |
311 | 0 | auto rand_tx = fuzzed_data_provider.ConsumeBool() ? |
312 | 0 | MakeTransactionSpending(PickCoins(fuzzed_data_provider), |
313 | 0 | /*num_outputs=*/fuzzed_data_provider.ConsumeIntegralInRange(1, 500), |
314 | 0 | /*add_witness=*/fuzzed_data_provider.ConsumeBool()) : |
315 | 0 | TRANSACTIONS.at(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, TRANSACTIONS.size() - 1)); |
316 | |
|
317 | 0 | CallOneOf( |
318 | 0 | fuzzed_data_provider, |
319 | 0 | [&] { |
320 | 0 | node::TxDownloadConnectionInfo info{ |
321 | 0 | .m_preferred = fuzzed_data_provider.ConsumeBool(), |
322 | 0 | .m_relay_permissions = HasRelayPermissions(rand_peer), |
323 | 0 | .m_wtxid_relay = fuzzed_data_provider.ConsumeBool() |
324 | 0 | }; |
325 | 0 | txdownload_impl.ConnectedPeer(rand_peer, info); |
326 | 0 | }, |
327 | 0 | [&] { |
328 | 0 | txdownload_impl.DisconnectedPeer(rand_peer); |
329 | 0 | txdownload_impl.CheckIsEmpty(rand_peer); |
330 | 0 | }, |
331 | 0 | [&] { |
332 | 0 | txdownload_impl.ActiveTipChange(); |
333 | | // After a block update, nothing should be in the rejection caches |
334 | 0 | for (const auto& tx : TRANSACTIONS) { |
335 | 0 | Assert(!txdownload_impl.RecentRejectsFilter().contains(tx->GetWitnessHash().ToUint256())); |
336 | 0 | Assert(!txdownload_impl.RecentRejectsFilter().contains(tx->GetHash().ToUint256())); |
337 | 0 | Assert(!txdownload_impl.RecentRejectsReconsiderableFilter().contains(tx->GetWitnessHash().ToUint256())); |
338 | 0 | Assert(!txdownload_impl.RecentRejectsReconsiderableFilter().contains(tx->GetHash().ToUint256())); |
339 | 0 | } |
340 | 0 | }, |
341 | 0 | [&] { |
342 | 0 | CBlock block; |
343 | 0 | block.vtx.push_back(rand_tx); |
344 | 0 | txdownload_impl.BlockConnected(std::make_shared<CBlock>(block)); |
345 | | // Block transactions must be removed from orphanage |
346 | 0 | Assert(!txdownload_impl.m_orphanage->HaveTx(rand_tx->GetWitnessHash())); |
347 | 0 | }, |
348 | 0 | [&] { |
349 | 0 | txdownload_impl.BlockDisconnected(); |
350 | 0 | Assert(!txdownload_impl.RecentConfirmedTransactionsFilter().contains(rand_tx->GetWitnessHash().ToUint256())); |
351 | 0 | Assert(!txdownload_impl.RecentConfirmedTransactionsFilter().contains(rand_tx->GetHash().ToUint256())); |
352 | 0 | }, |
353 | 0 | [&] { |
354 | 0 | txdownload_impl.MempoolAcceptedTx(rand_tx); |
355 | 0 | }, |
356 | 0 | [&] { |
357 | 0 | TxValidationState state; |
358 | 0 | state.Invalid(fuzzed_data_provider.PickValueInArray(TESTED_TX_RESULTS), ""); |
359 | 0 | bool first_time_failure{fuzzed_data_provider.ConsumeBool()}; |
360 | |
|
361 | 0 | bool reject_contains_wtxid{txdownload_impl.RecentRejectsFilter().contains(rand_tx->GetWitnessHash().ToUint256())}; |
362 | |
|
363 | 0 | node::RejectedTxTodo todo = txdownload_impl.MempoolRejectedTx(rand_tx, state, rand_peer, first_time_failure); |
364 | 0 | Assert(first_time_failure || !todo.m_should_add_extra_compact_tx); |
365 | 0 | if (!reject_contains_wtxid) Assert(todo.m_unique_parents.size() <= rand_tx->vin.size()); |
366 | 0 | }, |
367 | 0 | [&] { |
368 | 0 | auto gtxid = fuzzed_data_provider.ConsumeBool() ? |
369 | 0 | GenTxid{rand_tx->GetHash()} : |
370 | 0 | GenTxid{rand_tx->GetWitnessHash()}; |
371 | 0 | txdownload_impl.AddTxAnnouncement(rand_peer, gtxid, time); |
372 | 0 | }, |
373 | 0 | [&] { |
374 | 0 | const auto getdata_requests = txdownload_impl.GetRequestsToSend(rand_peer, time); |
375 | | // TxDownloadManager should not be telling us to request things we already have. |
376 | | // Exclude m_lazy_recent_rejects_reconsiderable because it may request low-feerate parent of orphan. |
377 | 0 | for (const auto& gtxid : getdata_requests) { |
378 | 0 | Assert(!txdownload_impl.AlreadyHaveTx(gtxid, /*include_reconsiderable=*/false)); |
379 | 0 | } |
380 | 0 | }, |
381 | 0 | [&] { |
382 | 0 | const auto& [should_validate, maybe_package] = txdownload_impl.ReceivedTx(rand_peer, rand_tx); |
383 | | // The only possible results should be: |
384 | | // - Don't validate the tx, no package. |
385 | | // - Don't validate the tx, package. |
386 | | // - Validate the tx, no package. |
387 | | // The only combination that doesn't make sense is validate both tx and package. |
388 | 0 | Assert(!(should_validate && maybe_package.has_value())); |
389 | 0 | if (should_validate) { |
390 | 0 | Assert(!txdownload_impl.AlreadyHaveTx(rand_tx->GetWitnessHash(), /*include_reconsiderable=*/true)); |
391 | 0 | } |
392 | 0 | if (maybe_package.has_value()) { |
393 | 0 | CheckPackageToValidate(*maybe_package, rand_peer); |
394 | |
|
395 | 0 | const auto& package = maybe_package->m_txns; |
396 | | // Parent is in m_lazy_recent_rejects_reconsiderable and child is in m_orphanage |
397 | 0 | Assert(txdownload_impl.RecentRejectsReconsiderableFilter().contains(rand_tx->GetWitnessHash().ToUint256())); |
398 | 0 | Assert(txdownload_impl.m_orphanage->HaveTx(maybe_package->m_txns.back()->GetWitnessHash())); |
399 | | // Package has not been rejected |
400 | 0 | Assert(!txdownload_impl.RecentRejectsReconsiderableFilter().contains(GetPackageHash(package))); |
401 | | // Neither is in m_lazy_recent_rejects |
402 | 0 | Assert(!txdownload_impl.RecentRejectsFilter().contains(package.front()->GetWitnessHash().ToUint256())); |
403 | 0 | Assert(!txdownload_impl.RecentRejectsFilter().contains(package.back()->GetWitnessHash().ToUint256())); |
404 | 0 | } |
405 | 0 | }, |
406 | 0 | [&] { |
407 | 0 | txdownload_impl.ReceivedNotFound(rand_peer, {rand_tx->GetWitnessHash()}); |
408 | 0 | }, |
409 | 0 | [&] { |
410 | 0 | const bool expect_work{txdownload_impl.HaveMoreWork(rand_peer)}; |
411 | 0 | const auto ptx{txdownload_impl.GetTxToReconsider(rand_peer)}; |
412 | | // expect_work=true doesn't necessarily mean the next item from the workset isn't a |
413 | | // nullptr, as the transaction could have been removed from orphanage without being |
414 | | // removed from the peer's workset. |
415 | 0 | if (ptx) { |
416 | | // However, if there was a non-null tx in the workset, HaveMoreWork should have |
417 | | // returned true. |
418 | 0 | Assert(expect_work); |
419 | 0 | Assert(txdownload_impl.AlreadyHaveTx(ptx->GetWitnessHash(), /*include_reconsiderable=*/false)); |
420 | | // Presumably we have validated this tx. Use "missing inputs" to keep it in the |
421 | | // orphanage longer. Later iterations might call MempoolAcceptedTx or |
422 | | // MempoolRejectedTx with a different error. |
423 | 0 | TxValidationState state_missing_inputs; |
424 | 0 | state_missing_inputs.Invalid(TxValidationResult::TX_MISSING_INPUTS, ""); |
425 | 0 | txdownload_impl.MempoolRejectedTx(ptx, state_missing_inputs, rand_peer, fuzzed_data_provider.ConsumeBool()); |
426 | 0 | } |
427 | 0 | }); |
428 | |
|
429 | 0 | auto time_skip = fuzzed_data_provider.PickValueInArray(TIME_SKIPS); |
430 | 0 | if (fuzzed_data_provider.ConsumeBool()) time_skip *= -1; |
431 | 0 | time += time_skip; |
432 | 0 | } |
433 | 0 | CheckInvariants(txdownload_impl); |
434 | | // Disconnect everybody, check that all data structures are empty. |
435 | 0 | for (NodeId nodeid = 0; nodeid < NUM_PEERS; ++nodeid) { |
436 | 0 | txdownload_impl.DisconnectedPeer(nodeid); |
437 | 0 | txdownload_impl.CheckIsEmpty(nodeid); |
438 | 0 | } |
439 | 0 | txdownload_impl.CheckIsEmpty(); |
440 | 0 | } |
441 | | |
442 | | } // namespace |