/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 |