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