Coverage Report

Created: 2026-04-29 19:21

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/vector.h>
18
19
#include <cassert>
20
#include <cstdlib>
21
#include <iterator>
22
#include <utility>
23
24
static constexpr uint8_t DB_COIN{'C'};
25
static constexpr uint8_t DB_BEST_BLOCK{'B'};
26
static constexpr uint8_t DB_HEAD_BLOCKS{'H'};
27
// Keys used in previous version that might still be found in the DB:
28
static constexpr uint8_t DB_COINS{'c'};
29
30
// Threshold for warning when writing this many dirty cache entries to disk.
31
static constexpr size_t WARN_FLUSH_COINS_COUNT{10'000'000};
32
33
bool CCoinsViewDB::NeedsUpgrade()
34
1.17k
{
35
1.17k
    std::unique_ptr<CDBIterator> cursor{m_db->NewIterator()};
36
    // DB_COINS was deprecated in v0.15.0, commit
37
    // 1088b02f0ccd7358d2b7076bb9e122d59d502d02
38
1.17k
    cursor->Seek(std::make_pair(DB_COINS, uint256{}));
39
1.17k
    return cursor->Valid();
40
1.17k
}
41
42
namespace {
43
44
struct CoinEntry {
45
    COutPoint* outpoint;
46
    uint8_t key{DB_COIN};
47
7.24M
    explicit CoinEntry(const COutPoint* ptr) : outpoint(const_cast<COutPoint*>(ptr)) {}
48
49
7.24M
    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
49
7.02M
    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
49
221k
    SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint->hash, VARINT(obj.outpoint->n)); }
50
};
51
52
} // namespace
53
54
CCoinsViewDB::CCoinsViewDB(DBParams db_params, CoinsViewOptions options) :
55
1.23k
    m_db_params{std::move(db_params)},
56
1.23k
    m_options{std::move(options)},
57
1.23k
    m_db{std::make_unique<CDBWrapper>(m_db_params)} { }
58
59
void CCoinsViewDB::ResizeCache(size_t new_cache_size)
60
126
{
61
    // We can't do this operation with an in-memory DB since we'll lose all the coins upon
62
    // reset.
63
126
    if (!m_db_params.memory_only) {
64
        // Have to do a reset first to get the original `m_db` state to release its
65
        // filesystem lock.
66
118
        m_db.reset();
67
118
        m_db_params.cache_bytes = new_cache_size;
68
118
        m_db_params.wipe_data = false;
69
118
        m_db = std::make_unique<CDBWrapper>(m_db_params);
70
118
    }
71
126
}
72
73
std::optional<Coin> CCoinsViewDB::GetCoin(const COutPoint& outpoint) const
74
6.71M
{
75
6.71M
    if (Coin coin; m_db->Read(CoinEntry(&outpoint), coin)) {
76
83.5k
        Assert(!coin.IsSpent()); // The UTXO database should never contain spent coins
77
83.5k
        return coin;
78
83.5k
    }
79
6.63M
    return std::nullopt;
80
6.71M
}
81
82
std::optional<Coin> CCoinsViewDB::PeekCoin(const COutPoint& outpoint) const
83
382k
{
84
382k
    return GetCoin(outpoint);
85
382k
}
86
87
bool CCoinsViewDB::HaveCoin(const COutPoint& outpoint) const
88
44
{
89
44
    return m_db->Exists(CoinEntry(&outpoint));
90
44
}
91
92
7.54k
uint256 CCoinsViewDB::GetBestBlock() const {
93
7.54k
    uint256 hashBestChain;
94
7.54k
    if (!m_db->Read(DB_BEST_BLOCK, hashBestChain))
95
1.66k
        return uint256();
96
5.87k
    return hashBestChain;
97
7.54k
}
98
99
1.52k
std::vector<uint256> CCoinsViewDB::GetHeadBlocks() const {
100
1.52k
    std::vector<uint256> vhashHeadBlocks;
101
1.52k
    if (!m_db->Read(DB_HEAD_BLOCKS, vhashHeadBlocks)) {
102
1.52k
        return std::vector<uint256>();
103
1.52k
    }
104
0
    return vhashHeadBlocks;
105
1.52k
}
106
107
void CCoinsViewDB::BatchWrite(CoinsViewCacheCursor& cursor, const uint256& block_hash)
108
3.67k
{
109
3.67k
    CDBBatch batch(*m_db);
110
3.67k
    size_t count = 0;
111
3.67k
    const size_t dirty_count{cursor.GetDirtyCount()};
112
3.67k
    assert(!block_hash.IsNull());
113
114
3.67k
    uint256 old_tip = GetBestBlock();
115
3.67k
    if (old_tip.IsNull()) {
116
        // We may be in the middle of replaying.
117
345
        std::vector<uint256> old_heads = GetHeadBlocks();
118
345
        if (old_heads.size() == 2) {
119
0
            if (old_heads[0] != block_hash) {
120
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");
121
0
            }
122
0
            assert(old_heads[0] == block_hash);
123
0
            old_tip = old_heads[1];
124
0
        }
125
345
    }
126
127
3.67k
    if (dirty_count > WARN_FLUSH_COINS_COUNT) LogWarning("Flushing large (%d entries) UTXO set to disk, it may take several minutes", dirty_count);
128
3.67k
    LOG_TIME_MILLIS_WITH_CATEGORY(strprintf("write coins cache to disk (%d out of %d cached coins)",
129
3.67k
        dirty_count, cursor.GetTotalCount()), BCLog::BENCH);
130
131
    // In the first batch, mark the database as being in the middle of a
132
    // transition from old_tip to block_hash.
133
    // A vector is used for future extensibility, as we may want to support
134
    // interrupting after partial writes from multiple independent reorgs.
135
3.67k
    batch.Erase(DB_BEST_BLOCK);
136
3.67k
    batch.Write(DB_HEAD_BLOCKS, Vector(block_hash, old_tip));
137
138
309k
    for (auto it{cursor.Begin()}; it != cursor.End();) {
139
306k
        if (it->second.IsDirty()) {
140
306k
            CoinEntry entry(&it->first);
141
306k
            if (it->second.coin.IsSpent()) {
142
33.4k
                batch.Erase(entry);
143
272k
            } else {
144
272k
                batch.Write(entry, it->second.coin);
145
272k
            }
146
306k
        }
147
306k
        count++;
148
306k
        it = cursor.NextAndMaybeErase(*it);
149
306k
        if (batch.ApproximateSize() > m_options.batch_write_bytes) {
150
0
            LogDebug(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.ApproximateSize() / double(1_MiB));
151
152
0
            m_db->WriteBatch(batch);
153
0
            batch.Clear();
154
0
            if (m_options.simulate_crash_ratio) {
155
0
                static FastRandomContext rng;
156
0
                if (rng.randrange(m_options.simulate_crash_ratio) == 0) {
157
0
                    LogError("Simulating a crash. Goodbye.");
158
0
                    _Exit(0);
159
0
                }
160
0
            }
161
0
        }
162
306k
    }
163
164
    // In the last batch, mark the database as consistent with block_hash again.
165
3.67k
    batch.Erase(DB_HEAD_BLOCKS);
166
3.67k
    batch.Write(DB_BEST_BLOCK, block_hash);
167
168
3.67k
    LogDebug(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.ApproximateSize() / double(1_MiB));
169
3.67k
    m_db->WriteBatch(batch);
170
3.67k
    LogDebug(BCLog::COINDB, "Committed %u changed transaction outputs (out of %u) to coin database...", (unsigned int)dirty_count, (unsigned int)count);
171
3.67k
}
172
173
size_t CCoinsViewDB::EstimateSize() const
174
102
{
175
102
    return m_db->EstimateSize(DB_COIN, uint8_t(DB_COIN + 1));
176
102
}
177
178
/** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */
179
class CCoinsViewDBCursor: public CCoinsViewCursor
180
{
181
public:
182
    // Prefer using CCoinsViewDB::Cursor() since we want to perform some
183
    // cache warmup on instantiation.
184
    CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256& in_block_hash):
185
1.22k
        CCoinsViewCursor(in_block_hash), pcursor(pcursorIn) {}
186
1.22k
    ~CCoinsViewDBCursor() = default;
187
188
    bool GetKey(COutPoint &key) const override;
189
    bool GetValue(Coin &coin) const override;
190
191
    bool Valid() const override;
192
    void Next() override;
193
194
private:
195
    std::unique_ptr<CDBIterator> pcursor;
196
    std::pair<char, COutPoint> keyTmp;
197
198
    friend class CCoinsViewDB;
199
};
200
201
std::unique_ptr<CCoinsViewCursor> CCoinsViewDB::Cursor() const
202
1.22k
{
203
1.22k
    auto i = std::make_unique<CCoinsViewDBCursor>(
204
1.22k
        const_cast<CDBWrapper&>(*m_db).NewIterator(), GetBestBlock());
205
    /* It seems that there are no "const iterators" for LevelDB.  Since we
206
       only need read operations on it, use a const-cast to get around
207
       that restriction.  */
208
1.22k
    i->pcursor->Seek(DB_COIN);
209
    // Cache key of first record
210
1.22k
    if (i->pcursor->Valid()) {
211
1.19k
        CoinEntry entry(&i->keyTmp.second);
212
1.19k
        i->pcursor->GetKey(entry);
213
1.19k
        i->keyTmp.first = entry.key;
214
1.19k
    } else {
215
25
        i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false
216
25
    }
217
1.22k
    return i;
218
1.22k
}
219
220
bool CCoinsViewDBCursor::GetKey(COutPoint &key) const
221
221k
{
222
    // Return cached key
223
221k
    if (keyTmp.first == DB_COIN) {
224
221k
        key = keyTmp.second;
225
221k
        return true;
226
221k
    }
227
0
    return false;
228
221k
}
229
230
bool CCoinsViewDBCursor::GetValue(Coin &coin) const
231
221k
{
232
221k
    return pcursor->GetValue(coin);
233
221k
}
234
235
bool CCoinsViewDBCursor::Valid() const
236
222k
{
237
222k
    return keyTmp.first == DB_COIN;
238
222k
}
239
240
void CCoinsViewDBCursor::Next()
241
221k
{
242
221k
    pcursor->Next();
243
221k
    CoinEntry entry(&keyTmp.second);
244
221k
    if (!pcursor->Valid() || !pcursor->GetKey(entry)) {
245
1.19k
        keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false
246
220k
    } else {
247
220k
        keyTmp.first = entry.key;
248
220k
    }
249
221k
}