/ src / txdb.cpp
txdb.cpp
  1  // Copyright (c) 2009-2010 Satoshi Nakamoto
  2  // Copyright (c) 2009-2022 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.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  bool CCoinsViewDB::GetCoin(const COutPoint &outpoint, Coin &coin) const {
 69      return m_db->Read(CoinEntry(&outpoint), coin);
 70  }
 71  
 72  bool CCoinsViewDB::HaveCoin(const COutPoint &outpoint) const {
 73      return m_db->Exists(CoinEntry(&outpoint));
 74  }
 75  
 76  uint256 CCoinsViewDB::GetBestBlock() const {
 77      uint256 hashBestChain;
 78      if (!m_db->Read(DB_BEST_BLOCK, hashBestChain))
 79          return uint256();
 80      return hashBestChain;
 81  }
 82  
 83  std::vector<uint256> CCoinsViewDB::GetHeadBlocks() const {
 84      std::vector<uint256> vhashHeadBlocks;
 85      if (!m_db->Read(DB_HEAD_BLOCKS, vhashHeadBlocks)) {
 86          return std::vector<uint256>();
 87      }
 88      return vhashHeadBlocks;
 89  }
 90  
 91  bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase) {
 92      CDBBatch batch(*m_db);
 93      size_t count = 0;
 94      size_t changed = 0;
 95      assert(!hashBlock.IsNull());
 96  
 97      uint256 old_tip = GetBestBlock();
 98      if (old_tip.IsNull()) {
 99          // We may be in the middle of replaying.
100          std::vector<uint256> old_heads = GetHeadBlocks();
101          if (old_heads.size() == 2) {
102              if (old_heads[0] != hashBlock) {
103                  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 bitcoind with the -reindex-chainstate or -reindex configuration option.\n");
104              }
105              assert(old_heads[0] == hashBlock);
106              old_tip = old_heads[1];
107          }
108      }
109  
110      // In the first batch, mark the database as being in the middle of a
111      // transition from old_tip to hashBlock.
112      // A vector is used for future extensibility, as we may want to support
113      // interrupting after partial writes from multiple independent reorgs.
114      batch.Erase(DB_BEST_BLOCK);
115      batch.Write(DB_HEAD_BLOCKS, Vector(hashBlock, old_tip));
116  
117      for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
118          if (it->second.flags & CCoinsCacheEntry::DIRTY) {
119              CoinEntry entry(&it->first);
120              if (it->second.coin.IsSpent())
121                  batch.Erase(entry);
122              else
123                  batch.Write(entry, it->second.coin);
124              changed++;
125          }
126          count++;
127          it = erase ? mapCoins.erase(it) : std::next(it);
128          if (batch.SizeEstimate() > m_options.batch_write_bytes) {
129              LogPrint(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0));
130              m_db->WriteBatch(batch);
131              batch.Clear();
132              if (m_options.simulate_crash_ratio) {
133                  static FastRandomContext rng;
134                  if (rng.randrange(m_options.simulate_crash_ratio) == 0) {
135                      LogPrintf("Simulating a crash. Goodbye.\n");
136                      _Exit(0);
137                  }
138              }
139          }
140      }
141  
142      // In the last batch, mark the database as consistent with hashBlock again.
143      batch.Erase(DB_HEAD_BLOCKS);
144      batch.Write(DB_BEST_BLOCK, hashBlock);
145  
146      LogPrint(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0));
147      bool ret = m_db->WriteBatch(batch);
148      LogPrint(BCLog::COINDB, "Committed %u changed transaction outputs (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count);
149      return ret;
150  }
151  
152  size_t CCoinsViewDB::EstimateSize() const
153  {
154      return m_db->EstimateSize(DB_COIN, uint8_t(DB_COIN + 1));
155  }
156  
157  /** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */
158  class CCoinsViewDBCursor: public CCoinsViewCursor
159  {
160  public:
161      // Prefer using CCoinsViewDB::Cursor() since we want to perform some
162      // cache warmup on instantiation.
163      CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256&hashBlockIn):
164          CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {}
165      ~CCoinsViewDBCursor() = default;
166  
167      bool GetKey(COutPoint &key) const override;
168      bool GetValue(Coin &coin) const override;
169  
170      bool Valid() const override;
171      void Next() override;
172  
173  private:
174      std::unique_ptr<CDBIterator> pcursor;
175      std::pair<char, COutPoint> keyTmp;
176  
177      friend class CCoinsViewDB;
178  };
179  
180  std::unique_ptr<CCoinsViewCursor> CCoinsViewDB::Cursor() const
181  {
182      auto i = std::make_unique<CCoinsViewDBCursor>(
183          const_cast<CDBWrapper&>(*m_db).NewIterator(), GetBestBlock());
184      /* It seems that there are no "const iterators" for LevelDB.  Since we
185         only need read operations on it, use a const-cast to get around
186         that restriction.  */
187      i->pcursor->Seek(DB_COIN);
188      // Cache key of first record
189      if (i->pcursor->Valid()) {
190          CoinEntry entry(&i->keyTmp.second);
191          i->pcursor->GetKey(entry);
192          i->keyTmp.first = entry.key;
193      } else {
194          i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false
195      }
196      return i;
197  }
198  
199  bool CCoinsViewDBCursor::GetKey(COutPoint &key) const
200  {
201      // Return cached key
202      if (keyTmp.first == DB_COIN) {
203          key = keyTmp.second;
204          return true;
205      }
206      return false;
207  }
208  
209  bool CCoinsViewDBCursor::GetValue(Coin &coin) const
210  {
211      return pcursor->GetValue(coin);
212  }
213  
214  bool CCoinsViewDBCursor::Valid() const
215  {
216      return keyTmp.first == DB_COIN;
217  }
218  
219  void CCoinsViewDBCursor::Next()
220  {
221      pcursor->Next();
222      CoinEntry entry(&keyTmp.second);
223      if (!pcursor->Valid() || !pcursor->GetKey(entry)) {
224          keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false
225      } else {
226          keyTmp.first = entry.key;
227      }
228  }