Coverage Report

Created: 2025-09-19 18:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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