/ 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 <validation.h>
 18  
 19  #include <boost/test/unit_test.hpp>
 20  #include <future>
 21  
 22  using node::BlockAssembler;
 23  using node::BlockManager;
 24  using node::CBlockTemplate;
 25  
 26  BOOST_AUTO_TEST_SUITE(blockfilter_index_tests)
 27  
 28  struct BuildChainTestingSetup : public TestChain100Setup {
 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  
 67  CBlock BuildChainTestingSetup::CreateBlock(const CBlockIndex* prev,
 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  
 97  bool BuildChainTestingSetup::BuildChain(const CBlockIndex* pindex,
 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  {
119      BlockFilterIndex filter_index(interfaces::MakeChain(m_node), BlockFilterType::BASIC, 1 << 20, true);
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  
277      filter_index = GetBlockFilterIndex(BlockFilterType::BASIC);
278      BOOST_CHECK(filter_index == nullptr);
279  
280      BOOST_CHECK(InitBlockFilterIndex([&]{ return interfaces::MakeChain(m_node); }, BlockFilterType::BASIC, 1 << 20, true, false));
281  
282      filter_index = GetBlockFilterIndex(BlockFilterType::BASIC);
283      BOOST_CHECK(filter_index != nullptr);
284      BOOST_CHECK(filter_index->GetFilterType() == BlockFilterType::BASIC);
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  
293      BOOST_CHECK(DestroyBlockFilterIndex(BlockFilterType::BASIC));
294  
295      // Destroy returns false because index was already destroyed.
296      BOOST_CHECK(!DestroyBlockFilterIndex(BlockFilterType::BASIC));
297  
298      filter_index = GetBlockFilterIndex(BlockFilterType::BASIC);
299      BOOST_CHECK(filter_index == nullptr);
300  
301      // Reinitialize index.
302      BOOST_CHECK(InitBlockFilterIndex([&]{ return interfaces::MakeChain(m_node); }, BlockFilterType::BASIC, 1 << 20, true, false));
303  
304      DestroyAllBlockFilterIndexes();
305  
306      filter_index = GetBlockFilterIndex(BlockFilterType::BASIC);
307      BOOST_CHECK(filter_index == nullptr);
308  }
309  
310  class IndexReorgCrash : public BaseIndex
311  {
312  private:
313      std::unique_ptr<BaseIndex::DB> m_db;
314      std::shared_future<void> m_blocker;
315      int m_blocking_height;
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  
344  BOOST_FIXTURE_TEST_CASE(index_reorg_crash, BuildChainTestingSetup)
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  
387  BOOST_AUTO_TEST_SUITE_END()