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