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