coinstats.cpp raw

   1  // Copyright (c) 2022 The Limenka 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          const 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
 195