coinstats.cpp raw

   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
 188