coinstatsindex.cpp raw

   1  // Copyright (c) 2020-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 <index/coinstatsindex.h>
   6  
   7  #include <arith_uint256.h>
   8  #include <chain.h>
   9  #include <chainparams.h>
  10  #include <coins.h>
  11  #include <common/args.h>
  12  #include <consensus/amount.h>
  13  #include <crypto/muhash.h>
  14  #include <dbwrapper.h>
  15  #include <index/base.h>
  16  #include <index/db_key.h>
  17  #include <interfaces/chain.h>
  18  #include <interfaces/types.h>
  19  #include <kernel/coinstats.h>
  20  #include <primitives/block.h>
  21  #include <primitives/transaction.h>
  22  #include <script/script.h>
  23  #include <serialize.h>
  24  #include <uint256.h>
  25  #include <undo.h>
  26  #include <util/check.h>
  27  #include <util/fs.h>
  28  #include <util/log.h>
  29  #include <validation.h>
  30  
  31  #include <compare>
  32  #include <limits>
  33  #include <string>
  34  #include <utility>
  35  #include <vector>
  36  
  37  using kernel::ApplyCoinHash;
  38  using kernel::CCoinsStats;
  39  using kernel::GetBogoSize;
  40  using kernel::RemoveCoinHash;
  41  
  42  static constexpr uint8_t DB_MUHASH{'M'};
  43  
  44  namespace {
  45  
  46  struct DBVal {
  47      uint256 muhash{uint256::ZERO};
  48      uint64_t transaction_output_count{0};
  49      uint64_t bogo_size{0};
  50      CAmount total_amount{0};
  51      CAmount total_subsidy{0};
  52      arith_uint256 total_prevout_spent_amount{0};
  53      arith_uint256 total_new_outputs_ex_coinbase_amount{0};
  54      arith_uint256 total_coinbase_amount{0};
  55      CAmount total_unspendables_genesis_block{0};
  56      CAmount total_unspendables_bip30{0};
  57      CAmount total_unspendables_scripts{0};
  58      CAmount total_unspendables_unclaimed_rewards{0};
  59  
  60      SERIALIZE_METHODS(DBVal, obj)
  61      {
  62          uint256 prevout_spent, new_outputs, coinbase;
  63          SER_WRITE(obj, prevout_spent = ArithToUint256(obj.total_prevout_spent_amount));
  64          SER_WRITE(obj, new_outputs = ArithToUint256(obj.total_new_outputs_ex_coinbase_amount));
  65          SER_WRITE(obj, coinbase = ArithToUint256(obj.total_coinbase_amount));
  66  
  67          READWRITE(obj.muhash);
  68          READWRITE(obj.transaction_output_count);
  69          READWRITE(obj.bogo_size);
  70          READWRITE(obj.total_amount);
  71          READWRITE(obj.total_subsidy);
  72          READWRITE(prevout_spent);
  73          READWRITE(new_outputs);
  74          READWRITE(coinbase);
  75          READWRITE(obj.total_unspendables_genesis_block);
  76          READWRITE(obj.total_unspendables_bip30);
  77          READWRITE(obj.total_unspendables_scripts);
  78          READWRITE(obj.total_unspendables_unclaimed_rewards);
  79  
  80          SER_READ(obj, obj.total_prevout_spent_amount = UintToArith256(prevout_spent));
  81          SER_READ(obj, obj.total_new_outputs_ex_coinbase_amount = UintToArith256(new_outputs));
  82          SER_READ(obj, obj.total_coinbase_amount = UintToArith256(coinbase));
  83      }
  84  };
  85  }; // namespace
  86  
  87  std::unique_ptr<CoinStatsIndex> g_coin_stats_index;
  88  
  89  CoinStatsIndex::CoinStatsIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe)
  90      : BaseIndex(std::move(chain), "coinstatsindex", "coinstatsidx")
  91  {
  92      // An earlier version of the index used "indexes/coinstats" but it contained
  93      // a bug and is superseded by a fixed version at "indexes/coinstatsindex".
  94      // The original index is kept around until the next release in case users
  95      // decide to downgrade their node.
  96      auto old_path = gArgs.GetDataDirNet() / "indexes" / "coinstats";
  97      if (fs::exists(old_path)) {
  98          // TODO: Change this to deleting the old index with v31.
  99          LogWarning("Old version of coinstatsindex found at %s. This folder can be safely deleted unless you " \
 100              "plan to downgrade your node to version 29 or lower.", fs::PathToString(old_path));
 101      }
 102      fs::path path{gArgs.GetDataDirNet() / "indexes" / "coinstatsindex"};
 103      fs::create_directories(path);
 104  
 105      m_db = std::make_unique<CoinStatsIndex::DB>(path / "db", n_cache_size, f_memory, f_wipe);
 106  }
 107  
 108  bool CoinStatsIndex::CustomAppend(const interfaces::BlockInfo& block)
 109  {
 110      const CAmount block_subsidy{GetBlockSubsidy(block.height, Params().GetConsensus())};
 111      m_total_subsidy += block_subsidy;
 112  
 113      // Ignore genesis block
 114      if (block.height > 0) {
 115          uint256 expected_block_hash{*Assert(block.prev_hash)};
 116          if (m_current_block_hash != expected_block_hash) {
 117              LogError("previous block header belongs to unexpected block %s; expected %s",
 118                        m_current_block_hash.ToString(), expected_block_hash.ToString());
 119              return false;
 120          }
 121  
 122          // Add the new utxos created from the block
 123          assert(block.data);
 124          for (size_t i = 0; i < block.data->vtx.size(); ++i) {
 125              const auto& tx{block.data->vtx.at(i)};
 126              const bool is_coinbase{tx->IsCoinBase()};
 127  
 128              // Skip duplicate txid coinbase transactions (BIP30).
 129              if (is_coinbase && IsBIP30Unspendable(block.hash, block.height)) {
 130                  m_total_unspendables_bip30 += block_subsidy;
 131                  continue;
 132              }
 133  
 134              for (uint32_t j = 0; j < tx->vout.size(); ++j) {
 135                  const CTxOut& out{tx->vout[j]};
 136                  const Coin coin{out, block.height, is_coinbase};
 137                  const COutPoint outpoint{tx->GetHash(), j};
 138  
 139                  // Skip unspendable coins
 140                  if (coin.out.scriptPubKey.IsUnspendable()) {
 141                      m_total_unspendables_scripts += coin.out.nValue;
 142                      continue;
 143                  }
 144  
 145                  ApplyCoinHash(m_muhash, outpoint, coin);
 146  
 147                  if (is_coinbase) {
 148                      m_total_coinbase_amount += coin.out.nValue;
 149                  } else {
 150                      m_total_new_outputs_ex_coinbase_amount += coin.out.nValue;
 151                  }
 152  
 153                  ++m_transaction_output_count;
 154                  m_total_amount += coin.out.nValue;
 155                  m_bogo_size += GetBogoSize(coin.out.scriptPubKey);
 156              }
 157  
 158              // The coinbase tx has no undo data since no former output is spent
 159              if (!is_coinbase) {
 160                  const auto& tx_undo{Assert(block.undo_data)->vtxundo.at(i - 1)};
 161  
 162                  for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
 163                      const Coin& coin{tx_undo.vprevout[j]};
 164                      const COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};
 165  
 166                      RemoveCoinHash(m_muhash, outpoint, coin);
 167  
 168                      m_total_prevout_spent_amount += coin.out.nValue;
 169  
 170                      --m_transaction_output_count;
 171                      m_total_amount -= coin.out.nValue;
 172                      m_bogo_size -= GetBogoSize(coin.out.scriptPubKey);
 173                  }
 174              }
 175          }
 176      } else {
 177          // genesis block
 178          m_total_unspendables_genesis_block += block_subsidy;
 179      }
 180  
 181      // If spent prevouts + block subsidy are still a higher amount than
 182      // new outputs + coinbase + current unspendable amount this means
 183      // the miner did not claim the full block reward. Unclaimed block
 184      // rewards are also unspendable.
 185      const CAmount temp_total_unspendable_amount{m_total_unspendables_genesis_block + m_total_unspendables_bip30 + m_total_unspendables_scripts + m_total_unspendables_unclaimed_rewards};
 186      const arith_uint256 unclaimed_rewards{(m_total_prevout_spent_amount + m_total_subsidy) - (m_total_new_outputs_ex_coinbase_amount + m_total_coinbase_amount + temp_total_unspendable_amount)};
 187      assert(unclaimed_rewards <= arith_uint256(std::numeric_limits<CAmount>::max()));
 188      m_total_unspendables_unclaimed_rewards += static_cast<CAmount>(unclaimed_rewards.GetLow64());
 189  
 190      std::pair<uint256, DBVal> value;
 191      value.first = block.hash;
 192      value.second.transaction_output_count = m_transaction_output_count;
 193      value.second.bogo_size = m_bogo_size;
 194      value.second.total_amount = m_total_amount;
 195      value.second.total_subsidy = m_total_subsidy;
 196      value.second.total_prevout_spent_amount = m_total_prevout_spent_amount;
 197      value.second.total_new_outputs_ex_coinbase_amount = m_total_new_outputs_ex_coinbase_amount;
 198      value.second.total_coinbase_amount = m_total_coinbase_amount;
 199      value.second.total_unspendables_genesis_block = m_total_unspendables_genesis_block;
 200      value.second.total_unspendables_bip30 = m_total_unspendables_bip30;
 201      value.second.total_unspendables_scripts = m_total_unspendables_scripts;
 202      value.second.total_unspendables_unclaimed_rewards = m_total_unspendables_unclaimed_rewards;
 203  
 204      uint256 out;
 205      m_muhash.Finalize(out);
 206      value.second.muhash = out;
 207  
 208      m_current_block_hash = block.hash;
 209  
 210      // Intentionally do not update DB_MUHASH here so it stays in sync with
 211      // DB_BEST_BLOCK, and the index is not corrupted if there is an unclean shutdown.
 212      m_db->Write(index_util::DBHeightKey(block.height), value);
 213      return true;
 214  }
 215  
 216  bool CoinStatsIndex::CustomRemove(const interfaces::BlockInfo& block)
 217  {
 218      CDBBatch batch(*m_db);
 219      std::unique_ptr<CDBIterator> db_it(m_db->NewIterator());
 220  
 221      // During a reorg, copy the block's hash digest from the height index to the hash index,
 222      // ensuring it's still accessible after the height index entry is overwritten.
 223      if (!index_util::CopyHeightIndexToHashIndex<DBVal>(*db_it, batch, m_name, block.height)) {
 224          return false;
 225      }
 226  
 227      m_db->WriteBatch(batch);
 228  
 229      if (!RevertBlock(block)) {
 230          return false; // failure cause logged internally
 231      }
 232  
 233      return true;
 234  }
 235  
 236  std::optional<CCoinsStats> CoinStatsIndex::LookUpStats(const CBlockIndex& block_index) const
 237  {
 238      CCoinsStats stats{block_index.nHeight, block_index.GetBlockHash()};
 239      stats.index_used = true;
 240  
 241      DBVal entry;
 242      if (!index_util::LookUpOne(*m_db, {block_index.GetBlockHash(), block_index.nHeight}, entry)) {
 243          return std::nullopt;
 244      }
 245  
 246      stats.hashSerialized = entry.muhash;
 247      stats.nTransactionOutputs = entry.transaction_output_count;
 248      stats.nBogoSize = entry.bogo_size;
 249      stats.total_amount = entry.total_amount;
 250      stats.total_subsidy = entry.total_subsidy;
 251      stats.total_prevout_spent_amount = entry.total_prevout_spent_amount;
 252      stats.total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount;
 253      stats.total_coinbase_amount = entry.total_coinbase_amount;
 254      stats.total_unspendables_genesis_block = entry.total_unspendables_genesis_block;
 255      stats.total_unspendables_bip30 = entry.total_unspendables_bip30;
 256      stats.total_unspendables_scripts = entry.total_unspendables_scripts;
 257      stats.total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards;
 258  
 259      return stats;
 260  }
 261  
 262  bool CoinStatsIndex::CustomInit(const std::optional<interfaces::BlockRef>& block)
 263  {
 264      if (!m_db->Read(DB_MUHASH, m_muhash)) {
 265          // Check that the cause of the read failure is that the key does not
 266          // exist. Any other errors indicate database corruption or a disk
 267          // failure, and starting the index would cause further corruption.
 268          if (m_db->Exists(DB_MUHASH)) {
 269              LogError("Cannot read current %s state; index may be corrupted",
 270                        GetName());
 271              return false;
 272          }
 273      }
 274  
 275      if (block) {
 276          DBVal entry;
 277          if (!index_util::LookUpOne(*m_db, *block, entry)) {
 278              LogError("Cannot read current %s state; index may be corrupted",
 279                        GetName());
 280              return false;
 281          }
 282  
 283          uint256 out;
 284          m_muhash.Finalize(out);
 285          if (entry.muhash != out) {
 286              LogError("Cannot read current %s state; index may be corrupted",
 287                        GetName());
 288              return false;
 289          }
 290  
 291          m_transaction_output_count = entry.transaction_output_count;
 292          m_bogo_size = entry.bogo_size;
 293          m_total_amount = entry.total_amount;
 294          m_total_subsidy = entry.total_subsidy;
 295          m_total_prevout_spent_amount = entry.total_prevout_spent_amount;
 296          m_total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount;
 297          m_total_coinbase_amount = entry.total_coinbase_amount;
 298          m_total_unspendables_genesis_block = entry.total_unspendables_genesis_block;
 299          m_total_unspendables_bip30 = entry.total_unspendables_bip30;
 300          m_total_unspendables_scripts = entry.total_unspendables_scripts;
 301          m_total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards;
 302          m_current_block_hash = block->hash;
 303      }
 304  
 305      return true;
 306  }
 307  
 308  bool CoinStatsIndex::CustomCommit(CDBBatch& batch)
 309  {
 310      // DB_MUHASH should always be committed in a batch together with DB_BEST_BLOCK
 311      // to prevent an inconsistent state of the DB.
 312      batch.Write(DB_MUHASH, m_muhash);
 313      return true;
 314  }
 315  
 316  interfaces::Chain::NotifyOptions CoinStatsIndex::CustomOptions()
 317  {
 318      interfaces::Chain::NotifyOptions options;
 319      options.connect_undo_data = true;
 320      options.disconnect_data = true;
 321      options.disconnect_undo_data = true;
 322      return options;
 323  }
 324  
 325  // Revert a single block as part of a reorg
 326  bool CoinStatsIndex::RevertBlock(const interfaces::BlockInfo& block)
 327  {
 328      std::pair<uint256, DBVal> read_out;
 329  
 330      // Ignore genesis block
 331      if (block.height > 0) {
 332          if (!m_db->Read(index_util::DBHeightKey(block.height - 1), read_out)) {
 333              return false;
 334          }
 335  
 336          uint256 expected_block_hash{*block.prev_hash};
 337          if (read_out.first != expected_block_hash) {
 338              LogWarning("previous block header belongs to unexpected block %s; expected %s",
 339                        read_out.first.ToString(), expected_block_hash.ToString());
 340  
 341              if (!m_db->Read(index_util::DBHashKey(expected_block_hash), read_out)) {
 342                  LogError("previous block header not found; expected %s",
 343                            expected_block_hash.ToString());
 344                  return false;
 345              }
 346          }
 347      }
 348  
 349      // Roll back muhash by removing the new UTXOs that were created by the
 350      // block and reapplying the old UTXOs that were spent by the block
 351      assert(block.data);
 352      assert(block.undo_data);
 353      for (size_t i = 0; i < block.data->vtx.size(); ++i) {
 354          const auto& tx{block.data->vtx.at(i)};
 355          const bool is_coinbase{tx->IsCoinBase()};
 356  
 357          if (is_coinbase && IsBIP30Unspendable(block.hash, block.height)) {
 358              continue;
 359          }
 360  
 361          for (uint32_t j = 0; j < tx->vout.size(); ++j) {
 362              const CTxOut& out{tx->vout[j]};
 363              const COutPoint outpoint{tx->GetHash(), j};
 364              const Coin coin{out, block.height, is_coinbase};
 365  
 366              if (!coin.out.scriptPubKey.IsUnspendable()) {
 367                  RemoveCoinHash(m_muhash, outpoint, coin);
 368              }
 369          }
 370  
 371          // The coinbase tx has no undo data since no former output is spent
 372          if (!is_coinbase) {
 373              const auto& tx_undo{block.undo_data->vtxundo.at(i - 1)};
 374  
 375              for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
 376                  const Coin& coin{tx_undo.vprevout[j]};
 377                  const COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};
 378                  ApplyCoinHash(m_muhash, outpoint, coin);
 379              }
 380          }
 381      }
 382  
 383      // Check that the rolled back muhash is consistent with the DB read out
 384      uint256 out;
 385      m_muhash.Finalize(out);
 386      Assert(read_out.second.muhash == out);
 387  
 388      // Apply the other values from the DB to the member variables
 389      m_transaction_output_count = read_out.second.transaction_output_count;
 390      m_total_amount = read_out.second.total_amount;
 391      m_bogo_size = read_out.second.bogo_size;
 392      m_total_subsidy = read_out.second.total_subsidy;
 393      m_total_prevout_spent_amount = read_out.second.total_prevout_spent_amount;
 394      m_total_new_outputs_ex_coinbase_amount = read_out.second.total_new_outputs_ex_coinbase_amount;
 395      m_total_coinbase_amount = read_out.second.total_coinbase_amount;
 396      m_total_unspendables_genesis_block = read_out.second.total_unspendables_genesis_block;
 397      m_total_unspendables_bip30 = read_out.second.total_unspendables_bip30;
 398      m_total_unspendables_scripts = read_out.second.total_unspendables_scripts;
 399      m_total_unspendables_unclaimed_rewards = read_out.second.total_unspendables_unclaimed_rewards;
 400      m_current_block_hash = *block.prev_hash;
 401  
 402      return true;
 403  }
 404