Bitcoin Core  31.0.0
P2P Digital Currency
blockfilter_index_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 <blockfilter.h>
7 #include <chainparams.h>
8 #include <consensus/merkle.h>
9 #include <consensus/validation.h>
10 #include <index/blockfilterindex.h>
11 #include <interfaces/chain.h>
12 #include <node/miner.h>
13 #include <pow.h>
14 #include <test/util/blockfilter.h>
15 #include <test/util/common.h>
16 #include <test/util/setup_common.h>
17 #include <validation.h>
18 
19 #include <boost/test/unit_test.hpp>
20 #include <future>
21 
23 using node::BlockManager;
25 
26 BOOST_AUTO_TEST_SUITE(blockfilter_index_tests)
27 
29  CBlock CreateBlock(const CBlockIndex* prev, const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey);
30  bool BuildChain(const CBlockIndex* pindex, const CScript& coinbase_script_pub_key, size_t length, std::vector<std::shared_ptr<CBlock>>& chain);
31 };
32 
33 static bool CheckFilterLookups(BlockFilterIndex& filter_index, const CBlockIndex* block_index,
34  uint256& last_header, const BlockManager& blockman)
35 {
36  BlockFilter expected_filter;
37  if (!ComputeFilter(filter_index.GetFilterType(), *block_index, expected_filter, blockman)) {
38  BOOST_ERROR("ComputeFilter failed on block " << block_index->nHeight);
39  return false;
40  }
41 
42  BlockFilter filter;
43  uint256 filter_header;
44  std::vector<BlockFilter> filters;
45  std::vector<uint256> filter_hashes;
46 
47  BOOST_CHECK(filter_index.LookupFilter(block_index, filter));
48  BOOST_CHECK(filter_index.LookupFilterHeader(block_index, filter_header));
49  BOOST_CHECK(filter_index.LookupFilterRange(block_index->nHeight, block_index, filters));
50  BOOST_CHECK(filter_index.LookupFilterHashRange(block_index->nHeight, block_index,
51  filter_hashes));
52 
53  BOOST_CHECK_EQUAL(filters.size(), 1U);
54  BOOST_CHECK_EQUAL(filter_hashes.size(), 1U);
55 
56  BOOST_CHECK_EQUAL(filter.GetHash(), expected_filter.GetHash());
57  BOOST_CHECK_EQUAL(filter_header, expected_filter.ComputeHeader(last_header));
58  BOOST_CHECK_EQUAL(filters[0].GetHash(), expected_filter.GetHash());
59  BOOST_CHECK_EQUAL(filter_hashes[0], expected_filter.GetHash());
60 
61  filters.clear();
62  filter_hashes.clear();
63  last_header = filter_header;
64  return true;
65 }
66 
68  const std::vector<CMutableTransaction>& txns,
69  const CScript& scriptPubKey)
70 {
71  BlockAssembler::Options options;
72  options.coinbase_output_script = scriptPubKey;
73  options.include_dummy_extranonce = true;
74  std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get(), options}.CreateNewBlock();
75  CBlock& block = pblocktemplate->block;
76  block.hashPrevBlock = prev->GetBlockHash();
77  block.nTime = prev->nTime + 1;
78 
79  // Replace mempool-selected txns with just coinbase plus passed-in txns:
80  block.vtx.resize(1);
81  for (const CMutableTransaction& tx : txns) {
82  block.vtx.push_back(MakeTransactionRef(tx));
83  }
84  {
85  CMutableTransaction tx_coinbase{*block.vtx.at(0)};
86  tx_coinbase.nLockTime = static_cast<uint32_t>(prev->nHeight);
87  tx_coinbase.vin.at(0).scriptSig = CScript{} << prev->nHeight + 1;
88  block.vtx.at(0) = MakeTransactionRef(std::move(tx_coinbase));
89  block.hashMerkleRoot = BlockMerkleRoot(block);
90  }
91 
92  while (!CheckProofOfWork(block.GetHash(), block.nBits, m_node.chainman->GetConsensus())) ++block.nNonce;
93 
94  return block;
95 }
96 
98  const CScript& coinbase_script_pub_key,
99  size_t length,
100  std::vector<std::shared_ptr<CBlock>>& chain)
101 {
102  std::vector<CMutableTransaction> no_txns;
103 
104  chain.resize(length);
105  for (auto& block : chain) {
106  block = std::make_shared<CBlock>(CreateBlock(pindex, no_txns, coinbase_script_pub_key));
107 
108  BlockValidationState state;
109  if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({{*block}}, true, state, &pindex)) {
110  return false;
111  }
112  }
113 
114  return true;
115 }
116 
117 BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
118 {
120  BOOST_REQUIRE(filter_index.Init());
121 
122  uint256 last_header;
123 
124  // Filter should not be found in the index before it is started.
125  {
126  LOCK(cs_main);
127 
128  BlockFilter filter;
129  uint256 filter_header;
130  std::vector<BlockFilter> filters;
131  std::vector<uint256> filter_hashes;
132 
133  for (const CBlockIndex* block_index = m_node.chainman->ActiveChain().Genesis();
134  block_index != nullptr;
135  block_index = m_node.chainman->ActiveChain().Next(block_index)) {
136  BOOST_CHECK(!filter_index.LookupFilter(block_index, filter));
137  BOOST_CHECK(!filter_index.LookupFilterHeader(block_index, filter_header));
138  BOOST_CHECK(!filter_index.LookupFilterRange(block_index->nHeight, block_index, filters));
139  BOOST_CHECK(!filter_index.LookupFilterHashRange(block_index->nHeight, block_index,
140  filter_hashes));
141  }
142  }
143 
144  // BlockUntilSyncedToCurrentChain should return false before index is started.
145  BOOST_CHECK(!filter_index.BlockUntilSyncedToCurrentChain());
146 
147  filter_index.Sync();
148 
149  // Check that filter index has all blocks that were in the chain before it started.
150  {
151  LOCK(cs_main);
152  const CBlockIndex* block_index;
153  for (block_index = m_node.chainman->ActiveChain().Genesis();
154  block_index != nullptr;
155  block_index = m_node.chainman->ActiveChain().Next(block_index)) {
156  CheckFilterLookups(filter_index, block_index, last_header, m_node.chainman->m_blockman);
157  }
158  }
159 
160  // Create two forks.
161  const CBlockIndex* tip;
162  {
163  LOCK(cs_main);
164  tip = m_node.chainman->ActiveChain().Tip();
165  }
166  CKey coinbase_key_A = GenerateRandomKey();
167  CKey coinbase_key_B = GenerateRandomKey();
168  CScript coinbase_script_pub_key_A = GetScriptForDestination(PKHash(coinbase_key_A.GetPubKey()));
169  CScript coinbase_script_pub_key_B = GetScriptForDestination(PKHash(coinbase_key_B.GetPubKey()));
170  std::vector<std::shared_ptr<CBlock>> chainA, chainB;
171  BOOST_REQUIRE(BuildChain(tip, coinbase_script_pub_key_A, 10, chainA));
172  BOOST_REQUIRE(BuildChain(tip, coinbase_script_pub_key_B, 10, chainB));
173 
174  // Check that new blocks on chain A get indexed.
175  uint256 chainA_last_header = last_header;
176  for (size_t i = 0; i < 2; i++) {
177  const auto& block = chainA[i];
178  BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr));
179  }
180  for (size_t i = 0; i < 2; i++) {
181  const auto& block = chainA[i];
182  const CBlockIndex* block_index;
183  {
184  LOCK(cs_main);
185  block_index = m_node.chainman->m_blockman.LookupBlockIndex(block->GetHash());
186  }
187 
188  BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain());
189  CheckFilterLookups(filter_index, block_index, chainA_last_header, m_node.chainman->m_blockman);
190  }
191 
192  // Reorg to chain B.
193  uint256 chainB_last_header = last_header;
194  for (size_t i = 0; i < 3; i++) {
195  const auto& block = chainB[i];
196  BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr));
197  }
198  for (size_t i = 0; i < 3; i++) {
199  const auto& block = chainB[i];
200  const CBlockIndex* block_index;
201  {
202  LOCK(cs_main);
203  block_index = m_node.chainman->m_blockman.LookupBlockIndex(block->GetHash());
204  }
205 
206  BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain());
207  CheckFilterLookups(filter_index, block_index, chainB_last_header, m_node.chainman->m_blockman);
208  }
209 
210  // Check that filters for stale blocks on A can be retrieved.
211  chainA_last_header = last_header;
212  for (size_t i = 0; i < 2; i++) {
213  const auto& block = chainA[i];
214  const CBlockIndex* block_index;
215  {
216  LOCK(cs_main);
217  block_index = m_node.chainman->m_blockman.LookupBlockIndex(block->GetHash());
218  }
219 
220  BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain());
221  CheckFilterLookups(filter_index, block_index, chainA_last_header, m_node.chainman->m_blockman);
222  }
223 
224  // Reorg back to chain A.
225  for (size_t i = 2; i < 4; i++) {
226  const auto& block = chainA[i];
227  BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr));
228  }
229 
230  // Check that chain A and B blocks can be retrieved.
231  chainA_last_header = last_header;
232  chainB_last_header = last_header;
233  for (size_t i = 0; i < 3; i++) {
234  const CBlockIndex* block_index;
235 
236  {
237  LOCK(cs_main);
238  block_index = m_node.chainman->m_blockman.LookupBlockIndex(chainA[i]->GetHash());
239  }
240  BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain());
241  CheckFilterLookups(filter_index, block_index, chainA_last_header, m_node.chainman->m_blockman);
242 
243  {
244  LOCK(cs_main);
245  block_index = m_node.chainman->m_blockman.LookupBlockIndex(chainB[i]->GetHash());
246  }
247  BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain());
248  CheckFilterLookups(filter_index, block_index, chainB_last_header, m_node.chainman->m_blockman);
249  }
250 
251  // Test lookups for a range of filters/hashes.
252  std::vector<BlockFilter> filters;
253  std::vector<uint256> filter_hashes;
254 
255  {
256  LOCK(cs_main);
257  tip = m_node.chainman->ActiveChain().Tip();
258  }
259  BOOST_CHECK(filter_index.LookupFilterRange(0, tip, filters));
260  BOOST_CHECK(filter_index.LookupFilterHashRange(0, tip, filter_hashes));
261 
262  assert(tip->nHeight >= 0);
263  BOOST_CHECK_EQUAL(filters.size(), tip->nHeight + 1U);
264  BOOST_CHECK_EQUAL(filter_hashes.size(), tip->nHeight + 1U);
265 
266  filters.clear();
267  filter_hashes.clear();
268 
269  filter_index.Interrupt();
270  filter_index.Stop();
271 }
272 
273 BOOST_FIXTURE_TEST_CASE(blockfilter_index_init_destroy, BasicTestingSetup)
274 {
275  BlockFilterIndex* filter_index;
276 
278  BOOST_CHECK(filter_index == nullptr);
279 
281 
283  BOOST_CHECK(filter_index != nullptr);
285 
286  // Initialize returns false if index already exists.
287  BOOST_CHECK(!InitBlockFilterIndex([&]{ return interfaces::MakeChain(m_node); }, BlockFilterType::BASIC, 1 << 20, true, false));
288 
289  int iter_count = 0;
290  ForEachBlockFilterIndex([&iter_count](BlockFilterIndex& _index) { iter_count++; });
291  BOOST_CHECK_EQUAL(iter_count, 1);
292 
294 
295  // Destroy returns false because index was already destroyed.
297 
299  BOOST_CHECK(filter_index == nullptr);
300 
301  // Reinitialize index.
303 
305 
307  BOOST_CHECK(filter_index == nullptr);
308 }
309 
311 {
312 private:
313  std::unique_ptr<BaseIndex::DB> m_db;
314  std::shared_future<void> m_blocker;
316 
317 public:
318  explicit IndexReorgCrash(std::unique_ptr<interfaces::Chain> chain, std::shared_future<void> blocker,
319  int blocking_height) : BaseIndex(std::move(chain), "test index"), m_blocker(blocker),
320  m_blocking_height(blocking_height)
321  {
322  const fs::path path = gArgs.GetDataDirNet() / "index";
323  fs::create_directories(path);
324  m_db = std::make_unique<BaseIndex::DB>(path / "db", /*n_cache_size=*/0, /*f_memory=*/true, /*f_wipe=*/false);
325  }
326 
327  bool AllowPrune() const override { return false; }
328  BaseIndex::DB& GetDB() const override { return *m_db; }
329 
330  bool CustomAppend(const interfaces::BlockInfo& block) override
331  {
332  // Simulate a delay so new blocks can get connected during the initial sync
333  if (block.height == m_blocking_height) m_blocker.wait();
334 
335  // Move mock time forward so the best index gets updated only when we are not at the blocking height
336  if (block.height == m_blocking_height - 1 || block.height > m_blocking_height) {
337  SetMockTime(GetTime<std::chrono::seconds>() + 31s);
338  }
339 
340  return true;
341  }
342 };
343 
345 {
346  // Enable mock time
347  SetMockTime(GetTime<std::chrono::minutes>());
348 
349  std::promise<void> promise;
350  std::shared_future<void> blocker(promise.get_future());
351  int blocking_height = WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->nHeight);
352 
353  IndexReorgCrash index(interfaces::MakeChain(m_node), blocker, blocking_height);
354  BOOST_REQUIRE(index.Init());
355  BOOST_REQUIRE(index.StartBackgroundSync());
356 
357  auto func_wait_until = [&](int height, std::chrono::milliseconds timeout) {
358  auto deadline = std::chrono::steady_clock::now() + timeout;
359  while (index.GetSummary().best_block_height < height) {
360  if (std::chrono::steady_clock::now() > deadline) {
361  BOOST_FAIL(strprintf("Timeout waiting for index height %d (current: %d)", height, index.GetSummary().best_block_height));
362  return;
363  }
364  std::this_thread::sleep_for(100ms);
365  }
366  };
367 
368  // Wait until the index is one block before the fork point
369  func_wait_until(blocking_height - 1, /*timeout=*/5s);
370 
371  // Create a fork to trigger the reorg
372  std::vector<std::shared_ptr<CBlock>> fork;
373  const CBlockIndex* prev_tip = WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->pprev);
374  BOOST_REQUIRE(BuildChain(prev_tip, GetScriptForDestination(PKHash(GenerateRandomKey().GetPubKey())), 3, fork));
375 
376  for (const auto& block : fork) {
377  BOOST_REQUIRE(m_node.chainman->ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr));
378  }
379 
380  // Unblock the index thread so it can process the reorg
381  promise.set_value();
382  // Wait for the index to reach the new tip
383  func_wait_until(blocking_height + 2, 5s);
384  index.Stop();
385 }
386 
uint32_t nNonce
Definition: block.h:35
bool LookupFilter(const CBlockIndex *block_index, BlockFilter &filter_out) const
Get a single filter by block.
Interval between compact filter checkpoints.
IndexReorgCrash(std::unique_ptr< interfaces::Chain > chain, std::shared_future< void > blocker, int blocking_height)
bool Init()
Initializes the sync state and registers the instance to the validation interface so that it stays in...
Definition: base.cpp:104
bool InitBlockFilterIndex(std::function< std::unique_ptr< interfaces::Chain >()> make_chain, BlockFilterType filter_type, size_t n_cache_size, bool f_memory, bool f_wipe)
Initialize a block filter index for the given type if one does not already exist. ...
assert(!tx.IsCoinBase())
Generate a new block, without valid proof-of-work.
Definition: miner.h:60
Definition: block.h:73
BOOST_FAIL("Test unconditionally fails.")
node::NodeContext m_node
Definition: bitcoin-gui.cpp:43
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1172
CPubKey GetPubKey() const
Compute the public key from a private key.
Definition: key.cpp:183
BlockFilterIndex * GetBlockFilterIndex(BlockFilterType filter_type)
Get a block filter index by type.
void ForEachBlockFilterIndex(std::function< void(BlockFilterIndex &)> fn)
Iterate over all running block filter indexes, invoking fn on each.
Definition: common.h:29
uint32_t nTime
Definition: chain.h:142
uint32_t nTime
Definition: block.h:33
uint256 GetHash() const
Compute the filter hash.
void Stop()
Stops the instance from staying in sync with blockchain updates.
Definition: base.cpp:461
The database stores a block locator of the chain the database is synced to so that the index can effi...
Definition: base.h:64
Basic testing setup.
Definition: setup_common.h:64
std::unique_ptr< CTxMemPool > mempool
Definition: context.h:68
bool CustomAppend(const interfaces::BlockInfo &block) override
Write update index entries for a newly connected block.
CKey GenerateRandomKey(bool compressed) noexcept
Definition: key.cpp:475
uint256 GetBlockHash() const
Definition: chain.h:198
static bool CheckFilterLookups(BlockFilterIndex &filter_index, const CBlockIndex *block_index, uint256 &last_header, const BlockManager &blockman)
Block data sent with blockConnected, blockDisconnected notifications.
Definition: chain.h:19
bool ComputeFilter(BlockFilterType filter_type, const CBlockIndex &block_index, BlockFilter &filter, const BlockManager &blockman)
Definition: blockfilter.cpp:15
Base class for indices of blockchain data.
Definition: base.h:54
fs::path GetDataDirNet() const
Get data directory path with appended network identifier.
Definition: args.h:239
uint256 hashMerkleRoot
Definition: block.h:32
BaseIndex::DB & GetDB() const override
#define LOCK(cs)
Definition: sync.h:258
bool LookupFilterHeader(const CBlockIndex *block_index, uint256 &header_out) EXCLUSIVE_LOCKS_REQUIRED(!m_cs_headers_cache)
Get a single filter header by block.
Complete block filter struct as defined in BIP 157.
Definition: blockfilter.h:115
BOOST_AUTO_TEST_SUITE_END()
void DestroyAllBlockFilterIndexes()
Destroy all open block filter indexes.
void Sync()
Sync the index with the block index starting from the current best block.
Definition: base.cpp:201
uint256 hashPrevBlock
Definition: block.h:31
uint256 BlockMerkleRoot(const CBlock &block, bool *mutated)
Definition: merkle.cpp:66
std::unique_ptr< BaseIndex::DB > m_db
Maintains a tree of blocks (stored in m_block_index) which is consulted to determine where the most-w...
Definition: blockstorage.h:191
bool LookupFilterHashRange(int start_height, const CBlockIndex *stop_index, std::vector< uint256 > &hashes_out) const
Get a range of filter hashes between two heights on a chain.
Testing fixture that pre-creates a 100-block REGTEST-mode block chain.
Definition: setup_common.h:146
bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params &params)
Check whether a block hash satisfies the proof-of-work requirement specified by nBits.
Definition: pow.cpp:140
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
Definition: sync.h:289
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a Bitcoin scriptPubKey for the given CTxDestination.
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Definition: transaction.h:404
ArgsManager gArgs
Definition: args.cpp:40
BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
uint256 GetHash() const
Definition: block.cpp:15
256-bit opaque blob.
Definition: uint256.h:195
bool BuildChain(const CBlockIndex *pindex, const CScript &coinbase_script_pub_key, size_t length, std::vector< std::shared_ptr< CBlock >> &chain)
std::vector< CTransactionRef > vtx
Definition: block.h:77
std::unique_ptr< Chain > MakeChain(node::NodeContext &node)
Return implementation of Chain interface.
bool AllowPrune() const override
#define BOOST_CHECK_EQUAL(v1, v2)
Definition: object.cpp:17
The block chain is a tree shaped structure starting with the genesis block at the root...
Definition: chain.h:93
bool DestroyBlockFilterIndex(BlockFilterType filter_type)
Destroy the block filter index with the given type.
Serialized script, used inside transaction inputs and outputs.
Definition: script.h:404
static bool GetPubKey(const SigningProvider &provider, const SignatureData &sigdata, const CKeyID &address, CPubKey &pubkey)
Definition: sign.cpp:221
BlockFilterType GetFilterType() const
CBlock CreateBlock(const CBlockIndex *prev, const std::vector< CMutableTransaction > &txns, const CScript &scriptPubKey)
void SetMockTime(int64_t nMockTimeIn)
DEPRECATED Use SetMockTime with chrono type.
Definition: time.cpp:44
A mutable version of CTransaction.
Definition: transaction.h:357
An encapsulated private key.
Definition: key.h:35
int nHeight
height of the entry in the chain. The genesis block has height 0
Definition: chain.h:106
Path class wrapper to block calls to the fs::path(std::string) implicit constructor and the fs::path:...
Definition: fs.h:33
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate...
Definition: cs_main.cpp:8
bool LookupFilterRange(int start_height, const CBlockIndex *stop_index, std::vector< BlockFilter > &filters_out) const
Get a range of filters between two heights on a chain.
node::NodeContext m_node
Definition: setup_common.h:66
std::unique_ptr< ChainstateManager > chainman
Definition: context.h:72
std::shared_future< void > m_blocker
#define Assert(val)
Identity function.
Definition: check.h:113
uint32_t nBits
Definition: block.h:34
#define BOOST_CHECK(expr)
Definition: object.cpp:16