Coverage Report

Created: 2026-09-14 20:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/tmp/bitcoin/src/txdb.cpp
Line
Count
Source
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/byte_units.h>
16
#include <util/log.h>
17
#include <util/threadnames.h>
18
#include <util/vector.h>
19
20
#include <cassert>
21
#include <chrono>
22
#include <cstdlib>
23
#include <exception>
24
#include <future>
25
#include <iterator>
26
#include <utility>
27
28
static constexpr uint8_t DB_COIN{'C'};
29
static constexpr uint8_t DB_BEST_BLOCK{'B'};
30
static constexpr uint8_t DB_HEAD_BLOCKS{'H'};
31
// Keys used in previous version that might still be found in the DB:
32
static constexpr uint8_t DB_COINS{'c'};
33
34
// Threshold for warning when writing this many dirty cache entries to disk.
35
static constexpr size_t WARN_FLUSH_COINS_COUNT{10'000'000};
36
37
bool CCoinsViewDB::NeedsUpgrade()
38
1.27k
{
39
1.27k
    std::unique_ptr<CDBIterator> cursor{m_db->NewIterator()};
40
    // DB_COINS was deprecated in v0.15.0, commit
41
    // 1088b02f0ccd7358d2b7076bb9e122d59d502d02
42
1.27k
    cursor->Seek(std::make_pair(DB_COINS, uint256{}));
43
1.27k
    return cursor->Valid();
44
1.27k
}
45
46
namespace {
47
48
struct CoinEntry {
49
    COutPoint* outpoint;
50
    uint8_t key{DB_COIN};
51
7.70M
    explicit CoinEntry(const COutPoint* ptr) : outpoint(const_cast<COutPoint*>(ptr)) {}
52
53
7.70M
    SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint->hash, VARINT(obj.outpoint->n)); }
txdb.cpp:void (anonymous namespace)::CoinEntry::SerializationOps<DataStream, (anonymous namespace)::CoinEntry const, ActionSerialize>((anonymous namespace)::CoinEntry const&, DataStream&, ActionSerialize)
Line
Count
Source
53
7.42M
    SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint->hash, VARINT(obj.outpoint->n)); }
txdb.cpp:void (anonymous namespace)::CoinEntry::SerializationOps<SpanReader, (anonymous namespace)::CoinEntry, ActionUnserialize>((anonymous namespace)::CoinEntry&, SpanReader&, ActionUnserialize)
Line
Count
Source
53
278k
    SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint->hash, VARINT(obj.outpoint->n)); }
54
};
55
56
} // namespace
57
58
CCoinsViewDB::CCoinsViewDB(DBParams db_params, CoinsViewOptions options) :
59
1.34k
    m_db_params{std::move(db_params)},
60
1.34k
    m_options{std::move(options)},
61
1.34k
    m_db{std::make_unique<CDBWrapper>(m_db_params)} { }
62
63
CCoinsViewDB::~CCoinsViewDB()
64
1.34k
{
65
1.34k
    if (m_compaction.valid()) {
66
5
        if (m_compaction.wait_for(std::chrono::seconds{0}) != std::future_status::ready) {
67
0
            LogInfo("Waiting for background chainstate compaction of %s", fs::PathToString(m_db_params.path));
68
0
        }
69
5
        m_compaction.wait();
70
5
    }
71
1.34k
}
72
73
void CCoinsViewDB::ResizeCache(size_t new_cache_size)
74
127
{
75
    // We can't do this operation with an in-memory DB since we'll lose all the coins upon
76
    // reset.
77
127
    if (!m_db_params.memory_only) {
78
118
        LOCK(m_db_mutex);
79
        // Have to do a reset first to get the original `m_db` state to release its
80
        // filesystem lock.
81
118
        m_db.reset();
82
118
        m_db_params.cache_bytes = new_cache_size;
83
118
        m_db_params.wipe_data = false;
84
118
        m_db = std::make_unique<CDBWrapper>(m_db_params);
85
118
    }
86
127
}
87
88
std::optional<Coin> CCoinsViewDB::GetCoin(const COutPoint& outpoint) const
89
7.10M
{
90
7.10M
    Coin coin;
91
7.10M
    const CDBWrapper::ReadStatus res = m_db->TryRead(CoinEntry(&outpoint), coin);
92
7.10M
    if (!res) {
93
        // Propagate errors so CCoinsViewErrorCatcher triggers a clean shutdown.
94
0
        switch (const auto& [err_code, err_msg] = res.error(); err_code) {
95
0
            case CDBWrapper::ReadFailure::Code::DeserializationError:
96
0
                throw dbwrapper_error{strprintf("Coin deserialization failure: %s", err_msg)};
97
0
            case CDBWrapper::ReadFailure::Code::DatabaseError:
98
0
                throw dbwrapper_error{strprintf("Coin DB read failure: %s", err_msg)};
99
0
        } // no default case, so the compiler can warn about missing cases
100
0
        std::abort(); // unreachable
101
0
    }
102
103
    // Check whether the coin exists
104
7.10M
    if (!res.value()) return std::nullopt;
105
    // Coin found, ensure UTXO database never contains spent coins
106
89.5k
    Assert(!coin.IsSpent());
107
89.5k
    return coin;
108
7.10M
}
109
110
std::optional<Coin> CCoinsViewDB::PeekCoin(const COutPoint& outpoint) const
111
391k
{
112
391k
    return GetCoin(outpoint);
113
391k
}
114
115
bool CCoinsViewDB::HaveCoin(const COutPoint& outpoint) const
116
44
{
117
44
    return m_db->Exists(CoinEntry(&outpoint));
118
44
}
119
120
8.05k
uint256 CCoinsViewDB::GetBestBlock() const {
121
8.05k
    uint256 hashBestChain;
122
8.05k
    if (!m_db->Read(DB_BEST_BLOCK, hashBestChain))
123
1.83k
        return uint256();
124
6.22k
    return hashBestChain;
125
8.05k
}
126
127
1.64k
std::vector<uint256> CCoinsViewDB::GetHeadBlocks() const {
128
1.64k
    std::vector<uint256> vhashHeadBlocks;
129
1.64k
    if (!m_db->Read(DB_HEAD_BLOCKS, vhashHeadBlocks)) {
130
1.64k
        return std::vector<uint256>();
131
1.64k
    }
132
0
    return vhashHeadBlocks;
133
1.64k
}
134
135
void CCoinsViewDB::BatchWrite(CoinsViewCacheCursor& cursor, const uint256& block_hash)
136
3.89k
{
137
3.89k
    CDBBatch batch(*m_db);
138
3.89k
    size_t count = 0;
139
3.89k
    const size_t dirty_count{cursor.GetDirtyCount()};
140
3.89k
    assert(!block_hash.IsNull());
141
142
3.89k
    uint256 old_tip = GetBestBlock();
143
3.89k
    if (old_tip.IsNull()) {
144
        // We may be in the middle of replaying.
145
368
        std::vector<uint256> old_heads = GetHeadBlocks();
146
368
        if (old_heads.size() == 2) {
147
0
            if (old_heads[0] != block_hash) {
148
0
                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");
149
0
            }
150
0
            assert(old_heads[0] == block_hash);
151
0
            old_tip = old_heads[1];
152
0
        }
153
368
    }
154
155
3.89k
    if (dirty_count > WARN_FLUSH_COINS_COUNT) LogWarning("Flushing large (%d entries) UTXO set to disk, it may take several minutes", dirty_count);
156
3.89k
    LOG_TIME_MILLIS_WITH_CATEGORY(strprintf("write coins cache to disk (%d out of %d cached coins)",
157
3.89k
        dirty_count, cursor.GetTotalCount()), BCLog::BENCH);
158
159
    // In the first batch, mark the database as being in the middle of a
160
    // transition from old_tip to block_hash.
161
    // A vector is used for future extensibility, as we may want to support
162
    // interrupting after partial writes from multiple independent reorgs.
163
3.89k
    batch.Erase(DB_BEST_BLOCK);
164
3.89k
    batch.Write(DB_HEAD_BLOCKS, Vector(block_hash, old_tip));
165
166
326k
    for (auto it{cursor.Begin()}; it != cursor.End();) {
167
322k
        if (it->second.IsDirty()) {
168
322k
            CoinEntry entry(&it->first);
169
322k
            if (it->second.coin.IsSpent()) {
170
33.1k
                batch.Erase(entry);
171
289k
            } else {
172
289k
                batch.Write(entry, it->second.coin);
173
289k
            }
174
322k
        }
175
322k
        count++;
176
322k
        it = cursor.NextAndMaybeErase(*it);
177
322k
        if (batch.ApproximateSize() > m_options.batch_write_bytes) {
178
0
            LogDebug(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.ApproximateSize() / double(1_MiB));
179
180
0
            m_db->WriteBatch(batch);
181
0
            batch.Clear();
182
0
            if (m_options.simulate_crash_ratio) {
183
0
                static FastRandomContext rng;
184
0
                if (rng.randrange(m_options.simulate_crash_ratio) == 0) {
185
0
                    LogError("Simulating a crash. Goodbye.");
186
0
                    _Exit(0);
187
0
                }
188
0
            }
189
0
        }
190
322k
    }
191
192
    // In the last batch, mark the database as consistent with block_hash again.
193
3.89k
    batch.Erase(DB_HEAD_BLOCKS);
194
3.89k
    batch.Write(DB_BEST_BLOCK, block_hash);
195
196
3.89k
    LogDebug(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.ApproximateSize() / double(1_MiB));
197
3.89k
    m_db->WriteBatch(batch);
198
3.89k
    LogDebug(BCLog::COINDB, "Committed %u changed transaction outputs (out of %u) to coin database...", (unsigned int)dirty_count, (unsigned int)count);
199
3.89k
}
200
201
size_t CCoinsViewDB::EstimateSize() const
202
102
{
203
102
    return m_db->EstimateSize(DB_COIN, uint8_t(DB_COIN + 1));
204
102
}
205
206
std::optional<std::string> CCoinsViewDB::GetDBProperty(const std::string& property)
207
2
{
208
2
    return m_db->GetProperty(property);
209
2
}
210
211
std::shared_future<void> CCoinsViewDB::CompactFullAsync()
212
5
{
213
5
    AssertLockHeld(::cs_main);
214
5
    if (m_compaction.valid() && m_compaction.wait_for(std::chrono::seconds{0}) != std::future_status::ready) return m_compaction;
215
5
    m_compaction = std::async(std::launch::async, [this] {
216
5
        try {
217
5
            util::ThreadRename("utxocompact");
218
5
            LOCK(m_db_mutex);
219
220
5
            LogDebug(BCLog::COINDB, "Starting chainstate compaction of %s", fs::PathToString(m_db_params.path));
221
5
            m_db->CompactFull();
222
5
            LogDebug(BCLog::COINDB, "Finished chainstate compaction of %s", fs::PathToString(m_db_params.path));
223
5
        } catch (const std::exception& e) {
224
0
            LogWarning("Failed chainstate compaction (%s)", e.what());
225
0
        }
226
5
    }).share();
227
5
    return m_compaction;
228
5
}
229
230
/** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */
231
class CCoinsViewDBCursor: public CCoinsViewCursor
232
{
233
public:
234
    // Prefer using CCoinsViewDB::Cursor() since we want to perform some
235
    // cache warmup on instantiation.
236
    CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256& in_block_hash):
237
1.26k
        CCoinsViewCursor(in_block_hash), pcursor(pcursorIn) {}
238
1.26k
    ~CCoinsViewDBCursor() = default;
239
240
    bool GetKey(COutPoint &key) const override;
241
    bool GetValue(Coin &coin) const override;
242
243
    bool Valid() const override;
244
    void Next() override;
245
246
private:
247
    std::unique_ptr<CDBIterator> pcursor;
248
    std::pair<char, COutPoint> keyTmp;
249
250
    friend class CCoinsViewDB;
251
};
252
253
std::unique_ptr<CCoinsViewCursor> CCoinsViewDB::Cursor() const
254
1.26k
{
255
1.26k
    auto i = std::make_unique<CCoinsViewDBCursor>(
256
1.26k
        const_cast<CDBWrapper&>(*m_db).NewIterator(), GetBestBlock());
257
    /* It seems that there are no "const iterators" for LevelDB.  Since we
258
       only need read operations on it, use a const-cast to get around
259
       that restriction.  */
260
1.26k
    i->pcursor->Seek(DB_COIN);
261
    // Cache key of first record
262
1.26k
    if (i->pcursor->Valid()) {
263
1.23k
        CoinEntry entry(&i->keyTmp.second);
264
1.23k
        i->pcursor->GetKey(entry);
265
1.23k
        i->keyTmp.first = entry.key;
266
1.23k
    } else {
267
25
        i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false
268
25
    }
269
1.26k
    return i;
270
1.26k
}
271
272
bool CCoinsViewDBCursor::GetKey(COutPoint &key) const
273
278k
{
274
    // Return cached key
275
278k
    if (keyTmp.first == DB_COIN) {
276
278k
        key = keyTmp.second;
277
278k
        return true;
278
278k
    }
279
0
    return false;
280
278k
}
281
282
bool CCoinsViewDBCursor::GetValue(Coin &coin) const
283
278k
{
284
278k
    return pcursor->GetValue(coin);
285
278k
}
286
287
bool CCoinsViewDBCursor::Valid() const
288
280k
{
289
280k
    return keyTmp.first == DB_COIN;
290
280k
}
291
292
void CCoinsViewDBCursor::Next()
293
278k
{
294
278k
    pcursor->Next();
295
278k
    CoinEntry entry(&keyTmp.second);
296
278k
    if (!pcursor->Valid() || !pcursor->GetKey(entry)) {
297
1.23k
        keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false
298
277k
    } else {
299
277k
        keyTmp.first = entry.key;
300
277k
    }
301
278k
}