fuzz coverage

Coverage Report

Created: 2025-09-17 22:41

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