Coverage Report

Created: 2026-04-29 19:21

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 <uint256.h>
18
#include <util/check.h>
19
#include <util/log.h>
20
#include <util/overflow.h>
21
#include <validation.h>
22
23
#include <cstddef>
24
#include <map>
25
#include <memory>
26
#include <utility>
27
28
namespace kernel {
29
30
CCoinsStats::CCoinsStats(int block_height, const uint256& block_hash)
31
172
    : nHeight(block_height),
32
172
      hashBlock(block_hash) {}
33
34
// Database-independent metric indicating the UTXO set size
35
uint64_t GetBogoSize(const CScript& script_pub_key)
36
22.6k
{
37
22.6k
    return 32 /* txid */ +
38
22.6k
           4 /* vout index */ +
39
22.6k
           4 /* height + coinbase */ +
40
22.6k
           8 /* amount */ +
41
22.6k
           2 /* scriptPubKey len */ +
42
22.6k
           script_pub_key.size() /* scriptPubKey */;
43
22.6k
}
44
45
template <typename T>
46
static void TxOutSer(T& ss, const COutPoint& outpoint, const Coin& coin)
47
22.2k
{
48
22.2k
    ss << outpoint;
49
22.2k
    ss << ((uint32_t{coin.nHeight} << 1) | uint32_t{coin.fCoinBase});
50
22.2k
    ss << coin.out;
51
22.2k
}
coinstats.cpp:void kernel::TxOutSer<HashWriter>(HashWriter&, COutPoint const&, Coin const&)
Line
Count
Source
47
16.4k
{
48
16.4k
    ss << outpoint;
49
16.4k
    ss << ((uint32_t{coin.nHeight} << 1) | uint32_t{coin.fCoinBase});
50
16.4k
    ss << coin.out;
51
16.4k
}
coinstats.cpp:void kernel::TxOutSer<DataStream>(DataStream&, COutPoint const&, Coin const&)
Line
Count
Source
47
5.86k
{
48
5.86k
    ss << outpoint;
49
5.86k
    ss << ((uint32_t{coin.nHeight} << 1) | uint32_t{coin.fCoinBase});
50
5.86k
    ss << coin.out;
51
5.86k
}
52
53
static void ApplyCoinHash(HashWriter& ss, const COutPoint& outpoint, const Coin& coin)
54
16.4k
{
55
16.4k
    TxOutSer(ss, outpoint, coin);
56
16.4k
}
57
58
void ApplyCoinHash(MuHash3072& muhash, const COutPoint& outpoint, const Coin& coin)
59
5.38k
{
60
5.38k
    DataStream ss{};
61
5.38k
    TxOutSer(ss, outpoint, coin);
62
5.38k
    muhash.Insert(MakeUCharSpan(ss));
63
5.38k
}
64
65
void RemoveCoinHash(MuHash3072& muhash, const COutPoint& outpoint, const Coin& coin)
66
483
{
67
483
    DataStream ss{};
68
483
    TxOutSer(ss, outpoint, coin);
69
483
    muhash.Remove(MakeUCharSpan(ss));
70
483
}
71
72
502
static void ApplyCoinHash(std::nullptr_t, const COutPoint& outpoint, const Coin& coin) {}
73
74
//! Warning: be very careful when changing this! assumeutxo and UTXO snapshot
75
//! validation commitments are reliant on the hash constructed by this
76
//! function.
77
//!
78
//! If the construction of this hash is changed, it will invalidate
79
//! existing UTXO snapshots. This will not result in any kind of consensus
80
//! failure, but it will force clients that were expecting to make use of
81
//! assumeutxo to do traditional IBD instead.
82
//!
83
//! It is also possible, though very unlikely, that a change in this
84
//! construction could cause a previously invalid (and potentially malicious)
85
//! UTXO snapshot to be considered valid.
86
template <typename T>
87
static void ApplyHash(T& hash_obj, const Txid& hash, const std::map<uint32_t, Coin>& outputs)
88
17.9k
{
89
35.8k
    for (auto it = outputs.begin(); it != outputs.end(); ++it) {
90
17.9k
        COutPoint outpoint = COutPoint(hash, it->first);
91
17.9k
        Coin coin = it->second;
92
17.9k
        ApplyCoinHash(hash_obj, outpoint, coin);
93
17.9k
    }
94
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
88
16.3k
{
89
32.7k
    for (auto it = outputs.begin(); it != outputs.end(); ++it) {
90
16.4k
        COutPoint outpoint = COutPoint(hash, it->first);
91
16.4k
        Coin coin = it->second;
92
16.4k
        ApplyCoinHash(hash_obj, outpoint, coin);
93
16.4k
    }
94
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
88
1.05k
{
89
2.10k
    for (auto it = outputs.begin(); it != outputs.end(); ++it) {
90
1.05k
        COutPoint outpoint = COutPoint(hash, it->first);
91
1.05k
        Coin coin = it->second;
92
1.05k
        ApplyCoinHash(hash_obj, outpoint, coin);
93
1.05k
    }
94
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
88
502
{
89
1.00k
    for (auto it = outputs.begin(); it != outputs.end(); ++it) {
90
502
        COutPoint outpoint = COutPoint(hash, it->first);
91
502
        Coin coin = it->second;
92
502
        ApplyCoinHash(hash_obj, outpoint, coin);
93
502
    }
94
502
}
95
96
static void ApplyStats(CCoinsStats& stats, const std::map<uint32_t, Coin>& outputs)
97
17.9k
{
98
17.9k
    assert(!outputs.empty());
99
17.9k
    stats.nTransactions++;
100
35.8k
    for (auto it = outputs.begin(); it != outputs.end(); ++it) {
101
17.9k
        stats.nTransactionOutputs++;
102
17.9k
        if (stats.total_amount.has_value()) {
103
17.9k
            stats.total_amount = CheckedAdd(*stats.total_amount, it->second.out.nValue);
104
17.9k
        }
105
17.9k
        stats.nBogoSize += GetBogoSize(it->second.out.scriptPubKey);
106
17.9k
    }
107
17.9k
}
108
109
//! Calculate statistics about the unspent transaction output set
110
template <typename T>
111
static std::optional<CCoinsStats> ComputeUTXOStats(T hash_obj, CCoinsView* view, node::BlockManager& blockman, const std::function<void()>& interruption_point)
112
102
{
113
102
    std::unique_ptr<CCoinsViewCursor> pcursor;
114
102
    CBlockIndex* pindex;
115
102
    {
116
102
        LOCK(::cs_main);
117
102
        pcursor = view->Cursor();
118
102
        pindex = blockman.LookupBlockIndex(pcursor->GetBestBlock());
119
102
    }
120
102
    assert(pcursor);
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, CCoinsView*, node::BlockManager&, std::function<void ()> const&)
Line
Count
Source
112
91
{
113
91
    std::unique_ptr<CCoinsViewCursor> pcursor;
114
91
    CBlockIndex* pindex;
115
91
    {
116
91
        LOCK(::cs_main);
117
91
        pcursor = view->Cursor();
118
91
        pindex = blockman.LookupBlockIndex(pcursor->GetBestBlock());
119
91
    }
120
91
    assert(pcursor);
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, CCoinsView*, node::BlockManager&, std::function<void ()> const&)
Line
Count
Source
112
8
{
113
8
    std::unique_ptr<CCoinsViewCursor> pcursor;
114
8
    CBlockIndex* pindex;
115
8
    {
116
8
        LOCK(::cs_main);
117
8
        pcursor = view->Cursor();
118
8
        pindex = blockman.LookupBlockIndex(pcursor->GetBestBlock());
119
8
    }
120
8
    assert(pcursor);
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, CCoinsView*, node::BlockManager&, std::function<void ()> const&)
Line
Count
Source
112
3
{
113
3
    std::unique_ptr<CCoinsViewCursor> pcursor;
114
3
    CBlockIndex* pindex;
115
3
    {
116
3
        LOCK(::cs_main);
117
3
        pcursor = view->Cursor();
118
3
        pindex = blockman.LookupBlockIndex(pcursor->GetBestBlock());
119
3
    }
120
3
    assert(pcursor);
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, CCoinsView* 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