Bitcoin Core Fuzz Coverage Report for wallet_tx_can_be_bumped

Coverage Report

Created: 2025-11-19 11:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/Users/brunogarcia/projects/bitcoin-core-dev/src/index/coinstatsindex.cpp
Line
Count
Source
1
// Copyright (c) 2020-2022 The Bitcoin Core developers
2
// Distributed under the MIT software license, see the accompanying
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5
#include <index/coinstatsindex.h>
6
7
#include <arith_uint256.h>
8
#include <chain.h>
9
#include <chainparams.h>
10
#include <coins.h>
11
#include <common/args.h>
12
#include <consensus/amount.h>
13
#include <crypto/muhash.h>
14
#include <dbwrapper.h>
15
#include <index/base.h>
16
#include <interfaces/chain.h>
17
#include <interfaces/types.h>
18
#include <kernel/coinstats.h>
19
#include <logging.h>
20
#include <primitives/block.h>
21
#include <primitives/transaction.h>
22
#include <script/script.h>
23
#include <serialize.h>
24
#include <uint256.h>
25
#include <undo.h>
26
#include <util/check.h>
27
#include <util/fs.h>
28
#include <validation.h>
29
30
#include <compare>
31
#include <ios>
32
#include <limits>
33
#include <span>
34
#include <string>
35
#include <utility>
36
#include <vector>
37
38
using kernel::ApplyCoinHash;
39
using kernel::CCoinsStats;
40
using kernel::GetBogoSize;
41
using kernel::RemoveCoinHash;
42
43
static constexpr uint8_t DB_BLOCK_HASH{'s'};
44
static constexpr uint8_t DB_BLOCK_HEIGHT{'t'};
45
static constexpr uint8_t DB_MUHASH{'M'};
46
47
namespace {
48
49
struct DBVal {
50
    uint256 muhash{uint256::ZERO};
51
    uint64_t transaction_output_count{0};
52
    uint64_t bogo_size{0};
53
    CAmount total_amount{0};
54
    CAmount total_subsidy{0};
55
    arith_uint256 total_prevout_spent_amount{0};
56
    arith_uint256 total_new_outputs_ex_coinbase_amount{0};
57
    arith_uint256 total_coinbase_amount{0};
58
    CAmount total_unspendables_genesis_block{0};
59
    CAmount total_unspendables_bip30{0};
60
    CAmount total_unspendables_scripts{0};
61
    CAmount total_unspendables_unclaimed_rewards{0};
62
63
    SERIALIZE_METHODS(DBVal, obj)
64
0
    {
65
0
        uint256 prevout_spent, new_outputs, coinbase;
66
0
        SER_WRITE(obj, prevout_spent = ArithToUint256(obj.total_prevout_spent_amount));
Line
Count
Source
147
0
#define SER_WRITE(obj, code) ser_action.SerWrite(s, obj, [&](Stream& s, const Type& obj) { code; })
        SER_WRITE(obj, prevout_spent = ArithToUint256(obj.total_prevout_spent_amount));
Line
Count
Source
147
0
#define SER_WRITE(obj, code) ser_action.SerWrite(s, obj, [&](Stream& s, const Type& obj) { code; })
67
0
        SER_WRITE(obj, new_outputs = ArithToUint256(obj.total_new_outputs_ex_coinbase_amount));
Line
Count
Source
147
0
#define SER_WRITE(obj, code) ser_action.SerWrite(s, obj, [&](Stream& s, const Type& obj) { code; })
        SER_WRITE(obj, new_outputs = ArithToUint256(obj.total_new_outputs_ex_coinbase_amount));
Line
Count
Source
147
0
#define SER_WRITE(obj, code) ser_action.SerWrite(s, obj, [&](Stream& s, const Type& obj) { code; })
68
0
        SER_WRITE(obj, coinbase = ArithToUint256(obj.total_coinbase_amount));
Line
Count
Source
147
0
#define SER_WRITE(obj, code) ser_action.SerWrite(s, obj, [&](Stream& s, const Type& obj) { code; })
        SER_WRITE(obj, coinbase = ArithToUint256(obj.total_coinbase_amount));
Line
Count
Source
147
0
#define SER_WRITE(obj, code) ser_action.SerWrite(s, obj, [&](Stream& s, const Type& obj) { code; })
69
70
0
        READWRITE(obj.muhash);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
        READWRITE(obj.muhash);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
71
0
        READWRITE(obj.transaction_output_count);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
        READWRITE(obj.transaction_output_count);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
72
0
        READWRITE(obj.bogo_size);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
        READWRITE(obj.bogo_size);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
73
0
        READWRITE(obj.total_amount);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
        READWRITE(obj.total_amount);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
74
0
        READWRITE(obj.total_subsidy);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
        READWRITE(obj.total_subsidy);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
75
0
        READWRITE(prevout_spent);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
        READWRITE(prevout_spent);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
76
0
        READWRITE(new_outputs);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
        READWRITE(new_outputs);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
77
0
        READWRITE(coinbase);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
        READWRITE(coinbase);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
78
0
        READWRITE(obj.total_unspendables_genesis_block);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
        READWRITE(obj.total_unspendables_genesis_block);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
79
0
        READWRITE(obj.total_unspendables_bip30);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
        READWRITE(obj.total_unspendables_bip30);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
80
0
        READWRITE(obj.total_unspendables_scripts);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
        READWRITE(obj.total_unspendables_scripts);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
81
0
        READWRITE(obj.total_unspendables_unclaimed_rewards);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
        READWRITE(obj.total_unspendables_unclaimed_rewards);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
82
83
0
        SER_READ(obj, obj.total_prevout_spent_amount = UintToArith256(prevout_spent));
Line
Count
Source
146
0
#define SER_READ(obj, code) ser_action.SerRead(s, obj, [&](Stream& s, std::remove_const_t<Type>& obj) { code; })
        SER_READ(obj, obj.total_prevout_spent_amount = UintToArith256(prevout_spent));
Line
Count
Source
146
0
#define SER_READ(obj, code) ser_action.SerRead(s, obj, [&](Stream& s, std::remove_const_t<Type>& obj) { code; })
84
0
        SER_READ(obj, obj.total_new_outputs_ex_coinbase_amount = UintToArith256(new_outputs));
Line
Count
Source
146
0
#define SER_READ(obj, code) ser_action.SerRead(s, obj, [&](Stream& s, std::remove_const_t<Type>& obj) { code; })
        SER_READ(obj, obj.total_new_outputs_ex_coinbase_amount = UintToArith256(new_outputs));
Line
Count
Source
146
0
#define SER_READ(obj, code) ser_action.SerRead(s, obj, [&](Stream& s, std::remove_const_t<Type>& obj) { code; })
85
0
        SER_READ(obj, obj.total_coinbase_amount = UintToArith256(coinbase));
Line
Count
Source
146
0
#define SER_READ(obj, code) ser_action.SerRead(s, obj, [&](Stream& s, std::remove_const_t<Type>& obj) { code; })
        SER_READ(obj, obj.total_coinbase_amount = UintToArith256(coinbase));
Line
Count
Source
146
0
#define SER_READ(obj, code) ser_action.SerRead(s, obj, [&](Stream& s, std::remove_const_t<Type>& obj) { code; })
86
0
    }
Unexecuted instantiation: coinstatsindex.cpp:void (anonymous namespace)::DBVal::SerializationOps<DataStream, (anonymous namespace)::DBVal, ActionUnserialize>((anonymous namespace)::DBVal&, DataStream&, ActionUnserialize)
Unexecuted instantiation: coinstatsindex.cpp:void (anonymous namespace)::DBVal::SerializationOps<DataStream, (anonymous namespace)::DBVal const, ActionSerialize>((anonymous namespace)::DBVal const&, DataStream&, ActionSerialize)
87
};
88
89
struct DBHeightKey {
90
    int height;
91
92
0
    explicit DBHeightKey(int height_in) : height(height_in) {}
93
94
    template <typename Stream>
95
    void Serialize(Stream& s) const
96
0
    {
97
0
        ser_writedata8(s, DB_BLOCK_HEIGHT);
98
0
        ser_writedata32be(s, height);
99
0
    }
100
101
    template <typename Stream>
102
    void Unserialize(Stream& s)
103
0
    {
104
0
        const uint8_t prefix{ser_readdata8(s)};
105
0
        if (prefix != DB_BLOCK_HEIGHT) {
106
0
            throw std::ios_base::failure("Invalid format for coinstatsindex DB height key");
107
0
        }
108
0
        height = ser_readdata32be(s);
109
0
    }
110
};
111
112
struct DBHashKey {
113
    uint256 block_hash;
114
115
0
    explicit DBHashKey(const uint256& hash_in) : block_hash(hash_in) {}
116
117
    SERIALIZE_METHODS(DBHashKey, obj)
118
0
    {
119
0
        uint8_t prefix{DB_BLOCK_HASH};
120
0
        READWRITE(prefix);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
121
0
        if (prefix != DB_BLOCK_HASH) {
122
0
            throw std::ios_base::failure("Invalid format for coinstatsindex DB hash key");
123
0
        }
124
125
0
        READWRITE(obj.block_hash);
Line
Count
Source
145
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
126
0
    }
127
};
128
129
}; // namespace
130
131
std::unique_ptr<CoinStatsIndex> g_coin_stats_index;
132
133
CoinStatsIndex::CoinStatsIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe)
134
0
    : BaseIndex(std::move(chain), "coinstatsindex")
135
0
{
136
    // An earlier version of the index used "indexes/coinstats" but it contained
137
    // a bug and is superseded by a fixed version at "indexes/coinstatsindex".
138
    // The original index is kept around until the next release in case users
139
    // decide to downgrade their node.
140
0
    auto old_path = gArgs.GetDataDirNet() / "indexes" / "coinstats";
141
0
    if (fs::exists(old_path)) {
142
        // TODO: Change this to deleting the old index with v31.
143
0
        LogWarning("Old version of coinstatsindex found at %s. This folder can be safely deleted unless you " \
Line
Count
Source
369
0
#define LogWarning(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Warning, /*should_ratelimit=*/true, __VA_ARGS__)
Line
Count
Source
362
0
#define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__)
144
0
            "plan to downgrade your node to version 29 or lower.", fs::PathToString(old_path));
145
0
    }
146
0
    fs::path path{gArgs.GetDataDirNet() / "indexes" / "coinstatsindex"};
147
0
    fs::create_directories(path);
148
149
0
    m_db = std::make_unique<CoinStatsIndex::DB>(path / "db", n_cache_size, f_memory, f_wipe);
150
0
}
151
152
bool CoinStatsIndex::CustomAppend(const interfaces::BlockInfo& block)
153
0
{
154
0
    const CAmount block_subsidy{GetBlockSubsidy(block.height, Params().GetConsensus())};
155
0
    m_total_subsidy += block_subsidy;
156
157
    // Ignore genesis block
158
0
    if (block.height > 0) {
159
0
        uint256 expected_block_hash{*Assert(block.prev_hash)};
Line
Count
Source
113
0
#define Assert(val) inline_assertion_check<true>(val, std::source_location::current(), #val)
160
0
        if (m_current_block_hash != expected_block_hash) {
161
0
            LogError("previous block header belongs to unexpected block %s; expected %s",
Line
Count
Source
370
0
#define LogError(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Error, /*should_ratelimit=*/true, __VA_ARGS__)
Line
Count
Source
362
0
#define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__)
162
0
                      m_current_block_hash.ToString(), expected_block_hash.ToString());
163
0
            return false;
164
0
        }
165
166
        // Add the new utxos created from the block
167
0
        assert(block.data);
168
0
        for (size_t i = 0; i < block.data->vtx.size(); ++i) {
169
0
            const auto& tx{block.data->vtx.at(i)};
170
0
            const bool is_coinbase{tx->IsCoinBase()};
171
172
            // Skip duplicate txid coinbase transactions (BIP30).
173
0
            if (is_coinbase && IsBIP30Unspendable(block.hash, block.height)) {
174
0
                m_total_unspendables_bip30 += block_subsidy;
175
0
                continue;
176
0
            }
177
178
0
            for (uint32_t j = 0; j < tx->vout.size(); ++j) {
179
0
                const CTxOut& out{tx->vout[j]};
180
0
                const Coin coin{out, block.height, is_coinbase};
181
0
                const COutPoint outpoint{tx->GetHash(), j};
182
183
                // Skip unspendable coins
184
0
                if (coin.out.scriptPubKey.IsUnspendable()) {
185
0
                    m_total_unspendables_scripts += coin.out.nValue;
186
0
                    continue;
187
0
                }
188
189
0
                ApplyCoinHash(m_muhash, outpoint, coin);
190
191
0
                if (is_coinbase) {
192
0
                    m_total_coinbase_amount += coin.out.nValue;
193
0
                } else {
194
0
                    m_total_new_outputs_ex_coinbase_amount += coin.out.nValue;
195
0
                }
196
197
0
                ++m_transaction_output_count;
198
0
                m_total_amount += coin.out.nValue;
199
0
                m_bogo_size += GetBogoSize(coin.out.scriptPubKey);
200
0
            }
201
202
            // The coinbase tx has no undo data since no former output is spent
203
0
            if (!is_coinbase) {
204
0
                const auto& tx_undo{Assert(block.undo_data)->vtxundo.at(i - 1)};
Line
Count
Source
113
0
#define Assert(val) inline_assertion_check<true>(val, std::source_location::current(), #val)
205
206
0
                for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
207
0
                    const Coin& coin{tx_undo.vprevout[j]};
208
0
                    const COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};
209
210
0
                    RemoveCoinHash(m_muhash, outpoint, coin);
211
212
0
                    m_total_prevout_spent_amount += coin.out.nValue;
213
214
0
                    --m_transaction_output_count;
215
0
                    m_total_amount -= coin.out.nValue;
216
0
                    m_bogo_size -= GetBogoSize(coin.out.scriptPubKey);
217
0
                }
218
0
            }
219
0
        }
220
0
    } else {
221
        // genesis block
222
0
        m_total_unspendables_genesis_block += block_subsidy;
223
0
    }
224
225
    // If spent prevouts + block subsidy are still a higher amount than
226
    // new outputs + coinbase + current unspendable amount this means
227
    // the miner did not claim the full block reward. Unclaimed block
228
    // rewards are also unspendable.
229
0
    const CAmount temp_total_unspendable_amount{m_total_unspendables_genesis_block + m_total_unspendables_bip30 + m_total_unspendables_scripts + m_total_unspendables_unclaimed_rewards};
230
0
    const arith_uint256 unclaimed_rewards{(m_total_prevout_spent_amount + m_total_subsidy) - (m_total_new_outputs_ex_coinbase_amount + m_total_coinbase_amount + temp_total_unspendable_amount)};
231
0
    assert(unclaimed_rewards <= arith_uint256(std::numeric_limits<CAmount>::max()));
232
0
    m_total_unspendables_unclaimed_rewards += static_cast<CAmount>(unclaimed_rewards.GetLow64());
233
234
0
    std::pair<uint256, DBVal> value;
235
0
    value.first = block.hash;
236
0
    value.second.transaction_output_count = m_transaction_output_count;
237
0
    value.second.bogo_size = m_bogo_size;
238
0
    value.second.total_amount = m_total_amount;
239
0
    value.second.total_subsidy = m_total_subsidy;
240
0
    value.second.total_prevout_spent_amount = m_total_prevout_spent_amount;
241
0
    value.second.total_new_outputs_ex_coinbase_amount = m_total_new_outputs_ex_coinbase_amount;
242
0
    value.second.total_coinbase_amount = m_total_coinbase_amount;
243
0
    value.second.total_unspendables_genesis_block = m_total_unspendables_genesis_block;
244
0
    value.second.total_unspendables_bip30 = m_total_unspendables_bip30;
245
0
    value.second.total_unspendables_scripts = m_total_unspendables_scripts;
246
0
    value.second.total_unspendables_unclaimed_rewards = m_total_unspendables_unclaimed_rewards;
247
248
0
    uint256 out;
249
0
    m_muhash.Finalize(out);
250
0
    value.second.muhash = out;
251
252
0
    m_current_block_hash = block.hash;
253
254
    // Intentionally do not update DB_MUHASH here so it stays in sync with
255
    // DB_BEST_BLOCK, and the index is not corrupted if there is an unclean shutdown.
256
0
    m_db->Write(DBHeightKey(block.height), value);
257
0
    return true;
258
0
}
259
260
[[nodiscard]] static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch,
261
                                                     const std::string& index_name, int height)
262
0
{
263
0
    DBHeightKey key{height};
264
0
    db_it.Seek(key);
265
266
0
    if (!db_it.GetKey(key) || key.height != height) {
267
0
        LogError("unexpected key in %s: expected (%c, %d)",
Line
Count
Source
370
0
#define LogError(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Error, /*should_ratelimit=*/true, __VA_ARGS__)
Line
Count
Source
362
0
#define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__)
268
0
                 index_name, DB_BLOCK_HEIGHT, height);
269
0
        return false;
270
0
    }
271
272
0
    std::pair<uint256, DBVal> value;
273
0
    if (!db_it.GetValue(value)) {
274
0
        LogError("unable to read value in %s at key (%c, %d)",
Line
Count
Source
370
0
#define LogError(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Error, /*should_ratelimit=*/true, __VA_ARGS__)
Line
Count
Source
362
0
#define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__)
275
0
                 index_name, DB_BLOCK_HEIGHT, height);
276
0
        return false;
277
0
    }
278
279
0
    batch.Write(DBHashKey(value.first), value.second);
280
0
    return true;
281
0
}
282
283
bool CoinStatsIndex::CustomRemove(const interfaces::BlockInfo& block)
284
0
{
285
0
    CDBBatch batch(*m_db);
286
0
    std::unique_ptr<CDBIterator> db_it(m_db->NewIterator());
287
288
    // During a reorg, copy the block's hash digest from the height index to the hash index,
289
    // ensuring it's still accessible after the height index entry is overwritten.
290
0
    if (!CopyHeightIndexToHashIndex(*db_it, batch, m_name, block.height)) {
291
0
        return false;
292
0
    }
293
294
0
    m_db->WriteBatch(batch);
295
296
0
    if (!RevertBlock(block)) {
297
0
        return false; // failure cause logged internally
298
0
    }
299
300
0
    return true;
301
0
}
302
303
static bool LookUpOne(const CDBWrapper& db, const interfaces::BlockRef& block, DBVal& result)
304
0
{
305
    // First check if the result is stored under the height index and the value
306
    // there matches the block hash. This should be the case if the block is on
307
    // the active chain.
308
0
    std::pair<uint256, DBVal> read_out;
309
0
    if (!db.Read(DBHeightKey(block.height), read_out)) {
310
0
        return false;
311
0
    }
312
0
    if (read_out.first == block.hash) {
313
0
        result = std::move(read_out.second);
314
0
        return true;
315
0
    }
316
317
    // If value at the height index corresponds to an different block, the
318
    // result will be stored in the hash index.
319
0
    return db.Read(DBHashKey(block.hash), result);
320
0
}
321
322
std::optional<CCoinsStats> CoinStatsIndex::LookUpStats(const CBlockIndex& block_index) const
323
0
{
324
0
    CCoinsStats stats{block_index.nHeight, block_index.GetBlockHash()};
325
0
    stats.index_used = true;
326
327
0
    DBVal entry;
328
0
    if (!LookUpOne(*m_db, {block_index.GetBlockHash(), block_index.nHeight}, entry)) {
329
0
        return std::nullopt;
330
0
    }
331
332
0
    stats.hashSerialized = entry.muhash;
333
0
    stats.nTransactionOutputs = entry.transaction_output_count;
334
0
    stats.nBogoSize = entry.bogo_size;
335
0
    stats.total_amount = entry.total_amount;
336
0
    stats.total_subsidy = entry.total_subsidy;
337
0
    stats.total_prevout_spent_amount = entry.total_prevout_spent_amount;
338
0
    stats.total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount;
339
0
    stats.total_coinbase_amount = entry.total_coinbase_amount;
340
0
    stats.total_unspendables_genesis_block = entry.total_unspendables_genesis_block;
341
0
    stats.total_unspendables_bip30 = entry.total_unspendables_bip30;
342
0
    stats.total_unspendables_scripts = entry.total_unspendables_scripts;
343
0
    stats.total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards;
344
345
0
    return stats;
346
0
}
347
348
bool CoinStatsIndex::CustomInit(const std::optional<interfaces::BlockRef>& block)
349
0
{
350
0
    if (!m_db->Read(DB_MUHASH, m_muhash)) {
351
        // Check that the cause of the read failure is that the key does not
352
        // exist. Any other errors indicate database corruption or a disk
353
        // failure, and starting the index would cause further corruption.
354
0
        if (m_db->Exists(DB_MUHASH)) {
355
0
            LogError("Cannot read current %s state; index may be corrupted",
Line
Count
Source
370
0
#define LogError(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Error, /*should_ratelimit=*/true, __VA_ARGS__)
Line
Count
Source
362
0
#define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__)
356
0
                      GetName());
357
0
            return false;
358
0
        }
359
0
    }
360
361
0
    if (block) {
362
0
        DBVal entry;
363
0
        if (!LookUpOne(*m_db, *block, entry)) {
364
0
            LogError("Cannot read current %s state; index may be corrupted",
Line
Count
Source
370
0
#define LogError(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Error, /*should_ratelimit=*/true, __VA_ARGS__)
Line
Count
Source
362
0
#define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__)
365
0
                      GetName());
366
0
            return false;
367
0
        }
368
369
0
        uint256 out;
370
0
        m_muhash.Finalize(out);
371
0
        if (entry.muhash != out) {
372
0
            LogError("Cannot read current %s state; index may be corrupted",
Line
Count
Source
370
0
#define LogError(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Error, /*should_ratelimit=*/true, __VA_ARGS__)
Line
Count
Source
362
0
#define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__)
373
0
                      GetName());
374
0
            return false;
375
0
        }
376
377
0
        m_transaction_output_count = entry.transaction_output_count;
378
0
        m_bogo_size = entry.bogo_size;
379
0
        m_total_amount = entry.total_amount;
380
0
        m_total_subsidy = entry.total_subsidy;
381
0
        m_total_prevout_spent_amount = entry.total_prevout_spent_amount;
382
0
        m_total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount;
383
0
        m_total_coinbase_amount = entry.total_coinbase_amount;
384
0
        m_total_unspendables_genesis_block = entry.total_unspendables_genesis_block;
385
0
        m_total_unspendables_bip30 = entry.total_unspendables_bip30;
386
0
        m_total_unspendables_scripts = entry.total_unspendables_scripts;
387
0
        m_total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards;
388
0
        m_current_block_hash = block->hash;
389
0
    }
390
391
0
    return true;
392
0
}
393
394
bool CoinStatsIndex::CustomCommit(CDBBatch& batch)
395
0
{
396
    // DB_MUHASH should always be committed in a batch together with DB_BEST_BLOCK
397
    // to prevent an inconsistent state of the DB.
398
0
    batch.Write(DB_MUHASH, m_muhash);
399
0
    return true;
400
0
}
401
402
interfaces::Chain::NotifyOptions CoinStatsIndex::CustomOptions()
403
0
{
404
0
    interfaces::Chain::NotifyOptions options;
405
0
    options.connect_undo_data = true;
406
0
    options.disconnect_data = true;
407
0
    options.disconnect_undo_data = true;
408
0
    return options;
409
0
}
410
411
// Revert a single block as part of a reorg
412
bool CoinStatsIndex::RevertBlock(const interfaces::BlockInfo& block)
413
0
{
414
0
    std::pair<uint256, DBVal> read_out;
415
416
    // Ignore genesis block
417
0
    if (block.height > 0) {
418
0
        if (!m_db->Read(DBHeightKey(block.height - 1), read_out)) {
419
0
            return false;
420
0
        }
421
422
0
        uint256 expected_block_hash{*block.prev_hash};
423
0
        if (read_out.first != expected_block_hash) {
424
0
            LogWarning("previous block header belongs to unexpected block %s; expected %s",
Line
Count
Source
369
0
#define LogWarning(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Warning, /*should_ratelimit=*/true, __VA_ARGS__)
Line
Count
Source
362
0
#define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__)
425
0
                      read_out.first.ToString(), expected_block_hash.ToString());
426
427
0
            if (!m_db->Read(DBHashKey(expected_block_hash), read_out)) {
428
0
                LogError("previous block header not found; expected %s",
Line
Count
Source
370
0
#define LogError(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Error, /*should_ratelimit=*/true, __VA_ARGS__)
Line
Count
Source
362
0
#define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__)
429
0
                          expected_block_hash.ToString());
430
0
                return false;
431
0
            }
432
0
        }
433
0
    }
434
435
    // Roll back muhash by removing the new UTXOs that were created by the
436
    // block and reapplying the old UTXOs that were spent by the block
437
0
    assert(block.data);
438
0
    assert(block.undo_data);
439
0
    for (size_t i = 0; i < block.data->vtx.size(); ++i) {
440
0
        const auto& tx{block.data->vtx.at(i)};
441
0
        const bool is_coinbase{tx->IsCoinBase()};
442
443
0
        if (is_coinbase && IsBIP30Unspendable(block.hash, block.height)) {
444
0
            continue;
445
0
        }
446
447
0
        for (uint32_t j = 0; j < tx->vout.size(); ++j) {
448
0
            const CTxOut& out{tx->vout[j]};
449
0
            const COutPoint outpoint{tx->GetHash(), j};
450
0
            const Coin coin{out, block.height, is_coinbase};
451
452
0
            if (!coin.out.scriptPubKey.IsUnspendable()) {
453
0
                RemoveCoinHash(m_muhash, outpoint, coin);
454
0
            }
455
0
        }
456
457
        // The coinbase tx has no undo data since no former output is spent
458
0
        if (!is_coinbase) {
459
0
            const auto& tx_undo{block.undo_data->vtxundo.at(i - 1)};
460
461
0
            for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
462
0
                const Coin& coin{tx_undo.vprevout[j]};
463
0
                const COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};
464
0
                ApplyCoinHash(m_muhash, outpoint, coin);
465
0
            }
466
0
        }
467
0
    }
468
469
    // Check that the rolled back muhash is consistent with the DB read out
470
0
    uint256 out;
471
0
    m_muhash.Finalize(out);
472
0
    Assert(read_out.second.muhash == out);
Line
Count
Source
113
0
#define Assert(val) inline_assertion_check<true>(val, std::source_location::current(), #val)
473
474
    // Apply the other values from the DB to the member variables
475
0
    m_transaction_output_count = read_out.second.transaction_output_count;
476
0
    m_total_amount = read_out.second.total_amount;
477
0
    m_bogo_size = read_out.second.bogo_size;
478
0
    m_total_subsidy = read_out.second.total_subsidy;
479
0
    m_total_prevout_spent_amount = read_out.second.total_prevout_spent_amount;
480
0
    m_total_new_outputs_ex_coinbase_amount = read_out.second.total_new_outputs_ex_coinbase_amount;
481
0
    m_total_coinbase_amount = read_out.second.total_coinbase_amount;
482
0
    m_total_unspendables_genesis_block = read_out.second.total_unspendables_genesis_block;
483
0
    m_total_unspendables_bip30 = read_out.second.total_unspendables_bip30;
484
0
    m_total_unspendables_scripts = read_out.second.total_unspendables_scripts;
485
0
    m_total_unspendables_unclaimed_rewards = read_out.second.total_unspendables_unclaimed_rewards;
486
0
    m_current_block_hash = *block.prev_hash;
487
488
0
    return true;
489
0
}