coinstats.cpp
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 <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 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