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