txdb.cpp raw

   1  // Copyright (c) 2009-2010 Satoshi Nakamoto
   2  // Copyright (c) 2009-2022 The Limenka 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.h>
  11  #include <primitives/transaction.h>
  12  #include <random.h>
  13  #include <serialize.h>
  14  #include <uint256.h>
  15  #include <util/vector.h>
  16  
  17  #include <cassert>
  18  #include <cstdlib>
  19  #include <iterator>
  20  #include <utility>
  21  
  22  static constexpr uint8_t DB_COIN{'C'};
  23  static constexpr uint8_t DB_BEST_BLOCK{'B'};
  24  static constexpr uint8_t DB_HEAD_BLOCKS{'H'};
  25  // Keys used in previous version that might still be found in the DB:
  26  static constexpr uint8_t DB_COINS{'c'};
  27  
  28  bool CCoinsViewDB::NeedsUpgrade()
  29  {
  30      std::unique_ptr<CDBIterator> cursor{m_db->NewIterator()};
  31      // DB_COINS was deprecated in v0.15.0, commit
  32      // 1088b02f0ccd7358d2b7076bb9e122d59d502d02
  33      cursor->Seek(std::make_pair(DB_COINS, uint256{}));
  34      return cursor->Valid();
  35  }
  36  
  37  namespace {
  38  
  39  struct CoinEntry {
  40      COutPoint* outpoint;
  41      uint8_t key;
  42      explicit CoinEntry(const COutPoint* ptr) : outpoint(const_cast<COutPoint*>(ptr)), key(DB_COIN)  {}
  43  
  44      SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint->hash, VARINT(obj.outpoint->n)); }
  45  };
  46  
  47  } // namespace
  48  
  49  CCoinsViewDB::CCoinsViewDB(DBParams db_params, CoinsViewOptions options) :
  50      m_db_params{std::move(db_params)},
  51      m_options{std::move(options)},
  52      m_db{std::make_unique<CDBWrapper>(m_db_params)} { }
  53  
  54  void CCoinsViewDB::ResizeCache(size_t new_cache_size)
  55  {
  56      // We can't do this operation with an in-memory DB since we'll lose all the coins upon
  57      // reset.
  58      if (!m_db_params.memory_only) {
  59          // Have to do a reset first to get the original `m_db` state to release its
  60          // filesystem lock.
  61          m_db.reset();
  62          m_db_params.cache_bytes = new_cache_size;
  63          m_db_params.wipe_data = false;
  64          m_db = std::make_unique<CDBWrapper>(m_db_params);
  65      }
  66  }
  67  
  68  std::optional<Coin> CCoinsViewDB::GetCoin(const COutPoint& outpoint) const
  69  {
  70      if (Coin coin; m_db->Read(CoinEntry(&outpoint), coin)) return coin;
  71      return std::nullopt;
  72  }
  73  
  74  bool CCoinsViewDB::HaveCoin(const COutPoint &outpoint) const {
  75      return m_db->Exists(CoinEntry(&outpoint));
  76  }
  77  
  78  uint256 CCoinsViewDB::GetBestBlock() const {
  79      uint256 hashBestChain;
  80      if (!m_db->Read(DB_BEST_BLOCK, hashBestChain))
  81          return uint256();
  82      return hashBestChain;
  83  }
  84  
  85  std::vector<uint256> CCoinsViewDB::GetHeadBlocks() const {
  86      std::vector<uint256> vhashHeadBlocks;
  87      if (!m_db->Read(DB_HEAD_BLOCKS, vhashHeadBlocks)) {
  88          return std::vector<uint256>();
  89      }
  90      return vhashHeadBlocks;
  91  }
  92  
  93  bool CCoinsViewDB::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlock) {
  94      CDBBatch batch(*m_db);
  95      size_t count = 0;
  96      size_t changed = 0;
  97      assert(!hashBlock.IsNull());
  98  
  99      uint256 old_tip = GetBestBlock();
 100      if (old_tip.IsNull()) {
 101          // We may be in the middle of replaying.
 102          std::vector<uint256> old_heads = GetHeadBlocks();
 103          if (old_heads.size() == 2) {
 104              if (old_heads[0] != hashBlock) {
 105                  LogPrintLevel(BCLog::COINDB, BCLog::Level::Error, "The coins database detected an inconsistent state, likely due to a previous crash or shutdown. You will need to restart limenkad with the -reindex-chainstate or -reindex configuration option.\n");
 106              }
 107              assert(old_heads[0] == hashBlock);
 108              old_tip = old_heads[1];
 109          }
 110      }
 111  
 112      // In the first batch, mark the database as being in the middle of a
 113      // transition from old_tip to hashBlock.
 114      // A vector is used for future extensibility, as we may want to support
 115      // interrupting after partial writes from multiple independent reorgs.
 116      batch.Erase(DB_BEST_BLOCK);
 117      batch.Write(DB_HEAD_BLOCKS, Vector(hashBlock, old_tip));
 118  
 119      for (auto it{cursor.Begin()}; it != cursor.End();) {
 120          if (it->second.IsDirty()) {
 121              CoinEntry entry(&it->first);
 122              if (it->second.coin.IsSpent())
 123                  batch.Erase(entry);
 124              else
 125                  batch.Write(entry, it->second.coin);
 126              changed++;
 127          }
 128          count++;
 129          it = cursor.NextAndMaybeErase(*it);
 130          if (batch.SizeEstimate() > m_options.batch_write_bytes) {
 131              LogDebug(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0));
 132              m_db->WriteBatch(batch);
 133              batch.Clear();
 134              if (m_options.simulate_crash_ratio) {
 135                  static FastRandomContext rng;
 136                  if (rng.randrange(m_options.simulate_crash_ratio) == 0) {
 137                      LogError("Simulating a crash. Goodbye.");
 138                      _Exit(0);
 139                  }
 140              }
 141          }
 142      }
 143  
 144      // In the last batch, mark the database as consistent with hashBlock again.
 145      batch.Erase(DB_HEAD_BLOCKS);
 146      batch.Write(DB_BEST_BLOCK, hashBlock);
 147  
 148      LogDebug(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0));
 149      bool ret = m_db->WriteBatch(batch);
 150      LogDebug(BCLog::COINDB, "Committed %u changed transaction outputs (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count);
 151      return ret;
 152  }
 153  
 154  size_t CCoinsViewDB::EstimateSize() const
 155  {
 156      return m_db->EstimateSize(DB_COIN, uint8_t(DB_COIN + 1));
 157  }
 158  
 159  /** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */
 160  class CCoinsViewDBCursor: public CCoinsViewCursor
 161  {
 162  public:
 163      // Prefer using CCoinsViewDB::Cursor() since we want to perform some
 164      // cache warmup on instantiation.
 165      CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256&hashBlockIn):
 166          CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {}
 167      ~CCoinsViewDBCursor() = default;
 168  
 169      bool GetKey(COutPoint &key) const override;
 170      bool GetValue(Coin &coin) const override;
 171  
 172      bool Valid() const override;
 173      void Next() override;
 174  
 175  private:
 176      std::unique_ptr<CDBIterator> pcursor;
 177      std::pair<char, COutPoint> keyTmp;
 178  
 179      friend class CCoinsViewDB;
 180  };
 181  
 182  std::unique_ptr<CCoinsViewCursor> CCoinsViewDB::Cursor() const
 183  {
 184      auto i = std::make_unique<CCoinsViewDBCursor>(
 185          const_cast<CDBWrapper&>(*m_db).NewIterator(), GetBestBlock());
 186      /* It seems that there are no "const iterators" for LevelDB.  Since we
 187         only need read operations on it, use a const-cast to get around
 188         that restriction.  */
 189      i->pcursor->Seek(DB_COIN);
 190      // Cache key of first record
 191      if (i->pcursor->Valid()) {
 192          CoinEntry entry(&i->keyTmp.second);
 193          i->pcursor->GetKey(entry);
 194          i->keyTmp.first = entry.key;
 195      } else {
 196          i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false
 197      }
 198      return i;
 199  }
 200  
 201  bool CCoinsViewDBCursor::GetKey(COutPoint &key) const
 202  {
 203      // Return cached key
 204      if (keyTmp.first == DB_COIN) {
 205          key = keyTmp.second;
 206          return true;
 207      }
 208      return false;
 209  }
 210  
 211  bool CCoinsViewDBCursor::GetValue(Coin &coin) const
 212  {
 213      return pcursor->GetValue(coin);
 214  }
 215  
 216  bool CCoinsViewDBCursor::Valid() const
 217  {
 218      return keyTmp.first == DB_COIN;
 219  }
 220  
 221  void CCoinsViewDBCursor::Next()
 222  {
 223      pcursor->Next();
 224      CoinEntry entry(&keyTmp.second);
 225      if (!pcursor->Valid() || !pcursor->GetKey(entry)) {
 226          keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false
 227      } else {
 228          keyTmp.first = entry.key;
 229      }
 230  }
 231