blockfilter_index_tests.cpp raw

   1  // Copyright (c) 2017-2022 The Limenka 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/index.h>
  16  #include <test/util/setup_common.h>
  17  #include <validation.h>
  18  
  19  #include <boost/test/unit_test.hpp>
  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      auto pblocktemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get(), options, m_node}.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.vin.at(0).scriptSig = CScript{} << prev->nHeight + 1;
  85          block.vtx.at(0) = MakeTransactionRef(std::move(tx_coinbase));
  86          block.hashMerkleRoot = BlockMerkleRoot(block);
  87      }
  88  
  89      while (!CheckProofOfWork(block.GetHash(), block.nBits, m_node.chainman->GetConsensus())) ++block.nNonce;
  90  
  91      return block;
  92  }
  93  
  94  bool BuildChainTestingSetup::BuildChain(const CBlockIndex* pindex,
  95      const CScript& coinbase_script_pub_key,
  96      size_t length,
  97      std::vector<std::shared_ptr<CBlock>>& chain)
  98  {
  99      std::vector<CMutableTransaction> no_txns;
 100  
 101      chain.resize(length);
 102      for (auto& block : chain) {
 103          block = std::make_shared<CBlock>(CreateBlock(pindex, no_txns, coinbase_script_pub_key));
 104          CBlockHeader header = block->GetBlockHeader();
 105  
 106          BlockValidationState state;
 107          if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({{header}}, true, state, &pindex)) {
 108              return false;
 109          }
 110      }
 111  
 112      return true;
 113  }
 114  
 115  BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
 116  {
 117      BlockFilterIndex filter_index(interfaces::MakeChain(m_node), BlockFilterType::BASIC, 1 << 20, true);
 118      BOOST_REQUIRE(filter_index.Init());
 119  
 120      uint256 last_header;
 121  
 122      // Filter should not be found in the index before it is started.
 123      {
 124          LOCK(cs_main);
 125  
 126          BlockFilter filter;
 127          uint256 filter_header;
 128          std::vector<BlockFilter> filters;
 129          std::vector<uint256> filter_hashes;
 130  
 131          for (const CBlockIndex* block_index = m_node.chainman->ActiveChain().Genesis();
 132               block_index != nullptr;
 133               block_index = m_node.chainman->ActiveChain().Next(block_index)) {
 134              BOOST_CHECK(!filter_index.LookupFilter(block_index, filter));
 135              BOOST_CHECK(!filter_index.LookupFilterHeader(block_index, filter_header));
 136              BOOST_CHECK(!filter_index.LookupFilterRange(block_index->nHeight, block_index, filters));
 137              BOOST_CHECK(!filter_index.LookupFilterHashRange(block_index->nHeight, block_index,
 138                                                              filter_hashes));
 139          }
 140      }
 141  
 142      // BlockUntilSyncedToCurrentChain should return false before index is started.
 143      BOOST_CHECK(!filter_index.BlockUntilSyncedToCurrentChain());
 144  
 145      BOOST_REQUIRE(filter_index.StartBackgroundSync());
 146  
 147      // Allow filter index to catch up with the block index.
 148      IndexWaitSynced(filter_index, *Assert(m_node.shutdown_signal));
 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 << 20, 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 << 20, 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 << 20, true, false));
 304  
 305      DestroyAllBlockFilterIndexes();
 306  
 307      filter_index = GetBlockFilterIndex(BlockFilterType::BASIC);
 308      BOOST_CHECK(filter_index == nullptr);
 309  }
 310  
 311  BOOST_AUTO_TEST_SUITE_END()
 312