/ 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  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 bitcoind 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              }
127  
128              changed++;
129          }
130          count++;
131          it = cursor.NextAndMaybeErase(*it);
132          if (batch.ApproximateSize() > m_options.batch_write_bytes) {
133              LogDebug(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.ApproximateSize() * (1.0 / 1048576.0));
134  
135              m_db->WriteBatch(batch);
136              batch.Clear();
137              if (m_options.simulate_crash_ratio) {
138                  static FastRandomContext rng;
139                  if (rng.randrange(m_options.simulate_crash_ratio) == 0) {
140                      LogPrintf("Simulating a crash. Goodbye.\n");
141                      _Exit(0);
142                  }
143              }
144          }
145      }
146  
147      // In the last batch, mark the database as consistent with hashBlock again.
148      batch.Erase(DB_HEAD_BLOCKS);
149      batch.Write(DB_BEST_BLOCK, hashBlock);
150  
151      LogDebug(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.ApproximateSize() * (1.0 / 1048576.0));
152      bool ret = m_db->WriteBatch(batch);
153      LogDebug(BCLog::COINDB, "Committed %u changed transaction outputs (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count);
154      return ret;
155  }
156  
157  size_t CCoinsViewDB::EstimateSize() const
158  {
159      return m_db->EstimateSize(DB_COIN, uint8_t(DB_COIN + 1));
160  }
161  
162  /** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */
163  class CCoinsViewDBCursor: public CCoinsViewCursor
164  {
165  public:
166      // Prefer using CCoinsViewDB::Cursor() since we want to perform some
167      // cache warmup on instantiation.
168      CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256&hashBlockIn):
169          CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {}
170      ~CCoinsViewDBCursor() = default;
171  
172      bool GetKey(COutPoint &key) const override;
173      bool GetValue(Coin &coin) const override;
174  
175      bool Valid() const override;
176      void Next() override;
177  
178  private:
179      std::unique_ptr<CDBIterator> pcursor;
180      std::pair<char, COutPoint> keyTmp;
181  
182      friend class CCoinsViewDB;
183  };
184  
185  std::unique_ptr<CCoinsViewCursor> CCoinsViewDB::Cursor() const
186  {
187      auto i = std::make_unique<CCoinsViewDBCursor>(
188          const_cast<CDBWrapper&>(*m_db).NewIterator(), GetBestBlock());
189      /* It seems that there are no "const iterators" for LevelDB.  Since we
190         only need read operations on it, use a const-cast to get around
191         that restriction.  */
192      i->pcursor->Seek(DB_COIN);
193      // Cache key of first record
194      if (i->pcursor->Valid()) {
195          CoinEntry entry(&i->keyTmp.second);
196          i->pcursor->GetKey(entry);
197          i->keyTmp.first = entry.key;
198      } else {
199          i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false
200      }
201      return i;
202  }
203  
204  bool CCoinsViewDBCursor::GetKey(COutPoint &key) const
205  {
206      // Return cached key
207      if (keyTmp.first == DB_COIN) {
208          key = keyTmp.second;
209          return true;
210      }
211      return false;
212  }
213  
214  bool CCoinsViewDBCursor::GetValue(Coin &coin) const
215  {
216      return pcursor->GetValue(coin);
217  }
218  
219  bool CCoinsViewDBCursor::Valid() const
220  {
221      return keyTmp.first == DB_COIN;
222  }
223  
224  void CCoinsViewDBCursor::Next()
225  {
226      pcursor->Next();
227      CoinEntry entry(&keyTmp.second);
228      if (!pcursor->Valid() || !pcursor->GetKey(entry)) {
229          keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false
230      } else {
231          keyTmp.first = entry.key;
232      }
233  }