LCOV - code coverage report
Current view: top level - src - txdb.cpp (source / functions) Hit Total Coverage
Test: fuzz_coverage.info Lines: 27 117 23.1 %
Date: 2023-09-26 12:08:55 Functions: 11 25 44.0 %

          Line data    Source code
       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           1 : bool CCoinsViewDB::NeedsUpgrade()
      29             : {
      30           1 :     std::unique_ptr<CDBIterator> cursor{m_db->NewIterator()};
      31             :     // DB_COINS was deprecated in v0.15.0, commit
      32             :     // 1088b02f0ccd7358d2b7076bb9e122d59d502d02
      33           1 :     cursor->Seek(std::make_pair(DB_COINS, uint256{}));
      34           1 :     return cursor->Valid();
      35           1 : }
      36             : 
      37             : namespace {
      38             : 
      39             : struct CoinEntry {
      40             :     COutPoint* outpoint;
      41             :     uint8_t key;
      42       19256 :     explicit CoinEntry(const COutPoint* ptr) : outpoint(const_cast<COutPoint*>(ptr)), key(DB_COIN)  {}
      43             : 
      44       57768 :     SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint->hash, VARINT(obj.outpoint->n)); }
      45             : };
      46             : 
      47             : } // namespace
      48             : 
      49           1 : CCoinsViewDB::CCoinsViewDB(DBParams db_params, CoinsViewOptions options) :
      50           1 :     m_db_params{std::move(db_params)},
      51           1 :     m_options{std::move(options)},
      52           2 :     m_db{std::make_unique<CDBWrapper>(m_db_params)} { }
      53             : 
      54           1 : 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           1 :     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           0 :         m_db.reset();
      62           0 :         m_db_params.cache_bytes = new_cache_size;
      63           0 :         m_db_params.wipe_data = false;
      64           0 :         m_db = std::make_unique<CDBWrapper>(m_db_params);
      65           0 :     }
      66           1 : }
      67             : 
      68       19256 : bool CCoinsViewDB::GetCoin(const COutPoint &outpoint, Coin &coin) const {
      69       19256 :     return m_db->Read(CoinEntry(&outpoint), coin);
      70             : }
      71             : 
      72           0 : bool CCoinsViewDB::HaveCoin(const COutPoint &outpoint) const {
      73           0 :     return m_db->Exists(CoinEntry(&outpoint));
      74           2 : }
      75             : 
      76           3 : uint256 CCoinsViewDB::GetBestBlock() const {
      77           3 :     uint256 hashBestChain;
      78           3 :     if (!m_db->Read(DB_BEST_BLOCK, hashBestChain))
      79           3 :         return uint256();
      80           0 :     return hashBestChain;
      81           3 : }
      82             : 
      83           1 : std::vector<uint256> CCoinsViewDB::GetHeadBlocks() const {
      84           1 :     std::vector<uint256> vhashHeadBlocks;
      85           1 :     if (!m_db->Read(DB_HEAD_BLOCKS, vhashHeadBlocks)) {
      86           1 :         return std::vector<uint256>();
      87             :     }
      88           0 :     return vhashHeadBlocks;
      89           1 : }
      90             : 
      91           0 : bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase) {
      92           0 :     CDBBatch batch(*m_db);
      93           0 :     size_t count = 0;
      94           0 :     size_t changed = 0;
      95           0 :     assert(!hashBlock.IsNull());
      96             : 
      97           0 :     uint256 old_tip = GetBestBlock();
      98           0 :     if (old_tip.IsNull()) {
      99             :         // We may be in the middle of replaying.
     100           0 :         std::vector<uint256> old_heads = GetHeadBlocks();
     101           0 :         if (old_heads.size() == 2) {
     102           0 :             if (old_heads[0] != hashBlock) {
     103           0 :                 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           0 :             }
     105           0 :             assert(old_heads[0] == hashBlock);
     106           0 :             old_tip = old_heads[1];
     107           0 :         }
     108           0 :     }
     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           0 :     batch.Erase(DB_BEST_BLOCK);
     115           0 :     batch.Write(DB_HEAD_BLOCKS, Vector(hashBlock, old_tip));
     116             : 
     117           0 :     for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
     118           0 :         if (it->second.flags & CCoinsCacheEntry::DIRTY) {
     119           0 :             CoinEntry entry(&it->first);
     120           0 :             if (it->second.coin.IsSpent())
     121           0 :                 batch.Erase(entry);
     122             :             else
     123           0 :                 batch.Write(entry, it->second.coin);
     124           0 :             changed++;
     125           0 :         }
     126           0 :         count++;
     127           0 :         it = erase ? mapCoins.erase(it) : std::next(it);
     128           0 :         if (batch.SizeEstimate() > m_options.batch_write_bytes) {
     129           0 :             LogPrint(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0));
     130           0 :             m_db->WriteBatch(batch);
     131           0 :             batch.Clear();
     132           0 :             if (m_options.simulate_crash_ratio) {
     133           0 :                 static FastRandomContext rng;
     134           0 :                 if (rng.randrange(m_options.simulate_crash_ratio) == 0) {
     135           0 :                     LogPrintf("Simulating a crash. Goodbye.\n");
     136           0 :                     _Exit(0);
     137             :                 }
     138           0 :             }
     139           0 :         }
     140             :     }
     141             : 
     142             :     // In the last batch, mark the database as consistent with hashBlock again.
     143           0 :     batch.Erase(DB_HEAD_BLOCKS);
     144           0 :     batch.Write(DB_BEST_BLOCK, hashBlock);
     145             : 
     146           0 :     LogPrint(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0));
     147           0 :     bool ret = m_db->WriteBatch(batch);
     148           0 :     LogPrint(BCLog::COINDB, "Committed %u changed transaction outputs (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count);
     149           0 :     return ret;
     150           0 : }
     151             : 
     152           0 : size_t CCoinsViewDB::EstimateSize() const
     153             : {
     154           0 :     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           0 :     CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256&hashBlockIn):
     164           0 :         CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {}
     165           0 :     ~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           0 : std::unique_ptr<CCoinsViewCursor> CCoinsViewDB::Cursor() const
     181             : {
     182           0 :     auto i = std::make_unique<CCoinsViewDBCursor>(
     183           0 :         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           0 :     i->pcursor->Seek(DB_COIN);
     188             :     // Cache key of first record
     189           0 :     if (i->pcursor->Valid()) {
     190           0 :         CoinEntry entry(&i->keyTmp.second);
     191           0 :         i->pcursor->GetKey(entry);
     192           0 :         i->keyTmp.first = entry.key;
     193           0 :     } else {
     194           0 :         i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false
     195             :     }
     196           0 :     return i;
     197           0 : }
     198             : 
     199           0 : bool CCoinsViewDBCursor::GetKey(COutPoint &key) const
     200             : {
     201             :     // Return cached key
     202           0 :     if (keyTmp.first == DB_COIN) {
     203           0 :         key = keyTmp.second;
     204           0 :         return true;
     205             :     }
     206           0 :     return false;
     207           0 : }
     208             : 
     209           0 : bool CCoinsViewDBCursor::GetValue(Coin &coin) const
     210             : {
     211           0 :     return pcursor->GetValue(coin);
     212             : }
     213             : 
     214           0 : bool CCoinsViewDBCursor::Valid() const
     215             : {
     216           0 :     return keyTmp.first == DB_COIN;
     217             : }
     218             : 
     219           0 : void CCoinsViewDBCursor::Next()
     220             : {
     221           0 :     pcursor->Next();
     222           0 :     CoinEntry entry(&keyTmp.second);
     223           0 :     if (!pcursor->Valid() || !pcursor->GetKey(entry)) {
     224           0 :         keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false
     225           0 :     } else {
     226           0 :         keyTmp.first = entry.key;
     227             :     }
     228           0 : }

Generated by: LCOV version 1.14