txdb.cpp raw

   1  // Copyright (c) 2009-2010 Satoshi Nakamoto
   2  // Copyright (c) 2009-present The Bitcoin Core developers
   3  // Distributed under the MIT software license, see the accompanying
   4  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   5  
   6  #include <txdb.h>
   7  
   8  #include <coins.h>
   9  #include <dbwrapper.h>
  10  #include <logging/timer.h>
  11  #include <primitives/transaction.h>
  12  #include <random.h>
  13  #include <serialize.h>
  14  #include <uint256.h>
  15  #include <util/byte_units.h>
  16  #include <util/log.h>
  17  #include <util/threadnames.h>
  18  #include <util/vector.h>
  19  
  20  #include <cassert>
  21  #include <chrono>
  22  #include <cstdlib>
  23  #include <exception>
  24  #include <future>
  25  #include <iterator>
  26  #include <utility>
  27  
  28  static constexpr uint8_t DB_COIN{'C'};
  29  static constexpr uint8_t DB_BEST_BLOCK{'B'};
  30  static constexpr uint8_t DB_HEAD_BLOCKS{'H'};
  31  // Keys used in previous version that might still be found in the DB:
  32  static constexpr uint8_t DB_COINS{'c'};
  33  
  34  // Threshold for warning when writing this many dirty cache entries to disk.
  35  static constexpr size_t WARN_FLUSH_COINS_COUNT{10'000'000};
  36  
  37  bool CCoinsViewDB::NeedsUpgrade()
  38  {
  39      std::unique_ptr<CDBIterator> cursor{m_db->NewIterator()};
  40      // DB_COINS was deprecated in v0.15.0, commit
  41      // 1088b02f0ccd7358d2b7076bb9e122d59d502d02
  42      cursor->Seek(std::make_pair(DB_COINS, uint256{}));
  43      return cursor->Valid();
  44  }
  45  
  46  namespace {
  47  
  48  struct CoinEntry {
  49      COutPoint* outpoint;
  50      uint8_t key{DB_COIN};
  51      explicit CoinEntry(const COutPoint* ptr) : outpoint(const_cast<COutPoint*>(ptr)) {}
  52  
  53      SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint->hash, VARINT(obj.outpoint->n)); }
  54  };
  55  
  56  } // namespace
  57  
  58  CCoinsViewDB::CCoinsViewDB(DBParams db_params, CoinsViewOptions options) :
  59      m_db_params{std::move(db_params)},
  60      m_options{std::move(options)},
  61      m_db{std::make_unique<CDBWrapper>(m_db_params)} { }
  62  
  63  CCoinsViewDB::~CCoinsViewDB()
  64  {
  65      if (m_compaction.valid()) {
  66          if (m_compaction.wait_for(std::chrono::seconds{0}) != std::future_status::ready) {
  67              LogInfo("Waiting for background chainstate compaction of %s", fs::PathToString(m_db_params.path));
  68          }
  69          m_compaction.wait();
  70      }
  71  }
  72  
  73  void CCoinsViewDB::ResizeCache(size_t new_cache_size)
  74  {
  75      // We can't do this operation with an in-memory DB since we'll lose all the coins upon
  76      // reset.
  77      if (!m_db_params.memory_only) {
  78          LOCK(m_db_mutex);
  79          // Have to do a reset first to get the original `m_db` state to release its
  80          // filesystem lock.
  81          m_db.reset();
  82          m_db_params.cache_bytes = new_cache_size;
  83          m_db_params.wipe_data = false;
  84          m_db = std::make_unique<CDBWrapper>(m_db_params);
  85      }
  86  }
  87  
  88  std::optional<Coin> CCoinsViewDB::GetCoin(const COutPoint& outpoint) const
  89  {
  90      if (Coin coin; m_db->Read(CoinEntry(&outpoint), coin)) {
  91          Assert(!coin.IsSpent()); // The UTXO database should never contain spent coins
  92          return coin;
  93      }
  94      return std::nullopt;
  95  }
  96  
  97  std::optional<Coin> CCoinsViewDB::PeekCoin(const COutPoint& outpoint) const
  98  {
  99      return GetCoin(outpoint);
 100  }
 101  
 102  bool CCoinsViewDB::HaveCoin(const COutPoint& outpoint) const
 103  {
 104      return m_db->Exists(CoinEntry(&outpoint));
 105  }
 106  
 107  uint256 CCoinsViewDB::GetBestBlock() const {
 108      uint256 hashBestChain;
 109      if (!m_db->Read(DB_BEST_BLOCK, hashBestChain))
 110          return uint256();
 111      return hashBestChain;
 112  }
 113  
 114  std::vector<uint256> CCoinsViewDB::GetHeadBlocks() const {
 115      std::vector<uint256> vhashHeadBlocks;
 116      if (!m_db->Read(DB_HEAD_BLOCKS, vhashHeadBlocks)) {
 117          return std::vector<uint256>();
 118      }
 119      return vhashHeadBlocks;
 120  }
 121  
 122  void CCoinsViewDB::BatchWrite(CoinsViewCacheCursor& cursor, const uint256& block_hash)
 123  {
 124      CDBBatch batch(*m_db);
 125      size_t count = 0;
 126      const size_t dirty_count{cursor.GetDirtyCount()};
 127      assert(!block_hash.IsNull());
 128  
 129      uint256 old_tip = GetBestBlock();
 130      if (old_tip.IsNull()) {
 131          // We may be in the middle of replaying.
 132          std::vector<uint256> old_heads = GetHeadBlocks();
 133          if (old_heads.size() == 2) {
 134              if (old_heads[0] != block_hash) {
 135                  LogError("The coins database detected an inconsistent state, likely due to a previous crash or shutdown. You will need to restart bitcoind with the -reindex-chainstate or -reindex configuration option.\n");
 136              }
 137              assert(old_heads[0] == block_hash);
 138              old_tip = old_heads[1];
 139          }
 140      }
 141  
 142      if (dirty_count > WARN_FLUSH_COINS_COUNT) LogWarning("Flushing large (%d entries) UTXO set to disk, it may take several minutes", dirty_count);
 143      LOG_TIME_MILLIS_WITH_CATEGORY(strprintf("write coins cache to disk (%d out of %d cached coins)",
 144          dirty_count, cursor.GetTotalCount()), BCLog::BENCH);
 145  
 146      // In the first batch, mark the database as being in the middle of a
 147      // transition from old_tip to block_hash.
 148      // A vector is used for future extensibility, as we may want to support
 149      // interrupting after partial writes from multiple independent reorgs.
 150      batch.Erase(DB_BEST_BLOCK);
 151      batch.Write(DB_HEAD_BLOCKS, Vector(block_hash, old_tip));
 152  
 153      for (auto it{cursor.Begin()}; it != cursor.End();) {
 154          if (it->second.IsDirty()) {
 155              CoinEntry entry(&it->first);
 156              if (it->second.coin.IsSpent()) {
 157                  batch.Erase(entry);
 158              } else {
 159                  batch.Write(entry, it->second.coin);
 160              }
 161          }
 162          count++;
 163          it = cursor.NextAndMaybeErase(*it);
 164          if (batch.ApproximateSize() > m_options.batch_write_bytes) {
 165              LogDebug(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.ApproximateSize() / double(1_MiB));
 166  
 167              m_db->WriteBatch(batch);
 168              batch.Clear();
 169              if (m_options.simulate_crash_ratio) {
 170                  static FastRandomContext rng;
 171                  if (rng.randrange(m_options.simulate_crash_ratio) == 0) {
 172                      LogError("Simulating a crash. Goodbye.");
 173                      _Exit(0);
 174                  }
 175              }
 176          }
 177      }
 178  
 179      // In the last batch, mark the database as consistent with block_hash again.
 180      batch.Erase(DB_HEAD_BLOCKS);
 181      batch.Write(DB_BEST_BLOCK, block_hash);
 182  
 183      LogDebug(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.ApproximateSize() / double(1_MiB));
 184      m_db->WriteBatch(batch);
 185      LogDebug(BCLog::COINDB, "Committed %u changed transaction outputs (out of %u) to coin database...", (unsigned int)dirty_count, (unsigned int)count);
 186  }
 187  
 188  size_t CCoinsViewDB::EstimateSize() const
 189  {
 190      return m_db->EstimateSize(DB_COIN, uint8_t(DB_COIN + 1));
 191  }
 192  
 193  std::optional<std::string> CCoinsViewDB::GetDBProperty(const std::string& property)
 194  {
 195      return m_db->GetProperty(property);
 196  }
 197  
 198  std::shared_future<void> CCoinsViewDB::CompactFullAsync()
 199  {
 200      AssertLockHeld(::cs_main);
 201      if (m_compaction.valid() && m_compaction.wait_for(std::chrono::seconds{0}) != std::future_status::ready) return m_compaction;
 202      m_compaction = std::async(std::launch::async, [this] {
 203          try {
 204              util::ThreadRename("utxocompact");
 205              LOCK(m_db_mutex);
 206  
 207              LogDebug(BCLog::COINDB, "Starting chainstate compaction of %s", fs::PathToString(m_db_params.path));
 208              m_db->CompactFull();
 209              LogDebug(BCLog::COINDB, "Finished chainstate compaction of %s", fs::PathToString(m_db_params.path));
 210          } catch (const std::exception& e) {
 211              LogWarning("Failed chainstate compaction (%s)", e.what());
 212          }
 213      }).share();
 214      return m_compaction;
 215  }
 216  
 217  /** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */
 218  class CCoinsViewDBCursor: public CCoinsViewCursor
 219  {
 220  public:
 221      // Prefer using CCoinsViewDB::Cursor() since we want to perform some
 222      // cache warmup on instantiation.
 223      CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256& in_block_hash):
 224          CCoinsViewCursor(in_block_hash), pcursor(pcursorIn) {}
 225      ~CCoinsViewDBCursor() = default;
 226  
 227      bool GetKey(COutPoint &key) const override;
 228      bool GetValue(Coin &coin) const override;
 229  
 230      bool Valid() const override;
 231      void Next() override;
 232  
 233  private:
 234      std::unique_ptr<CDBIterator> pcursor;
 235      std::pair<char, COutPoint> keyTmp;
 236  
 237      friend class CCoinsViewDB;
 238  };
 239  
 240  std::unique_ptr<CCoinsViewCursor> CCoinsViewDB::Cursor() const
 241  {
 242      auto i = std::make_unique<CCoinsViewDBCursor>(
 243          const_cast<CDBWrapper&>(*m_db).NewIterator(), GetBestBlock());
 244      /* It seems that there are no "const iterators" for LevelDB.  Since we
 245         only need read operations on it, use a const-cast to get around
 246         that restriction.  */
 247      i->pcursor->Seek(DB_COIN);
 248      // Cache key of first record
 249      if (i->pcursor->Valid()) {
 250          CoinEntry entry(&i->keyTmp.second);
 251          i->pcursor->GetKey(entry);
 252          i->keyTmp.first = entry.key;
 253      } else {
 254          i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false
 255      }
 256      return i;
 257  }
 258  
 259  bool CCoinsViewDBCursor::GetKey(COutPoint &key) const
 260  {
 261      // Return cached key
 262      if (keyTmp.first == DB_COIN) {
 263          key = keyTmp.second;
 264          return true;
 265      }
 266      return false;
 267  }
 268  
 269  bool CCoinsViewDBCursor::GetValue(Coin &coin) const
 270  {
 271      return pcursor->GetValue(coin);
 272  }
 273  
 274  bool CCoinsViewDBCursor::Valid() const
 275  {
 276      return keyTmp.first == DB_COIN;
 277  }
 278  
 279  void CCoinsViewDBCursor::Next()
 280  {
 281      pcursor->Next();
 282      CoinEntry entry(&keyTmp.second);
 283      if (!pcursor->Valid() || !pcursor->GetKey(entry)) {
 284          keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false
 285      } else {
 286          keyTmp.first = entry.key;
 287      }
 288  }
 289