Coverage Report

Created: 2026-07-23 20:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/tmp/bitcoin/src/kernel/coinstats.cpp
Line
Count
Source
1
// Copyright (c) 2022-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 <kernel/coinstats.h>
6
7
#include <chain.h>
8
#include <coins.h>
9
#include <crypto/muhash.h>
10
#include <hash.h>
11
#include <node/blockstorage.h>
12
#include <primitives/transaction.h>
13
#include <script/script.h>
14
#include <span.h>
15
#include <streams.h>
16
#include <sync.h>
17
#include <txdb.h>
18
#include <uint256.h>
19
#include <util/check.h>
20
#include <util/log.h>
21
#include <util/overflow.h>
22
#include <validation.h>
23
24
#include <cstddef>
25
#include <map>
26
#include <memory>
27
#include <utility>
28
29
namespace kernel {
30
31
CCoinsStats::CCoinsStats(int block_height, const uint256& block_hash)
32
172
    : nHeight(block_height),
33
172
      hashBlock(block_hash) {}
34
35
// Database-independent metric indicating the UTXO set size
36
uint64_t GetBogoSize(const CScript& script_pub_key)
37
22.9k
{
38
22.9k
    return 32 /* txid */ +
39
22.9k
           4 /* vout index */ +
40
22.9k
           4 /* height + coinbase */ +
41
22.9k
           8 /* amount */ +
42
22.9k
           2 /* scriptPubKey len */ +
43
22.9k
           script_pub_key.size() /* scriptPubKey */;
44
22.9k
}
45
46
template <typename T>
47
static void TxOutSer(T& ss, const COutPoint& outpoint, const Coin& coin)
48
22.5k
{
49
22.5k
    ss << outpoint;
50
22.5k
    ss << ((uint32_t{coin.nHeight} << 1) | uint32_t{coin.fCoinBase});
51
22.5k
    ss << coin.out;
52
22.5k
}
coinstats.cpp:void kernel::TxOutSer<HashWriter>(HashWriter&, COutPoint const&, Coin const&)
Line
Count
Source
48
16.4k
{
49
16.4k
    ss << outpoint;
50
16.4k
    ss << ((uint32_t{coin.nHeight} << 1) | uint32_t{coin.fCoinBase});
51
16.4k
    ss << coin.out;
52
16.4k
}
coinstats.cpp:void kernel::TxOutSer<DataStream>(DataStream&, COutPoint const&, Coin const&)
Line
Count
Source
48
6.18k
{
49
6.18k
    ss << outpoint;
50
6.18k
    ss << ((uint32_t{coin.nHeight} << 1) | uint32_t{coin.fCoinBase});
51
6.18k
    ss << coin.out;
52
6.18k
}
53
54
static void ApplyCoinHash(HashWriter& ss, const COutPoint& outpoint, const Coin& coin)
55
16.4k
{
56
16.4k
    TxOutSer(ss, outpoint, coin);
57
16.4k
}
58
59
void ApplyCoinHash(MuHash3072& muhash, const COutPoint& outpoint, const Coin& coin)
60
5.70k
{
61
5.70k
    DataStream ss{};
62
5.70k
    TxOutSer(ss, outpoint, coin);
63
5.70k
    muhash.Insert(MakeUCharSpan(ss));
64
5.70k
}
65
66
void RemoveCoinHash(MuHash3072& muhash, const COutPoint& outpoint, const Coin& coin)
67
483
{
68
483
    DataStream ss{};
69
483
    TxOutSer(ss, outpoint, coin);
70
483
    muhash.Remove(MakeUCharSpan(ss));
71
483
}
72
73
502
static void ApplyCoinHash(std::nullptr_t, const COutPoint& outpoint, const Coin& coin) {}
74
75
//! Warning: be very careful when changing this! assumeutxo and UTXO snapshot
76
//! validation commitments are reliant on the hash constructed by this
77
//! function.
78
//!
79
//! If the construction of this hash is changed, it will invalidate
80
//! existing UTXO snapshots. This will not result in any kind of consensus
81
//! failure, but it will force clients that were expecting to make use of
82
//! assumeutxo to do traditional IBD instead.
83
//!
84
//! It is also possible, though very unlikely, that a change in this
85
//! construction could cause a previously invalid (and potentially malicious)
86
//! UTXO snapshot to be considered valid.
87
template <typename T>
88
static void ApplyHash(T& hash_obj, const Txid& hash, const std::map<uint32_t, Coin>& outputs)
89
17.9k
{
90
35.8k
    for (auto it = outputs.begin(); it != outputs.end(); ++it) {
91
17.9k
        COutPoint outpoint = COutPoint(hash, it->first);
92
17.9k
        Coin coin = it->second;
93
17.9k
        ApplyCoinHash(hash_obj, outpoint, coin);
94
17.9k
    }
95
17.9k
}
coinstats.cpp:void kernel::ApplyHash<HashWriter>(HashWriter&, transaction_identifier<false> const&, std::map<unsigned int, Coin, std::less<unsigned int>, std::allocator<std::pair<unsigned int const, Coin>>> const&)
Line
Count
Source
89
16.3k
{
90
32.7k
    for (auto it = outputs.begin(); it != outputs.end(); ++it) {
91
16.4k
        COutPoint outpoint = COutPoint(hash, it->first);
92
16.4k
        Coin coin = it->second;
93
16.4k
        ApplyCoinHash(hash_obj, outpoint, coin);
94
16.4k
    }
95
16.3k
}
coinstats.cpp:void kernel::ApplyHash<MuHash3072>(MuHash3072&, transaction_identifier<false> const&, std::map<unsigned int, Coin, std::less<unsigned int>, std::allocator<std::pair<unsigned int const, Coin>>> const&)
Line
Count
Source
89
1.05k
{
90
2.10k
    for (auto it = outputs.begin(); it != outputs.end(); ++it) {
91
1.05k
        COutPoint outpoint = COutPoint(hash, it->first);
92
1.05k
        Coin coin = it->second;
93
1.05k
        ApplyCoinHash(hash_obj, outpoint, coin);
94
1.05k
    }
95
1.05k
}
coinstats.cpp:void kernel::ApplyHash<std::nullptr_t>(std::nullptr_t&, transaction_identifier<false> const&, std::map<unsigned int, Coin, std::less<unsigned int>, std::allocator<std::pair<unsigned int const, Coin>>> const&)
Line
Count
Source
89
502
{
90
1.00k
    for (auto it = outputs.begin(); it != outputs.end(); ++it) {
91
502
        COutPoint outpoint = COutPoint(hash, it->first);
92
502
        Coin coin = it->second;
93
502
        ApplyCoinHash(hash_obj, outpoint, coin);
94
502
    }
95
502
}
96
97
static void ApplyStats(CCoinsStats& stats, const std::map<uint32_t, Coin>& outputs)
98
17.9k
{
99
17.9k
    assert(!outputs.empty());
100
17.9k
    stats.nTransactions++;
101
35.8k
    for (auto it = outputs.begin(); it != outputs.end(); ++it) {
102
17.9k
        stats.nTransactionOutputs++;
103
17.9k
        if (stats.total_amount.has_value()) {
104
17.9k
            stats.total_amount = CheckedAdd(*stats.total_amount, it->second.out.nValue);
105
17.9k
        }
106
17.9k
        stats.nBogoSize += GetBogoSize(it->second.out.scriptPubKey);
107
17.9k
    }
108
17.9k
}
109
110
//! Calculate statistics about the unspent transaction output set
111
template <typename T>
112
static std::optional<CCoinsStats> ComputeUTXOStats(T hash_obj, const CCoinsViewDB& view, node::BlockManager& blockman, const std::function<void()>& interruption_point)
113
102
{
114
102
    std::unique_ptr<CCoinsViewCursor> pcursor;
115
102
    CBlockIndex* pindex;
116
102
    {
117
102
        LOCK(::cs_main);
118
102
        pcursor = view.Cursor();
119
102
        pindex = blockman.LookupBlockIndex(pcursor->GetBestBlock());
120
102
    }
121
102
    CCoinsStats stats{Assert(pindex)->nHeight, pindex->GetBlockHash()};
122
123
102
    Txid prevkey;
124
102
    std::map<uint32_t, Coin> outputs;
125
18.0k
    while (pcursor->Valid()) {
126
17.9k
        if (interruption_point) interruption_point();
127
17.9k
        COutPoint key;
128
17.9k
        Coin coin;
129
17.9k
        if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
130
17.9k
            if (!outputs.empty() && key.hash != prevkey) {
131
17.8k
                ApplyStats(stats, outputs);
132
17.8k
                ApplyHash(hash_obj, prevkey, outputs);
133
17.8k
                outputs.clear();
134
17.8k
            }
135
17.9k
            prevkey = key.hash;
136
17.9k
            outputs[key.n] = std::move(coin);
137
17.9k
            stats.coins_count++;
138
17.9k
        } else {
139
0
            LogError("%s: unable to read value\n", __func__);
140
0
            return std::nullopt;
141
0
        }
142
17.9k
        pcursor->Next();
143
17.9k
    }
144
102
    if (!outputs.empty()) {
145
100
        ApplyStats(stats, outputs);
146
100
        ApplyHash(hash_obj, prevkey, outputs);
147
100
    }
148
149
102
    FinalizeHash(hash_obj, stats);
150
151
102
    stats.nDiskSize = view.EstimateSize();
152
102
    return stats;
153
102
}
coinstats.cpp:std::optional<kernel::CCoinsStats> kernel::ComputeUTXOStats<HashWriter>(HashWriter, CCoinsViewDB const&, node::BlockManager&, std::function<void ()> const&)
Line
Count
Source
113
91
{
114
91
    std::unique_ptr<CCoinsViewCursor> pcursor;
115
91
    CBlockIndex* pindex;
116
91
    {
117
91
        LOCK(::cs_main);
118
91
        pcursor = view.Cursor();
119
91
        pindex = blockman.LookupBlockIndex(pcursor->GetBestBlock());
120
91
    }
121
91
    CCoinsStats stats{Assert(pindex)->nHeight, pindex->GetBlockHash()};
122
123
91
    Txid prevkey;
124
91
    std::map<uint32_t, Coin> outputs;
125
16.4k
    while (pcursor->Valid()) {
126
16.4k
        if (interruption_point) interruption_point();
127
16.4k
        COutPoint key;
128
16.4k
        Coin coin;
129
16.4k
        if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
130
16.4k
            if (!outputs.empty() && key.hash != prevkey) {
131
16.2k
                ApplyStats(stats, outputs);
132
16.2k
                ApplyHash(hash_obj, prevkey, outputs);
133
16.2k
                outputs.clear();
134
16.2k
            }
135
16.4k
            prevkey = key.hash;
136
16.4k
            outputs[key.n] = std::move(coin);
137
16.4k
            stats.coins_count++;
138
16.4k
        } else {
139
0
            LogError("%s: unable to read value\n", __func__);
140
0
            return std::nullopt;
141
0
        }
142
16.4k
        pcursor->Next();
143
16.4k
    }
144
91
    if (!outputs.empty()) {
145
89
        ApplyStats(stats, outputs);
146
89
        ApplyHash(hash_obj, prevkey, outputs);
147
89
    }
148
149
91
    FinalizeHash(hash_obj, stats);
150
151
91
    stats.nDiskSize = view.EstimateSize();
152
91
    return stats;
153
91
}
coinstats.cpp:std::optional<kernel::CCoinsStats> kernel::ComputeUTXOStats<MuHash3072>(MuHash3072, CCoinsViewDB const&, node::BlockManager&, std::function<void ()> const&)
Line
Count
Source
113
8
{
114
8
    std::unique_ptr<CCoinsViewCursor> pcursor;
115
8
    CBlockIndex* pindex;
116
8
    {
117
8
        LOCK(::cs_main);
118
8
        pcursor = view.Cursor();
119
8
        pindex = blockman.LookupBlockIndex(pcursor->GetBestBlock());
120
8
    }
121
8
    CCoinsStats stats{Assert(pindex)->nHeight, pindex->GetBlockHash()};
122
123
8
    Txid prevkey;
124
8
    std::map<uint32_t, Coin> outputs;
125
1.06k
    while (pcursor->Valid()) {
126
1.05k
        if (interruption_point) interruption_point();
127
1.05k
        COutPoint key;
128
1.05k
        Coin coin;
129
1.05k
        if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
130
1.05k
            if (!outputs.empty() && key.hash != prevkey) {
131
1.04k
                ApplyStats(stats, outputs);
132
1.04k
                ApplyHash(hash_obj, prevkey, outputs);
133
1.04k
                outputs.clear();
134
1.04k
            }
135
1.05k
            prevkey = key.hash;
136
1.05k
            outputs[key.n] = std::move(coin);
137
1.05k
            stats.coins_count++;
138
1.05k
        } else {
139
0
            LogError("%s: unable to read value\n", __func__);
140
0
            return std::nullopt;
141
0
        }
142
1.05k
        pcursor->Next();
143
1.05k
    }
144
8
    if (!outputs.empty()) {
145
8
        ApplyStats(stats, outputs);
146
8
        ApplyHash(hash_obj, prevkey, outputs);
147
8
    }
148
149
8
    FinalizeHash(hash_obj, stats);
150
151
8
    stats.nDiskSize = view.EstimateSize();
152
8
    return stats;
153
8
}
coinstats.cpp:std::optional<kernel::CCoinsStats> kernel::ComputeUTXOStats<std::nullptr_t>(std::nullptr_t, CCoinsViewDB const&, node::BlockManager&, std::function<void ()> const&)
Line
Count
Source
113
3
{
114
3
    std::unique_ptr<CCoinsViewCursor> pcursor;
115
3
    CBlockIndex* pindex;
116
3
    {
117
3
        LOCK(::cs_main);
118
3
        pcursor = view.Cursor();
119
3
        pindex = blockman.LookupBlockIndex(pcursor->GetBestBlock());
120
3
    }
121
3
    CCoinsStats stats{Assert(pindex)->nHeight, pindex->GetBlockHash()};
122
123
3
    Txid prevkey;
124
3
    std::map<uint32_t, Coin> outputs;
125
505
    while (pcursor->Valid()) {
126
502
        if (interruption_point) interruption_point();
127
502
        COutPoint key;
128
502
        Coin coin;
129
502
        if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
130
502
            if (!outputs.empty() && key.hash != prevkey) {
131
499
                ApplyStats(stats, outputs);
132
499
                ApplyHash(hash_obj, prevkey, outputs);
133
499
                outputs.clear();
134
499
            }
135
502
            prevkey = key.hash;
136
502
            outputs[key.n] = std::move(coin);
137
502
            stats.coins_count++;
138
502
        } else {
139
0
            LogError("%s: unable to read value\n", __func__);
140
0
            return std::nullopt;
141
0
        }
142
502
        pcursor->Next();
143
502
    }
144
3
    if (!outputs.empty()) {
145
3
        ApplyStats(stats, outputs);
146
3
        ApplyHash(hash_obj, prevkey, outputs);
147
3
    }
148
149
3
    FinalizeHash(hash_obj, stats);
150
151
3
    stats.nDiskSize = view.EstimateSize();
152
3
    return stats;
153
3
}
154
155
std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, const CCoinsViewDB& view, node::BlockManager& blockman, const std::function<void()>& interruption_point)
156
102
{
157
102
    return [&]() -> std::optional<CCoinsStats> {
158
102
        switch (hash_type) {
159
91
        case(CoinStatsHashType::HASH_SERIALIZED): {
160
91
            HashWriter ss{};
161
91
            return ComputeUTXOStats(ss, view, blockman, interruption_point);
162
0
        }
163
8
        case(CoinStatsHashType::MUHASH): {
164
8
            MuHash3072 muhash;
165
8
            return ComputeUTXOStats(muhash, view, blockman, interruption_point);
166
0
        }
167
3
        case(CoinStatsHashType::NONE): {
168
3
            return ComputeUTXOStats(nullptr, view, blockman, interruption_point);
169
0
        }
170
102
        } // no default case, so the compiler can warn about missing cases
171
102
        assert(false);
172
0
    }();
173
102
}
174
175
static void FinalizeHash(HashWriter& ss, CCoinsStats& stats)
176
91
{
177
91
    stats.hashSerialized = ss.GetHash();
178
91
}
179
static void FinalizeHash(MuHash3072& muhash, CCoinsStats& stats)
180
8
{
181
8
    uint256 out;
182
8
    muhash.Finalize(out);
183
8
    stats.hashSerialized = out;
184
8
}
185
3
static void FinalizeHash(std::nullptr_t, CCoinsStats& stats) {}
186
187
} // namespace kernel