Coverage Report

Created: 2026-09-01 13:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/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
3.14k
{
39
3.14k
    std::unique_ptr<CDBIterator> cursor{m_db->NewIterator()};
40
    // DB_COINS was deprecated in v0.15.0, commit
41
    // 1088b02f0ccd7358d2b7076bb9e122d59d502d02
42
3.14k
    cursor->Seek(std::make_pair(DB_COINS, uint256{}));
43
3.14k
    return cursor->Valid();
44
3.14k
}
45
46
namespace {
47
48
struct CoinEntry {
49
    COutPoint* outpoint;
50
    uint8_t key{DB_COIN};
51
10.6M
    explicit CoinEntry(const COutPoint* ptr) : outpoint(const_cast<COutPoint*>(ptr)) {}
52
53
10.4M
    SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint->hash, VARINT(obj.outpoint->n)); }
txdb.cpp:_ZN12_GLOBAL__N_19CoinEntry16SerializationOpsI10DataStreamKS0_15ActionSerializeEEvRT0_RT_T1_
Line
Count
Source
53
3.84M
    SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint->hash, VARINT(obj.outpoint->n)); }
txdb.cpp:_ZN12_GLOBAL__N_19CoinEntry16SerializationOpsI10SpanReaderS0_17ActionUnserializeEEvRT0_RT_T1_
Line
Count
Source
53
6.63M
    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
12.0k
    m_db_params{std::move(db_params)},
60
12.0k
    m_options{std::move(options)},
61
12.0k
    m_db{std::make_unique<CDBWrapper>(m_db_params)} { }
62
63
CCoinsViewDB::~CCoinsViewDB()
64
12.1k
{
65
12.1k
    if (m_compaction.valid()) {
  Branch (65:9): [True: 3.21k, False: 8.89k]
66
3.21k
        if (m_compaction.wait_for(std::chrono::seconds{0}) != std::future_status::ready) {
  Branch (66:13): [True: 1.27k, False: 1.94k]
67
1.27k
            LogInfo("Waiting for background chainstate compaction of %s", fs::PathToString(m_db_params.path));
68
1.27k
        }
69
3.21k
        m_compaction.wait();
70
3.21k
    }
71
12.1k
}
72
73
void CCoinsViewDB::ResizeCache(size_t new_cache_size)
74
4.30k
{
75
    // We can't do this operation with an in-memory DB since we'll lose all the coins upon
76
    // reset.
77
4.30k
    if (!m_db_params.memory_only) {
  Branch (77:9): [True: 0, False: 4.30k]
78
0
        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
0
        m_db.reset();
82
0
        m_db_params.cache_bytes = new_cache_size;
83
0
        m_db_params.wipe_data = false;
84
0
        m_db = std::make_unique<CDBWrapper>(m_db_params);
85
0
    }
86
4.30k
}
87
88
std::optional<Coin> CCoinsViewDB::GetCoin(const COutPoint& outpoint) const
89
3.61M
{
90
3.61M
    if (Coin coin; m_db->Read(CoinEntry(&outpoint), coin)) {
  Branch (90:20): [True: 453k, False: 3.16M]
91
453k
        Assert(!coin.IsSpent()); // The UTXO database should never contain spent coins
92
453k
        return coin;
93
453k
    }
94
3.16M
    return std::nullopt;
95
3.61M
}
96
97
std::optional<Coin> CCoinsViewDB::PeekCoin(const COutPoint& outpoint) const
98
1.91M
{
99
1.91M
    return GetCoin(outpoint);
100
1.91M
}
101
102
bool CCoinsViewDB::HaveCoin(const COutPoint& outpoint) const
103
8.70k
{
104
8.70k
    return m_db->Exists(CoinEntry(&outpoint));
105
8.70k
}
106
107
3.30M
uint256 CCoinsViewDB::GetBestBlock() const {
108
3.30M
    uint256 hashBestChain;
109
3.30M
    if (!m_db->Read(DB_BEST_BLOCK, hashBestChain))
  Branch (109:9): [True: 21.3k, False: 3.28M]
110
21.3k
        return uint256();
111
3.28M
    return hashBestChain;
112
3.30M
}
113
114
32.7k
std::vector<uint256> CCoinsViewDB::GetHeadBlocks() const {
115
32.7k
    std::vector<uint256> vhashHeadBlocks;
116
32.7k
    if (!m_db->Read(DB_HEAD_BLOCKS, vhashHeadBlocks)) {
  Branch (116:9): [True: 32.7k, False: 0]
117
32.7k
        return std::vector<uint256>();
118
32.7k
    }
119
0
    return vhashHeadBlocks;
120
32.7k
}
121
122
void CCoinsViewDB::BatchWrite(CoinsViewCacheCursor& cursor, const uint256& block_hash)
123
3.14M
{
124
3.14M
    CDBBatch batch(*m_db);
125
3.14M
    size_t count = 0;
126
3.14M
    const size_t dirty_count{cursor.GetDirtyCount()};
127
3.14M
    assert(!block_hash.IsNull());
  Branch (127:5): [True: 3.14M, False: 0]
128
129
3.14M
    uint256 old_tip = GetBestBlock();
130
3.14M
    if (old_tip.IsNull()) {
  Branch (130:9): [True: 8.27k, False: 3.13M]
131
        // We may be in the middle of replaying.
132
8.27k
        std::vector<uint256> old_heads = GetHeadBlocks();
133
8.27k
        if (old_heads.size() == 2) {
  Branch (133:13): [True: 0, False: 8.27k]
134
0
            if (old_heads[0] != block_hash) {
  Branch (134:17): [True: 0, False: 0]
135
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");
136
0
            }
137
0
            assert(old_heads[0] == block_hash);
  Branch (137:13): [True: 0, False: 0]
138
0
            old_tip = old_heads[1];
139
0
        }
140
8.27k
    }
141
142
3.14M
    if (dirty_count > WARN_FLUSH_COINS_COUNT) LogWarning("Flushing large (%d entries) UTXO set to disk, it may take several minutes", dirty_count);
  Branch (142:9): [True: 0, False: 3.14M]
143
3.14M
    LOG_TIME_MILLIS_WITH_CATEGORY(strprintf("write coins cache to disk (%d out of %d cached coins)",
144
3.14M
        dirty_count, cursor.GetTotalCount()), BCLog::BENCH);
145
146
    // In the first batch, mark the database as being in the middle of a
147
    // transition from old_tip to block_hash.
148
    // A vector is used for future extensibility, as we may want to support
149
    // interrupting after partial writes from multiple independent reorgs.
150
3.14M
    batch.Erase(DB_BEST_BLOCK);
151
3.14M
    batch.Write(DB_HEAD_BLOCKS, Vector(block_hash, old_tip));
152
153
3.37M
    for (auto it{cursor.Begin()}; it != cursor.End();) {
  Branch (153:35): [True: 223k, False: 3.14M]
154
223k
        if (it->second.IsDirty()) {
  Branch (154:13): [True: 223k, False: 0]
155
223k
            CoinEntry entry(&it->first);
156
223k
            if (it->second.coin.IsSpent()) {
  Branch (156:17): [True: 3.65k, False: 220k]
157
3.65k
                batch.Erase(entry);
158
220k
            } else {
159
220k
                batch.Write(entry, it->second.coin);
160
220k
            }
161
223k
        }
162
223k
        count++;
163
223k
        it = cursor.NextAndMaybeErase(*it);
164
223k
        if (batch.ApproximateSize() > m_options.batch_write_bytes) {
  Branch (164:13): [True: 0, False: 223k]
165
0
            LogDebug(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.ApproximateSize() / double(1_MiB));
166
167
0
            m_db->WriteBatch(batch);
168
0
            batch.Clear();
169
0
            if (m_options.simulate_crash_ratio) {
  Branch (169:17): [True: 0, False: 0]
170
0
                static FastRandomContext rng;
171
0
                if (rng.randrange(m_options.simulate_crash_ratio) == 0) {
  Branch (171:21): [True: 0, False: 0]
172
0
                    LogError("Simulating a crash. Goodbye.");
173
0
                    _Exit(0);
174
0
                }
175
0
            }
176
0
        }
177
223k
    }
178
179
    // In the last batch, mark the database as consistent with block_hash again.
180
3.14M
    batch.Erase(DB_HEAD_BLOCKS);
181
3.14M
    batch.Write(DB_BEST_BLOCK, block_hash);
182
183
3.14M
    LogDebug(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.ApproximateSize() / double(1_MiB));
184
3.14M
    m_db->WriteBatch(batch);
185
3.14M
    LogDebug(BCLog::COINDB, "Committed %u changed transaction outputs (out of %u) to coin database...", (unsigned int)dirty_count, (unsigned int)count);
186
3.14M
}
187
188
size_t CCoinsViewDB::EstimateSize() const
189
150k
{
190
150k
    return m_db->EstimateSize(DB_COIN, uint8_t(DB_COIN + 1));
191
150k
}
192
193
std::optional<std::string> CCoinsViewDB::GetDBProperty(const std::string& property)
194
0
{
195
0
    return m_db->GetProperty(property);
196
0
}
197
198
std::shared_future<void> CCoinsViewDB::CompactFullAsync()
199
422k
{
200
422k
    AssertLockHeld(::cs_main);
201
422k
    if (m_compaction.valid() && m_compaction.wait_for(std::chrono::seconds{0}) != std::future_status::ready) return m_compaction;
  Branch (201:9): [True: 419k, False: 3.21k]
  Branch (201:9): [True: 326k, False: 95.6k]
  Branch (201:33): [True: 326k, False: 92.4k]
202
95.6k
    m_compaction = std::async(std::launch::async, [this] {
203
95.6k
        try {
204
95.6k
            util::ThreadRename("utxocompact");
205
95.6k
            LOCK(m_db_mutex);
206
207
95.6k
            LogDebug(BCLog::COINDB, "Starting chainstate compaction of %s", fs::PathToString(m_db_params.path));
208
95.6k
            m_db->CompactFull();
209
95.6k
            LogDebug(BCLog::COINDB, "Finished chainstate compaction of %s", fs::PathToString(m_db_params.path));
210
95.6k
        } catch (const std::exception& e) {
211
0
            LogWarning("Failed chainstate compaction (%s)", e.what());
212
0
        }
213
95.6k
    }).share();
214
95.6k
    return m_compaction;
215
422k
}
216
217
/** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */
218
class CCoinsViewDBCursor: public CCoinsViewCursor
219
{
220
public:
221
    // Prefer using CCoinsViewDB::Cursor() since we want to perform some
222
    // cache warmup on instantiation.
223
    CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256& in_block_hash):
224
137k
        CCoinsViewCursor(in_block_hash), pcursor(pcursorIn) {}
225
137k
    ~CCoinsViewDBCursor() = default;
226
227
    bool GetKey(COutPoint &key) const override;
228
    bool GetValue(Coin &coin) const override;
229
230
    bool Valid() const override;
231
    void Next() override;
232
233
private:
234
    std::unique_ptr<CDBIterator> pcursor;
235
    std::pair<char, COutPoint> keyTmp;
236
237
    friend class CCoinsViewDB;
238
};
239
240
std::unique_ptr<CCoinsViewCursor> CCoinsViewDB::Cursor() const
241
137k
{
242
137k
    auto i = std::make_unique<CCoinsViewDBCursor>(
243
137k
        const_cast<CDBWrapper&>(*m_db).NewIterator(), GetBestBlock());
244
    /* It seems that there are no "const iterators" for LevelDB.  Since we
245
       only need read operations on it, use a const-cast to get around
246
       that restriction.  */
247
137k
    i->pcursor->Seek(DB_COIN);
248
    // Cache key of first record
249
137k
    if (i->pcursor->Valid()) {
  Branch (249:9): [True: 130k, False: 7.12k]
250
130k
        CoinEntry entry(&i->keyTmp.second);
251
130k
        i->pcursor->GetKey(entry);
252
130k
        i->keyTmp.first = entry.key;
253
130k
    } else {
254
7.12k
        i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false
255
7.12k
    }
256
137k
    return i;
257
137k
}
258
259
bool CCoinsViewDBCursor::GetKey(COutPoint &key) const
260
6.63M
{
261
    // Return cached key
262
6.63M
    if (keyTmp.first == DB_COIN) {
  Branch (262:9): [True: 6.63M, False: 0]
263
6.63M
        key = keyTmp.second;
264
6.63M
        return true;
265
6.63M
    }
266
0
    return false;
267
6.63M
}
268
269
bool CCoinsViewDBCursor::GetValue(Coin &coin) const
270
6.63M
{
271
6.63M
    return pcursor->GetValue(coin);
272
6.63M
}
273
274
bool CCoinsViewDBCursor::Valid() const
275
6.76M
{
276
6.76M
    return keyTmp.first == DB_COIN;
277
6.76M
}
278
279
void CCoinsViewDBCursor::Next()
280
6.63M
{
281
6.63M
    pcursor->Next();
282
6.63M
    CoinEntry entry(&keyTmp.second);
283
6.63M
    if (!pcursor->Valid() || !pcursor->GetKey(entry)) {
  Branch (283:9): [True: 127k, False: 6.50M]
  Branch (283:30): [True: 0, False: 6.50M]
284
127k
        keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false
285
6.50M
    } else {
286
6.50M
        keyTmp.first = entry.key;
287
6.50M
    }
288
6.63M
}