Coverage Report

Created: 2026-08-25 19:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/bitcoin/src/test/fuzz/package_eval.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 <chain.h>
6
#include <consensus/amount.h>
7
#include <consensus/consensus.h>
8
#include <consensus/validation.h>
9
#include <node/mining_types.h>
10
#include <policy/feerate.h>
11
#include <policy/packages.h>
12
#include <policy/policy.h>
13
#include <policy/settings.h>
14
#include <policy/truc_policy.h>
15
#include <primitives/transaction.h>
16
#include <script/script.h>
17
#include <sync.h>
18
#include <test/fuzz/FuzzedDataProvider.h>
19
#include <test/fuzz/fuzz.h>
20
#include <test/fuzz/util.h>
21
#include <test/fuzz/util/mempool.h>
22
#include <test/util/mining.h>
23
#include <test/util/random.h>
24
#include <test/util/script.h>
25
#include <test/util/setup_common.h>
26
#include <test/util/txmempool.h>
27
#include <txmempool.h>
28
#include <util/check.h>
29
#include <util/hasher.h>
30
#include <util/time.h>
31
#include <util/translation.h>
32
#include <validation.h>
33
#include <validationinterface.h>
34
35
#include <cstddef>
36
#include <cstdint>
37
#include <functional>
38
#include <iterator>
39
#include <limits>
40
#include <map>
41
#include <memory>
42
#include <optional>
43
#include <set>
44
#include <span>
45
#include <string>
46
#include <unordered_map>
47
#include <utility>
48
#include <vector>
49
using node::NodeContext;
50
51
namespace {
52
53
const TestingSetup* g_setup;
54
std::vector<COutPoint> g_outpoints_coinbase_init_mature;
55
56
struct MockedTxPool : public CTxMemPool {
57
    void RollingFeeUpdate() EXCLUSIVE_LOCKS_REQUIRED(!cs)
58
89.9k
    {
59
89.9k
        LOCK(cs);
60
89.9k
        lastRollingFeeUpdate = GetTime();
61
89.9k
        blockSinceLastRollingFeeBump = true;
62
89.9k
    }
63
};
64
65
void initialize_tx_pool()
66
0
{
67
0
    static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
68
0
    g_setup = testing_setup.get();
69
0
    SetMockTime(WITH_LOCK(g_setup->m_node.chainman->GetMutex(), return g_setup->m_node.chainman->ActiveTip()->Time()));
70
71
0
    for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) {
  Branch (71:21): [True: 0, False: 0]
72
0
        COutPoint prevout{MineBlock(g_setup->m_node, {
73
0
            .coinbase_output_script = P2WSH_EMPTY,
74
0
        })};
75
0
        if (i < COINBASE_MATURITY) {
  Branch (75:13): [True: 0, False: 0]
76
            // Remember the txids to avoid expensive disk access later on
77
0
            g_outpoints_coinbase_init_mature.push_back(prevout);
78
0
        }
79
0
    }
80
0
    g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
81
0
}
82
83
struct OutpointsUpdater final : public CValidationInterface {
84
    std::set<COutPoint>& m_mempool_outpoints;
85
86
    explicit OutpointsUpdater(std::set<COutPoint>& r)
87
6.17k
        : m_mempool_outpoints{r} {}
88
89
    void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /* mempool_sequence */) override
90
250k
    {
91
        // for coins spent we always want to be able to rbf so they're not removed
92
93
        // outputs from this tx can now be spent
94
1.17M
        for (uint32_t index{0}; index < tx.info.m_tx->vout.size(); ++index) {
  Branch (94:33): [True: 927k, False: 250k]
95
927k
            m_mempool_outpoints.insert(COutPoint{tx.info.m_tx->GetHash(), index});
96
927k
        }
97
250k
    }
98
99
    void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override
100
94.1k
    {
101
        // outpoints spent by this tx are now available
102
163k
        for (const auto& input : tx->vin) {
  Branch (102:32): [True: 163k, False: 94.1k]
103
            // Could already exist if this was a replacement
104
163k
            m_mempool_outpoints.insert(input.prevout);
105
163k
        }
106
        // outpoints created by this tx no longer exist
107
360k
        for (uint32_t index{0}; index < tx->vout.size(); ++index) {
  Branch (107:33): [True: 266k, False: 94.1k]
108
266k
            m_mempool_outpoints.erase(COutPoint{tx->GetHash(), index});
109
266k
        }
110
94.1k
    }
111
};
112
113
struct TransactionsDelta final : public CValidationInterface {
114
    std::set<CTransactionRef>& m_added;
115
116
    explicit TransactionsDelta(std::set<CTransactionRef>& a)
117
353k
        : m_added{a} {}
118
119
    void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /* mempool_sequence */) override
120
70.9k
    {
121
        // Transactions may be entered and booted any number of times
122
70.9k
        m_added.insert(tx.info.m_tx);
123
70.9k
    }
124
125
    void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override
126
51.7k
    {
127
        // Transactions may be entered and booted any number of times
128
51.7k
         m_added.erase(tx);
129
51.7k
    }
130
};
131
132
void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate)
133
184k
{
134
184k
    const auto time = ConsumeTime(fuzzed_data_provider,
135
184k
                                  chainstate.m_chain.Tip()->GetMedianTimePast() + 1,
136
184k
                                  std::numeric_limits<decltype(chainstate.m_chain.Tip()->nTime)>::max());
137
184k
    SetMockTime(time);
138
184k
}
139
140
std::unique_ptr<CTxMemPool> MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeContext& node)
141
3.19k
{
142
    // Take the default options for tests...
143
3.19k
    CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)};
144
145
146
    // ...override specific options for this specific fuzz suite
147
3.19k
    mempool_opts.limits.ancestor_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50);
148
3.19k
    mempool_opts.limits.descendant_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50);
149
3.19k
    mempool_opts.max_size_bytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 200) * 1'000'000;
150
3.19k
    mempool_opts.expiry = std::chrono::hours{fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999)};
151
    // Only interested in 2 cases: sigop cost 0 or when single legacy sigop cost is >> 1KvB
152
3.19k
    nBytesPerSigOp = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 1) * 10'000;
153
154
3.19k
    mempool_opts.check_ratio = 1;
155
3.19k
    mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool();
156
157
3.19k
    bilingual_str error;
158
    // ...and construct a CTxMemPool from it
159
3.19k
    auto mempool{std::make_unique<CTxMemPool>(std::move(mempool_opts), error)};
160
    // ... ignore the error since it might be beneficial to fuzz even when the
161
    // mempool size is unreasonably small
162
3.19k
    Assert(error.empty() || error.original.starts_with("-maxmempool must be at least "));
163
3.19k
    return mempool;
164
3.19k
}
165
166
std::unique_ptr<CTxMemPool> MakeEphemeralMempool(const NodeContext& node)
167
2.98k
{
168
    // Take the default options for tests...
169
2.98k
    CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)};
170
171
2.98k
    mempool_opts.check_ratio = 1;
172
173
    // Require standardness rules otherwise ephemeral dust is no-op
174
2.98k
    mempool_opts.require_standard = true;
175
176
    // And set minrelay to 0 to allow ephemeral parent tx even with non-TRUC
177
2.98k
    mempool_opts.min_relay_feerate = CFeeRate(0);
178
179
2.98k
    bilingual_str error;
180
    // ...and construct a CTxMemPool from it
181
2.98k
    auto mempool{std::make_unique<CTxMemPool>(std::move(mempool_opts), error)};
182
2.98k
    Assert(error.empty());
183
2.98k
    return mempool;
184
2.98k
}
185
186
// Scan mempool for a tx that has spent dust and return a
187
// prevout of the child that isn't the dusty parent itself.
188
// This is used to double-spend the child out of the mempool,
189
// leaving the parent childless.
190
// This assumes CheckMempoolEphemeralInvariants has passed for tx_pool.
191
std::optional<COutPoint> GetChildEvictingPrevout(const CTxMemPool& tx_pool)
192
243k
{
193
243k
    LOCK(tx_pool.cs);
194
9.59M
    for (const auto& tx_info : tx_pool.infoAll()) {
  Branch (194:30): [True: 9.59M, False: 235k]
195
9.59M
        const auto& entry = *Assert(tx_pool.GetEntry(tx_info.tx->GetHash()));
196
9.59M
        std::vector<uint32_t> dust_indexes{GetDust(*tx_info.tx, tx_pool.m_opts.dust_relay_feerate)};
197
9.59M
        if (!dust_indexes.empty()) {
  Branch (197:13): [True: 23.1k, False: 9.57M]
198
23.1k
            const auto& children = tx_pool.GetChildren(entry);
199
23.1k
            if (!children.empty()) {
  Branch (199:17): [True: 11.4k, False: 11.7k]
200
11.4k
                Assert(children.size() == 1);
201
                // Find an input that doesn't spend from parent's txid
202
11.4k
                const auto& only_child = children.begin()->get().GetTx();
203
18.3k
                for (const auto& tx_input : only_child.vin) {
  Branch (203:43): [True: 18.3k, False: 3.36k]
204
18.3k
                    if (tx_input.prevout.hash != tx_info.tx->GetHash()) {
  Branch (204:25): [True: 8.04k, False: 10.3k]
205
8.04k
                        return tx_input.prevout;
206
8.04k
                    }
207
18.3k
                }
208
11.4k
            }
209
23.1k
        }
210
9.59M
    }
211
212
235k
    return std::nullopt;
213
243k
}
214
215
FUZZ_TARGET(ephemeral_package_eval, .init = initialize_tx_pool)
216
2.98k
{
217
2.98k
    SeedRandomStateForTest(SeedRand::ZEROS);
218
2.98k
    FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
219
2.98k
    const auto& node = g_setup->m_node;
220
2.98k
    auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())};
221
222
2.98k
    MockTime(fuzzed_data_provider, chainstate);
223
224
    // All RBF-spendable outpoints outside of the unsubmitted package
225
2.98k
    std::set<COutPoint> mempool_outpoints;
226
2.98k
    std::unordered_map<COutPoint, CAmount, SaltedOutpointHasher> outpoints_value;
227
298k
    for (const auto& outpoint : g_outpoints_coinbase_init_mature) {
  Branch (227:31): [True: 298k, False: 2.98k]
228
298k
        Assert(mempool_outpoints.insert(outpoint).second);
229
298k
        outpoints_value[outpoint] = 50 * COIN;
230
298k
    }
231
232
2.98k
    auto outpoints_updater = std::make_shared<OutpointsUpdater>(mempool_outpoints);
233
2.98k
    node.validation_signals->RegisterSharedValidationInterface(outpoints_updater);
234
235
2.98k
    auto tx_pool_{MakeEphemeralMempool(node)};
236
2.98k
    MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(tx_pool_.get());
237
238
2.98k
    chainstate.SetMempool(&tx_pool);
239
240
533k
    LIMITED_WHILE (fuzzed_data_provider.remaining_bytes() > 0, 300) {
241
533k
        Assert(!mempool_outpoints.empty());
242
243
533k
        std::vector<CTransactionRef> txs;
244
245
        // Find something we may want to double-spend with two input single tx
246
533k
        std::optional<COutPoint> outpoint_to_rbf{fuzzed_data_provider.ConsumeBool() ? GetChildEvictingPrevout(tx_pool) : std::nullopt};
  Branch (246:50): [True: 243k, False: 290k]
247
248
        // Make small packages
249
533k
        const auto num_txs = outpoint_to_rbf ? 1 : fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 4);
  Branch (249:30): [True: 8.04k, False: 525k]
250
251
533k
        std::set<COutPoint> package_outpoints;
252
1.75M
        while (txs.size() < num_txs) {
  Branch (252:16): [True: 1.22M, False: 533k]
253
            // Create transaction to add to the mempool
254
1.22M
            txs.emplace_back([&] {
255
1.22M
                CMutableTransaction tx_mut;
256
1.22M
                tx_mut.version = CTransaction::CURRENT_VERSION;
257
1.22M
                tx_mut.nLockTime = 0;
258
                // Last transaction in a package needs to be a child of parents to get further in validation
259
                // so the last transaction to be generated(in a >1 package) must spend all package-made outputs
260
                // Note that this test currently only spends package outputs in last transaction.
261
1.22M
                bool last_tx = num_txs > 1 && txs.size() == num_txs - 1;
  Branch (261:32): [True: 1.03M, False: 181k]
  Branch (261:47): [True: 351k, False: 687k]
262
1.22M
                const auto num_in = outpoint_to_rbf ? 2 :
  Branch (262:37): [True: 8.04k, False: 1.21M]
263
1.22M
                    last_tx ? fuzzed_data_provider.ConsumeIntegralInRange<int>(package_outpoints.size()/2 + 1, package_outpoints.size()) :
  Branch (263:21): [True: 351k, False: 861k]
264
1.21M
                    fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 4);
265
1.22M
                const auto num_out = outpoint_to_rbf ? 1 : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 4);
  Branch (265:38): [True: 8.04k, False: 1.21M]
266
267
1.22M
                auto& outpoints = last_tx ? package_outpoints : mempool_outpoints;
  Branch (267:35): [True: 351k, False: 869k]
268
269
1.22M
                Assert((int)outpoints.size() >= num_in && num_in > 0);
270
271
1.22M
                CAmount amount_in{0};
272
4.55M
                for (int i = 0; i < num_in; ++i) {
  Branch (272:33): [True: 3.33M, False: 1.22M]
273
                    // Pop random outpoint. We erase them to avoid double-spending
274
                    // while in this loop, but later add them back (unless last_tx).
275
3.33M
                    auto pop = outpoints.begin();
276
3.33M
                    std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints.size() - 1));
277
3.33M
                    auto outpoint = *pop;
278
279
3.33M
                    if (i == 0 && outpoint_to_rbf) {
  Branch (279:25): [True: 1.22M, False: 2.11M]
  Branch (279:35): [True: 8.04k, False: 1.21M]
280
8.04k
                        outpoint = *outpoint_to_rbf;
281
8.04k
                        outpoints.erase(outpoint);
282
3.32M
                    } else {
283
3.32M
                        outpoints.erase(pop);
284
3.32M
                    }
285
                    // no need to update or erase from outpoints_value
286
3.33M
                    amount_in += outpoints_value.at(outpoint);
287
288
                    // Create input
289
3.33M
                    CTxIn in;
290
3.33M
                    in.prevout = outpoint;
291
3.33M
                    in.scriptWitness.stack = P2WSH_EMPTY_TRUE_STACK;
292
293
3.33M
                    tx_mut.vin.push_back(in);
294
3.33M
                }
295
296
1.22M
                const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, amount_in);
297
1.22M
                const auto amount_out = (amount_in - amount_fee) / num_out;
298
4.04M
                for (int i = 0; i < num_out; ++i) {
  Branch (298:33): [True: 2.82M, False: 1.22M]
299
2.82M
                    tx_mut.vout.emplace_back(amount_out, P2WSH_EMPTY);
300
2.82M
                }
301
302
                // Note output amounts can naturally drop to dust on their own.
303
1.22M
                if (!outpoint_to_rbf && fuzzed_data_provider.ConsumeBool()) {
  Branch (303:21): [True: 1.21M, False: 8.04k]
  Branch (303:41): [True: 380k, False: 831k]
304
380k
                    uint32_t dust_index = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, num_out);
305
380k
                    tx_mut.vout.insert(tx_mut.vout.begin() + dust_index, CTxOut(0, P2WSH_EMPTY));
306
380k
                }
307
308
1.22M
                auto tx = MakeTransactionRef(tx_mut);
309
                // Restore previously removed outpoints, except in-package outpoints (to allow RBF)
310
1.22M
                if (!last_tx) {
  Branch (310:21): [True: 869k, False: 351k]
311
1.93M
                    for (const auto& in : tx->vin) {
  Branch (311:41): [True: 1.93M, False: 869k]
312
1.93M
                        Assert(outpoints.insert(in.prevout).second);
313
1.93M
                    }
314
                    // Cache the in-package outpoints being made
315
3.04M
                    for (size_t i = 0; i < tx->vout.size(); ++i) {
  Branch (315:40): [True: 2.17M, False: 869k]
316
2.17M
                        package_outpoints.emplace(tx->GetHash(), i);
317
2.17M
                    }
318
869k
                }
319
                // We need newly-created values for the duration of this run
320
4.42M
                for (size_t i = 0; i < tx->vout.size(); ++i) {
  Branch (320:36): [True: 3.20M, False: 1.22M]
321
3.20M
                    outpoints_value[COutPoint(tx->GetHash(), i)] = tx->vout[i].nValue;
322
3.20M
                }
323
1.22M
                return tx;
324
1.22M
            }());
325
1.22M
        }
326
327
533k
        if (fuzzed_data_provider.ConsumeBool()) {
  Branch (327:13): [True: 212k, False: 321k]
328
212k
            const auto& txid = fuzzed_data_provider.ConsumeBool() ?
  Branch (328:32): [True: 69.4k, False: 142k]
329
69.4k
                                   txs.back()->GetHash() :
330
212k
                                   PickValue(fuzzed_data_provider, mempool_outpoints).hash;
331
212k
            const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN);
332
            // We only prioritise out of mempool transactions since PrioritiseTransaction doesn't
333
            // filter for ephemeral dust
334
212k
            if (tx_pool.exists(txid)) {
  Branch (334:17): [True: 64.4k, False: 147k]
335
64.4k
                const auto tx_info{tx_pool.info(txid)};
336
64.4k
                if (GetDust(*tx_info.tx, tx_pool.m_opts.dust_relay_feerate).empty()) {
  Branch (336:21): [True: 63.7k, False: 714]
337
63.7k
                    tx_pool.PrioritiseTransaction(txid, delta);
338
63.7k
                }
339
64.4k
            }
340
212k
        }
341
342
533k
        auto single_submit = txs.size() == 1;
343
344
533k
        const auto result_package = WITH_LOCK(::cs_main,
345
533k
                                    return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, /*client_maxfeerate=*/{}));
346
347
533k
        const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(),
348
533k
                                   /*bypass_limits=*/false, /*test_accept=*/!single_submit));
349
350
533k
        if (!single_submit && result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {
  Branch (350:13): [True: 351k, False: 181k]
  Branch (350:31): [True: 201k, False: 150k]
351
            // We don't know anything about the validity since transactions were randomly generated, so
352
            // just use result_package.m_state here. This makes the expect_valid check meaningless, but
353
            // we can still verify that the contents of m_tx_results are consistent with m_state.
354
201k
            const bool expect_valid{result_package.m_state.IsValid()};
355
201k
            Assert(!CheckPackageMempoolAcceptResult(txs, result_package, expect_valid, &tx_pool));
356
201k
        }
357
358
533k
        node.validation_signals->SyncWithValidationInterfaceQueue();
359
360
533k
        CheckMempoolEphemeralInvariants(tx_pool);
361
533k
    }
362
363
2.98k
    node.validation_signals->UnregisterSharedValidationInterface(outpoints_updater);
364
365
2.98k
    WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
366
2.98k
}
367
368
369
FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
370
3.19k
{
371
3.19k
    SeedRandomStateForTest(SeedRand::ZEROS);
372
3.19k
    FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
373
3.19k
    const auto& node = g_setup->m_node;
374
3.19k
    auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())};
375
376
3.19k
    MockTime(fuzzed_data_provider, chainstate);
377
378
    // All RBF-spendable outpoints outside of the unsubmitted package
379
3.19k
    std::set<COutPoint> mempool_outpoints;
380
3.19k
    std::unordered_map<COutPoint, CAmount, SaltedOutpointHasher> outpoints_value;
381
319k
    for (const auto& outpoint : g_outpoints_coinbase_init_mature) {
  Branch (381:31): [True: 319k, False: 3.19k]
382
319k
        Assert(mempool_outpoints.insert(outpoint).second);
383
319k
        outpoints_value[outpoint] = 50 * COIN;
384
319k
    }
385
386
3.19k
    auto outpoints_updater = std::make_shared<OutpointsUpdater>(mempool_outpoints);
387
3.19k
    node.validation_signals->RegisterSharedValidationInterface(outpoints_updater);
388
389
3.19k
    auto tx_pool_{MakeMempool(fuzzed_data_provider, node)};
390
3.19k
    MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(tx_pool_.get());
391
392
3.19k
    chainstate.SetMempool(&tx_pool);
393
394
353k
    LIMITED_WHILE (fuzzed_data_provider.remaining_bytes() > 0, 300) {
395
353k
        Assert(!mempool_outpoints.empty());
396
397
353k
        std::vector<CTransactionRef> txs;
398
399
        // Make packages of 1-to-26 transactions
400
353k
        const auto num_txs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 26);
401
353k
        std::set<COutPoint> package_outpoints;
402
1.20M
        while (txs.size() < num_txs) {
  Branch (402:16): [True: 852k, False: 353k]
403
            // Create transaction to add to the mempool
404
852k
            txs.emplace_back([&] {
405
852k
                CMutableTransaction tx_mut;
406
852k
                tx_mut.version = fuzzed_data_provider.ConsumeBool() ? TRUC_VERSION : CTransaction::CURRENT_VERSION;
  Branch (406:34): [True: 242k, False: 609k]
407
852k
                tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>();
  Branch (407:36): [True: 302k, False: 550k]
408
                // Last transaction in a package needs to be a child of parents to get further in validation
409
                // so the last transaction to be generated(in a >1 package) must spend all package-made outputs
410
                // Note that this test currently only spends package outputs in last transaction.
411
852k
                bool last_tx = num_txs > 1 && txs.size() == num_txs - 1;
  Branch (411:32): [True: 606k, False: 246k]
  Branch (411:47): [True: 107k, False: 498k]
412
852k
                const auto num_in = last_tx ? package_outpoints.size()  : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, mempool_outpoints.size());
  Branch (412:37): [True: 107k, False: 744k]
413
852k
                auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, mempool_outpoints.size() * 2);
414
415
852k
                auto& outpoints = last_tx ? package_outpoints : mempool_outpoints;
  Branch (415:35): [True: 107k, False: 744k]
416
417
852k
                Assert(!outpoints.empty());
418
419
852k
                CAmount amount_in{0};
420
16.3M
                for (size_t i = 0; i < num_in; ++i) {
  Branch (420:36): [True: 15.5M, False: 852k]
421
                    // Pop random outpoint. We erase them to avoid double-spending
422
                    // while in this loop, but later add them back (unless last_tx).
423
15.5M
                    auto pop = outpoints.begin();
424
15.5M
                    std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints.size() - 1));
425
15.5M
                    const auto outpoint = *pop;
426
15.5M
                    outpoints.erase(pop);
427
                    // no need to update or erase from outpoints_value
428
15.5M
                    amount_in += outpoints_value.at(outpoint);
429
430
                    // Create input
431
15.5M
                    const auto sequence = ConsumeSequence(fuzzed_data_provider);
432
15.5M
                    const auto script_sig = CScript{};
433
15.5M
                    const auto script_wit_stack = fuzzed_data_provider.ConsumeBool() ? P2WSH_EMPTY_TRUE_STACK : P2WSH_EMPTY_TWO_STACK;
  Branch (433:51): [True: 6.30M, False: 9.24M]
434
435
15.5M
                    CTxIn in;
436
15.5M
                    in.prevout = outpoint;
437
15.5M
                    in.nSequence = sequence;
438
15.5M
                    in.scriptSig = script_sig;
439
15.5M
                    in.scriptWitness.stack = script_wit_stack;
440
441
15.5M
                    tx_mut.vin.push_back(in);
442
15.5M
                }
443
444
                // Duplicate an input
445
852k
                bool dup_input = fuzzed_data_provider.ConsumeBool();
446
852k
                if (dup_input) {
  Branch (446:21): [True: 266k, False: 585k]
447
266k
                    tx_mut.vin.push_back(tx_mut.vin.back());
448
266k
                }
449
450
                // Refer to a non-existent input
451
852k
                if (fuzzed_data_provider.ConsumeBool()) {
  Branch (451:21): [True: 244k, False: 607k]
452
244k
                    tx_mut.vin.emplace_back();
453
244k
                }
454
455
                // Make a p2pk output to make sigops adjusted vsize to violate TRUC rules, potentially, which is never spent
456
852k
                if (last_tx && amount_in > 1000 && fuzzed_data_provider.ConsumeBool()) {
  Branch (456:21): [True: 107k, False: 744k]
  Branch (456:32): [True: 107k, False: 146]
  Branch (456:52): [True: 42.0k, False: 65.2k]
457
42.0k
                    tx_mut.vout.emplace_back(1000, CScript() << std::vector<unsigned char>(33, 0x02) << OP_CHECKSIG);
458
                    // Don't add any other outputs.
459
42.0k
                    num_out = 1;
460
42.0k
                    amount_in -= 1000;
461
42.0k
                }
462
463
852k
                const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, amount_in);
464
852k
                const auto amount_out = (amount_in - amount_fee) / num_out;
465
21.3M
                for (int i = 0; i < num_out; ++i) {
  Branch (465:33): [True: 20.5M, False: 852k]
466
20.5M
                    tx_mut.vout.emplace_back(amount_out, P2WSH_EMPTY);
467
20.5M
                }
468
852k
                auto tx = MakeTransactionRef(tx_mut);
469
                // Restore previously removed outpoints, except in-package outpoints
470
852k
                if (!last_tx) {
  Branch (470:21): [True: 744k, False: 107k]
471
7.74M
                    for (const auto& in : tx->vin) {
  Branch (471:41): [True: 7.74M, False: 744k]
472
                        // It's a fake input, or a new input, or a duplicate
473
7.74M
                        Assert(in == CTxIn() || outpoints.insert(in.prevout).second || dup_input);
474
7.74M
                    }
475
                    // Cache the in-package outpoints being made
476
19.8M
                    for (size_t i = 0; i < tx->vout.size(); ++i) {
  Branch (476:40): [True: 19.0M, False: 744k]
477
19.0M
                        package_outpoints.emplace(tx->GetHash(), i);
478
19.0M
                    }
479
744k
                }
480
                // We need newly-created values for the duration of this run
481
21.4M
                for (size_t i = 0; i < tx->vout.size(); ++i) {
  Branch (481:36): [True: 20.5M, False: 852k]
482
20.5M
                    outpoints_value[COutPoint(tx->GetHash(), i)] = tx->vout[i].nValue;
483
20.5M
                }
484
852k
                return tx;
485
852k
            }());
486
852k
        }
487
488
353k
        if (fuzzed_data_provider.ConsumeBool()) {
  Branch (488:13): [True: 178k, False: 175k]
489
178k
            MockTime(fuzzed_data_provider, chainstate);
490
178k
        }
491
353k
        if (fuzzed_data_provider.ConsumeBool()) {
  Branch (491:13): [True: 89.9k, False: 263k]
492
89.9k
            tx_pool.RollingFeeUpdate();
493
89.9k
        }
494
353k
        if (fuzzed_data_provider.ConsumeBool()) {
  Branch (494:13): [True: 112k, False: 241k]
495
112k
            const auto& txid = fuzzed_data_provider.ConsumeBool() ?
  Branch (495:32): [True: 55.7k, False: 56.8k]
496
55.7k
                                   txs.back()->GetHash() :
497
112k
                                   PickValue(fuzzed_data_provider, mempool_outpoints).hash;
498
112k
            const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN);
499
112k
            tx_pool.PrioritiseTransaction(txid, delta);
500
112k
        }
501
502
        // Remember all added transactions
503
353k
        std::set<CTransactionRef> added;
504
353k
        auto txr = std::make_shared<TransactionsDelta>(added);
505
353k
        node.validation_signals->RegisterSharedValidationInterface(txr);
506
507
        // When there are multiple transactions in the package, we call ProcessNewPackage(txs, test_accept=false)
508
        // and AcceptToMemoryPool(txs.back(), test_accept=true). When there is only 1 transaction, we might flip it
509
        // (the package is a test accept and ATMP is a submission).
510
353k
        auto single_submit = txs.size() == 1 && fuzzed_data_provider.ConsumeBool();
  Branch (510:30): [True: 246k, False: 107k]
  Branch (510:49): [True: 36.4k, False: 209k]
511
512
        // Exercise client_maxfeerate logic
513
353k
        std::optional<CFeeRate> client_maxfeerate{};
514
353k
        if (fuzzed_data_provider.ConsumeBool()) {
  Branch (514:13): [True: 82.1k, False: 271k]
515
82.1k
            client_maxfeerate = CFeeRate(fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-1, 50 * COIN), 100);
516
82.1k
        }
517
518
353k
        const auto result_package = WITH_LOCK(::cs_main,
519
353k
                                    return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, client_maxfeerate));
520
521
        // Always set bypass_limits to false because it is not supported in ProcessNewPackage and
522
        // can be a source of divergence.
523
353k
        const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(),
524
353k
                                   /*bypass_limits=*/false, /*test_accept=*/!single_submit));
525
353k
        const bool passed = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
526
527
353k
        node.validation_signals->SyncWithValidationInterfaceQueue();
528
353k
        node.validation_signals->UnregisterSharedValidationInterface(txr);
529
530
        // There is only 1 transaction in the package. We did a test-package-accept and a ATMP
531
353k
        if (single_submit) {
  Branch (531:13): [True: 36.4k, False: 317k]
532
36.4k
            Assert(passed != added.empty());
533
36.4k
            Assert(passed == res.m_state.IsValid());
534
36.4k
            if (passed) {
  Branch (534:17): [True: 4.43k, False: 32.0k]
535
4.43k
                Assert(added.size() == 1);
536
4.43k
                Assert(txs.back() == *added.begin());
537
4.43k
            }
538
317k
        } else if (result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {
  Branch (538:20): [True: 264k, False: 52.5k]
539
            // We don't know anything about the validity since transactions were randomly generated, so
540
            // just use result_package.m_state here. This makes the expect_valid check meaningless, but
541
            // we can still verify that the contents of m_tx_results are consistent with m_state.
542
264k
            const bool expect_valid{result_package.m_state.IsValid()};
543
264k
            Assert(!CheckPackageMempoolAcceptResult(txs, result_package, expect_valid, &tx_pool));
544
264k
        } else {
545
            // This is empty if it fails early checks, or "full" if transactions are looked at deeper
546
52.5k
            Assert(result_package.m_tx_results.size() == txs.size() || result_package.m_tx_results.empty());
547
52.5k
        }
548
549
353k
        CheckMempoolTRUCInvariants(tx_pool);
550
551
        // Dust checks only make sense when dust is enforced
552
353k
        if (tx_pool.m_opts.require_standard) {
  Branch (552:13): [True: 205k, False: 147k]
553
205k
            CheckMempoolEphemeralInvariants(tx_pool);
554
205k
        }
555
353k
    }
556
557
3.19k
    node.validation_signals->UnregisterSharedValidationInterface(outpoints_updater);
558
559
3.19k
    WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
560
3.19k
}
561
} // namespace