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