Coverage Report

Created: 2026-07-23 20:35

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