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