Coverage Report

Created: 2026-06-18 19:04

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-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