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 32 : CCoinsStats::CCoinsStats(int block_height, const uint256& block_hash)
22 16 : : nHeight(block_height),
23 32 : hashBlock(block_hash) {}
24 :
25 : // Database-independent metric indicating the UTXO set size
26 1611 : uint64_t GetBogoSize(const CScript& script_pub_key)
27 : {
28 1611 : return 32 /* txid */ +
29 : 4 /* vout index */ +
30 : 4 /* height + coinbase */ +
31 : 8 /* amount */ +
32 1611 : 2 /* scriptPubKey len */ +
33 1611 : script_pub_key.size() /* scriptPubKey */;
34 : }
35 :
36 202 : CDataStream TxOutSer(const COutPoint& outpoint, const Coin& coin) {
37 202 : CDataStream ss(SER_DISK, PROTOCOL_VERSION);
38 202 : ss << outpoint;
39 202 : ss << static_cast<uint32_t>(coin.nHeight * 2 + coin.fCoinBase);
40 202 : ss << coin.out;
41 202 : return ss;
42 202 : }
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 1409 : static void ApplyHash(HashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
57 : {
58 2818 : for (auto it = outputs.begin(); it != outputs.end(); ++it) {
59 1409 : if (it == outputs.begin()) {
60 1409 : ss << hash;
61 1409 : ss << VARINT(it->second.nHeight * 2 + it->second.fCoinBase ? 1u : 0u);
62 1409 : }
63 :
64 1409 : ss << VARINT(it->first + 1);
65 1409 : ss << it->second.out.scriptPubKey;
66 1409 : ss << VARINT_MODE(it->second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED);
67 :
68 1409 : if (it == std::prev(outputs.end())) {
69 1409 : ss << VARINT(0u);
70 1409 : }
71 1409 : }
72 1409 : }
73 :
74 0 : static void ApplyHash(std::nullptr_t, const uint256& hash, const std::map<uint32_t, Coin>& outputs) {}
75 :
76 0 : static void ApplyHash(MuHash3072& muhash, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
77 : {
78 0 : for (auto it = outputs.begin(); it != outputs.end(); ++it) {
79 0 : COutPoint outpoint = COutPoint(hash, it->first);
80 0 : Coin coin = it->second;
81 0 : muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
82 0 : }
83 0 : }
84 :
85 1409 : static void ApplyStats(CCoinsStats& stats, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
86 : {
87 1409 : assert(!outputs.empty());
88 1409 : stats.nTransactions++;
89 2818 : for (auto it = outputs.begin(); it != outputs.end(); ++it) {
90 1409 : stats.nTransactionOutputs++;
91 1409 : if (stats.total_amount.has_value()) {
92 1409 : stats.total_amount = CheckedAdd(*stats.total_amount, it->second.out.nValue);
93 1409 : }
94 1409 : stats.nBogoSize += GetBogoSize(it->second.out.scriptPubKey);
95 1409 : }
96 1409 : }
97 :
98 : //! Calculate statistics about the unspent transaction output set
99 : template <typename T>
100 12 : static bool ComputeUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point)
101 : {
102 12 : std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
103 12 : assert(pcursor);
104 :
105 12 : PrepareHash(hash_obj, stats);
106 :
107 12 : uint256 prevkey;
108 12 : std::map<uint32_t, Coin> outputs;
109 1421 : while (pcursor->Valid()) {
110 1409 : if (interruption_point) interruption_point();
111 1409 : COutPoint key;
112 1409 : Coin coin;
113 1409 : if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
114 1409 : if (!outputs.empty() && key.hash != prevkey) {
115 1397 : ApplyStats(stats, prevkey, outputs);
116 1397 : ApplyHash(hash_obj, prevkey, outputs);
117 1397 : outputs.clear();
118 1397 : }
119 1409 : prevkey = key.hash;
120 1409 : outputs[key.n] = std::move(coin);
121 1409 : stats.coins_count++;
122 1409 : } else {
123 0 : return error("%s: unable to read value", __func__);
124 : }
125 1409 : pcursor->Next();
126 1409 : }
127 12 : if (!outputs.empty()) {
128 12 : ApplyStats(stats, prevkey, outputs);
129 12 : ApplyHash(hash_obj, prevkey, outputs);
130 12 : }
131 :
132 12 : FinalizeHash(hash_obj, stats);
133 :
134 12 : stats.nDiskSize = view->EstimateSize();
135 :
136 12 : return true;
137 12 : }
138 :
139 12 : std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsView* view, node::BlockManager& blockman, const std::function<void()>& interruption_point)
140 : {
141 24 : CBlockIndex* pindex = WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock()));
142 12 : CCoinsStats stats{Assert(pindex)->nHeight, pindex->GetBlockHash()};
143 :
144 24 : bool success = [&]() -> bool {
145 12 : switch (hash_type) {
146 : case(CoinStatsHashType::HASH_SERIALIZED): {
147 12 : HashWriter ss{};
148 12 : return ComputeUTXOStats(view, stats, ss, interruption_point);
149 : }
150 : case(CoinStatsHashType::MUHASH): {
151 0 : MuHash3072 muhash;
152 0 : return ComputeUTXOStats(view, stats, muhash, interruption_point);
153 : }
154 : case(CoinStatsHashType::NONE): {
155 0 : 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 12 : }();
160 :
161 12 : if (!success) {
162 0 : return std::nullopt;
163 : }
164 12 : return stats;
165 12 : }
166 :
167 : // The legacy hash serializes the hashBlock
168 12 : static void PrepareHash(HashWriter& ss, const CCoinsStats& stats)
169 : {
170 12 : ss << stats.hashBlock;
171 12 : }
172 : // MuHash does not need the prepare step
173 0 : static void PrepareHash(MuHash3072& muhash, CCoinsStats& stats) {}
174 0 : static void PrepareHash(std::nullptr_t, CCoinsStats& stats) {}
175 :
176 12 : static void FinalizeHash(HashWriter& ss, CCoinsStats& stats)
177 : {
178 12 : stats.hashSerialized = ss.GetHash();
179 12 : }
180 0 : static void FinalizeHash(MuHash3072& muhash, CCoinsStats& stats)
181 : {
182 0 : uint256 out;
183 0 : muhash.Finalize(out);
184 0 : stats.hashSerialized = out;
185 0 : }
186 0 : static void FinalizeHash(std::nullptr_t, CCoinsStats& stats) {}
187 :
188 : } // namespace kernel
|