coinstats.cpp raw
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 : nHeight(block_height),
32 hashBlock(block_hash) {}
33
34 // Database-independent metric indicating the UTXO set size
35 uint64_t GetBogoSize(const CScript& script_pub_key)
36 {
37 return 32 /* txid */ +
38 4 /* vout index */ +
39 4 /* height + coinbase */ +
40 8 /* amount */ +
41 2 /* scriptPubKey len */ +
42 script_pub_key.size() /* scriptPubKey */;
43 }
44
45 template <typename T>
46 static void TxOutSer(T& ss, const COutPoint& outpoint, const Coin& coin)
47 {
48 ss << outpoint;
49 ss << ((uint32_t{coin.nHeight} << 1) | uint32_t{coin.fCoinBase});
50 ss << coin.out;
51 }
52
53 static void ApplyCoinHash(HashWriter& ss, const COutPoint& outpoint, const Coin& coin)
54 {
55 TxOutSer(ss, outpoint, coin);
56 }
57
58 void ApplyCoinHash(MuHash3072& muhash, const COutPoint& outpoint, const Coin& coin)
59 {
60 DataStream ss{};
61 TxOutSer(ss, outpoint, coin);
62 muhash.Insert(MakeUCharSpan(ss));
63 }
64
65 void RemoveCoinHash(MuHash3072& muhash, const COutPoint& outpoint, const Coin& coin)
66 {
67 DataStream ss{};
68 TxOutSer(ss, outpoint, coin);
69 muhash.Remove(MakeUCharSpan(ss));
70 }
71
72 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 {
89 for (auto it = outputs.begin(); it != outputs.end(); ++it) {
90 COutPoint outpoint = COutPoint(hash, it->first);
91 Coin coin = it->second;
92 ApplyCoinHash(hash_obj, outpoint, coin);
93 }
94 }
95
96 static void ApplyStats(CCoinsStats& stats, const std::map<uint32_t, Coin>& outputs)
97 {
98 assert(!outputs.empty());
99 stats.nTransactions++;
100 for (auto it = outputs.begin(); it != outputs.end(); ++it) {
101 stats.nTransactionOutputs++;
102 if (stats.total_amount.has_value()) {
103 stats.total_amount = CheckedAdd(*stats.total_amount, it->second.out.nValue);
104 }
105 stats.nBogoSize += GetBogoSize(it->second.out.scriptPubKey);
106 }
107 }
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 {
113 std::unique_ptr<CCoinsViewCursor> pcursor;
114 CBlockIndex* pindex;
115 {
116 LOCK(::cs_main);
117 pcursor = view->Cursor();
118 pindex = blockman.LookupBlockIndex(pcursor->GetBestBlock());
119 }
120 assert(pcursor);
121 CCoinsStats stats{Assert(pindex)->nHeight, pindex->GetBlockHash()};
122
123 Txid prevkey;
124 std::map<uint32_t, Coin> outputs;
125 while (pcursor->Valid()) {
126 if (interruption_point) interruption_point();
127 COutPoint key;
128 Coin coin;
129 if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
130 if (!outputs.empty() && key.hash != prevkey) {
131 ApplyStats(stats, outputs);
132 ApplyHash(hash_obj, prevkey, outputs);
133 outputs.clear();
134 }
135 prevkey = key.hash;
136 outputs[key.n] = std::move(coin);
137 stats.coins_count++;
138 } else {
139 LogError("%s: unable to read value\n", __func__);
140 return std::nullopt;
141 }
142 pcursor->Next();
143 }
144 if (!outputs.empty()) {
145 ApplyStats(stats, outputs);
146 ApplyHash(hash_obj, prevkey, outputs);
147 }
148
149 FinalizeHash(hash_obj, stats);
150
151 stats.nDiskSize = view->EstimateSize();
152 return stats;
153 }
154
155 std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsView* view, node::BlockManager& blockman, const std::function<void()>& interruption_point)
156 {
157 return [&]() -> std::optional<CCoinsStats> {
158 switch (hash_type) {
159 case(CoinStatsHashType::HASH_SERIALIZED): {
160 HashWriter ss{};
161 return ComputeUTXOStats(ss, view, blockman, interruption_point);
162 }
163 case(CoinStatsHashType::MUHASH): {
164 MuHash3072 muhash;
165 return ComputeUTXOStats(muhash, view, blockman, interruption_point);
166 }
167 case(CoinStatsHashType::NONE): {
168 return ComputeUTXOStats(nullptr, view, blockman, interruption_point);
169 }
170 } // no default case, so the compiler can warn about missing cases
171 assert(false);
172 }();
173 }
174
175 static void FinalizeHash(HashWriter& ss, CCoinsStats& stats)
176 {
177 stats.hashSerialized = ss.GetHash();
178 }
179 static void FinalizeHash(MuHash3072& muhash, CCoinsStats& stats)
180 {
181 uint256 out;
182 muhash.Finalize(out);
183 stats.hashSerialized = out;
184 }
185 static void FinalizeHash(std::nullptr_t, CCoinsStats& stats) {}
186
187 } // namespace kernel
188