/Users/eugenesiegel/btc/bitcoin/src/txdb.cpp
| Line | Count | Source (jump to first uncovered line) | 
| 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 | 51.2k | { | 
| 30 | 51.2k |     std::unique_ptr<CDBIterator> cursor{m_db->NewIterator()}; | 
| 31 |  |     // DB_COINS was deprecated in v0.15.0, commit | 
| 32 |  |     // 1088b02f0ccd7358d2b7076bb9e122d59d502d02 | 
| 33 | 51.2k |     cursor->Seek(std::make_pair(DB_COINS, uint256{})); | 
| 34 | 51.2k |     return cursor->Valid(); | 
| 35 | 51.2k | } | 
| 36 |  |  | 
| 37 |  | namespace { | 
| 38 |  |  | 
| 39 |  | struct CoinEntry { | 
| 40 |  |     COutPoint* outpoint; | 
| 41 |  |     uint8_t key; | 
| 42 | 1.54M |     explicit CoinEntry(const COutPoint* ptr) : outpoint(const_cast<COutPoint*>(ptr)), key(DB_COIN)  {} | 
| 43 |  |  | 
| 44 | 1.54M |     SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint->hash, VARINT(obj.outpoint->n)); }| Line | Count | Source |  | 145 | 1.54M | #define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__)) | 
 |     SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint->hash, VARINT(obj.outpoint->n)); }| Line | Count | Source |  | 145 | 0 | #define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__)) | 
txdb.cpp:_ZN12_GLOBAL__N_19CoinEntry16SerializationOpsI10DataStreamKS0_15ActionSerializeEEvRT0_RT_T1_| Line | Count | Source |  | 44 | 1.54M |     SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint->hash, VARINT(obj.outpoint->n)); }| Line | Count | Source |  | 145 | 1.54M | #define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__)) | 
 | 
Unexecuted instantiation: txdb.cpp:_ZN12_GLOBAL__N_19CoinEntry16SerializationOpsI10DataStreamS0_17ActionUnserializeEEvRT0_RT_T1_ | 
| 45 |  | }; | 
| 46 |  |  | 
| 47 |  | } // namespace | 
| 48 |  |  | 
| 49 |  | CCoinsViewDB::CCoinsViewDB(DBParams db_params, CoinsViewOptions options) : | 
| 50 | 51.2k |     m_db_params{std::move(db_params)}, | 
| 51 | 51.2k |     m_options{std::move(options)}, | 
| 52 | 51.2k |     m_db{std::make_unique<CDBWrapper>(m_db_params)} { } | 
| 53 |  |  | 
| 54 |  | void CCoinsViewDB::ResizeCache(size_t new_cache_size) | 
| 55 | 0 | { | 
| 56 |  |     // We can't do this operation with an in-memory DB since we'll lose all the coins upon | 
| 57 |  |     // reset. | 
| 58 | 0 |     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 | 0 | } | 
| 67 |  |  | 
| 68 |  | std::optional<Coin> CCoinsViewDB::GetCoin(const COutPoint& outpoint) const | 
| 69 | 1.52M | { | 
| 70 | 1.52M |     if (Coin coin; m_db->Read(CoinEntry(&outpoint), coin)) return coin1.34M; | 
| 71 | 184k |     return std::nullopt; | 
| 72 | 1.52M | } | 
| 73 |  |  | 
| 74 | 0 | bool CCoinsViewDB::HaveCoin(const COutPoint &outpoint) const { | 
| 75 | 0 |     return m_db->Exists(CoinEntry(&outpoint)); | 
| 76 | 0 | } | 
| 77 |  |  | 
| 78 | 107k | uint256 CCoinsViewDB::GetBestBlock() const { | 
| 79 | 107k |     uint256 hashBestChain; | 
| 80 | 107k |     if (!m_db->Read(DB_BEST_BLOCK, hashBestChain)) | 
| 81 | 0 |         return uint256(); | 
| 82 | 107k |     return hashBestChain; | 
| 83 | 107k | } | 
| 84 |  |  | 
| 85 | 51.2k | std::vector<uint256> CCoinsViewDB::GetHeadBlocks() const { | 
| 86 | 51.2k |     std::vector<uint256> vhashHeadBlocks; | 
| 87 | 51.2k |     if (!m_db->Read(DB_HEAD_BLOCKS, vhashHeadBlocks)) { | 
| 88 | 51.2k |         return std::vector<uint256>(); | 
| 89 | 51.2k |     } | 
| 90 | 0 |     return vhashHeadBlocks; | 
| 91 | 51.2k | } | 
| 92 |  |  | 
| 93 | 4.58k | bool CCoinsViewDB::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlock) { | 
| 94 | 4.58k |     CDBBatch batch(*m_db); | 
| 95 | 4.58k |     size_t count = 0; | 
| 96 | 4.58k |     size_t changed = 0; | 
| 97 | 4.58k |     assert(!hashBlock.IsNull()); | 
| 98 |  |  | 
| 99 | 4.58k |     uint256 old_tip = GetBestBlock(); | 
| 100 | 4.58k |     if (old_tip.IsNull()) { | 
| 101 |  |         // We may be in the middle of replaying. | 
| 102 | 0 |         std::vector<uint256> old_heads = GetHeadBlocks(); | 
| 103 | 0 |         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"); | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
| 106 | 0 |             } | 
| 107 | 0 |             assert(old_heads[0] == hashBlock); | 
| 108 | 0 |             old_tip = old_heads[1]; | 
| 109 | 0 |         } | 
| 110 | 0 |     } | 
| 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 | 4.58k |     batch.Erase(DB_BEST_BLOCK); | 
| 117 | 4.58k |     batch.Write(DB_HEAD_BLOCKS, Vector(hashBlock, old_tip)); | 
| 118 |  |  | 
| 119 | 22.1k |     for (auto it{cursor.Begin()}; it != cursor.End();) { | 
| 120 | 17.5k |         if (it->second.IsDirty()) { | 
| 121 | 17.5k |             CoinEntry entry(&it->first); | 
| 122 | 17.5k |             if (it->second.coin.IsSpent()) { | 
| 123 | 7.36k |                 batch.Erase(entry); | 
| 124 | 10.1k |             } else { | 
| 125 | 10.1k |                 batch.Write(entry, it->second.coin); | 
| 126 | 10.1k |             } | 
| 127 |  |  | 
| 128 | 17.5k |             changed++; | 
| 129 | 17.5k |         } | 
| 130 | 17.5k |         count++; | 
| 131 | 17.5k |         it = cursor.NextAndMaybeErase(*it); | 
| 132 | 17.5k |         if (batch.ApproximateSize() > m_options.batch_write_bytes) { | 
| 133 | 0 |             LogDebug(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.ApproximateSize() * (1.0 / 1048576.0)); | Line | Count | Source |  | 381 | 0 | #define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__) | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
 | 
| 134 |  | 
 | 
| 135 | 0 |             m_db->WriteBatch(batch); | 
| 136 | 0 |             batch.Clear(); | 
| 137 | 0 |             if (m_options.simulate_crash_ratio) { | 
| 138 | 0 |                 static FastRandomContext rng; | 
| 139 | 0 |                 if (rng.randrange(m_options.simulate_crash_ratio) == 0) { | 
| 140 | 0 |                     LogPrintf("Simulating a crash. Goodbye.\n");| Line | Count | Source |  | 361 | 0 | #define LogPrintf(...) LogInfo(__VA_ARGS__) | Line | Count | Source |  | 356 | 0 | #define LogInfo(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Info, /*should_ratelimit=*/true, __VA_ARGS__) | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 | 
 | 
 | 
| 141 | 0 |                     _Exit(0); | 
| 142 | 0 |                 } | 
| 143 | 0 |             } | 
| 144 | 0 |         } | 
| 145 | 17.5k |     } | 
| 146 |  |  | 
| 147 |  |     // In the last batch, mark the database as consistent with hashBlock again. | 
| 148 | 4.58k |     batch.Erase(DB_HEAD_BLOCKS); | 
| 149 | 4.58k |     batch.Write(DB_BEST_BLOCK, hashBlock); | 
| 150 |  |  | 
| 151 | 4.58k |     LogDebug(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.ApproximateSize() * (1.0 / 1048576.0)); | Line | Count | Source |  | 381 | 4.58k | #define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__) | Line | Count | Source |  | 373 | 4.58k |     do {                                                              \ |  | 374 | 4.58k |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 4.58k |     } while (0) | 
 | 
 | 
| 152 | 4.58k |     bool ret = m_db->WriteBatch(batch); | 
| 153 | 4.58k |     LogDebug(BCLog::COINDB, "Committed %u changed transaction outputs (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count); | Line | Count | Source |  | 381 | 4.58k | #define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__) | Line | Count | Source |  | 373 | 4.58k |     do {                                                              \ |  | 374 | 4.58k |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 4.58k |     } while (0) | 
 | 
 | 
| 154 | 4.58k |     return ret; | 
| 155 | 4.58k | } | 
| 156 |  |  | 
| 157 |  | size_t CCoinsViewDB::EstimateSize() const | 
| 158 | 0 | { | 
| 159 | 0 |     return m_db->EstimateSize(DB_COIN, uint8_t(DB_COIN + 1)); | 
| 160 | 0 | } | 
| 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 | 0 |         CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {} | 
| 170 | 0 |     ~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 | 0 | { | 
| 187 | 0 |     auto i = std::make_unique<CCoinsViewDBCursor>( | 
| 188 | 0 |         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 | 0 |     i->pcursor->Seek(DB_COIN); | 
| 193 |  |     // Cache key of first record | 
| 194 | 0 |     if (i->pcursor->Valid()) { | 
| 195 | 0 |         CoinEntry entry(&i->keyTmp.second); | 
| 196 | 0 |         i->pcursor->GetKey(entry); | 
| 197 | 0 |         i->keyTmp.first = entry.key; | 
| 198 | 0 |     } else { | 
| 199 | 0 |         i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false | 
| 200 | 0 |     } | 
| 201 | 0 |     return i; | 
| 202 | 0 | } | 
| 203 |  |  | 
| 204 |  | bool CCoinsViewDBCursor::GetKey(COutPoint &key) const | 
| 205 | 0 | { | 
| 206 |  |     // Return cached key | 
| 207 | 0 |     if (keyTmp.first == DB_COIN) { | 
| 208 | 0 |         key = keyTmp.second; | 
| 209 | 0 |         return true; | 
| 210 | 0 |     } | 
| 211 | 0 |     return false; | 
| 212 | 0 | } | 
| 213 |  |  | 
| 214 |  | bool CCoinsViewDBCursor::GetValue(Coin &coin) const | 
| 215 | 0 | { | 
| 216 | 0 |     return pcursor->GetValue(coin); | 
| 217 | 0 | } | 
| 218 |  |  | 
| 219 |  | bool CCoinsViewDBCursor::Valid() const | 
| 220 | 0 | { | 
| 221 | 0 |     return keyTmp.first == DB_COIN; | 
| 222 | 0 | } | 
| 223 |  |  | 
| 224 |  | void CCoinsViewDBCursor::Next() | 
| 225 | 0 | { | 
| 226 | 0 |     pcursor->Next(); | 
| 227 | 0 |     CoinEntry entry(&keyTmp.second); | 
| 228 | 0 |     if (!pcursor->Valid() || !pcursor->GetKey(entry)) { | 
| 229 | 0 |         keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false | 
| 230 | 0 |     } else { | 
| 231 | 0 |         keyTmp.first = entry.key; | 
| 232 | 0 |     } | 
| 233 | 0 | } |