Coverage Report

Created: 2026-06-12 16:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/bitcoin/src/test/fuzz/txorphan.cpp
Line
Count
Source
1
// Copyright (c) 2022-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/amount.h>
6
#include <consensus/validation.h>
7
#include <net_processing.h>
8
#include <node/eviction.h>
9
#include <node/txorphanage.h>
10
#include <policy/policy.h>
11
#include <primitives/transaction.h>
12
#include <script/script.h>
13
#include <sync.h>
14
#include <test/fuzz/FuzzedDataProvider.h>
15
#include <test/fuzz/fuzz.h>
16
#include <test/fuzz/util.h>
17
#include <test/util/setup_common.h>
18
#include <test/util/time.h>
19
#include <uint256.h>
20
#include <util/check.h>
21
#include <util/feefrac.h>
22
#include <util/time.h>
23
24
#include <algorithm>
25
#include <bitset>
26
#include <cmath>
27
#include <cstdint>
28
#include <iostream>
29
#include <memory>
30
#include <set>
31
#include <utility>
32
#include <vector>
33
34
void initialize_orphanage()
35
0
{
36
0
    static const auto testing_setup = MakeNoLogFileContext();
37
0
}
38
39
FUZZ_TARGET(txorphan, .init = initialize_orphanage)
40
0
{
41
0
    SeedRandomStateForTest(SeedRand::ZEROS);
42
0
    FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
43
0
    FastRandomContext orphanage_rng{ConsumeUInt256(fuzzed_data_provider)};
44
0
    FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)};
45
46
0
    auto orphanage = node::MakeTxOrphanage();
47
0
    std::vector<COutPoint> outpoints; // Duplicates are tolerated
48
0
    outpoints.reserve(200'000);
49
50
    // initial outpoints used to construct transactions later
51
0
    for (uint8_t i = 0; i < 4; i++) {
  Branch (51:25): [True: 0, False: 0]
52
0
        outpoints.emplace_back(Txid::FromUint256(uint256{i}), 0);
53
0
    }
54
55
0
    CTransactionRef ptx_potential_parent = nullptr;
56
57
0
    std::vector<CTransactionRef> tx_history;
58
59
0
    LIMITED_WHILE(outpoints.size() < 200'000 && fuzzed_data_provider.ConsumeBool(), 1000)
60
0
    {
61
        // construct transaction
62
0
        const CTransactionRef tx = [&] {
63
0
            CMutableTransaction tx_mut;
64
0
            const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size());
65
0
            const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, 256);
66
            // pick outpoints from outpoints as input. We allow input duplicates on purpose, given we are not
67
            // running any transaction validation logic before adding transactions to the orphanage
68
0
            tx_mut.vin.reserve(num_in);
69
0
            for (uint32_t i = 0; i < num_in; i++) {
  Branch (69:34): [True: 0, False: 0]
70
0
                auto& prevout = PickValue(fuzzed_data_provider, outpoints);
71
                // try making transactions unique by setting a random nSequence, but allow duplicate transactions if they happen
72
0
                tx_mut.vin.emplace_back(prevout, CScript{}, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, CTxIn::SEQUENCE_FINAL));
73
0
            }
74
            // output amount will not affect txorphanage
75
0
            tx_mut.vout.reserve(num_out);
76
0
            for (uint32_t i = 0; i < num_out; i++) {
  Branch (76:34): [True: 0, False: 0]
77
0
                tx_mut.vout.emplace_back(CAmount{0}, CScript{});
78
0
            }
79
0
            auto new_tx = MakeTransactionRef(tx_mut);
80
            // add newly constructed outpoints to the coin pool
81
0
            for (uint32_t i = 0; i < num_out; i++) {
  Branch (81:34): [True: 0, False: 0]
82
0
                outpoints.emplace_back(new_tx->GetHash(), i);
83
0
            }
84
0
            return new_tx;
85
0
        }();
86
87
0
        tx_history.push_back(tx);
88
89
0
        const auto wtxid{tx->GetWitnessHash()};
90
91
        // Trigger orphanage functions that are called using parents. ptx_potential_parent is a tx we constructed in a
92
        // previous loop and potentially the parent of this tx.
93
0
        if (ptx_potential_parent) {
  Branch (93:13): [True: 0, False: 0]
94
            // Set up future GetTxToReconsider call.
95
0
            orphanage->AddChildrenToWorkSet(*ptx_potential_parent, orphanage_rng);
96
97
            // Check that all txns returned from GetChildrenFrom* are indeed a direct child of this tx.
98
0
            NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>();
99
0
            for (const auto& child : orphanage->GetChildrenFromSamePeer(ptx_potential_parent, peer_id)) {
  Branch (99:36): [True: 0, False: 0]
100
0
                assert(std::any_of(child->vin.cbegin(), child->vin.cend(), [&](const auto& input) {
  Branch (100:17): [True: 0, False: 0]
101
0
                    return input.prevout.hash == ptx_potential_parent->GetHash();
102
0
                }));
103
0
            }
104
0
        }
105
106
        // trigger orphanage functions
107
0
        LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 1000)
108
0
        {
109
0
            NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>();
110
0
            const auto total_bytes_start{orphanage->TotalOrphanUsage()};
111
0
            const auto total_peer_bytes_start{orphanage->UsageByPeer(peer_id)};
112
0
            const auto tx_weight{GetTransactionWeight(*tx)};
113
114
0
            CallOneOf(
115
0
                fuzzed_data_provider,
116
0
                [&] {
117
0
                    {
118
0
                        CTransactionRef ref = orphanage->GetTxToReconsider(peer_id);
119
0
                        if (ref) {
  Branch (119:29): [True: 0, False: 0]
120
0
                            Assert(orphanage->HaveTx(ref->GetWitnessHash()));
121
0
                        }
122
0
                    }
123
0
                },
124
0
                [&] {
125
0
                    bool have_tx = orphanage->HaveTx(tx->GetWitnessHash());
126
0
                    bool have_tx_and_peer = orphanage->HaveTxFromPeer(wtxid, peer_id);
127
                    // AddTx should return false if tx is too big or already have it
128
                    // tx weight is unknown, we only check when tx is already in orphanage
129
0
                    {
130
0
                        bool add_tx = orphanage->AddTx(tx, peer_id);
131
                        // have_tx == true -> add_tx == false
132
0
                        Assert(!have_tx || !add_tx);
133
                        // have_tx_and_peer == true -> add_tx == false
134
0
                        Assert(!have_tx_and_peer || !add_tx);
135
                        // After AddTx, the orphanage may trim itself, so the peer's usage may have gone up or down.
136
137
0
                        if (add_tx) {
  Branch (137:29): [True: 0, False: 0]
138
0
                            Assert(tx_weight <= MAX_STANDARD_TX_WEIGHT);
139
0
                        } else {
140
                            // Peer may have been added as an announcer.
141
0
                            if (orphanage->UsageByPeer(peer_id) > total_peer_bytes_start) {
  Branch (141:33): [True: 0, False: 0]
142
0
                                Assert(orphanage->HaveTxFromPeer(wtxid, peer_id));
143
0
                            }
144
145
                            // If announcement was added, total bytes does not increase.
146
                            // However, if eviction was triggered, the value may decrease.
147
0
                            Assert(orphanage->TotalOrphanUsage() <= total_bytes_start);
148
0
                        }
149
0
                    }
150
                    // We are not guaranteed to have_tx after AddTx. There are a few possible reasons:
151
                    // - tx itself exceeds the per-peer memory usage limit, so LimitOrphans had to remove it immediately
152
                    // - tx itself exceeds the per-peer latency score limit, so LimitOrphans had to remove it immediately
153
                    // - the orphanage needed trim and all other announcements from this peer are reconsiderable
154
0
                },
155
0
                [&] {
156
0
                    bool have_tx = orphanage->HaveTx(tx->GetWitnessHash());
157
0
                    bool have_tx_and_peer = orphanage->HaveTxFromPeer(tx->GetWitnessHash(), peer_id);
158
                    // AddAnnouncer should return false if tx doesn't exist or we already HaveTxFromPeer.
159
0
                    {
160
0
                        bool added_announcer = orphanage->AddAnnouncer(tx->GetWitnessHash(), peer_id);
161
                        // have_tx == false -> added_announcer == false
162
0
                        Assert(have_tx || !added_announcer);
163
                        // have_tx_and_peer == true -> added_announcer == false
164
0
                        Assert(!have_tx_and_peer || !added_announcer);
165
166
                        // If announcement was added, total bytes does not increase.
167
                        // However, if eviction was triggered, the value may decrease.
168
0
                        Assert(orphanage->TotalOrphanUsage() <= total_bytes_start);
169
0
                    }
170
0
                },
171
0
                [&] {
172
0
                    bool have_tx = orphanage->HaveTx(tx->GetWitnessHash());
173
0
                    bool have_tx_and_peer{orphanage->HaveTxFromPeer(wtxid, peer_id)};
174
                    // EraseTx should return 0 if m_orphans doesn't have the tx
175
0
                    {
176
0
                        auto bytes_from_peer_before{orphanage->UsageByPeer(peer_id)};
177
0
                        Assert(have_tx == orphanage->EraseTx(tx->GetWitnessHash()));
178
                        // After EraseTx, the orphanage may trim itself, so all peers' usage may have gone up or down.
179
0
                        if (have_tx) {
  Branch (179:29): [True: 0, False: 0]
180
0
                            if (!have_tx_and_peer) {
  Branch (180:33): [True: 0, False: 0]
181
0
                                Assert(orphanage->UsageByPeer(peer_id) == bytes_from_peer_before);
182
0
                            }
183
0
                        }
184
0
                    }
185
0
                    have_tx = orphanage->HaveTx(tx->GetWitnessHash());
186
0
                    have_tx_and_peer = orphanage->HaveTxFromPeer(wtxid, peer_id);
187
                    // have_tx should be false and EraseTx should fail
188
0
                    {
189
0
                        Assert(!have_tx && !have_tx_and_peer && !orphanage->EraseTx(wtxid));
190
0
                    }
191
0
                },
192
0
                [&] {
193
0
                    orphanage->EraseForPeer(peer_id);
194
0
                    Assert(!orphanage->HaveTxFromPeer(tx->GetWitnessHash(), peer_id));
195
0
                    Assert(orphanage->UsageByPeer(peer_id) == 0);
196
0
                },
197
0
                [&] {
198
                    // Make a block out of txs and then EraseForBlock
199
0
                    CBlock block;
200
0
                    int64_t block_weight{0};
201
0
                    int num_txs = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 1000);
202
0
                    for (int i{0}; i < num_txs; ++i) {
  Branch (202:36): [True: 0, False: 0]
203
0
                        auto& tx_to_remove = PickValue(fuzzed_data_provider, tx_history);
204
0
                        const auto tx_weight = GetTransactionWeight(*tx_to_remove);
205
0
                        if (block_weight + tx_weight > MAX_BLOCK_WEIGHT) break;
  Branch (205:29): [True: 0, False: 0]
206
0
                        block_weight += tx_weight;
207
0
                        block.vtx.push_back(tx_to_remove);
208
0
                    }
209
0
                    orphanage->EraseForBlock(block);
210
0
                    for (const auto& tx_removed : block.vtx) {
  Branch (210:49): [True: 0, False: 0]
211
0
                        Assert(!orphanage->HaveTx(tx_removed->GetWitnessHash()));
212
0
                        Assert(!orphanage->HaveTxFromPeer(tx_removed->GetWitnessHash(), peer_id));
213
0
                    }
214
0
                }
215
0
            );
216
0
        }
217
218
        // Set tx as potential parent to be used for future GetChildren() calls.
219
0
        if (!ptx_potential_parent || fuzzed_data_provider.ConsumeBool()) {
  Branch (219:13): [True: 0, False: 0]
  Branch (219:38): [True: 0, False: 0]
220
0
            ptx_potential_parent = tx;
221
0
        }
222
223
0
        const bool have_tx{orphanage->HaveTx(tx->GetWitnessHash())};
224
0
        const bool get_tx_nonnull{orphanage->GetTx(tx->GetWitnessHash()) != nullptr};
225
0
        Assert(have_tx == get_tx_nonnull);
226
0
    }
227
0
    orphanage->SanityCheck();
228
0
}
229
230
FUZZ_TARGET(txorphan_protected, .init = initialize_orphanage)
231
0
{
232
0
    SeedRandomStateForTest(SeedRand::ZEROS);
233
0
    FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
234
0
    FastRandomContext orphanage_rng{ConsumeUInt256(fuzzed_data_provider)};
235
0
    FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)};
236
237
    // We have num_peers peers. Some subset of them will never exceed their reserved weight or announcement count, and
238
    // should therefore never have any orphans evicted.
239
0
    const unsigned int MAX_PEERS = 125;
240
0
    const unsigned int num_peers = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, MAX_PEERS);
241
    // Generate a vector of bools for whether each peer is protected from eviction
242
0
    std::bitset<MAX_PEERS> protected_peers;
243
0
    for (unsigned int i = 0; i < num_peers; i++) {
  Branch (243:30): [True: 0, False: 0]
244
0
        protected_peers.set(i, fuzzed_data_provider.ConsumeBool());
245
0
    }
246
247
    // Params for orphanage.
248
0
    const unsigned int global_latency_score_limit = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(num_peers, 6'000);
249
0
    const int64_t per_peer_weight_reservation = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(1, 4'040'000);
250
0
    auto orphanage = node::MakeTxOrphanage(global_latency_score_limit, per_peer_weight_reservation);
251
252
    // The actual limit, MaxPeerLatencyScore(), may be higher, since TxOrphanage only counts peers
253
    // that have announced an orphan. The honest peer will not experience evictions if it never
254
    // exceeds this.
255
0
    const unsigned int honest_latency_limit = global_latency_score_limit / num_peers;
256
    // Honest peer will not experience evictions if it never exceeds this.
257
0
    const int64_t honest_mem_limit = per_peer_weight_reservation;
258
259
0
    std::vector<COutPoint> outpoints; // Duplicates are tolerated
260
0
    outpoints.reserve(400);
261
262
    // initial outpoints used to construct transactions later
263
0
    for (uint8_t i = 0; i < 4; i++) {
  Branch (263:25): [True: 0, False: 0]
264
0
        outpoints.emplace_back(Txid::FromUint256(uint256{i}), 0);
265
0
    }
266
267
    // These are honest peer's live announcements. We expect them to be protected from eviction.
268
0
    std::set<Wtxid> protected_wtxids;
269
270
0
    LIMITED_WHILE(outpoints.size() < 400 && fuzzed_data_provider.ConsumeBool(), 1000)
271
0
    {
272
        // construct transaction
273
0
        const CTransactionRef tx = [&] {
274
0
            CMutableTransaction tx_mut;
275
0
            const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size());
276
0
            const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, 256);
277
            // pick outpoints from outpoints as input. We allow input duplicates on purpose, given we are not
278
            // running any transaction validation logic before adding transactions to the orphanage
279
0
            tx_mut.vin.reserve(num_in);
280
0
            for (uint32_t i = 0; i < num_in; i++) {
  Branch (280:34): [True: 0, False: 0]
281
0
                auto& prevout = PickValue(fuzzed_data_provider, outpoints);
282
                // try making transactions unique by setting a random nSequence, but allow duplicate transactions if they happen
283
0
                tx_mut.vin.emplace_back(prevout, CScript{}, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, CTxIn::SEQUENCE_FINAL));
284
0
            }
285
            // output amount or spendability will not affect txorphanage
286
0
            tx_mut.vout.reserve(num_out);
287
0
            for (uint32_t i = 0; i < num_out; i++) {
  Branch (287:34): [True: 0, False: 0]
288
0
                const auto payload_size = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 100000);
289
0
                if (payload_size) {
  Branch (289:21): [True: 0, False: 0]
290
0
                    tx_mut.vout.emplace_back(0, CScript() << OP_RETURN << std::vector<unsigned char>(payload_size));
291
0
                } else {
292
0
                    tx_mut.vout.emplace_back(0, CScript{});
293
0
                }
294
0
            }
295
0
            auto new_tx = MakeTransactionRef(tx_mut);
296
            // add newly constructed outpoints to the coin pool
297
0
            for (uint32_t i = 0; i < num_out; i++) {
  Branch (297:34): [True: 0, False: 0]
298
0
                outpoints.emplace_back(new_tx->GetHash(), i);
299
0
            }
300
0
            return new_tx;
301
0
        }();
302
303
0
        const auto wtxid{tx->GetWitnessHash()};
304
305
        // orphanage functions
306
0
        LIMITED_WHILE(fuzzed_data_provider.remaining_bytes(), 10 * global_latency_score_limit)
307
0
        {
308
0
            NodeId peer_id = fuzzed_data_provider.ConsumeIntegralInRange<NodeId>(0, num_peers - 1);
309
0
            const auto tx_weight{GetTransactionWeight(*tx)};
310
311
            // This protected peer will never send orphans that would
312
            // exceed their own personal allotment, so is never evicted.
313
0
            const bool peer_is_protected{protected_peers[peer_id]};
314
315
0
            CallOneOf(
316
0
                fuzzed_data_provider,
317
0
                [&] { // AddTx
318
0
                    bool have_tx_and_peer = orphanage->HaveTxFromPeer(wtxid, peer_id);
319
0
                    if (peer_is_protected && !have_tx_and_peer &&
  Branch (319:25): [True: 0, False: 0]
  Branch (319:46): [True: 0, False: 0]
320
0
                        (orphanage->UsageByPeer(peer_id) + tx_weight > honest_mem_limit ||
  Branch (320:26): [True: 0, False: 0]
321
0
                        orphanage->LatencyScoreFromPeer(peer_id) + (tx->vin.size() / 10) + 1 > honest_latency_limit)) {
  Branch (321:25): [True: 0, False: 0]
322
                        // We never want our protected peer oversized or over-announced
323
0
                    } else {
324
0
                        orphanage->AddTx(tx, peer_id);
325
0
                        if (peer_is_protected && orphanage->HaveTxFromPeer(wtxid, peer_id)) {
  Branch (325:29): [True: 0, False: 0]
  Branch (325:50): [True: 0, False: 0]
326
0
                            protected_wtxids.insert(wtxid);
327
0
                        }
328
0
                    }
329
0
                },
330
0
                [&] { // AddAnnouncer
331
0
                    bool have_tx_and_peer = orphanage->HaveTxFromPeer(tx->GetWitnessHash(), peer_id);
332
                    // AddAnnouncer should return false if tx doesn't exist or we already HaveTxFromPeer.
333
0
                    {
334
0
                        if (peer_is_protected && !have_tx_and_peer &&
  Branch (334:29): [True: 0, False: 0]
  Branch (334:50): [True: 0, False: 0]
335
0
                            (orphanage->UsageByPeer(peer_id) + tx_weight > honest_mem_limit ||
  Branch (335:30): [True: 0, False: 0]
336
0
                            orphanage->LatencyScoreFromPeer(peer_id) + (tx->vin.size() / 10) + 1 > honest_latency_limit)) {
  Branch (336:29): [True: 0, False: 0]
337
                            // We never want our protected peer oversized
338
0
                        } else {
339
0
                            orphanage->AddAnnouncer(tx->GetWitnessHash(), peer_id);
340
0
                            if (peer_is_protected && orphanage->HaveTxFromPeer(wtxid, peer_id)) {
  Branch (340:33): [True: 0, False: 0]
  Branch (340:54): [True: 0, False: 0]
341
0
                                protected_wtxids.insert(wtxid);
342
0
                            }
343
0
                        }
344
0
                    }
345
0
                },
346
0
                [&] { // EraseTx
347
0
                    if (protected_wtxids.contains(tx->GetWitnessHash())) {
  Branch (347:25): [True: 0, False: 0]
348
0
                        protected_wtxids.erase(wtxid);
349
0
                    }
350
0
                    orphanage->EraseTx(wtxid);
351
0
                    Assert(!orphanage->HaveTx(wtxid));
352
0
                },
353
0
                [&] { // EraseForPeer
354
0
                    if (!protected_peers[peer_id]) {
  Branch (354:25): [True: 0, False: 0]
355
0
                        orphanage->EraseForPeer(peer_id);
356
0
                        Assert(orphanage->UsageByPeer(peer_id) == 0);
357
0
                        Assert(orphanage->LatencyScoreFromPeer(peer_id) == 0);
358
0
                        Assert(orphanage->AnnouncementsFromPeer(peer_id) == 0);
359
0
                    }
360
0
                }
361
0
            );
362
0
        }
363
0
    }
364
365
0
    orphanage->SanityCheck();
366
    // All of the honest peer's announcements are still present.
367
0
    for (const auto& wtxid : protected_wtxids) {
  Branch (367:28): [True: 0, False: 0]
368
0
        Assert(orphanage->HaveTx(wtxid));
369
0
    }
370
0
}
371
372
FUZZ_TARGET(txorphanage_sim)
373
0
{
374
0
    SeedRandomStateForTest(SeedRand::ZEROS);
375
    // This is a comprehensive simulation fuzz test, which runs through a scenario involving up to
376
    // 16 transactions (which may have simple or complex topology, and may have duplicate txids
377
    // with distinct wtxids, and up to 16 peers. The scenario is performed both on a real
378
    // TxOrphanage object and the behavior is compared with a naive reimplementation (just a vector
379
    // of announcements) where possible, and tested for desired properties where not possible.
380
381
    //
382
    // 1. Setup.
383
    //
384
385
    /** The total number of transactions this simulation uses (not all of which will necessarily
386
     *  be present in the orphanage at once). */
387
0
    static constexpr unsigned NUM_TX = 16;
388
    /** The number of peers this simulation uses (not all of which will necessarily be present in
389
     *  the orphanage at once). */
390
0
    static constexpr unsigned NUM_PEERS = 16;
391
    /** The maximum number of announcements this simulation uses (which may be higher than the
392
     *  number permitted inside the orphanage). */
393
0
    static constexpr unsigned MAX_ANN = 64;
394
395
0
    FuzzedDataProvider provider(buffer.data(), buffer.size());
396
    /** Local RNG. Only used for topology/sizes of the transaction set, the order of transactions
397
     *  in EraseForBlock, and for the randomized passed to AddChildrenToWorkSet. */
398
0
    InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
399
400
    //
401
    // 2. Construct an interesting set of 16 transactions.
402
    //
403
404
    // - Pick a topological order among the transactions.
405
0
    std::vector<unsigned> txorder(NUM_TX);
406
0
    std::iota(txorder.begin(), txorder.end(), unsigned{0});
407
0
    std::shuffle(txorder.begin(), txorder.end(), rng);
408
    // - Pick a set of dependencies (pair<child_index, parent_index>).
409
0
    std::vector<std::pair<unsigned, unsigned>> deps;
410
0
    deps.reserve((NUM_TX * (NUM_TX - 1)) / 2);
411
0
    for (unsigned p = 0; p < NUM_TX - 1; ++p) {
  Branch (411:26): [True: 0, False: 0]
412
0
        for (unsigned c = p + 1; c < NUM_TX; ++c) {
  Branch (412:34): [True: 0, False: 0]
413
0
            deps.emplace_back(c, p);
414
0
        }
415
0
    }
416
0
    std::shuffle(deps.begin(), deps.end(), rng);
417
0
    deps.resize(provider.ConsumeIntegralInRange<unsigned>(0, NUM_TX * 4 - 1));
418
    // - Construct the actual transactions.
419
0
    std::set<Wtxid> wtxids;
420
0
    std::vector<CTransactionRef> txn(NUM_TX);
421
0
    node::TxOrphanage::Usage total_usage{0};
422
0
    for (unsigned t = 0; t < NUM_TX; ++t) {
  Branch (422:26): [True: 0, False: 0]
423
0
        CMutableTransaction tx;
424
0
        if (t > 0 && rng.randrange(4) == 0) {
  Branch (424:13): [True: 0, False: 0]
  Branch (424:22): [True: 0, False: 0]
425
            // Occasionally duplicate the previous transaction, so that repetitions of the same
426
            // txid are possible (with different wtxid).
427
0
            tx = CMutableTransaction(*txn[txorder[t - 1]]);
428
0
        } else {
429
0
            tx.version = 1;
430
0
            tx.nLockTime = 0xffffffff;
431
            // Construct 1 to 16 outputs.
432
0
            auto num_outputs = rng.randrange<unsigned>(1 << rng.randrange<unsigned>(5)) + 1;
433
0
            for (unsigned output = 0; output < num_outputs; ++output) {
  Branch (433:39): [True: 0, False: 0]
434
0
                CScript scriptpubkey;
435
0
                scriptpubkey.resize(provider.ConsumeIntegralInRange<unsigned>(20, 34));
436
0
                tx.vout.emplace_back(CAmount{0}, std::move(scriptpubkey));
437
0
            }
438
            // Construct inputs (one for each dependency).
439
0
            for (auto& [child, parent] : deps) {
  Branch (439:40): [True: 0, False: 0]
440
0
                if (child == t) {
  Branch (440:21): [True: 0, False: 0]
441
0
                    auto& partx = txn[txorder[parent]];
442
0
                    assert(partx->version == 1);
  Branch (442:21): [True: 0, False: 0]
443
0
                    COutPoint outpoint(partx->GetHash(), rng.randrange<size_t>(partx->vout.size()));
444
0
                    tx.vin.emplace_back(outpoint);
445
0
                    tx.vin.back().scriptSig.resize(provider.ConsumeIntegralInRange<unsigned>(16, 200));
446
0
                }
447
0
            }
448
            // Construct fallback input in case there are no dependencies.
449
0
            if (tx.vin.empty()) {
  Branch (449:17): [True: 0, False: 0]
450
0
                COutPoint outpoint(Txid::FromUint256(rng.rand256()), rng.randrange<size_t>(16));
451
0
                tx.vin.emplace_back(outpoint);
452
0
                tx.vin.back().scriptSig.resize(provider.ConsumeIntegralInRange<unsigned>(16, 200));
453
0
            }
454
0
        }
455
        // Optionally modify the witness (allowing wtxid != txid), and certainly when the wtxid
456
        // already exists.
457
0
        while (wtxids.contains(CTransaction(tx).GetWitnessHash()) || rng.randrange(4) == 0) {
  Branch (457:16): [True: 0, False: 0]
  Branch (457:16): [True: 0, False: 0]
  Branch (457:70): [True: 0, False: 0]
458
0
            auto& input = tx.vin[rng.randrange(tx.vin.size())];
459
0
            if (rng.randbool()) {
  Branch (459:17): [True: 0, False: 0]
460
0
                input.scriptWitness.stack.resize(1);
461
0
                input.scriptWitness.stack[0].resize(rng.randrange(100));
462
0
            } else {
463
0
                input.scriptWitness.stack.resize(0);
464
0
            }
465
0
        }
466
        // Convert to CTransactionRef.
467
0
        txn[txorder[t]] = MakeTransactionRef(std::move(tx));
468
0
        wtxids.insert(txn[txorder[t]]->GetWitnessHash());
469
0
        auto weight = GetTransactionWeight(*txn[txorder[t]]);
470
0
        assert(weight < MAX_STANDARD_TX_WEIGHT);
  Branch (470:9): [True: 0, False: 0]
471
0
        total_usage += GetTransactionWeight(*txn[txorder[t]]);
472
0
    }
473
474
    //
475
    // 3. Initialize real orphanage
476
    //
477
478
0
    auto max_global_latency_score = provider.ConsumeIntegralInRange<node::TxOrphanage::Count>(NUM_PEERS, MAX_ANN);
479
0
    auto reserved_peer_usage = provider.ConsumeIntegralInRange<node::TxOrphanage::Usage>(1, total_usage);
480
0
    auto real = node::MakeTxOrphanage(max_global_latency_score, reserved_peer_usage);
481
482
    //
483
    // 4. Functions and data structures for the simulation.
484
    //
485
486
    /** Data structure representing one announcement (pair of (tx, peer), plus whether it's
487
     *  reconsiderable or not. */
488
0
    struct SimAnnouncement
489
0
    {
490
0
        unsigned tx;
491
0
        NodeId announcer;
492
0
        bool reconsider{false};
493
0
        SimAnnouncement(unsigned tx_in, NodeId announcer_in, bool reconsider_in) noexcept :
494
0
            tx(tx_in), announcer(announcer_in), reconsider(reconsider_in) {}
495
0
    };
496
    /** The entire simulated orphanage is represented by this list of announcements, in
497
     *  announcement order (unlike TxOrphanageImpl which uses a sequence number to represent
498
     *  announcement order). New announcements are added to the back. */
499
0
    std::vector<SimAnnouncement> sim_announcements;
500
501
    /** Consume a transaction (index into txn) from provider. */
502
0
    auto read_tx_fn = [&]() -> unsigned { return provider.ConsumeIntegralInRange<unsigned>(0, NUM_TX - 1); };
503
    /** Consume a NodeId from provider. */
504
0
    auto read_peer_fn = [&]() -> NodeId { return provider.ConsumeIntegralInRange<unsigned>(0, NUM_PEERS - 1); };
505
    /** Consume both a transaction (index into txn) and a NodeId from provider. */
506
0
    auto read_tx_peer_fn = [&]() -> std::pair<unsigned, NodeId> {
507
0
        auto code = provider.ConsumeIntegralInRange<unsigned>(0, NUM_TX * NUM_PEERS - 1);
508
0
        return {code % NUM_TX, code / NUM_TX};
509
0
    };
510
    /** Determine if we have any announcements of the given transaction in the simulation. */
511
0
    auto have_tx_fn = [&](unsigned tx) -> bool {
512
0
        for (auto& ann : sim_announcements) {
  Branch (512:24): [True: 0, False: 0]
513
0
            if (ann.tx == tx) return true;
  Branch (513:17): [True: 0, False: 0]
514
0
        }
515
0
        return false;
516
0
    };
517
    /** Count the number of peers in the simulation. */
518
0
    auto count_peers_fn = [&]() -> unsigned {
519
0
        std::bitset<NUM_PEERS> mask;
520
0
        for (auto& ann : sim_announcements) {
  Branch (520:24): [True: 0, False: 0]
521
0
            mask.set(ann.announcer);
522
0
        }
523
0
        return mask.count();
524
0
    };
525
    /** Determine if we have any reconsiderable announcements of a given transaction. */
526
0
    auto have_reconsiderable_fn = [&](unsigned tx) -> bool {
527
0
        for (auto& ann : sim_announcements) {
  Branch (527:24): [True: 0, False: 0]
528
0
            if (ann.reconsider && ann.tx == tx) return true;
  Branch (528:17): [True: 0, False: 0]
  Branch (528:35): [True: 0, False: 0]
529
0
        }
530
0
        return false;
531
0
    };
532
    /** Determine if a peer has any transactions to reconsider. */
533
0
    auto have_reconsider_fn = [&](NodeId peer) -> bool {
534
0
        for (auto& ann : sim_announcements) {
  Branch (534:24): [True: 0, False: 0]
535
0
            if (ann.reconsider && ann.announcer == peer) return true;
  Branch (535:17): [True: 0, False: 0]
  Branch (535:35): [True: 0, False: 0]
536
0
        }
537
0
        return false;
538
0
    };
539
    /** Get an iterator to an existing (wtxid, peer) pair in the simulation. */
540
0
    auto find_announce_wtxid_fn = [&](const Wtxid& wtxid, NodeId peer) -> std::vector<SimAnnouncement>::iterator {
541
0
        for (auto it = sim_announcements.begin(); it != sim_announcements.end(); ++it) {
  Branch (541:51): [True: 0, False: 0]
542
0
            if (txn[it->tx]->GetWitnessHash() == wtxid && it->announcer == peer) return it;
  Branch (542:17): [True: 0, False: 0]
  Branch (542:59): [True: 0, False: 0]
543
0
        }
544
0
        return sim_announcements.end();
545
0
    };
546
    /** Get an iterator to an existing (tx, peer) pair in the simulation. */
547
0
    auto find_announce_fn = [&](unsigned tx, NodeId peer) {
548
0
        for (auto it = sim_announcements.begin(); it != sim_announcements.end(); ++it) {
  Branch (548:51): [True: 0, False: 0]
549
0
            if (it->tx == tx && it->announcer == peer) return it;
  Branch (549:17): [True: 0, False: 0]
  Branch (549:33): [True: 0, False: 0]
550
0
        }
551
0
        return sim_announcements.end();
552
0
    };
553
    /** Compute a peer's DoS score according to simulation data. */
554
0
    auto dos_score_fn = [&](NodeId peer, int32_t max_count, int32_t max_usage) -> FeeFrac {
555
0
        int64_t count{0};
556
0
        int64_t usage{0};
557
0
        for (auto& ann : sim_announcements) {
  Branch (557:24): [True: 0, False: 0]
558
0
            if (ann.announcer != peer) continue;
  Branch (558:17): [True: 0, False: 0]
559
0
            count += 1 + (txn[ann.tx]->vin.size() / 10);
560
0
            usage += GetTransactionWeight(*txn[ann.tx]);
561
0
        }
562
0
        return std::max<ByRatioNegSize<FeeFrac>>(FeeFrac{count, max_count}, FeeFrac{usage, max_usage});
563
0
    };
564
565
    //
566
    // 5. Run through a scenario of mutators on both real and simulated orphanage.
567
    //
568
569
0
    LIMITED_WHILE(provider.remaining_bytes() > 0, 200) {
570
0
        int command = provider.ConsumeIntegralInRange<uint8_t>(0, 15);
571
0
        while (true) {
  Branch (571:16): [Folded - Ignored]
572
0
            if (sim_announcements.size() < MAX_ANN && command-- == 0) {
  Branch (572:17): [True: 0, False: 0]
  Branch (572:55): [True: 0, False: 0]
573
                // AddTx
574
0
                auto [tx, peer] = read_tx_peer_fn();
575
0
                bool added = real->AddTx(txn[tx], peer);
576
0
                bool sim_have_tx = have_tx_fn(tx);
577
0
                assert(added == !sim_have_tx);
  Branch (577:17): [True: 0, False: 0]
578
0
                if (find_announce_fn(tx, peer) == sim_announcements.end()) {
  Branch (578:21): [True: 0, False: 0]
579
0
                    sim_announcements.emplace_back(tx, peer, false);
580
0
                }
581
0
                break;
582
0
            } else if (sim_announcements.size() < MAX_ANN && command-- == 0) {
  Branch (582:24): [True: 0, False: 0]
  Branch (582:62): [True: 0, False: 0]
583
                // AddAnnouncer
584
0
                auto [tx, peer] = read_tx_peer_fn();
585
0
                bool added = real->AddAnnouncer(txn[tx]->GetWitnessHash(), peer);
586
0
                bool sim_have_tx = have_tx_fn(tx);
587
0
                auto sim_it = find_announce_fn(tx, peer);
588
0
                assert(added == (sim_it == sim_announcements.end() && sim_have_tx));
  Branch (588:17): [True: 0, False: 0]
  Branch (588:17): [True: 0, False: 0]
  Branch (588:17): [True: 0, False: 0]
589
0
                if (added) {
  Branch (589:21): [True: 0, False: 0]
590
0
                    sim_announcements.emplace_back(tx, peer, false);
591
0
                }
592
0
                break;
593
0
            } else if (command-- == 0) {
  Branch (593:24): [True: 0, False: 0]
594
                // EraseTx
595
0
                auto tx = read_tx_fn();
596
0
                bool erased = real->EraseTx(txn[tx]->GetWitnessHash());
597
0
                bool sim_have = have_tx_fn(tx);
598
0
                assert(erased == sim_have);
  Branch (598:17): [True: 0, False: 0]
599
0
                std::erase_if(sim_announcements, [&](auto& ann) { return ann.tx == tx; });
600
0
                break;
601
0
           } else if (command-- == 0) {
  Branch (601:23): [True: 0, False: 0]
602
                // EraseForPeer
603
0
                auto peer = read_peer_fn();
604
0
                real->EraseForPeer(peer);
605
0
                std::erase_if(sim_announcements, [&](auto& ann) { return ann.announcer == peer; });
606
0
                break;
607
0
            } else if (command-- == 0) {
  Branch (607:24): [True: 0, False: 0]
608
                // EraseForBlock
609
0
                auto pattern = provider.ConsumeIntegralInRange<uint64_t>(0, (uint64_t{1} << NUM_TX) - 1);
610
0
                CBlock block;
611
0
                std::set<COutPoint> spent;
612
0
                for (unsigned tx = 0; tx < NUM_TX; ++tx) {
  Branch (612:39): [True: 0, False: 0]
613
0
                    if ((pattern >> tx) & 1) {
  Branch (613:25): [True: 0, False: 0]
614
0
                        block.vtx.emplace_back(txn[tx]);
615
0
                        for (auto& txin : block.vtx.back()->vin) {
  Branch (615:41): [True: 0, False: 0]
616
0
                            spent.insert(txin.prevout);
617
0
                        }
618
0
                    }
619
0
                }
620
0
                std::shuffle(block.vtx.begin(), block.vtx.end(), rng);
621
0
                real->EraseForBlock(block);
622
0
                std::erase_if(sim_announcements, [&](auto& ann) {
623
0
                    for (auto& txin : txn[ann.tx]->vin) {
  Branch (623:37): [True: 0, False: 0]
624
0
                        if (spent.contains(txin.prevout)) return true;
  Branch (624:29): [True: 0, False: 0]
625
0
                    }
626
0
                    return false;
627
0
                });
628
0
                break;
629
0
            } else if (command-- == 0) {
  Branch (629:24): [True: 0, False: 0]
630
                // AddChildrenToWorkSet
631
0
                auto tx = read_tx_fn();
632
0
                FastRandomContext rand_ctx(rng.rand256());
633
0
                auto added = real->AddChildrenToWorkSet(*txn[tx], rand_ctx);
634
                /** Set of not-already-reconsiderable child wtxids. */
635
0
                std::set<Wtxid> child_wtxids;
636
0
                for (unsigned child_tx = 0; child_tx < NUM_TX; ++child_tx) {
  Branch (636:45): [True: 0, False: 0]
637
0
                    if (!have_tx_fn(child_tx)) continue;
  Branch (637:25): [True: 0, False: 0]
638
0
                    if (have_reconsiderable_fn(child_tx)) continue;
  Branch (638:25): [True: 0, False: 0]
639
0
                    bool child_of = false;
640
0
                    for (auto& txin : txn[child_tx]->vin) {
  Branch (640:37): [True: 0, False: 0]
641
0
                        if (txin.prevout.hash == txn[tx]->GetHash()) {
  Branch (641:29): [True: 0, False: 0]
642
0
                            child_of = true;
643
0
                            break;
644
0
                        }
645
0
                    }
646
0
                    if (child_of) {
  Branch (646:25): [True: 0, False: 0]
647
0
                        child_wtxids.insert(txn[child_tx]->GetWitnessHash());
648
0
                    }
649
0
                }
650
0
                for (auto& [wtxid, peer] : added) {
  Branch (650:42): [True: 0, False: 0]
651
                    // Wtxid must be a child of tx that is not yet reconsiderable.
652
0
                    auto child_wtxid_it = child_wtxids.find(wtxid);
653
0
                    assert(child_wtxid_it != child_wtxids.end());
  Branch (653:21): [True: 0, False: 0]
654
                    // Announcement must exist.
655
0
                    auto sim_ann_it = find_announce_wtxid_fn(wtxid, peer);
656
0
                    assert(sim_ann_it != sim_announcements.end());
  Branch (656:21): [True: 0, False: 0]
657
                    // Announcement must not yet be reconsiderable.
658
0
                    assert(sim_ann_it->reconsider == false);
  Branch (658:21): [True: 0, False: 0]
659
                    // Make reconsiderable.
660
0
                    sim_ann_it->reconsider = true;
661
                    // Remove from child_wtxids map, to disallow it being reported a second time in added.
662
0
                    child_wtxids.erase(wtxid);
663
0
                }
664
                // Verify that AddChildrenToWorkSet does not select announcements that were already reconsiderable:
665
                // Check all child wtxids which did not occur at least once in the result were already reconsiderable
666
                // due to a previous AddChildrenToWorkSet.
667
0
                assert(child_wtxids.empty());
  Branch (667:17): [True: 0, False: 0]
668
0
                break;
669
0
            } else if (command-- == 0) {
  Branch (669:24): [True: 0, False: 0]
670
                // GetTxToReconsider.
671
0
                auto peer = read_peer_fn();
672
0
                auto result = real->GetTxToReconsider(peer);
673
0
                if (result) {
  Branch (673:21): [True: 0, False: 0]
674
                    // A transaction was found. It must have a corresponding reconsiderable
675
                    // announcement from peer.
676
0
                    auto sim_ann_it = find_announce_wtxid_fn(result->GetWitnessHash(), peer);
677
0
                    assert(sim_ann_it != sim_announcements.end());
  Branch (677:21): [True: 0, False: 0]
678
0
                    assert(sim_ann_it->announcer == peer);
  Branch (678:21): [True: 0, False: 0]
679
0
                    assert(sim_ann_it->reconsider);
  Branch (679:21): [True: 0, False: 0]
680
                    // Make it non-reconsiderable.
681
0
                    sim_ann_it->reconsider = false;
682
0
                } else {
683
                    // No reconsiderable transaction was found from peer. Verify that it does not
684
                    // have any.
685
0
                    assert(!have_reconsider_fn(peer));
  Branch (685:21): [True: 0, False: 0]
686
0
                }
687
0
                break;
688
0
            }
689
0
        }
690
        // Always trim after each command if needed.
691
0
        const auto max_ann = max_global_latency_score / std::max<unsigned>(1, count_peers_fn());
692
0
        const auto max_mem = reserved_peer_usage;
693
0
        while (true) {
  Branch (693:16): [Folded - Ignored]
694
            // Count global usage and number of peers.
695
0
            node::TxOrphanage::Usage total_usage{0};
696
0
            node::TxOrphanage::Count total_latency_score = sim_announcements.size();
697
0
            for (unsigned tx = 0; tx < NUM_TX; ++tx) {
  Branch (697:35): [True: 0, False: 0]
698
0
                if (have_tx_fn(tx)) {
  Branch (698:21): [True: 0, False: 0]
699
0
                    total_usage += GetTransactionWeight(*txn[tx]);
700
0
                    total_latency_score += txn[tx]->vin.size() / 10;
701
0
                }
702
0
            }
703
0
            auto num_peers = count_peers_fn();
704
0
            bool oversized = (total_usage > reserved_peer_usage * num_peers) ||
  Branch (704:30): [True: 0, False: 0]
705
0
                                (total_latency_score > real->MaxGlobalLatencyScore());
  Branch (705:33): [True: 0, False: 0]
706
0
            if (!oversized) break;
  Branch (706:17): [True: 0, False: 0]
707
            // Find worst peer.
708
0
            FeeFrac worst_dos_score{0, 1};
709
0
            unsigned worst_peer = unsigned(-1);
710
0
            for (unsigned peer = 0; peer < NUM_PEERS; ++peer) {
  Branch (710:37): [True: 0, False: 0]
711
0
                auto dos_score = dos_score_fn(peer, max_ann, max_mem);
712
                // Use >= so that the more recent peer (higher NodeId) wins in case of
713
                // ties.
714
0
                if (ByRatioNegSize{dos_score} >= ByRatioNegSize{worst_dos_score}) {
  Branch (714:21): [True: 0, False: 0]
715
0
                    worst_dos_score = dos_score;
716
0
                    worst_peer = peer;
717
0
                }
718
0
            }
719
0
            assert(worst_peer != unsigned(-1));
  Branch (719:13): [True: 0, False: 0]
720
0
            assert(ByRatio{worst_dos_score} > ByRatio{FeeFrac(1, 1)});
  Branch (720:13): [True: 0, False: 0]
721
            // Find oldest announcement from worst_peer, preferring non-reconsiderable ones.
722
0
            bool done{false};
723
0
            for (int reconsider = 0; reconsider < 2; ++reconsider) {
  Branch (723:38): [True: 0, False: 0]
724
0
                for (auto it = sim_announcements.begin(); it != sim_announcements.end(); ++it) {
  Branch (724:59): [True: 0, False: 0]
725
0
                    if (it->announcer != worst_peer || it->reconsider != reconsider) continue;
  Branch (725:25): [True: 0, False: 0]
  Branch (725:56): [True: 0, False: 0]
726
0
                    sim_announcements.erase(it);
727
0
                    done = true;
728
0
                    break;
729
0
                }
730
0
                if (done) break;
  Branch (730:21): [True: 0, False: 0]
731
0
            }
732
0
            assert(done);
  Branch (732:13): [True: 0, False: 0]
733
0
        }
734
        // We must now be within limits, otherwise LimitOrphans should have continued further.
735
        // We don't check the contents of the orphanage until the end to make fuzz runs faster.
736
0
        assert(real->TotalLatencyScore() <= real->MaxGlobalLatencyScore());
  Branch (736:9): [True: 0, False: 0]
737
0
        assert(real->TotalOrphanUsage() <= real->MaxGlobalUsage());
  Branch (737:9): [True: 0, False: 0]
738
0
    }
739
740
    //
741
    // 6. Perform a full comparison between the real orphanage's inspectors and the simulation.
742
    //
743
744
0
    real->SanityCheck();
745
746
747
0
    auto all_orphans = real->GetOrphanTransactions();
748
0
    node::TxOrphanage::Usage orphan_usage{0};
749
0
    std::vector<node::TxOrphanage::Usage> usage_by_peer(NUM_PEERS);
750
0
    node::TxOrphanage::Count unique_orphans{0};
751
0
    std::vector<node::TxOrphanage::Count> count_by_peer(NUM_PEERS);
752
0
    node::TxOrphanage::Count total_latency_score = sim_announcements.size();
753
0
    for (unsigned tx = 0; tx < NUM_TX; ++tx) {
  Branch (753:27): [True: 0, False: 0]
754
0
        bool sim_have_tx = have_tx_fn(tx);
755
0
        if (sim_have_tx) {
  Branch (755:13): [True: 0, False: 0]
756
0
            orphan_usage += GetTransactionWeight(*txn[tx]);
757
0
            total_latency_score += txn[tx]->vin.size() / 10;
758
0
        }
759
0
        unique_orphans += sim_have_tx;
760
0
        auto orphans_it = std::find_if(all_orphans.begin(), all_orphans.end(), [&](auto& orph) { return orph.tx->GetWitnessHash() == txn[tx]->GetWitnessHash(); });
761
        // GetOrphanTransactions (OrphanBase existence)
762
0
        assert((orphans_it != all_orphans.end()) == sim_have_tx);
  Branch (762:9): [True: 0, False: 0]
763
        // HaveTx
764
0
        bool have_tx = real->HaveTx(txn[tx]->GetWitnessHash());
765
0
        assert(have_tx == sim_have_tx);
  Branch (765:9): [True: 0, False: 0]
766
        // GetTx
767
0
        auto txref = real->GetTx(txn[tx]->GetWitnessHash());
768
0
        assert(!!txref == sim_have_tx);
  Branch (768:9): [True: 0, False: 0]
769
0
        if (sim_have_tx) assert(txref->GetWitnessHash() == txn[tx]->GetWitnessHash());
  Branch (769:13): [True: 0, False: 0]
  Branch (769:26): [True: 0, False: 0]
770
771
0
        for (NodeId peer = 0; peer < NUM_PEERS; ++peer) {
  Branch (771:31): [True: 0, False: 0]
772
0
            auto it_sim_ann = find_announce_fn(tx, peer);
773
0
            bool sim_have_ann = it_sim_ann != sim_announcements.end();
774
0
            if (sim_have_ann) usage_by_peer[peer] += GetTransactionWeight(*txn[tx]);
  Branch (774:17): [True: 0, False: 0]
775
0
            count_by_peer[peer] += sim_have_ann;
776
            // GetOrphanTransactions (announcers presence)
777
0
            if (sim_have_ann) assert(sim_have_tx);
  Branch (777:17): [True: 0, False: 0]
  Branch (777:31): [True: 0, False: 0]
778
0
            if (sim_have_tx) assert(orphans_it->announcers.count(peer) == sim_have_ann);
  Branch (778:17): [True: 0, False: 0]
  Branch (778:30): [True: 0, False: 0]
779
            // HaveTxFromPeer
780
0
            bool have_ann = real->HaveTxFromPeer(txn[tx]->GetWitnessHash(), peer);
781
0
            assert(sim_have_ann == have_ann);
  Branch (781:13): [True: 0, False: 0]
782
            // GetChildrenFromSamePeer
783
0
            auto children_from_peer = real->GetChildrenFromSamePeer(txn[tx], peer);
784
0
            auto it = children_from_peer.rbegin();
785
0
            for (int phase = 0; phase < 2; ++phase) {
  Branch (785:33): [True: 0, False: 0]
786
                // First expect all children which have reconsiderable announcement from peer, then the others.
787
0
                for (auto& ann : sim_announcements) {
  Branch (787:32): [True: 0, False: 0]
788
0
                    if (ann.announcer != peer) continue;
  Branch (788:25): [True: 0, False: 0]
789
0
                    if (ann.reconsider != (phase == 1)) continue;
  Branch (789:25): [True: 0, False: 0]
790
0
                    bool matching_parent{false};
791
0
                    for (const auto& vin : txn[ann.tx]->vin) {
  Branch (791:42): [True: 0, False: 0]
792
0
                        if (vin.prevout.hash == txn[tx]->GetHash()) matching_parent = true;
  Branch (792:29): [True: 0, False: 0]
793
0
                    }
794
0
                    if (!matching_parent) continue;
  Branch (794:25): [True: 0, False: 0]
795
                    // Found an announcement from peer which is a child of txn[tx].
796
0
                    assert(it != children_from_peer.rend());
  Branch (796:21): [True: 0, False: 0]
797
0
                    assert((*it)->GetWitnessHash() == txn[ann.tx]->GetWitnessHash());
  Branch (797:21): [True: 0, False: 0]
798
0
                    ++it;
799
0
                }
800
0
            }
801
0
            assert(it == children_from_peer.rend());
  Branch (801:13): [True: 0, False: 0]
802
0
        }
803
0
    }
804
    // TotalOrphanUsage
805
0
    assert(orphan_usage == real->TotalOrphanUsage());
  Branch (805:5): [True: 0, False: 0]
806
0
    for (NodeId peer = 0; peer < NUM_PEERS; ++peer) {
  Branch (806:27): [True: 0, False: 0]
807
0
        bool sim_have_reconsider = have_reconsider_fn(peer);
808
        // HaveTxToReconsider
809
0
        bool have_reconsider = real->HaveTxToReconsider(peer);
810
0
        assert(have_reconsider == sim_have_reconsider);
  Branch (810:9): [True: 0, False: 0]
811
        // UsageByPeer
812
0
        assert(usage_by_peer[peer] == real->UsageByPeer(peer));
  Branch (812:9): [True: 0, False: 0]
813
        // AnnouncementsFromPeer
814
0
        assert(count_by_peer[peer] == real->AnnouncementsFromPeer(peer));
  Branch (814:9): [True: 0, False: 0]
815
0
    }
816
    // CountAnnouncements
817
0
    assert(sim_announcements.size() == real->CountAnnouncements());
  Branch (817:5): [True: 0, False: 0]
818
    // CountUniqueOrphans
819
0
    assert(unique_orphans == real->CountUniqueOrphans());
  Branch (819:5): [True: 0, False: 0]
820
    // MaxGlobalLatencyScore
821
0
    assert(max_global_latency_score == real->MaxGlobalLatencyScore());
  Branch (821:5): [True: 0, False: 0]
822
    // ReservedPeerUsage
823
0
    assert(reserved_peer_usage == real->ReservedPeerUsage());
  Branch (823:5): [True: 0, False: 0]
824
    // MaxPeerLatencyScore
825
0
    auto present_peers = count_peers_fn();
826
0
    assert(max_global_latency_score / std::max<unsigned>(1, present_peers) == real->MaxPeerLatencyScore());
  Branch (826:5): [True: 0, False: 0]
827
    // MaxGlobalUsage
828
0
    assert(reserved_peer_usage * std::max<unsigned>(1, present_peers) == real->MaxGlobalUsage());
  Branch (828:5): [True: 0, False: 0]
829
    // TotalLatencyScore.
830
0
    assert(real->TotalLatencyScore() == total_latency_score);
  Branch (830:5): [True: 0, False: 0]
831
0
}