Branch data Line data Source code
1 : : // Copyright (c) 2017-present 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/txindex.h>
6 : :
7 : : #include <chain.h>
8 : : #include <common/args.h>
9 : : #include <crypto/siphash.h>
10 : : #include <dbwrapper.h>
11 : : #include <flatfile.h>
12 : : #include <index/base.h>
13 : : #include <index/disktxpos.h>
14 : : #include <index/txindex_key.h>
15 : : #include <interfaces/chain.h>
16 : : #include <node/blockstorage.h>
17 : : #include <primitives/block.h>
18 : : #include <primitives/transaction.h>
19 : : #include <random.h>
20 : : #include <serialize.h>
21 : : #include <streams.h>
22 : : #include <sync.h>
23 : : #include <uint256.h>
24 : : #include <util/fs.h>
25 : : #include <util/log.h>
26 : : #include <validation.h>
27 : :
28 : : #include <algorithm>
29 : : #include <array>
30 : : #include <cassert>
31 : : #include <cstdint>
32 : : #include <cstdio>
33 : : #include <exception>
34 : : #include <functional>
35 : : #include <memory>
36 : : #include <optional>
37 : : #include <string>
38 : : #include <utility>
39 : : #include <vector>
40 : :
41 : : std::unique_ptr<TxIndex> g_txindex;
42 : :
43 : : namespace {
44 : 10 : SipHasher13UJ ReadOrCreateTxidHasher(CDBWrapper& db)
45 : : {
46 : 10 : std::pair<uint64_t, uint64_t> salt;
47 [ + + ]: 10 : if (!db.Read(txindex::DB_TXID_HASH_SALT, salt)) {
48 : 7 : FastRandomContext rng{};
49 [ + - ]: 7 : salt = {rng.rand64(), rng.rand64()};
50 [ + - ]: 7 : db.Write(txindex::DB_TXID_HASH_SALT, salt, /*fSync=*/true);
51 : 7 : }
52 : 10 : return SipHasher13UJ{salt.first, salt.second};
53 : : }
54 : : } // namespace
55 : :
56 : : /** Access to the txindex database (indexes/txindex/) */
57 : : class TxIndex::DB : public BaseIndex::DB
58 : : {
59 : : public:
60 : : explicit DB(size_t n_cache_size, bool f_memory = false, bool f_wipe = false);
61 : :
62 : : /// Write a block of transaction positions to the DB.
63 : : void WriteTxs(const interfaces::BlockInfo& block);
64 : :
65 : : /// Used to hash the txid to compute the prefix.
66 : : const SipHasher13UJ m_hasher;
67 : :
68 : : /// Whether the database contains any legacy ('t' + txid) entries.
69 : : const bool m_has_legacy;
70 : :
71 : : CBlockLocator ReadBestBlock() const override;
72 : : void WriteBestBlock(CDBBatch& batch, const CBlockLocator& locator) override;
73 : :
74 : : private:
75 : : DB(size_t n_cache_size, bool f_memory, bool f_wipe, bool has_legacy);
76 : : };
77 : :
78 [ + - + - ]: 95 : static fs::path TxIndexDBPath() { return gArgs.GetDataDirNet() / "indexes" / "txindex"; }
79 : :
80 : 10 : TxIndex::DB::DB(size_t n_cache_size, bool f_memory, bool f_wipe) :
81 : : // Bloom filters are built for every key but only consulted by point reads,
82 : : // which iterators bypass: the per-tx hashed ('x') lookups seek with an
83 : : // iterator, and the 's'/'h' point reads are at most one per block against a
84 : : // tiny keyspace. Only the legacy entries' per-tx point lookups benefit, so
85 : : // enable the filters only for databases still containing them.
86 : : DB(n_cache_size, f_memory, f_wipe,
87 [ + + + - : 25 : /*has_legacy=*/!f_memory && !f_wipe && CDBWrapper::HasKeyStartingWith(TxIndexDBPath(), txindex::DB_TXINDEX))
+ + + - +
+ - - ]
88 : 10 : {}
89 : :
90 : 10 : TxIndex::DB::DB(size_t n_cache_size, bool f_memory, bool f_wipe, bool has_legacy) :
91 : 0 : BaseIndex::DB(TxIndexDBPath(), n_cache_size, f_memory, f_wipe, /*f_obfuscate=*/false, /*f_bloom=*/has_legacy),
92 : 10 : m_hasher{ReadOrCreateTxidHasher(*this)},
93 [ + - + - ]: 20 : m_has_legacy{has_legacy}
94 : 10 : {}
95 : :
96 : 14 : CBlockLocator TxIndex::DB::ReadBestBlock() const
97 : : {
98 : 14 : CBlockLocator locator;
99 [ + - + + ]: 14 : if (Read(txindex::DB_BEST_BLOCK_V2, locator)) {
100 : 4 : return locator;
101 : : }
102 : : // If we don't have a locator yet, start from the legacy best block.
103 [ + - ]: 10 : return BaseIndex::DB::ReadBestBlock();
104 : 14 : }
105 : :
106 : 2 : void TxIndex::DB::WriteBestBlock(CDBBatch& batch, const CBlockLocator& locator)
107 : : {
108 : 2 : batch.Write(txindex::DB_BEST_BLOCK_V2, locator);
109 : 2 : }
110 : :
111 : 818 : void TxIndex::DB::WriteTxs(const interfaces::BlockInfo& block)
112 : : {
113 : : // A block may be submitted again after it was already indexed, e.g. when it
114 : : // reconnects after a reorg or is re-processed after an unclean shutdown. It
115 : : // keeps its original sequence number, so skip it to avoid duplicate entries.
116 [ + + ]: 818 : if (Exists(txindex::BlockHashKey{block.hash})) return;
117 : :
118 : 616 : uint32_t block_seq{0};
119 : 616 : Read(txindex::DB_NEXT_BLOCK_SEQ, block_seq);
120 : :
121 : 616 : CDBBatch batch(*this);
122 [ + - ]: 616 : batch.Write(txindex::BlockHashKey{block.hash}, block_seq);
123 [ + - ]: 616 : batch.Write(txindex::BlockSeqKey{block_seq}, block.hash);
124 [ + - ]: 616 : batch.Write(txindex::DB_NEXT_BLOCK_SEQ, block_seq + 1);
125 [ - + - + ]: 616 : uint32_t tx_offset_in_block{txindex::BLOCK_HEADER_SIZE + GetSizeOfCompactSize(block.data->vtx.size())};
126 [ + + ]: 1234 : for (const auto& tx : block.data->vtx) {
127 [ + - ]: 618 : const txindex::DBKey key{txindex::CreateKeyPrefix(m_hasher, tx->GetHash()),
128 : 618 : txindex::BlockTxPosition{block_seq, tx_offset_in_block}};
129 [ + - ]: 618 : batch.Write(key, txindex::EMPTY_VALUE);
130 [ + - ]: 618 : tx_offset_in_block += tx->ComputeTotalSize();
131 : : }
132 [ + - ]: 616 : WriteBatch(batch);
133 : 616 : }
134 : :
135 : 10 : TxIndex::TxIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe)
136 [ + - + - : 10 : : BaseIndex(std::move(chain), "txindex", "txidx"), m_db(std::make_unique<TxIndex::DB>(n_cache_size, f_memory, f_wipe))
+ - ]
137 : : {
138 [ + + ]: 10 : if (m_db->m_has_legacy) {
139 [ + - + - ]: 3 : LogInfo("txindex contains entries in the legacy format, which uses excessive disk space. "
140 : : "To reclaim disk space, stop the node, delete %s and restart to rebuild the index.",
141 : : fs::PathToString(TxIndexDBPath()));
142 : : }
143 : 10 : }
144 : :
145 : 15 : TxIndex::~TxIndex() = default;
146 : :
147 : 826 : bool TxIndex::CustomAppend(const interfaces::BlockInfo& block)
148 : : {
149 : : // Exclude genesis block transaction because outputs are not spendable.
150 [ + + ]: 826 : if (block.height == 0) return true;
151 : :
152 [ - + ]: 818 : assert(block.data);
153 : 818 : m_db->WriteTxs(block);
154 : 818 : return true;
155 : : }
156 : :
157 : 22 : BaseIndex::DB& TxIndex::GetDB() const { return *m_db; }
158 : :
159 : 218 : std::optional<TxIndexResult> TxIndex::FindTx(const Txid& tx_hash) const
160 : : {
161 : 218 : struct Candidate {
162 : : FlatFilePos tx_position;
163 : : uint256 block_hash;
164 : : uint32_t block_seq;
165 : : //! Whether this candidate's block is currently in the active chain.
166 : : //! Active chain candidates are attempted first, so duplicate entries
167 : : //! in both active and stale blocks will always return the active block hash.
168 : : bool in_active_chain;
169 : : };
170 : 218 : std::vector<Candidate> candidates;
171 : 218 : {
172 [ + - + - ]: 218 : std::unique_ptr<CDBIterator> it{m_db->NewIterator()};
173 [ + - ]: 218 : const txindex::TxHashKeyPrefix prefix{txindex::CreateKeyPrefix(m_db->m_hasher, tx_hash)};
174 : 218 : txindex::DBKey key{prefix, {}};
175 [ + - + - : 336 : for (it->Seek(key); it->Valid() && it->GetKey(key) && key.hash_prefix == prefix; it->Next()) {
+ - + + +
- + - +
+ ]
176 : 118 : uint256 candidate_block_hash;
177 [ - + + - ]: 118 : if (!m_db->Read(txindex::BlockSeqKey{key.pos.block_seq}, candidate_block_hash)) {
178 [ # # # # ]: 0 : LogWarning("Block sequence %u not found for txid %s", key.pos.block_seq, tx_hash.ToString());
179 : 0 : continue;
180 : : }
181 [ + - ]: 118 : LOCK(cs_main);
182 [ + - ]: 118 : const CBlockIndex* block_index{m_chainstate->m_blockman.LookupBlockIndex(candidate_block_hash)};
183 [ - + ]: 118 : if (!block_index) {
184 [ # # # # : 0 : LogWarning("Block index entry %s not found for txid %s", candidate_block_hash.ToString(), tx_hash.ToString());
# # ]
185 : 0 : continue;
186 : : }
187 [ - + ]: 118 : if (!(block_index->nStatus & BLOCK_HAVE_DATA)) continue;
188 [ + - ]: 118 : const FlatFilePos tx_position{block_index->nFile, block_index->nDataPos + key.pos.tx_offset_in_block};
189 [ + - ]: 118 : candidates.emplace_back(tx_position, candidate_block_hash, key.pos.block_seq, m_chainstate->m_chain.Contains(*block_index));
190 : 118 : }
191 : 218 : }
192 : :
193 : : // Prefer active-chain matches, then later-connected blocks.
194 : 218 : std::ranges::sort(candidates, std::greater{}, [](const Candidate& c) {
195 : 4 : return std::pair{c.in_active_chain, c.block_seq};
196 : : });
197 : :
198 [ + + ]: 219 : for (const auto& candidate : candidates) {
199 [ + - ]: 116 : AutoFile file{m_chainstate->m_blockman.OpenBlockFile(candidate.tx_position, /*fReadOnly=*/true)};
200 [ - + ]: 116 : if (file.IsNull()) {
201 [ # # # # ]: 0 : LogWarning("OpenBlockFile failed for txid %s", tx_hash.ToString());
202 : 0 : continue;
203 : : }
204 : 116 : CTransactionRef tx;
205 : 116 : try {
206 [ + - ]: 116 : file >> TX_WITH_WITNESS(tx);
207 [ - - ]: 0 : } catch (const std::exception& e) {
208 [ - - ]: 0 : LogWarning("Deserialize or I/O error - %s", e.what());
209 : 0 : continue;
210 [ - - ]: 0 : }
211 [ + + + - ]: 116 : if (tx->GetHash() == tx_hash) {
212 : 115 : return TxIndexResult{candidate.block_hash, std::move(tx)};
213 : : }
214 : 116 : }
215 : : // Fall back to legacy if no hashed entry matched. This makes misses pay an
216 : : // extra lookup, but keeps existing full-txid entries readable after upgrade.
217 [ + + + - ]: 103 : return m_db->m_has_legacy ? FindLegacyTx(tx_hash) : std::nullopt;
218 : 218 : }
219 : :
220 : 1 : std::optional<TxIndexResult> TxIndex::FindLegacyTx(const Txid& tx_hash) const
221 : : {
222 : 1 : CDiskTxPos postx;
223 [ - + ]: 1 : if (!m_db->Read(txindex::LegacyTxKey(tx_hash), postx)) {
224 : 0 : return std::nullopt;
225 : : }
226 : :
227 : 1 : AutoFile file{m_chainstate->m_blockman.OpenBlockFile(postx, /*fReadOnly=*/true)};
228 [ - + ]: 1 : if (file.IsNull()) {
229 [ # # ]: 0 : LogError("OpenBlockFile failed");
230 : 0 : return std::nullopt;
231 : : }
232 : 1 : CBlockHeader header;
233 : 1 : CTransactionRef tx;
234 : 1 : try {
235 [ + - ]: 1 : file >> header;
236 [ + - ]: 1 : file.seek(postx.nTxOffset, SEEK_CUR);
237 [ + - ]: 1 : file >> TX_WITH_WITNESS(tx);
238 [ - - ]: 0 : } catch (const std::exception& e) {
239 [ - - ]: 0 : LogError("Deserialize or I/O error - %s", e.what());
240 : 0 : return std::nullopt;
241 : 0 : }
242 [ + - ]: 1 : if (tx->GetHash() != tx_hash) {
243 [ # # ]: 0 : LogError("txid mismatch");
244 : 0 : return std::nullopt;
245 : : }
246 [ + - ]: 1 : return TxIndexResult{header.GetHash(), std::move(tx)};
247 : 1 : }
|