Coverage Report

Created: 2025-03-18 19:34

/root/bitcoin/src/test/fuzz/utxo_total_supply.cpp
Line
Count
Source (jump to first uncovered line)
1
// Copyright (c) 2020 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 <chainparams.h>
6
#include <consensus/consensus.h>
7
#include <consensus/merkle.h>
8
#include <kernel/coinstats.h>
9
#include <node/miner.h>
10
#include <script/interpreter.h>
11
#include <streams.h>
12
#include <test/fuzz/FuzzedDataProvider.h>
13
#include <test/fuzz/fuzz.h>
14
#include <test/fuzz/util.h>
15
#include <test/util/mining.h>
16
#include <test/util/setup_common.h>
17
#include <util/chaintype.h>
18
#include <util/time.h>
19
#include <validation.h>
20
21
using node::BlockAssembler;
22
23
FUZZ_TARGET(utxo_total_supply)
24
0
{
25
0
    SeedRandomStateForTest(SeedRand::ZEROS);
26
0
    FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
27
0
    const auto mock_time{ConsumeTime(fuzzed_data_provider, /*min=*/1296688602)}; // regtest genesis block timestamp
28
    /** The testing setup that creates a chainman only (no chainstate) */
29
0
    ChainTestingSetup test_setup{
30
0
        ChainType::REGTEST,
31
0
        {
32
0
            .extra_args = {
33
0
                "-testactivationheight=bip34@2",
34
0
                strprintf("-mocktime=%d", mock_time).c_str()
35
0
            },
36
0
        },
37
0
    };
38
    // Create chainstate
39
0
    test_setup.LoadVerifyActivateChainstate();
40
0
    auto& node{test_setup.m_node};
41
0
    auto& chainman{*Assert(test_setup.m_node.chainman)};
42
43
0
    const auto ActiveHeight = [&]() {
44
0
        LOCK(chainman.GetMutex());
45
0
        return chainman.ActiveHeight();
46
0
    };
47
0
    BlockAssembler::Options options;
48
0
    options.coinbase_output_script = CScript() << OP_FALSE;
49
0
    const auto PrepareNextBlock = [&]() {
50
        // Use OP_FALSE to avoid BIP30 check from hitting early
51
0
        auto block = PrepareBlock(node, options);
52
        // Replace OP_FALSE with OP_TRUE
53
0
        {
54
0
            CMutableTransaction tx{*block->vtx.back()};
55
0
            tx.vout.at(0).scriptPubKey = CScript{} << OP_TRUE;
56
0
            block->vtx.back() = MakeTransactionRef(tx);
57
0
        }
58
0
        return block;
59
0
    };
60
61
    /** The block template this fuzzer is working on */
62
0
    auto current_block = PrepareNextBlock();
63
    /** Append-only set of tx outpoints, entries are not removed when spent */
64
0
    std::vector<std::pair<COutPoint, CTxOut>> txos;
65
    /** The utxo stats at the chain tip */
66
0
    kernel::CCoinsStats utxo_stats;
67
    /** The total amount of coins in the utxo set */
68
0
    CAmount circulation{0};
69
70
71
    // Store the tx out in the txo map
72
0
    const auto StoreLastTxo = [&]() {
73
        // get last tx
74
0
        const CTransaction& tx = *current_block->vtx.back();
75
        // get last out
76
0
        const uint32_t i = tx.vout.size() - 1;
77
        // store it
78
0
        txos.emplace_back(COutPoint{tx.GetHash(), i}, tx.vout.at(i));
79
0
        if (current_block->vtx.size() == 1 && tx.vout.at(i).scriptPubKey[0] == OP_RETURN) {
  Branch (79:13): [True: 0, False: 0]
  Branch (79:47): [True: 0, False: 0]
80
            // also store coinbase
81
0
            const uint32_t i = tx.vout.size() - 2;
82
0
            txos.emplace_back(COutPoint{tx.GetHash(), i}, tx.vout.at(i));
83
0
        }
84
0
    };
85
0
    const auto AppendRandomTxo = [&](CMutableTransaction& tx) {
86
0
        const auto& txo = txos.at(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, txos.size() - 1));
87
0
        tx.vin.emplace_back(txo.first);
88
0
        tx.vout.emplace_back(txo.second.nValue, txo.second.scriptPubKey); // "Forward" coin with no fee
89
0
    };
90
0
    const auto UpdateUtxoStats = [&]() {
91
0
        LOCK(chainman.GetMutex());
92
0
        chainman.ActiveChainstate().ForceFlushStateToDisk();
93
0
        utxo_stats = std::move(
94
0
            *Assert(kernel::ComputeUTXOStats(kernel::CoinStatsHashType::NONE, &chainman.ActiveChainstate().CoinsDB(), chainman.m_blockman, {})));
95
        // Check that miner can't print more money than they are allowed to
96
0
        assert(circulation == utxo_stats.total_amount);
97
0
    };
98
99
100
    // Update internal state to chain tip
101
0
    StoreLastTxo();
102
0
    UpdateUtxoStats();
103
0
    assert(ActiveHeight() == 0);
104
    // Get at which height we duplicate the coinbase
105
    // Assuming that the fuzzer will mine relatively short chains (less than 200 blocks), we want the duplicate coinbase to be not too high.
106
    // Up to 300 seems reasonable.
107
0
    int64_t duplicate_coinbase_height = fuzzed_data_provider.ConsumeIntegralInRange(0, 300);
108
    // Always pad with OP_0 at the end to avoid bad-cb-length error
109
0
    const CScript duplicate_coinbase_script = CScript() << duplicate_coinbase_height << OP_0;
110
    // Mine the first block with this duplicate
111
0
    current_block = PrepareNextBlock();
112
0
    StoreLastTxo();
113
114
0
    {
115
        // Create duplicate (CScript should match exact format as in CreateNewBlock)
116
0
        CMutableTransaction tx{*current_block->vtx.front()};
117
0
        tx.vin.at(0).scriptSig = duplicate_coinbase_script;
118
119
        // Mine block and create next block template
120
0
        current_block->vtx.front() = MakeTransactionRef(tx);
121
0
    }
122
0
    current_block->hashMerkleRoot = BlockMerkleRoot(*current_block);
123
0
    assert(!MineBlock(node, current_block).IsNull());
124
0
    circulation += GetBlockSubsidy(ActiveHeight(), Params().GetConsensus());
125
126
0
    assert(ActiveHeight() == 1);
127
0
    UpdateUtxoStats();
128
0
    current_block = PrepareNextBlock();
129
0
    StoreLastTxo();
130
131
    // Limit to avoid timeout, but enough to cover duplicate_coinbase_height
132
    // and CVE-2018-17144.
133
0
    LIMITED_WHILE(fuzzed_data_provider.remaining_bytes(), 2'00)
134
0
    {
135
0
        CallOneOf(
136
0
            fuzzed_data_provider,
137
0
            [&] {
138
                // Append an input-output pair to the last tx in the current block
139
0
                CMutableTransaction tx{*current_block->vtx.back()};
140
0
                AppendRandomTxo(tx);
141
0
                current_block->vtx.back() = MakeTransactionRef(tx);
142
0
                StoreLastTxo();
143
0
            },
144
0
            [&] {
145
                // Append a tx to the list of txs in the current block
146
0
                CMutableTransaction tx{};
147
0
                AppendRandomTxo(tx);
148
0
                current_block->vtx.push_back(MakeTransactionRef(tx));
149
0
                StoreLastTxo();
150
0
            },
151
0
            [&] {
152
                // Append the current block to the active chain
153
0
                node::RegenerateCommitments(*current_block, chainman);
154
0
                const bool was_valid = !MineBlock(node, current_block).IsNull();
155
156
0
                const auto prev_utxo_stats = utxo_stats;
157
0
                if (was_valid) {
  Branch (157:21): [True: 0, False: 0]
158
0
                    if (duplicate_coinbase_height == ActiveHeight()) {
  Branch (158:25): [True: 0, False: 0]
159
                        // we mined the duplicate coinbase
160
0
                        assert(current_block->vtx.at(0)->vin.at(0).scriptSig == duplicate_coinbase_script);
161
0
                    }
162
163
0
                    circulation += GetBlockSubsidy(ActiveHeight(), Params().GetConsensus());
164
0
                }
165
166
0
                UpdateUtxoStats();
167
168
0
                if (!was_valid) {
  Branch (168:21): [True: 0, False: 0]
169
                    // utxo stats must not change
170
0
                    assert(prev_utxo_stats.hashSerialized == utxo_stats.hashSerialized);
171
0
                }
172
173
0
                current_block = PrepareNextBlock();
174
0
                StoreLastTxo();
175
0
            });
176
0
    }
177
0
}