Coverage Report

Created: 2024-10-21 15:10

/root/bitcoin/src/test/fuzz/utxo_snapshot.cpp
Line
Count
Source (jump to first uncovered line)
1
// Copyright (c) 2021-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 <chainparams.h>
7
#include <coins.h>
8
#include <consensus/consensus.h>
9
#include <consensus/validation.h>
10
#include <node/blockstorage.h>
11
#include <node/utxo_snapshot.h>
12
#include <primitives/block.h>
13
#include <primitives/transaction.h>
14
#include <serialize.h>
15
#include <span.h>
16
#include <streams.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/util/mining.h>
22
#include <test/util/setup_common.h>
23
#include <uint256.h>
24
#include <util/check.h>
25
#include <util/fs.h>
26
#include <util/result.h>
27
#include <validation.h>
28
29
#include <cstdint>
30
#include <functional>
31
#include <ios>
32
#include <memory>
33
#include <optional>
34
#include <vector>
35
36
using node::SnapshotMetadata;
37
38
namespace {
39
40
const std::vector<std::shared_ptr<CBlock>>* g_chain;
41
TestingSetup* g_setup;
42
43
template <bool INVALID>
44
void initialize_chain()
45
0
{
46
0
    const auto params{CreateChainParams(ArgsManager{}, ChainType::REGTEST)};
47
0
    static const auto chain{CreateBlockChain(2 * COINBASE_MATURITY, *params)};
48
0
    g_chain = &chain;
49
0
    static const auto setup{
50
0
        MakeNoLogFileContext<TestingSetup>(ChainType::REGTEST,
51
0
                                           TestOpts{
52
0
                                               .setup_net = false,
53
0
                                               .setup_validation_interface = false,
54
0
                                               .min_validation_cache = true,
55
0
                                           }),
56
0
    };
57
0
    if constexpr (INVALID) {
58
0
        auto& chainman{*setup->m_node.chainman};
59
0
        for (const auto& block : chain) {
60
0
            BlockValidationState dummy;
61
0
            bool processed{chainman.ProcessNewBlockHeaders({{block->GetBlockHeader()}}, true, dummy)};
62
0
            Assert(processed);
63
0
            const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
64
0
            Assert(index);
65
0
        }
66
0
    }
67
0
    g_setup = setup.get();
68
0
}
Unexecuted instantiation: utxo_snapshot.cpp:_ZN12_GLOBAL__N_116initialize_chainILb0EEEvv
Unexecuted instantiation: utxo_snapshot.cpp:_ZN12_GLOBAL__N_116initialize_chainILb1EEEvv
69
70
template <bool INVALID>
71
void utxo_snapshot_fuzz(FuzzBufferType buffer)
72
0
{
73
0
    FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
74
0
    auto& setup{*g_setup};
75
0
    bool dirty_chainman{false}; // Re-use the global chainman, but reset it when it is dirty
76
0
    auto& chainman{*setup.m_node.chainman};
77
78
0
    const auto snapshot_path = gArgs.GetDataDirNet() / "fuzzed_snapshot.dat";
79
80
0
    Assert(!chainman.SnapshotBlockhash());
81
82
0
    {
83
0
        AutoFile outfile{fsbridge::fopen(snapshot_path, "wb")};
84
        // Metadata
85
0
        if (fuzzed_data_provider.ConsumeBool()) {
86
0
            std::vector<uint8_t> metadata{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
87
0
            outfile << Span{metadata};
88
0
        } else {
89
0
            auto msg_start = chainman.GetParams().MessageStart();
90
0
            int base_blockheight{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 2 * COINBASE_MATURITY)};
91
0
            uint256 base_blockhash{g_chain->at(base_blockheight - 1)->GetHash()};
92
0
            uint64_t m_coins_count{fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(1, 3 * COINBASE_MATURITY)};
93
0
            SnapshotMetadata metadata{msg_start, base_blockhash, m_coins_count};
94
0
            outfile << metadata;
95
0
        }
96
        // Coins
97
0
        if (fuzzed_data_provider.ConsumeBool()) {
98
0
            std::vector<uint8_t> file_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
99
0
            outfile << Span{file_data};
100
0
        } else {
101
0
            int height{0};
102
0
            for (const auto& block : *g_chain) {
103
0
                auto coinbase{block->vtx.at(0)};
104
0
                outfile << coinbase->GetHash();
105
0
                WriteCompactSize(outfile, 1); // number of coins for the hash
106
0
                WriteCompactSize(outfile, 0); // index of coin
107
0
                outfile << Coin(coinbase->vout[0], height, /*fCoinBaseIn=*/1);
108
0
                height++;
109
0
            }
110
0
        }
111
0
        if constexpr (INVALID) {
112
            // Append an invalid coin to ensure invalidity. This error will be
113
            // detected late in PopulateAndValidateSnapshot, and allows the
114
            // INVALID fuzz target to reach more potential code coverage.
115
0
            const auto& coinbase{g_chain->back()->vtx.back()};
116
0
            outfile << coinbase->GetHash();
117
0
            WriteCompactSize(outfile, 1);   // number of coins for the hash
118
0
            WriteCompactSize(outfile, 999); // index of coin
119
0
            outfile << Coin{coinbase->vout[0], /*nHeightIn=*/999, /*fCoinBaseIn=*/0};
120
0
        }
121
0
    }
122
123
0
    const auto ActivateFuzzedSnapshot{[&] {
124
0
        AutoFile infile{fsbridge::fopen(snapshot_path, "rb")};
125
0
        auto msg_start = chainman.GetParams().MessageStart();
126
0
        SnapshotMetadata metadata{msg_start};
127
0
        try {
128
0
            infile >> metadata;
129
0
        } catch (const std::ios_base::failure&) {
130
0
            return false;
131
0
        }
132
0
        return !!chainman.ActivateSnapshot(infile, metadata, /*in_memory=*/true);
133
0
    }};
Unexecuted instantiation: utxo_snapshot.cpp:_ZZN12_GLOBAL__N_118utxo_snapshot_fuzzILb0EEEvSt4spanIKhLm18446744073709551615EEENKUlvE_clEv
Unexecuted instantiation: utxo_snapshot.cpp:_ZZN12_GLOBAL__N_118utxo_snapshot_fuzzILb1EEEvSt4spanIKhLm18446744073709551615EEENKUlvE_clEv
134
135
0
    if (fuzzed_data_provider.ConsumeBool()) {
136
        // Consume the bool, but skip the code for the INVALID fuzz target
137
0
        if constexpr (!INVALID) {
138
0
            for (const auto& block : *g_chain) {
139
0
                BlockValidationState dummy;
140
0
                bool processed{chainman.ProcessNewBlockHeaders({{block->GetBlockHeader()}}, true, dummy)};
141
0
                Assert(processed);
142
0
                const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
143
0
                Assert(index);
144
0
            }
145
0
            dirty_chainman = true;
146
0
        }
147
0
    }
148
149
0
    if (ActivateFuzzedSnapshot()) {
150
0
        LOCK(::cs_main);
151
0
        Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
152
0
        Assert(*chainman.ActiveChainstate().m_from_snapshot_blockhash ==
153
0
               *chainman.SnapshotBlockhash());
154
0
        const auto& coinscache{chainman.ActiveChainstate().CoinsTip()};
155
0
        for (const auto& block : *g_chain) {
156
0
            Assert(coinscache.HaveCoin(COutPoint{block->vtx.at(0)->GetHash(), 0}));
157
0
            const auto* index{chainman.m_blockman.LookupBlockIndex(block->GetHash())};
158
0
            Assert(index);
159
0
            Assert(index->nTx == 0);
160
0
            if (index->nHeight == chainman.GetSnapshotBaseHeight()) {
161
0
                auto params{chainman.GetParams().AssumeutxoForHeight(index->nHeight)};
162
0
                Assert(params.has_value());
163
0
                Assert(params.value().m_chain_tx_count == index->m_chain_tx_count);
164
0
            } else {
165
0
                Assert(index->m_chain_tx_count == 0);
166
0
            }
167
0
        }
168
0
        Assert(g_chain->size() == coinscache.GetCacheSize());
169
0
        dirty_chainman = true;
170
0
    } else {
171
0
        Assert(!chainman.SnapshotBlockhash());
172
0
        Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
173
0
    }
174
    // Snapshot should refuse to load a second time regardless of validity
175
0
    Assert(!ActivateFuzzedSnapshot());
176
0
    if constexpr (INVALID) {
177
        // Activating the snapshot, or any other action that makes the chainman
178
        // "dirty" can and must not happen for the INVALID fuzz target
179
0
        Assert(!dirty_chainman);
180
0
    }
181
0
    if (dirty_chainman) {
182
0
        setup.m_node.chainman.reset();
183
0
        setup.m_make_chainman();
184
0
        setup.LoadVerifyActivateChainstate();
185
0
    }
186
0
}
Unexecuted instantiation: utxo_snapshot.cpp:_ZN12_GLOBAL__N_118utxo_snapshot_fuzzILb0EEEvSt4spanIKhLm18446744073709551615EE
Unexecuted instantiation: utxo_snapshot.cpp:_ZN12_GLOBAL__N_118utxo_snapshot_fuzzILb1EEEvSt4spanIKhLm18446744073709551615EE
187
188
// There are two fuzz targets:
189
//
190
// The target 'utxo_snapshot', which allows valid snapshots, but is slow,
191
// because it has to reset the chainstate manager on almost all fuzz inputs.
192
// Otherwise, a dirty header tree or dirty chainstate could leak from one fuzz
193
// input execution into the next, which makes execution non-deterministic.
194
//
195
// The target 'utxo_snapshot_invalid', which is fast and does not require any
196
// expensive state to be reset.
197
0
FUZZ_TARGET(utxo_snapshot /*valid*/, .init = initialize_chain<false>) { utxo_snapshot_fuzz<false>(buffer); }
198
0
FUZZ_TARGET(utxo_snapshot_invalid, .init = initialize_chain<true>) { utxo_snapshot_fuzz<true>(buffer); }
199
200
} // namespace