/ src / test / blockfilter_index_tests.cpp
blockfilter_index_tests.cpp
  1  // Copyright (c) 2017-2022 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/setup_common.h>
 16  #include <validation.h>
 17  
 18  #include <boost/test/unit_test.hpp>
 19  #include <future>
 20  
 21  using node::BlockAssembler;
 22  using node::BlockManager;
 23  using node::CBlockTemplate;
 24  
 25  BOOST_AUTO_TEST_SUITE(blockfilter_index_tests)
 26  
 27  struct BuildChainTestingSetup : public TestChain100Setup {
 28      CBlock CreateBlock(const CBlockIndex* prev, const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey);
 29      bool BuildChain(const CBlockIndex* pindex, const CScript& coinbase_script_pub_key, size_t length, std::vector<std::shared_ptr<CBlock>>& chain);
 30  };
 31  
 32  static bool CheckFilterLookups(BlockFilterIndex& filter_index, const CBlockIndex* block_index,
 33                                 uint256& last_header, const BlockManager& blockman)
 34  {
 35      BlockFilter expected_filter;
 36      if (!ComputeFilter(filter_index.GetFilterType(), *block_index, expected_filter, blockman)) {
 37          BOOST_ERROR("ComputeFilter failed on block " << block_index->nHeight);
 38          return false;
 39      }
 40  
 41      BlockFilter filter;
 42      uint256 filter_header;
 43      std::vector<BlockFilter> filters;
 44      std::vector<uint256> filter_hashes;
 45  
 46      BOOST_CHECK(filter_index.LookupFilter(block_index, filter));
 47      BOOST_CHECK(filter_index.LookupFilterHeader(block_index, filter_header));
 48      BOOST_CHECK(filter_index.LookupFilterRange(block_index->nHeight, block_index, filters));
 49      BOOST_CHECK(filter_index.LookupFilterHashRange(block_index->nHeight, block_index,
 50                                                     filter_hashes));
 51  
 52      BOOST_CHECK_EQUAL(filters.size(), 1U);
 53      BOOST_CHECK_EQUAL(filter_hashes.size(), 1U);
 54  
 55      BOOST_CHECK_EQUAL(filter.GetHash(), expected_filter.GetHash());
 56      BOOST_CHECK_EQUAL(filter_header, expected_filter.ComputeHeader(last_header));
 57      BOOST_CHECK_EQUAL(filters[0].GetHash(), expected_filter.GetHash());
 58      BOOST_CHECK_EQUAL(filter_hashes[0], expected_filter.GetHash());
 59  
 60      filters.clear();
 61      filter_hashes.clear();
 62      last_header = filter_header;
 63      return true;
 64  }
 65  
 66  CBlock BuildChainTestingSetup::CreateBlock(const CBlockIndex* prev,
 67      const std::vector<CMutableTransaction>& txns,
 68      const CScript& scriptPubKey)
 69  {
 70      BlockAssembler::Options options;
 71      options.coinbase_output_script = scriptPubKey;
 72      std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get(), options}.CreateNewBlock();
 73      CBlock& block = pblocktemplate->block;
 74      block.hashPrevBlock = prev->GetBlockHash();
 75      block.nTime = prev->nTime + 1;
 76  
 77      // Replace mempool-selected txns with just coinbase plus passed-in txns:
 78      block.vtx.resize(1);
 79      for (const CMutableTransaction& tx : txns) {
 80          block.vtx.push_back(MakeTransactionRef(tx));
 81      }
 82      {
 83          CMutableTransaction tx_coinbase{*block.vtx.at(0)};
 84          tx_coinbase.nLockTime = static_cast<uint32_t>(prev->nHeight);
 85          tx_coinbase.vin.at(0).scriptSig = CScript{} << prev->nHeight + 1;
 86          block.vtx.at(0) = MakeTransactionRef(std::move(tx_coinbase));
 87          block.hashMerkleRoot = BlockMerkleRoot(block);
 88      }
 89  
 90      while (!CheckProofOfWork(block.GetHash(), block.nBits, m_node.chainman->GetConsensus())) ++block.nNonce;
 91  
 92      return block;
 93  }
 94  
 95  bool BuildChainTestingSetup::BuildChain(const CBlockIndex* pindex,
 96      const CScript& coinbase_script_pub_key,
 97      size_t length,
 98      std::vector<std::shared_ptr<CBlock>>& chain)
 99  {
100      std::vector<CMutableTransaction> no_txns;
101  
102      chain.resize(length);
103      for (auto& block : chain) {
104          block = std::make_shared<CBlock>(CreateBlock(pindex, no_txns, coinbase_script_pub_key));
105          CBlockHeader header = block->GetBlockHeader();
106  
107          BlockValidationState state;
108          if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({{header}}, true, state, &pindex)) {
109              return false;
110          }
111      }
112  
113      return true;
114  }
115  
116  BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
117  {
118      BlockFilterIndex filter_index(interfaces::MakeChain(m_node), BlockFilterType::BASIC, 1 << 20, true);
119      BOOST_REQUIRE(filter_index.Init());
120  
121      uint256 last_header;
122  
123      // Filter should not be found in the index before it is started.
124      {
125          LOCK(cs_main);
126  
127          BlockFilter filter;
128          uint256 filter_header;
129          std::vector<BlockFilter> filters;
130          std::vector<uint256> filter_hashes;
131  
132          for (const CBlockIndex* block_index = m_node.chainman->ActiveChain().Genesis();
133               block_index != nullptr;
134               block_index = m_node.chainman->ActiveChain().Next(block_index)) {
135              BOOST_CHECK(!filter_index.LookupFilter(block_index, filter));
136              BOOST_CHECK(!filter_index.LookupFilterHeader(block_index, filter_header));
137              BOOST_CHECK(!filter_index.LookupFilterRange(block_index->nHeight, block_index, filters));
138              BOOST_CHECK(!filter_index.LookupFilterHashRange(block_index->nHeight, block_index,
139                                                              filter_hashes));
140          }
141      }
142  
143      // BlockUntilSyncedToCurrentChain should return false before index is started.
144      BOOST_CHECK(!filter_index.BlockUntilSyncedToCurrentChain());
145  
146      filter_index.Sync();
147  
148      // Check that filter index has all blocks that were in the chain before it started.
149      {
150          LOCK(cs_main);
151          const CBlockIndex* block_index;
152          for (block_index = m_node.chainman->ActiveChain().Genesis();
153               block_index != nullptr;
154               block_index = m_node.chainman->ActiveChain().Next(block_index)) {
155              CheckFilterLookups(filter_index, block_index, last_header, m_node.chainman->m_blockman);
156          }
157      }
158  
159      // Create two forks.
160      const CBlockIndex* tip;
161      {
162          LOCK(cs_main);
163          tip = m_node.chainman->ActiveChain().Tip();
164      }
165      CKey coinbase_key_A = GenerateRandomKey();
166      CKey coinbase_key_B = GenerateRandomKey();
167      CScript coinbase_script_pub_key_A = GetScriptForDestination(PKHash(coinbase_key_A.GetPubKey()));
168      CScript coinbase_script_pub_key_B = GetScriptForDestination(PKHash(coinbase_key_B.GetPubKey()));
169      std::vector<std::shared_ptr<CBlock>> chainA, chainB;
170      BOOST_REQUIRE(BuildChain(tip, coinbase_script_pub_key_A, 10, chainA));
171      BOOST_REQUIRE(BuildChain(tip, coinbase_script_pub_key_B, 10, chainB));
172  
173      // Check that new blocks on chain A get indexed.
174      uint256 chainA_last_header = last_header;
175      for (size_t i = 0; i < 2; i++) {
176          const auto& block = chainA[i];
177          BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr));
178      }
179      for (size_t i = 0; i < 2; i++) {
180          const auto& block = chainA[i];
181          const CBlockIndex* block_index;
182          {
183              LOCK(cs_main);
184              block_index = m_node.chainman->m_blockman.LookupBlockIndex(block->GetHash());
185          }
186  
187          BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain());
188          CheckFilterLookups(filter_index, block_index, chainA_last_header, m_node.chainman->m_blockman);
189      }
190  
191      // Reorg to chain B.
192      uint256 chainB_last_header = last_header;
193      for (size_t i = 0; i < 3; i++) {
194          const auto& block = chainB[i];
195          BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr));
196      }
197      for (size_t i = 0; i < 3; i++) {
198          const auto& block = chainB[i];
199          const CBlockIndex* block_index;
200          {
201              LOCK(cs_main);
202              block_index = m_node.chainman->m_blockman.LookupBlockIndex(block->GetHash());
203          }
204  
205          BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain());
206          CheckFilterLookups(filter_index, block_index, chainB_last_header, m_node.chainman->m_blockman);
207      }
208  
209      // Check that filters for stale blocks on A can be retrieved.
210      chainA_last_header = last_header;
211      for (size_t i = 0; i < 2; i++) {
212          const auto& block = chainA[i];
213          const CBlockIndex* block_index;
214          {
215              LOCK(cs_main);
216              block_index = m_node.chainman->m_blockman.LookupBlockIndex(block->GetHash());
217          }
218  
219          BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain());
220          CheckFilterLookups(filter_index, block_index, chainA_last_header, m_node.chainman->m_blockman);
221      }
222  
223      // Reorg back to chain A.
224       for (size_t i = 2; i < 4; i++) {
225           const auto& block = chainA[i];
226           BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr));
227       }
228  
229       // Check that chain A and B blocks can be retrieved.
230       chainA_last_header = last_header;
231       chainB_last_header = last_header;
232       for (size_t i = 0; i < 3; i++) {
233           const CBlockIndex* block_index;
234  
235           {
236               LOCK(cs_main);
237               block_index = m_node.chainman->m_blockman.LookupBlockIndex(chainA[i]->GetHash());
238           }
239           BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain());
240           CheckFilterLookups(filter_index, block_index, chainA_last_header, m_node.chainman->m_blockman);
241  
242           {
243               LOCK(cs_main);
244               block_index = m_node.chainman->m_blockman.LookupBlockIndex(chainB[i]->GetHash());
245           }
246           BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain());
247           CheckFilterLookups(filter_index, block_index, chainB_last_header, m_node.chainman->m_blockman);
248       }
249  
250      // Test lookups for a range of filters/hashes.
251      std::vector<BlockFilter> filters;
252      std::vector<uint256> filter_hashes;
253  
254      {
255          LOCK(cs_main);
256          tip = m_node.chainman->ActiveChain().Tip();
257      }
258      BOOST_CHECK(filter_index.LookupFilterRange(0, tip, filters));
259      BOOST_CHECK(filter_index.LookupFilterHashRange(0, tip, filter_hashes));
260  
261      assert(tip->nHeight >= 0);
262      BOOST_CHECK_EQUAL(filters.size(), tip->nHeight + 1U);
263      BOOST_CHECK_EQUAL(filter_hashes.size(), tip->nHeight + 1U);
264  
265      filters.clear();
266      filter_hashes.clear();
267  
268      filter_index.Interrupt();
269      filter_index.Stop();
270  }
271  
272  BOOST_FIXTURE_TEST_CASE(blockfilter_index_init_destroy, BasicTestingSetup)
273  {
274      BlockFilterIndex* filter_index;
275  
276      filter_index = GetBlockFilterIndex(BlockFilterType::BASIC);
277      BOOST_CHECK(filter_index == nullptr);
278  
279      BOOST_CHECK(InitBlockFilterIndex([&]{ return interfaces::MakeChain(m_node); }, BlockFilterType::BASIC, 1 << 20, true, false));
280  
281      filter_index = GetBlockFilterIndex(BlockFilterType::BASIC);
282      BOOST_CHECK(filter_index != nullptr);
283      BOOST_CHECK(filter_index->GetFilterType() == BlockFilterType::BASIC);
284  
285      // Initialize returns false if index already exists.
286      BOOST_CHECK(!InitBlockFilterIndex([&]{ return interfaces::MakeChain(m_node); }, BlockFilterType::BASIC, 1 << 20, true, false));
287  
288      int iter_count = 0;
289      ForEachBlockFilterIndex([&iter_count](BlockFilterIndex& _index) { iter_count++; });
290      BOOST_CHECK_EQUAL(iter_count, 1);
291  
292      BOOST_CHECK(DestroyBlockFilterIndex(BlockFilterType::BASIC));
293  
294      // Destroy returns false because index was already destroyed.
295      BOOST_CHECK(!DestroyBlockFilterIndex(BlockFilterType::BASIC));
296  
297      filter_index = GetBlockFilterIndex(BlockFilterType::BASIC);
298      BOOST_CHECK(filter_index == nullptr);
299  
300      // Reinitialize index.
301      BOOST_CHECK(InitBlockFilterIndex([&]{ return interfaces::MakeChain(m_node); }, BlockFilterType::BASIC, 1 << 20, true, false));
302  
303      DestroyAllBlockFilterIndexes();
304  
305      filter_index = GetBlockFilterIndex(BlockFilterType::BASIC);
306      BOOST_CHECK(filter_index == nullptr);
307  }
308  
309  class IndexReorgCrash : public BaseIndex
310  {
311  private:
312      std::unique_ptr<BaseIndex::DB> m_db;
313      std::shared_future<void> m_blocker;
314      int m_blocking_height;
315  
316  public:
317      explicit IndexReorgCrash(std::unique_ptr<interfaces::Chain> chain, std::shared_future<void> blocker,
318                               int blocking_height) : BaseIndex(std::move(chain), "test index"), m_blocker(blocker),
319                                                      m_blocking_height(blocking_height)
320      {
321          const fs::path path = gArgs.GetDataDirNet() / "index";
322          fs::create_directories(path);
323          m_db = std::make_unique<BaseIndex::DB>(path / "db", /*n_cache_size=*/0, /*f_memory=*/true, /*f_wipe=*/false);
324      }
325  
326      bool AllowPrune() const override { return false; }
327      BaseIndex::DB& GetDB() const override { return *m_db; }
328  
329      bool CustomAppend(const interfaces::BlockInfo& block) override
330      {
331          // Simulate a delay so new blocks can get connected during the initial sync
332          if (block.height == m_blocking_height) m_blocker.wait();
333  
334          // Move mock time forward so the best index gets updated only when we are not at the blocking height
335          if (block.height == m_blocking_height - 1 || block.height > m_blocking_height) {
336              SetMockTime(GetTime<std::chrono::seconds>() + 31s);
337          }
338  
339          return true;
340      }
341  };
342  
343  BOOST_FIXTURE_TEST_CASE(index_reorg_crash, BuildChainTestingSetup)
344  {
345      // Enable mock time
346      SetMockTime(GetTime<std::chrono::minutes>());
347  
348      std::promise<void> promise;
349      std::shared_future<void> blocker(promise.get_future());
350      int blocking_height = WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->nHeight);
351  
352      IndexReorgCrash index(interfaces::MakeChain(m_node), blocker, blocking_height);
353      BOOST_REQUIRE(index.Init());
354      BOOST_REQUIRE(index.StartBackgroundSync());
355  
356      auto func_wait_until = [&](int height, std::chrono::milliseconds timeout) {
357          auto deadline = std::chrono::steady_clock::now() + timeout;
358          while (index.GetSummary().best_block_height < height) {
359              if (std::chrono::steady_clock::now() > deadline) {
360                  BOOST_FAIL(strprintf("Timeout waiting for index height %d (current: %d)", height, index.GetSummary().best_block_height));
361                  return;
362              }
363              std::this_thread::sleep_for(100ms);
364          }
365      };
366  
367      // Wait until the index is one block before the fork point
368      func_wait_until(blocking_height - 1, /*timeout=*/5s);
369  
370      // Create a fork to trigger the reorg
371      std::vector<std::shared_ptr<CBlock>> fork;
372      const CBlockIndex* prev_tip = WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->pprev);
373      BOOST_REQUIRE(BuildChain(prev_tip, GetScriptForDestination(PKHash(GenerateRandomKey().GetPubKey())), 3, fork));
374  
375      for (const auto& block : fork) {
376          BOOST_REQUIRE(m_node.chainman->ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr));
377      }
378  
379      // Unblock the index thread so it can process the reorg
380      promise.set_value();
381      // Wait for the index to reach the new tip
382      func_wait_until(blocking_height + 2, 5s);
383      index.Stop();
384  }
385  
386  BOOST_AUTO_TEST_SUITE_END()