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