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