Coverage Report

Created: 2024-10-29 12:10

/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