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