aboutsummaryrefslogtreecommitdiff
path: root/src/test/fuzz/utxo_total_supply.cpp
blob: b0f1a1251a66e4e8d87f3638a89f8dbe12a1bead (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// Copyright (c) 2020 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <chainparams.h>
#include <consensus/consensus.h>
#include <consensus/merkle.h>
#include <kernel/coinstats.h>
#include <node/miner.h>
#include <script/interpreter.h>
#include <streams.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/util/mining.h>
#include <test/util/setup_common.h>
#include <util/chaintype.h>
#include <validation.h>

FUZZ_TARGET(utxo_total_supply)
{
    /** The testing setup that creates a chainman only (no chainstate) */
    ChainTestingSetup test_setup{
        ChainType::REGTEST,
        {
            .extra_args = {"-testactivationheight=bip34@2"},
        },
    };
    // Create chainstate
    test_setup.LoadVerifyActivateChainstate();
    auto& node{test_setup.m_node};
    auto& chainman{*Assert(test_setup.m_node.chainman)};
    FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());

    const auto ActiveHeight = [&]() {
        LOCK(chainman.GetMutex());
        return chainman.ActiveHeight();
    };
    const auto PrepareNextBlock = [&]() {
        // Use OP_FALSE to avoid BIP30 check from hitting early
        auto block = PrepareBlock(node, CScript{} << OP_FALSE);
        // Replace OP_FALSE with OP_TRUE
        {
            CMutableTransaction tx{*block->vtx.back()};
            tx.vout.at(0).scriptPubKey = CScript{} << OP_TRUE;
            block->vtx.back() = MakeTransactionRef(tx);
        }
        return block;
    };

    /** The block template this fuzzer is working on */
    auto current_block = PrepareNextBlock();
    /** Append-only set of tx outpoints, entries are not removed when spent */
    std::vector<std::pair<COutPoint, CTxOut>> txos;
    /** The utxo stats at the chain tip */
    kernel::CCoinsStats utxo_stats;
    /** The total amount of coins in the utxo set */
    CAmount circulation{0};


    // Store the tx out in the txo map
    const auto StoreLastTxo = [&]() {
        // get last tx
        const CTransaction& tx = *current_block->vtx.back();
        // get last out
        const uint32_t i = tx.vout.size() - 1;
        // store it
        txos.emplace_back(COutPoint{tx.GetHash(), i}, tx.vout.at(i));
        if (current_block->vtx.size() == 1 && tx.vout.at(i).scriptPubKey[0] == OP_RETURN) {
            // also store coinbase
            const uint32_t i = tx.vout.size() - 2;
            txos.emplace_back(COutPoint{tx.GetHash(), i}, tx.vout.at(i));
        }
    };
    const auto AppendRandomTxo = [&](CMutableTransaction& tx) {
        const auto& txo = txos.at(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, txos.size() - 1));
        tx.vin.emplace_back(txo.first);
        tx.vout.emplace_back(txo.second.nValue, txo.second.scriptPubKey); // "Forward" coin with no fee
    };
    const auto UpdateUtxoStats = [&]() {
        LOCK(chainman.GetMutex());
        chainman.ActiveChainstate().ForceFlushStateToDisk();
        utxo_stats = std::move(
            *Assert(kernel::ComputeUTXOStats(kernel::CoinStatsHashType::NONE, &chainman.ActiveChainstate().CoinsDB(), chainman.m_blockman, {})));
        // Check that miner can't print more money than they are allowed to
        assert(circulation == utxo_stats.total_amount);
    };


    // Update internal state to chain tip
    StoreLastTxo();
    UpdateUtxoStats();
    assert(ActiveHeight() == 0);
    // Get at which height we duplicate the coinbase
    // Assuming that the fuzzer will mine relatively short chains (less than 200 blocks), we want the duplicate coinbase to be not too high.
    // Up to 300 seems reasonable.
    int64_t duplicate_coinbase_height = fuzzed_data_provider.ConsumeIntegralInRange(0, 300);
    // Always pad with OP_0 at the end to avoid bad-cb-length error
    const CScript duplicate_coinbase_script = CScript() << duplicate_coinbase_height << OP_0;
    // Mine the first block with this duplicate
    current_block = PrepareNextBlock();
    StoreLastTxo();

    {
        // Create duplicate (CScript should match exact format as in CreateNewBlock)
        CMutableTransaction tx{*current_block->vtx.front()};
        tx.vin.at(0).scriptSig = duplicate_coinbase_script;

        // Mine block and create next block template
        current_block->vtx.front() = MakeTransactionRef(tx);
    }
    current_block->hashMerkleRoot = BlockMerkleRoot(*current_block);
    assert(!MineBlock(node, current_block).IsNull());
    circulation += GetBlockSubsidy(ActiveHeight(), Params().GetConsensus());

    assert(ActiveHeight() == 1);
    UpdateUtxoStats();
    current_block = PrepareNextBlock();
    StoreLastTxo();

    // Limit to avoid timeout, but enough to cover duplicate_coinbase_height
    // and CVE-2018-17144.
    LIMITED_WHILE(fuzzed_data_provider.remaining_bytes(), 2'00)
    {
        CallOneOf(
            fuzzed_data_provider,
            [&] {
                // Append an input-output pair to the last tx in the current block
                CMutableTransaction tx{*current_block->vtx.back()};
                AppendRandomTxo(tx);
                current_block->vtx.back() = MakeTransactionRef(tx);
                StoreLastTxo();
            },
            [&] {
                // Append a tx to the list of txs in the current block
                CMutableTransaction tx{};
                AppendRandomTxo(tx);
                current_block->vtx.push_back(MakeTransactionRef(tx));
                StoreLastTxo();
            },
            [&] {
                // Append the current block to the active chain
                node::RegenerateCommitments(*current_block, chainman);
                const bool was_valid = !MineBlock(node, current_block).IsNull();

                const auto prev_utxo_stats = utxo_stats;
                if (was_valid) {
                    if (duplicate_coinbase_height == ActiveHeight()) {
                        // we mined the duplicate coinbase
                        assert(current_block->vtx.at(0)->vin.at(0).scriptSig == duplicate_coinbase_script);
                    }

                    circulation += GetBlockSubsidy(ActiveHeight(), Params().GetConsensus());
                }

                UpdateUtxoStats();

                if (!was_valid) {
                    // utxo stats must not change
                    assert(prev_utxo_stats.hashSerialized == utxo_stats.hashSerialized);
                }

                current_block = PrepareNextBlock();
                StoreLastTxo();
            });
    }
}