Coverage Report

Created: 2025-09-19 18:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/bitcoin/src/test/fuzz/mini_miner.cpp
Line
Count
Source
1
// Copyright (c) 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 <test/fuzz/FuzzedDataProvider.h>
6
#include <test/fuzz/fuzz.h>
7
#include <test/fuzz/util.h>
8
#include <test/fuzz/util/mempool.h>
9
#include <test/util/script.h>
10
#include <test/util/setup_common.h>
11
#include <test/util/txmempool.h>
12
#include <test/util/mining.h>
13
14
#include <node/miner.h>
15
#include <node/mini_miner.h>
16
#include <node/types.h>
17
#include <primitives/transaction.h>
18
#include <random.h>
19
#include <txmempool.h>
20
#include <util/check.h>
21
#include <util/time.h>
22
#include <util/translation.h>
23
24
#include <deque>
25
#include <vector>
26
27
namespace {
28
29
const TestingSetup* g_setup;
30
std::deque<COutPoint> g_available_coins;
31
void initialize_miner()
32
0
{
33
0
    static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
34
0
    g_setup = testing_setup.get();
35
0
    for (uint32_t i = 0; i < uint32_t{100}; ++i) {
36
0
        g_available_coins.emplace_back(Txid::FromUint256(uint256::ZERO), i);
37
0
    }
38
0
}
39
40
// Test that the MiniMiner can run with various outpoints and feerates.
41
FUZZ_TARGET(mini_miner, .init = initialize_miner)
42
1.23k
{
43
1.23k
    SeedRandomStateForTest(SeedRand::ZEROS);
44
1.23k
    FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
45
1.23k
    SetMockTime(ConsumeTime(fuzzed_data_provider));
46
1.23k
    bilingual_str error;
47
1.23k
    CTxMemPool pool{CTxMemPool::Options{}, error};
48
1.23k
    Assert(error.empty());
49
1.23k
    std::vector<COutPoint> outpoints;
50
1.23k
    std::deque<COutPoint> available_coins = g_available_coins;
51
1.23k
    LOCK2(::cs_main, pool.cs);
52
    // Cluster size cannot exceed 500
53
1.23k
    LIMITED_WHILE(!available_coins.empty(), 100)
54
48.4k
    {
55
48.4k
        CMutableTransaction mtx = CMutableTransaction();
56
48.4k
        const size_t num_inputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, available_coins.size());
57
48.4k
        const size_t num_outputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 50);
58
384k
        for (size_t n{0}; n < num_inputs; ++n) {
59
336k
            auto prevout = available_coins.front();
60
336k
            mtx.vin.emplace_back(prevout, CScript());
61
336k
            available_coins.pop_front();
62
336k
        }
63
504k
        for (uint32_t n{0}; n < num_outputs; ++n) {
64
455k
            mtx.vout.emplace_back(100, P2WSH_OP_TRUE);
65
455k
        }
66
48.4k
        CTransactionRef tx = MakeTransactionRef(mtx);
67
48.4k
        TestMemPoolEntryHelper entry;
68
48.4k
        const CAmount fee{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)};
69
48.4k
        assert(MoneyRange(fee));
70
48.4k
        AddToMempool(pool, entry.Fee(fee).FromTx(tx));
71
72
        // All outputs are available to spend
73
504k
        for (uint32_t n{0}; n < num_outputs; ++n) {
74
455k
            if (fuzzed_data_provider.ConsumeBool()) {
75
314k
                available_coins.emplace_back(tx->GetHash(), n);
76
314k
            }
77
455k
        }
78
79
48.4k
        if (fuzzed_data_provider.ConsumeBool() && !tx->vout.empty()) {
80
            // Add outpoint from this tx (may or not be spent by a later tx)
81
25.7k
            outpoints.emplace_back(tx->GetHash(),
82
25.7k
                                          (uint32_t)fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, tx->vout.size()));
83
25.7k
        } else {
84
            // Add some random outpoint (will be interpreted as confirmed or not yet submitted
85
            // to mempool).
86
22.6k
            auto outpoint = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
87
22.6k
            if (outpoint.has_value() && std::find(outpoints.begin(), outpoints.end(), *outpoint) == outpoints.end()) {
88
1.89k
                outpoints.push_back(*outpoint);
89
1.89k
            }
90
22.6k
        }
91
92
48.4k
    }
93
94
1.23k
    const CFeeRate target_feerate{CFeeRate{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/1000)}};
95
1.23k
    std::optional<CAmount> total_bumpfee;
96
1.23k
    CAmount sum_fees = 0;
97
1.23k
    {
98
1.23k
        node::MiniMiner mini_miner{pool, outpoints};
99
1.23k
        assert(mini_miner.IsReadyToCalculate());
100
615
        const auto bump_fees = mini_miner.CalculateBumpFees(target_feerate);
101
27.6k
        for (const auto& outpoint : outpoints) {
102
27.6k
            auto it = bump_fees.find(outpoint);
103
27.6k
            assert(it != bump_fees.end());
104
27.6k
            assert(it->second >= 0);
105
27.6k
            sum_fees += it->second;
106
27.6k
        }
107
615
        assert(!mini_miner.IsReadyToCalculate());
108
615
    }
109
615
    {
110
615
        node::MiniMiner mini_miner{pool, outpoints};
111
615
        assert(mini_miner.IsReadyToCalculate());
112
615
        total_bumpfee = mini_miner.CalculateTotalBumpFees(target_feerate);
113
615
        assert(total_bumpfee.has_value());
114
615
        assert(!mini_miner.IsReadyToCalculate());
115
615
    }
116
    // Overlapping ancestry across multiple outpoints can only reduce the total bump fee.
117
615
    assert (sum_fees >= *total_bumpfee);
118
615
}
119
120
// Test that MiniMiner and BlockAssembler build the same block given the same transactions and constraints.
121
FUZZ_TARGET(mini_miner_selection, .init = initialize_miner)
122
0
{
123
0
    SeedRandomStateForTest(SeedRand::ZEROS);
124
0
    FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
125
0
    SetMockTime(ConsumeTime(fuzzed_data_provider));
126
0
    bilingual_str error;
127
0
    CTxMemPool pool{CTxMemPool::Options{}, error};
128
0
    Assert(error.empty());
129
    // Make a copy to preserve determinism.
130
0
    std::deque<COutPoint> available_coins = g_available_coins;
131
0
    std::vector<CTransactionRef> transactions;
132
133
0
    LOCK2(::cs_main, pool.cs);
134
0
    LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100)
135
0
    {
136
0
        CMutableTransaction mtx = CMutableTransaction();
137
0
        assert(!available_coins.empty());
138
0
        const size_t num_inputs = std::min(size_t{2}, available_coins.size());
139
0
        const size_t num_outputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(2, 5);
140
0
        for (size_t n{0}; n < num_inputs; ++n) {
141
0
            auto prevout = available_coins.at(0);
142
0
            mtx.vin.emplace_back(prevout, CScript());
143
0
            available_coins.pop_front();
144
0
        }
145
0
        for (uint32_t n{0}; n < num_outputs; ++n) {
146
0
            mtx.vout.emplace_back(100, P2WSH_OP_TRUE);
147
0
        }
148
0
        CTransactionRef tx = MakeTransactionRef(mtx);
149
150
        // First 2 outputs are available to spend. The rest are added to outpoints to calculate bumpfees.
151
        // There is no overlap between spendable coins and outpoints passed to MiniMiner because the
152
        // MiniMiner interprets spent coins as to-be-replaced and excludes them.
153
0
        for (uint32_t n{0}; n < num_outputs - 1; ++n) {
154
0
            if (fuzzed_data_provider.ConsumeBool()) {
155
0
                available_coins.emplace_front(tx->GetHash(), n);
156
0
            } else {
157
0
                available_coins.emplace_back(tx->GetHash(), n);
158
0
            }
159
0
        }
160
161
0
        const auto block_adjusted_max_weight = MAX_BLOCK_WEIGHT - DEFAULT_BLOCK_RESERVED_WEIGHT;
162
        // Stop if pool reaches block_adjusted_max_weight because BlockAssembler will stop when the
163
        // block template reaches that, but the MiniMiner will keep going.
164
0
        if (pool.GetTotalTxSize() + GetVirtualTransactionSize(*tx) >= block_adjusted_max_weight) break;
165
0
        TestMemPoolEntryHelper entry;
166
0
        const CAmount fee{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)};
167
0
        assert(MoneyRange(fee));
168
0
        AddToMempool(pool, entry.Fee(fee).FromTx(tx));
169
0
        transactions.push_back(tx);
170
0
    }
171
0
    std::vector<COutPoint> outpoints;
172
0
    for (const auto& coin : g_available_coins) {
173
0
        if (!pool.GetConflictTx(coin)) outpoints.push_back(coin);
174
0
    }
175
0
    for (const auto& tx : transactions) {
176
0
        assert(pool.exists(tx->GetHash()));
177
0
        for (uint32_t n{0}; n < tx->vout.size(); ++n) {
178
0
            COutPoint coin{tx->GetHash(), n};
179
0
            if (!pool.GetConflictTx(coin)) outpoints.push_back(coin);
180
0
        }
181
0
    }
182
0
    const CFeeRate target_feerate{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)};
183
184
0
    node::BlockAssembler::Options miner_options;
185
0
    miner_options.blockMinFeeRate = target_feerate;
186
0
    miner_options.nBlockMaxWeight = MAX_BLOCK_WEIGHT;
187
0
    miner_options.test_block_validity = false;
188
0
    miner_options.coinbase_output_script = CScript() << OP_0;
189
190
0
    node::BlockAssembler miner{g_setup->m_node.chainman->ActiveChainstate(), &pool, miner_options};
191
0
    node::MiniMiner mini_miner{pool, outpoints};
192
0
    assert(mini_miner.IsReadyToCalculate());
193
194
    // Use BlockAssembler as oracle. BlockAssembler and MiniMiner should select the same
195
    // transactions, stopping once packages do not meet target_feerate.
196
0
    const auto blocktemplate{miner.CreateNewBlock()};
197
0
    mini_miner.BuildMockTemplate(target_feerate);
198
0
    assert(!mini_miner.IsReadyToCalculate());
199
0
    auto mock_template_txids = mini_miner.GetMockTemplateTxids();
200
    // MiniMiner doesn't add a coinbase tx.
201
0
    assert(mock_template_txids.count(blocktemplate->block.vtx[0]->GetHash()) == 0);
202
0
    auto [iter, new_entry] = mock_template_txids.emplace(blocktemplate->block.vtx[0]->GetHash());
203
0
    assert(new_entry);
204
205
0
    assert(mock_template_txids.size() == blocktemplate->block.vtx.size());
206
0
    for (const auto& tx : blocktemplate->block.vtx) {
207
        assert(mock_template_txids.count(tx->GetHash()));
208
0
    }
209
0
}
210
} // namespace