/ src / kernel / coinstats.cpp
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