LCOV - code coverage report
Current view: top level - src - txdb.cpp (source / functions) Coverage Total Hit
Test: fuzz_coverage.info Lines: 82.1 % 106 87
Test Date: 2024-12-02 12:23:29 Functions: 88.9 % 18 16
Branches: 44.0 % 116 51

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

Generated by: LCOV version 2.0-1