fuzz coverage

Coverage Report

Created: 2026-04-24 13:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/bitcoin/src/index/txospenderindex.cpp
Line
Count
Source
1
// Copyright (c) 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/txospenderindex.h>
6
7
#include <common/args.h>
8
#include <crypto/siphash.h>
9
#include <dbwrapper.h>
10
#include <flatfile.h>
11
#include <index/base.h>
12
#include <index/disktxpos.h>
13
#include <interfaces/chain.h>
14
#include <logging.h>
15
#include <node/blockstorage.h>
16
#include <primitives/block.h>
17
#include <primitives/transaction.h>
18
#include <random.h>
19
#include <serialize.h>
20
#include <streams.h>
21
#include <tinyformat.h>
22
#include <uint256.h>
23
#include <util/fs.h>
24
#include <validation.h>
25
26
#include <cstdio>
27
#include <exception>
28
#include <ios>
29
#include <string>
30
#include <utility>
31
#include <vector>
32
33
/* The database is used to find the spending transaction of a given utxo.
34
 * For every input of every transaction it stores a key that is a pair(siphash(input outpoint), transaction location on disk) and an empty value.
35
 * To find the spending transaction of an outpoint, we perform a range query on siphash(outpoint), and for each returned key load the transaction
36
 * and return it if it does spend the provided outpoint.
37
 */
38
39
// LevelDB key prefix. We only have one key for now but it will make it easier to add others if needed.
40
constexpr uint8_t DB_TXOSPENDERINDEX{'s'};
41
42
std::unique_ptr<TxoSpenderIndex> g_txospenderindex;
43
44
struct DBKey {
45
    uint64_t hash;
46
    CDiskTxPos pos;
47
48
0
    explicit DBKey(const uint64_t& hash_in, const CDiskTxPos& pos_in) : hash(hash_in), pos(pos_in) {}
49
50
    SERIALIZE_METHODS(DBKey, obj)
51
0
    {
52
0
        uint8_t prefix{DB_TXOSPENDERINDEX};
53
0
        READWRITE(prefix);
Line
Count
Source
147
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
        READWRITE(prefix);
Line
Count
Source
147
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
54
0
        if (prefix != DB_TXOSPENDERINDEX) {
55
0
            throw std::ios_base::failure("Invalid format for spender index DB key");
56
0
        }
57
0
        READWRITE(obj.hash);
Line
Count
Source
147
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
        READWRITE(obj.hash);
Line
Count
Source
147
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
58
0
        READWRITE(obj.pos);
Line
Count
Source
147
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
        READWRITE(obj.pos);
Line
Count
Source
147
0
#define READWRITE(...) (ser_action.SerReadWriteMany(s, __VA_ARGS__))
59
0
    }
Unexecuted instantiation: void DBKey::SerializationOps<DataStream, DBKey const, ActionSerialize>(DBKey const&, DataStream&, ActionSerialize)
Unexecuted instantiation: void DBKey::SerializationOps<DataStream, DBKey, ActionUnserialize>(DBKey&, DataStream&, ActionUnserialize)
60
};
61
62
TxoSpenderIndex::TxoSpenderIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe)
63
0
    : BaseIndex(std::move(chain), "txospenderindex"), m_db{std::make_unique<DB>(gArgs.GetDataDirNet() / "indexes" / "txospenderindex" / "db", n_cache_size, f_memory, f_wipe)}
64
0
{
65
0
    if (!m_db->Read("siphash_key", m_siphash_key)) {
66
0
        FastRandomContext rng(false);
67
0
        m_siphash_key = {rng.rand64(), rng.rand64()};
68
0
        m_db->Write("siphash_key", m_siphash_key, /*fSync=*/ true);
69
0
    }
70
0
}
71
72
interfaces::Chain::NotifyOptions TxoSpenderIndex::CustomOptions()
73
0
{
74
0
    interfaces::Chain::NotifyOptions options;
75
0
    options.disconnect_data = true;
76
0
    return options;
77
0
}
78
79
static uint64_t CreateKeyPrefix(std::pair<uint64_t, uint64_t> siphash_key, const COutPoint& vout)
80
0
{
81
0
    return PresaltedSipHasher(siphash_key.first, siphash_key.second)(vout.hash.ToUint256(), vout.n);
82
0
}
83
84
static DBKey CreateKey(std::pair<uint64_t, uint64_t> siphash_key, const COutPoint& vout, const CDiskTxPos& pos)
85
0
{
86
0
    return DBKey(CreateKeyPrefix(siphash_key, vout), pos);
87
0
}
88
89
void TxoSpenderIndex::WriteSpenderInfos(const std::vector<std::pair<COutPoint, CDiskTxPos>>& items)
90
0
{
91
0
    CDBBatch batch(*m_db);
92
0
    for (const auto& [outpoint, pos] : items) {
93
0
        DBKey key(CreateKey(m_siphash_key, outpoint, pos));
94
        // key is hash(spent outpoint) | disk pos, value is empty
95
0
        batch.Write(key, "");
96
0
    }
97
0
    m_db->WriteBatch(batch);
98
0
}
99
100
101
void TxoSpenderIndex::EraseSpenderInfos(const std::vector<std::pair<COutPoint, CDiskTxPos>>& items)
102
0
{
103
0
    CDBBatch batch(*m_db);
104
0
    for (const auto& [outpoint, pos] : items) {
105
0
        batch.Erase(CreateKey(m_siphash_key, outpoint, pos));
106
0
    }
107
0
    m_db->WriteBatch(batch);
108
0
}
109
110
static std::vector<std::pair<COutPoint, CDiskTxPos>> BuildSpenderPositions(const interfaces::BlockInfo& block)
111
0
{
112
0
    std::vector<std::pair<COutPoint, CDiskTxPos>> items;
113
0
    items.reserve(block.data->vtx.size());
114
115
0
    CDiskTxPos pos({block.file_number, block.data_pos}, GetSizeOfCompactSize(block.data->vtx.size()));
116
0
    for (const auto& tx : block.data->vtx) {
117
0
        if (!tx->IsCoinBase()) {
118
0
            for (const auto& input : tx->vin) {
119
0
                items.emplace_back(input.prevout, pos);
120
0
            }
121
0
        }
122
0
        pos.nTxOffset += ::GetSerializeSize(TX_WITH_WITNESS(*tx));
123
0
    }
124
125
0
    return items;
126
0
}
127
128
129
bool TxoSpenderIndex::CustomAppend(const interfaces::BlockInfo& block)
130
0
{
131
0
    WriteSpenderInfos(BuildSpenderPositions(block));
132
0
    return true;
133
0
}
134
135
bool TxoSpenderIndex::CustomRemove(const interfaces::BlockInfo& block)
136
0
{
137
0
    EraseSpenderInfos(BuildSpenderPositions(block));
138
0
    return true;
139
0
}
140
141
util::Expected<TxoSpender, std::string> TxoSpenderIndex::ReadTransaction(const CDiskTxPos& tx_pos) const
142
0
{
143
0
    AutoFile file{m_chainstate->m_blockman.OpenBlockFile(tx_pos, /*fReadOnly=*/true)};
144
0
    if (file.IsNull()) {
145
0
        return util::Unexpected("cannot open block");
146
0
    }
147
0
    CBlockHeader header;
148
0
    TxoSpender spender;
149
0
    try {
150
0
        file >> header;
151
0
        file.seek(tx_pos.nTxOffset, SEEK_CUR);
152
0
        file >> TX_WITH_WITNESS(spender.tx);
153
0
        spender.block_hash = header.GetHash();
154
0
        return spender;
155
0
    } catch (const std::exception& e) {
156
0
        return util::Unexpected(e.what());
157
0
    }
158
0
}
159
160
util::Expected<std::optional<TxoSpender>, std::string> TxoSpenderIndex::FindSpender(const COutPoint& txo) const
161
0
{
162
0
    const uint64_t prefix{CreateKeyPrefix(m_siphash_key, txo)};
163
0
    std::unique_ptr<CDBIterator> it(m_db->NewIterator());
164
0
    DBKey key(prefix, CDiskTxPos());
165
166
    // find all keys that start with the outpoint hash, load the transaction at the location specified in the key
167
    // and return it if it does spend the provided outpoint
168
0
    for (it->Seek(std::pair{DB_TXOSPENDERINDEX, prefix}); it->Valid() && it->GetKey(key) && key.hash == prefix; it->Next()) {
169
0
        if (const auto spender{ReadTransaction(key.pos)}) {
170
0
            for (const auto& input : spender->tx->vin) {
171
0
                if (input.prevout == txo) {
172
0
                    return std::optional{*spender};
173
0
                }
174
0
            }
175
0
        } else {
176
0
            LogError("Deserialize or I/O error - %s", spender.error());
Line
Count
Source
99
0
#define LogError(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Error, /*should_ratelimit=*/true, __VA_ARGS__)
Line
Count
Source
91
0
#define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(SourceLocation{__func__}, category, level, should_ratelimit, __VA_ARGS__)
177
0
            return util::Unexpected{strprintf("IO error finding spending tx for outpoint %s:%d.", txo.hash.GetHex(), txo.n)};
Line
Count
Source
1172
0
#define strprintf tfm::format
178
0
        }
179
0
    }
180
0
    return util::Expected<std::optional<TxoSpender>, std::string>(std::nullopt);
181
0
}
182
183
0
BaseIndex::DB& TxoSpenderIndex::GetDB() const { return *m_db; }