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