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