Bitcoin Core 31.99.0
P2P Digital Currency
txindex_tests.cpp
Go to the documentation of this file.
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 <addresstype.h>
6#include <chain.h>
7#include <chainparams.h>
8#include <common/args.h>
9#include <consensus/amount.h>
11#include <crypto/hex_base.h>
12#include <dbwrapper.h>
13#include <flatfile.h>
14#include <index/disktxpos.h>
15#include <index/txindex.h>
16#include <index/txindex_key.h>
17#include <interfaces/chain.h>
18#include <key.h>
19#include <node/blockstorage.h>
20#include <primitives/block.h>
21#include <script/script.h>
22#include <streams.h>
23#include <sync.h>
25#include <util/byte_units.h>
26#include <util/check.h>
27#include <util/strencodings.h>
28#include <validation.h>
29
30#include <cstdint>
31#include <memory>
32#include <string>
33#include <string_view>
34#include <utility>
35#include <vector>
36
37#include <boost/test/unit_test.hpp>
38
39BOOST_AUTO_TEST_SUITE(txindex_tests)
40
41// Grants tests access to the otherwise non-public txindex database handle.
43{
44public:
45 static CDBWrapper& GetDB(const TxIndex& txindex) { return txindex.GetDB(); }
46 static CBlockLocator ReadBestBlock(const TxIndex& txindex) { return txindex.GetDB().ReadBestBlock(); }
47 static void WriteBestBlock(const TxIndex& txindex, const CBlockLocator& locator)
48 {
49 auto& db{txindex.GetDB()};
50 CDBBatch batch{db};
51 db.WriteBestBlock(batch, locator);
52 db.WriteBatch(batch);
53 }
54};
55
56namespace {
57
58SipHasher13UJ ReadHasher(const CDBWrapper& db)
59{
60 std::pair<uint64_t, uint64_t> salt;
61 BOOST_REQUIRE(db.Read(txindex::DB_TXID_HASH_SALT, salt));
62 return SipHasher13UJ{salt.first, salt.second};
63}
64
65std::vector<txindex::BlockTxPosition> BucketPositions(CDBWrapper& db, txindex::TxHashKeyPrefix prefix)
66{
67 std::vector<txindex::BlockTxPosition> positions;
68 std::unique_ptr<CDBIterator> it{db.NewIterator()};
69 txindex::DBKey key{prefix, {}};
70 for (it->Seek(key); it->Valid() && it->GetKey(key) && key.hash_prefix == prefix; it->Next()) {
71 positions.push_back(key.pos);
72 }
73 return positions;
74}
75
76FlatFilePos BlockFilePos(const ChainstateManager& chainman, uint32_t height)
77{
79 const CBlockIndex* block_index{chainman.ActiveChain()[height]};
80 BOOST_REQUIRE(block_index);
81 return {block_index->nFile, block_index->nDataPos};
82}
83
84uint256 LookupTx(const TxIndex& txindex, const Txid& txid)
85{
86 const auto result{txindex.FindTx(txid)};
87 BOOST_REQUIRE(result);
88 BOOST_CHECK(result->tx->GetHash() == txid);
89 return result->block_hash;
90}
91
92void InvalidateBlock(ChainstateManager& chainman, const uint256& block_hash)
93{
94 CBlockIndex* block_index{WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(block_hash))};
95 BOOST_REQUIRE(block_index);
97 BOOST_REQUIRE(chainman.ActiveChainstate().InvalidateBlock(state, block_index));
98}
99
100} // namespace
101
102BOOST_AUTO_TEST_CASE(txindex_position_encoding)
103{
104 constexpr struct { txindex::BlockTxPosition position; std::string_view encoded; } test_vectors[]{
105 {{0, 0}, "00000000"},
106 {{1, 2}, "01000002"},
107 {{10'000'000, 123}, "83e1ac0000007b"},
108 {{456, 3'999'999}, "82483d08ff"},
109 };
110
111 for (const auto& [position, encoded] : test_vectors) {
112 BOOST_CHECK_EQUAL(HexStr(DataStream{} << position), encoded);
113
115 BOOST_CHECK((DataStream{ParseHex(encoded)} >> decoded).empty());
116 BOOST_CHECK(decoded == position);
117 }
118
119 // Pin the full key encodings, including the type prefixes.
121 BOOST_CHECK_EQUAL(HexStr(DataStream{} << txindex::DBKey{0x0102030405, {1, 2}}),
122 "78010203040501000002");
123
125}
126
127BOOST_AUTO_TEST_CASE(txindex_hash_prefix)
128{
131 SipHasher13UJ{0x0706050403020100ULL, 0x0F0E0D0C0B0A0908ULL},
132 Txid{"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"}),
133 0xc67d87b08cULL);
134}
135
137{
138 TxIndex txindex(interfaces::MakeChain(m_node), /*n_cache_size=*/1_MiB, /*f_memory=*/true);
139 BOOST_REQUIRE(txindex.Init());
140
141 // Transaction should not be found in the index before it is started.
142 for (const auto& txn : m_coinbase_txns) {
143 BOOST_CHECK(!txindex.FindTx(txn->GetHash()));
144 }
145
146 // BlockUntilSyncedToCurrentChain should return false before txindex is started.
147 BOOST_CHECK(!txindex.BlockUntilSyncedToCurrentChain());
148
149 txindex.Sync();
150
151 // Check that txindex excludes genesis block transactions.
152 const CBlock& genesis_block = Params().GenesisBlock();
153 for (const auto& txn : genesis_block.vtx) {
154 BOOST_CHECK(!txindex.FindTx(txn->GetHash()));
155 }
156
157 // Check that txindex has all txs that were in the chain before it started.
158 for (const auto& txn : m_coinbase_txns) {
159 LookupTx(txindex, txn->GetHash());
160 }
161
162 // Check that new transactions in new blocks make it into the index.
163 for (int i = 0; i < 10; i++) {
164 CScript coinbase_script_pub_key = GetScriptForDestination(PKHash(coinbaseKey.GetPubKey()));
165 std::vector<CMutableTransaction> no_txns;
166 const CBlock& block = CreateAndProcessBlock(no_txns, coinbase_script_pub_key);
167 const CTransaction& txn = *block.vtx[0];
168
169 BOOST_CHECK(txindex.BlockUntilSyncedToCurrentChain());
170 LookupTx(txindex, txn.GetHash());
171 }
172
173 // shutdown sequence (c.f. Shutdown() in init.cpp)
174 txindex.Stop();
175}
176
177BOOST_FIXTURE_TEST_CASE(txindex_collision_scan_path, TestChain100Setup)
178{
179 // On-disk, so the legacy-entry probe at construction runs against a fresh
180 // database, as it would on a node whose index was created by this version.
181 TxIndex txindex(interfaces::MakeChain(m_node), /*n_cache_size=*/1_MiB, /*f_memory=*/false);
182 BOOST_REQUIRE(txindex.Init());
183 txindex.Sync();
184
186 const SipHasher13UJ hasher{ReadHasher(db)};
187
188 // Lookups scan candidates in descending sequence order, so entries of
189 // later-connected blocks are tried first. Forge a colliding entry under the
190 // first coinbase's prefix pointing at the last coinbase, so looking up the
191 // first tx must scan that false positive first.
192 const Txid fake_txid{m_coinbase_txns.back()->GetHash()};
193 const Txid target_txid{m_coinbase_txns.front()->GetHash()};
194 const auto fake_prefix{txindex::CreateKeyPrefix(hasher, fake_txid)};
195 const auto target_prefix{txindex::CreateKeyPrefix(hasher, target_txid)};
196 // Distinct prefixes guarantee the target's bucket initially holds only the target.
197 BOOST_REQUIRE(fake_prefix != target_prefix);
198
199 // Read the last coinbase's encoded position straight from its bucket.
200 const auto fake_bucket{BucketPositions(db, fake_prefix)};
201 BOOST_REQUIRE_EQUAL(fake_bucket.size(), 1U);
202 const txindex::BlockTxPosition fake_pos{fake_bucket.front()};
203
204 db.Write(txindex::DBKey{target_prefix, fake_pos}, txindex::EMPTY_VALUE);
205
206 // The target's bucket now holds the real target first (lower sequence
207 // number), then the forged false positive, which the descending scan tries first.
208 const auto target_bucket{BucketPositions(db, target_prefix)};
209 BOOST_REQUIRE_EQUAL(target_bucket.size(), 2U);
210 BOOST_CHECK(target_bucket[0] != fake_pos);
211 BOOST_CHECK(target_bucket[1] == fake_pos);
212
213 LookupTx(txindex, target_txid);
214
215 // A database created fresh by this version cannot contain legacy entries, so
216 // lookups skip the legacy fallback: drop the last coinbase's hashed entry and
217 // re-add it under the old 't' + txid schema (a physical CDiskTxPos), then
218 // confirm the lookup misses even though the legacy row exists.
219 // BlockTxPosition offsets are from the block start (header included), while
220 // the legacy CDiskTxPos.nTxOffset is measured after the header.
221 const CDiskTxPos fake_physical{BlockFilePos(*m_node.chainman, fake_pos.block_seq + 1), fake_pos.tx_offset_in_block - txindex::BLOCK_HEADER_SIZE};
222 db.Erase(txindex::DBKey{fake_prefix, fake_pos});
223 db.Write(txindex::LegacyTxKey(fake_txid), fake_physical);
224 BOOST_CHECK(!txindex.FindTx(fake_txid));
225
226 txindex.Stop();
227}
228
230{
231 // Seed the on-disk database with a legacy ('t' + txid) entry before the index
232 // is opened, as if it had been written by a pre-hashing version.
233 const Txid legacy_txid{m_coinbase_txns.front()->GetHash()};
234 // The block at height 1 holds only the coinbase, so the tx starts right after
235 // the header and the 1-byte tx count.
236 const CDiskTxPos legacy_pos{BlockFilePos(*m_node.chainman, 1), 1};
237 {
238 CDBWrapper db{DBParams{.path = gArgs.GetDataDirNet() / "indexes" / "txindex", .cache_bytes = 1_MiB}};
239 db.Write(txindex::LegacyTxKey(legacy_txid), legacy_pos);
240 }
241
242 TxIndex txindex(interfaces::MakeChain(m_node), /*n_cache_size=*/1_MiB, /*f_memory=*/false);
243 BOOST_REQUIRE(txindex.Init());
244 txindex.Sync();
245
246 // Drop the hashed entries so only the legacy row remains, then confirm the
247 // lookup succeeds through the fallback.
249 const auto prefix{txindex::CreateKeyPrefix(ReadHasher(db), legacy_txid)};
250 const auto bucket{BucketPositions(db, prefix)};
251 BOOST_REQUIRE(!bucket.empty());
252 for (const auto& pos : bucket) db.Erase(txindex::DBKey{prefix, pos});
253
254 LookupTx(txindex, legacy_txid);
255
256 txindex.Stop();
257}
258
260{
261 uint256 legacy_hash, new_hash;
262 {
263 LOCK(cs_main);
264 legacy_hash = Assert(m_node.chainman->ActiveChain()[1])->GetBlockHash();
265 new_hash = Assert(m_node.chainman->ActiveChain().Tip())->GetBlockHash();
266 }
267 CBlockLocator legacy_locator{{legacy_hash}}, new_locator{{new_hash}};
268 { CDBWrapper{DBParams{.path = gArgs.GetDataDirNet() / "indexes" / "txindex", .cache_bytes = 1_MiB}}.Write(uint8_t{'B'}, legacy_locator); }
269
270 TxIndex txindex(interfaces::MakeChain(m_node), /*n_cache_size=*/1_MiB, /*f_memory=*/false);
271 BOOST_CHECK(TxIndexTest::ReadBestBlock(txindex).vHave == legacy_locator.vHave);
272
275
276 CBlockLocator stored_legacy_locator;
277 BOOST_REQUIRE(TxIndexTest::GetDB(txindex).Read(uint8_t{'B'}, stored_legacy_locator));
278 BOOST_CHECK(stored_legacy_locator.vHave == legacy_locator.vHave);
279}
280
281BOOST_FIXTURE_TEST_CASE(txindex_reorg_keeps_stale_entries, TestChain100Setup)
282{
283 TxIndex txindex(interfaces::MakeChain(m_node), /*n_cache_size=*/1_MiB, /*f_memory=*/true);
284 BOOST_REQUIRE(txindex.Init());
285 txindex.Sync();
286
287 const CScript coinbase_script{CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG};
288
289 // Mine a unique (non-coinbase) transaction into a new block at height 101.
290 CMutableTransaction unique_mtx{CreateValidMempoolTransaction(
291 /*input_transaction=*/m_coinbase_txns[0],
292 /*input_vout=*/0,
293 /*input_height=*/1,
294 /*input_signing_key=*/coinbaseKey,
295 /*output_destination=*/CScript() << OP_TRUE,
296 /*output_amount=*/CAmount{1 * COIN},
297 /*submit=*/false)};
298 const Txid unique_txid{MakeTransactionRef(unique_mtx)->GetHash()};
299 const uint256 stale_block_hash{CreateAndProcessBlock({unique_mtx}, coinbase_script).GetHash()};
300 BOOST_REQUIRE(txindex.BlockUntilSyncedToCurrentChain());
301
302 BOOST_CHECK(LookupTx(txindex, unique_txid) == stale_block_hash);
303
305 const auto prefix{txindex::CreateKeyPrefix(ReadHasher(db), unique_txid)};
306 const auto original_bucket{BucketPositions(db, prefix)};
307 BOOST_REQUIRE_EQUAL(original_bucket.size(), 1U);
308
310
311 // Invalidate the block holding the unique transaction.
312 InvalidateBlock(chainman, stale_block_hash);
313 BOOST_REQUIRE(txindex.BlockUntilSyncedToCurrentChain());
314
315 // The disconnected transaction is still found, in the now-stale block.
316 BOOST_CHECK(LookupTx(txindex, unique_txid) == stale_block_hash);
317 {
318 LOCK(cs_main);
319 const CBlockIndex* stale_index{chainman.m_blockman.LookupBlockIndex(stale_block_hash)};
320 BOOST_REQUIRE(stale_index);
321 BOOST_CHECK(!chainman.ActiveChain().Contains(*stale_index));
322 }
323
324 // Mine the same transaction into a replacement branch, which gets a later
325 // sequence number. The lookup must now return the branch block in the active chain.
326 const uint256 branch_block_hash{CreateAndProcessBlock({unique_mtx}, CScript() << OP_TRUE).GetHash()};
327 CreateAndProcessBlock({}, coinbase_script);
328 BOOST_REQUIRE(txindex.BlockUntilSyncedToCurrentChain());
329 BOOST_CHECK(LookupTx(txindex, unique_txid) == branch_block_hash);
330
331 // Reorg back to the original branch. The original branch block must be
332 // now be preferred even though the replacement branch has a later sequence.
333 {
334 LOCK(cs_main);
335 chainman.ActiveChainstate().ResetBlockFailureFlags(chainman.m_blockman.LookupBlockIndex(stale_block_hash));
336 }
337 InvalidateBlock(chainman, branch_block_hash);
338 {
340 BOOST_REQUIRE(chainman.ActiveChainstate().ActivateBestChain(state));
341 }
342 BOOST_REQUIRE(txindex.BlockUntilSyncedToCurrentChain());
343 BOOST_CHECK(WITH_LOCK(cs_main, return chainman.ActiveChain().Tip()->GetBlockHash()) == stale_block_hash);
344
345 BOOST_CHECK(LookupTx(txindex, unique_txid) == stale_block_hash);
346
347 // Reconnecting the original block must not create duplicate entries.
348 const auto reorg_bucket{BucketPositions(db, prefix)};
349 BOOST_REQUIRE_EQUAL(reorg_bucket.size(), 2U);
350 BOOST_CHECK(reorg_bucket.front() == original_bucket.front());
351
352 txindex.Stop();
353}
354
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a Bitcoin scriptPubKey for the given CTxDestination.
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
constexpr CAmount COIN
The amount of satoshis in one BTC.
Definition: amount.h:15
ArgsManager gArgs
Definition: args.cpp:38
node::NodeContext m_node
Definition: bitcoin-gui.cpp:47
void InvalidateBlock(ChainstateManager &chainman, const uint256 block_hash)
const CChainParams & Params()
Return the currently selected parameters.
#define Assert(val)
Identity function.
Definition: check.h:116
fs::path GetDataDirNet() const EXCLUSIVE_LOCKS_REQUIRED(!cs_args)
Get data directory path with appended network identifier.
Definition: args.cpp:328
Nodes collect new transactions into a block, hash them into a hash tree, and scan through nonce value...
Definition: block.h:27
Definition: block.h:74
std::vector< CTransactionRef > vtx
Definition: block.h:77
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: chain.h:94
uint256 GetBlockHash() const
Definition: chain.h:198
bool Contains(const CBlockIndex &index) const
Efficiently check whether a block is present in this chain.
Definition: chain.h:410
CBlockIndex * Tip() const
Returns the index entry for the tip of this chain, or nullptr if none.
Definition: chain.h:396
const CBlock & GenesisBlock() const
Definition: chainparams.h:94
Batch of changes queued to be written to a CDBWrapper.
Definition: dbwrapper.h:88
bool Read(const K &key, V &value) const
Definition: dbwrapper.h:220
CDBIterator * NewIterator()
Definition: dbwrapper.cpp:404
void Erase(const K &key, bool fSync=false)
Definition: dbwrapper.h:257
void WriteBatch(CDBBatch &batch, bool fSync=false)
Definition: dbwrapper.cpp:312
void Write(const K &key, const V &value, bool fSync=false)
Definition: dbwrapper.h:240
Serialized script, used inside transaction inputs and outputs.
Definition: script.h:406
The basic transaction that is broadcasted on the network and contained in blocks.
Definition: transaction.h:281
const Txid & GetHash() const LIFETIMEBOUND
Definition: transaction.h:328
bool PreciousBlock(BlockValidationState &state, CBlockIndex *pindex) LOCKS_EXCLUDED(bool InvalidateBlock(BlockValidationState &state, CBlockIndex *pindex) LOCKS_EXCLUDED(void SetBlockFailureFlags(CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(voi ResetBlockFailureFlags)(CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
Mark a block as precious and reorganize.
Definition: validation.h:808
Interface for managing multiple Chainstate objects, where each chainstate is associated with chainsta...
Definition: validation.h:945
Chainstate & ActiveChainstate() const
Alternatives to CurrentChainstate() used by older code to query latest chainstate information without...
CChain & ActiveChain() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex())
Definition: validation.h:1173
node::BlockManager m_blockman
A single BlockManager instance is shared across each constructed chainstate to avoid duplicating bloc...
Definition: validation.h:1043
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:165
A custom weaker variant of SipHash-1-3 without padding, and supporting "jumbo" inputs.
Definition: siphash.h:161
TxIndex is used to look up transactions included in the blockchain by hash.
Definition: txindex.h:37
static CBlockLocator ReadBestBlock(const TxIndex &txindex)
static CDBWrapper & GetDB(const TxIndex &txindex)
static void WriteBestBlock(const TxIndex &txindex, const CBlockLocator &locator)
CBlockIndex * LookupBlockIndex(const uint256 &hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
256-bit opaque blob.
Definition: uint256.h:196
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate.
Definition: cs_main.cpp:8
BOOST_AUTO_TEST_SUITE_END()
std::string HexStr(const std::span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
Definition: hex_base.cpp:30
headers Read(reader)
BOOST_CHECK_EQUAL(headers.FindFirst("key"), "value")
std::unique_ptr< Chain > MakeChain(node::NodeContext &node)
Return implementation of Chain interface.
const std::string DB_TXID_HASH_SALT
Definition: txindex_key.h:39
constexpr std::array< std::byte, 0 > EMPTY_VALUE
Empty value of a hashed txindex row, whose position is encoded in its key.
Definition: txindex_key.h:45
TxHashKeyPrefix CreateKeyPrefix(const SipHasher13UJ &hasher, const Txid &txid)
Definition: txindex_key.h:102
constexpr uint32_t BLOCK_HEADER_SIZE
Serialized size of a block header, the offset of the first byte after it.
Definition: txindex_key.h:48
uint64_t TxHashKeyPrefix
Definition: txindex_key.h:100
std::pair< uint8_t, uint256 > LegacyTxKey(const Txid &txid)
Key of a legacy (pre-hashing) txindex row: the full txid under the 't' prefix.
Definition: txindex_key.h:121
#define BOOST_CHECK(expr)
Definition: object.cpp:16
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Definition: transaction.h:404
const char * prefix
Definition: rest.cpp:1180
@ OP_CHECKSIG
Definition: script.h:191
@ OP_TRUE
Definition: script.h:85
std::vector< unsigned char > ToByteVector(const T &in)
Definition: script.h:68
uint64_t GetSerializeSize(const T &t)
Definition: serialize.h:1157
std::vector< Byte > ParseHex(std::string_view hex_str)
Like TryParseHex, but returns an empty vector on invalid input.
Definition: strencodings.h:69
Describes a place in the block chain to another node such that if the other node doesn't have the sam...
Definition: block.h:117
std::vector< uint256 > vHave
Definition: block.h:127
A mutable version of CTransaction.
Definition: transaction.h:358
Application-specific storage settings.
Definition: dbwrapper.h:41
fs::path path
Location in the filesystem where leveldb data will be stored.
Definition: dbwrapper.h:43
Testing fixture that pre-creates a 100-block REGTEST-mode block chain.
Definition: setup_common.h:139
std::unique_ptr< ChainstateManager > chainman
Definition: context.h:76
Key for looking up the hash of the block with the given sequence number.
Definition: txindex_key.h:74
The location of a transaction: the sequence number of the block that contains it and the transaction'...
Definition: txindex_key.h:55
#define LOCK(cs)
Definition: sync.h:268
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
Definition: sync.h:299
CDBWrapper db
Definition: dbwrapper.cpp:371
BOOST_FIXTURE_TEST_CASE(txindex_initial_sync, TestChain100Setup)
BOOST_AUTO_TEST_CASE(txindex_position_encoding)