Line data Source code
1 : // Copyright (c) 2022 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 <coins.h>
8 : #include <crypto/muhash.h>
9 : #include <hash.h>
10 : #include <serialize.h>
11 : #include <uint256.h>
12 : #include <util/check.h>
13 : #include <util/overflow.h>
14 : #include <util/system.h>
15 : #include <validation.h>
16 :
17 : #include <map>
18 :
19 : namespace kernel {
20 :
21 406 : CCoinsStats::CCoinsStats(int block_height, const uint256& block_hash)
22 203 : : nHeight(block_height),
23 406 : hashBlock(block_hash) {}
24 :
25 : // Database-independent metric indicating the UTXO set size
26 27507 : uint64_t GetBogoSize(const CScript& script_pub_key)
27 : {
28 27507 : return 32 /* txid */ +
29 : 4 /* vout index */ +
30 : 4 /* height + coinbase */ +
31 : 8 /* amount */ +
32 27507 : 2 /* scriptPubKey len */ +
33 27507 : script_pub_key.size() /* scriptPubKey */;
34 : }
35 :
36 3067 : CDataStream TxOutSer(const COutPoint& outpoint, const Coin& coin) {
37 3067 : CDataStream ss(SER_DISK, PROTOCOL_VERSION);
38 3067 : ss << outpoint;
39 3067 : ss << static_cast<uint32_t>(coin.nHeight * 2 + coin.fCoinBase);
40 3067 : ss << coin.out;
41 3067 : return ss;
42 3067 : }
43 :
44 : //! Warning: be very careful when changing this! assumeutxo and UTXO snapshot
45 : //! validation commitments are reliant on the hash constructed by this
46 : //! function.
47 : //!
48 : //! If the construction of this hash is changed, it will invalidate
49 : //! existing UTXO snapshots. This will not result in any kind of consensus
50 : //! failure, but it will force clients that were expecting to make use of
51 : //! assumeutxo to do traditional IBD instead.
52 : //!
53 : //! It is also possible, though very unlikely, that a change in this
54 : //! construction could cause a previously invalid (and potentially malicious)
55 : //! UTXO snapshot to be considered valid.
56 15761 : static void ApplyHash(HashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
57 : {
58 39197 : for (auto it = outputs.begin(); it != outputs.end(); ++it) {
59 23436 : if (it == outputs.begin()) {
60 15761 : ss << hash;
61 15761 : ss << VARINT(it->second.nHeight * 2 + it->second.fCoinBase ? 1u : 0u);
62 15761 : }
63 :
64 23436 : ss << VARINT(it->first + 1);
65 23436 : ss << it->second.out.scriptPubKey;
66 23436 : ss << VARINT_MODE(it->second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED);
67 :
68 23436 : if (it == std::prev(outputs.end())) {
69 15761 : ss << VARINT(0u);
70 15761 : }
71 23436 : }
72 15761 : }
73 :
74 1004 : static void ApplyHash(std::nullptr_t, const uint256& hash, const std::map<uint32_t, Coin>& outputs) {}
75 :
76 1868 : static void ApplyHash(MuHash3072& muhash, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
77 : {
78 3742 : for (auto it = outputs.begin(); it != outputs.end(); ++it) {
79 1874 : COutPoint outpoint = COutPoint(hash, it->first);
80 1874 : Coin coin = it->second;
81 1874 : muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
82 1874 : }
83 1868 : }
84 :
85 18633 : static void ApplyStats(CCoinsStats& stats, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
86 : {
87 18633 : assert(!outputs.empty());
88 18633 : stats.nTransactions++;
89 44947 : for (auto it = outputs.begin(); it != outputs.end(); ++it) {
90 26314 : stats.nTransactionOutputs++;
91 26314 : if (stats.total_amount.has_value()) {
92 26314 : stats.total_amount = CheckedAdd(*stats.total_amount, it->second.out.nValue);
93 26314 : }
94 26314 : stats.nBogoSize += GetBogoSize(it->second.out.scriptPubKey);
95 26314 : }
96 18633 : }
97 :
98 : //! Calculate statistics about the unspent transaction output set
99 : template <typename T>
100 111 : static bool ComputeUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point)
101 : {
102 111 : std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
103 111 : assert(pcursor);
104 :
105 111 : PrepareHash(hash_obj, stats);
106 :
107 111 : uint256 prevkey;
108 111 : std::map<uint32_t, Coin> outputs;
109 26425 : while (pcursor->Valid()) {
110 26314 : if (interruption_point) interruption_point();
111 26314 : COutPoint key;
112 26314 : Coin coin;
113 26314 : if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
114 26314 : if (!outputs.empty() && key.hash != prevkey) {
115 18526 : ApplyStats(stats, prevkey, outputs);
116 18526 : ApplyHash(hash_obj, prevkey, outputs);
117 18526 : outputs.clear();
118 18526 : }
119 26314 : prevkey = key.hash;
120 26314 : outputs[key.n] = std::move(coin);
121 26314 : stats.coins_count++;
122 26314 : } else {
123 0 : return error("%s: unable to read value", __func__);
124 : }
125 26314 : pcursor->Next();
126 26314 : }
127 111 : if (!outputs.empty()) {
128 107 : ApplyStats(stats, prevkey, outputs);
129 107 : ApplyHash(hash_obj, prevkey, outputs);
130 107 : }
131 :
132 111 : FinalizeHash(hash_obj, stats);
133 :
134 111 : stats.nDiskSize = view->EstimateSize();
135 :
136 111 : return true;
137 111 : }
138 :
139 111 : std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsView* view, node::BlockManager& blockman, const std::function<void()>& interruption_point)
140 : {
141 222 : CBlockIndex* pindex = WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock()));
142 111 : CCoinsStats stats{Assert(pindex)->nHeight, pindex->GetBlockHash()};
143 :
144 222 : bool success = [&]() -> bool {
145 111 : switch (hash_type) {
146 : case(CoinStatsHashType::HASH_SERIALIZED): {
147 91 : HashWriter ss{};
148 91 : return ComputeUTXOStats(view, stats, ss, interruption_point);
149 : }
150 : case(CoinStatsHashType::MUHASH): {
151 14 : MuHash3072 muhash;
152 14 : return ComputeUTXOStats(view, stats, muhash, interruption_point);
153 : }
154 : case(CoinStatsHashType::NONE): {
155 6 : return ComputeUTXOStats(view, stats, nullptr, interruption_point);
156 : }
157 : } // no default case, so the compiler can warn about missing cases
158 0 : assert(false);
159 111 : }();
160 :
161 111 : if (!success) {
162 0 : return std::nullopt;
163 : }
164 111 : return stats;
165 111 : }
166 :
167 : // The legacy hash serializes the hashBlock
168 91 : static void PrepareHash(HashWriter& ss, const CCoinsStats& stats)
169 : {
170 91 : ss << stats.hashBlock;
171 91 : }
172 : // MuHash does not need the prepare step
173 14 : static void PrepareHash(MuHash3072& muhash, CCoinsStats& stats) {}
174 6 : static void PrepareHash(std::nullptr_t, CCoinsStats& stats) {}
175 :
176 91 : static void FinalizeHash(HashWriter& ss, CCoinsStats& stats)
177 : {
178 91 : stats.hashSerialized = ss.GetHash();
179 91 : }
180 14 : static void FinalizeHash(MuHash3072& muhash, CCoinsStats& stats)
181 : {
182 14 : uint256 out;
183 14 : muhash.Finalize(out);
184 14 : stats.hashSerialized = out;
185 14 : }
186 6 : static void FinalizeHash(std::nullptr_t, CCoinsStats& stats) {}
187 :
188 : } // namespace kernel
|