/ 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/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      std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get()}.CreateNewBlock(scriptPubKey);
 71      CBlock& block = pblocktemplate->block;
 72      block.hashPrevBlock = prev->GetBlockHash();
 73      block.nTime = prev->nTime + 1;
 74  
 75      // Replace mempool-selected txns with just coinbase plus passed-in txns:
 76      block.vtx.resize(1);
 77      for (const CMutableTransaction& tx : txns) {
 78          block.vtx.push_back(MakeTransactionRef(tx));
 79      }
 80      {
 81          CMutableTransaction tx_coinbase{*block.vtx.at(0)};
 82          tx_coinbase.vin.at(0).scriptSig = CScript{} << prev->nHeight + 1;
 83          block.vtx.at(0) = MakeTransactionRef(std::move(tx_coinbase));
 84          block.hashMerkleRoot = BlockMerkleRoot(block);
 85      }
 86  
 87      while (!CheckProofOfWork(block.GetHash(), block.nBits, m_node.chainman->GetConsensus())) ++block.nNonce;
 88  
 89      return block;
 90  }
 91  
 92  bool BuildChainTestingSetup::BuildChain(const CBlockIndex* pindex,
 93      const CScript& coinbase_script_pub_key,
 94      size_t length,
 95      std::vector<std::shared_ptr<CBlock>>& chain)
 96  {
 97      std::vector<CMutableTransaction> no_txns;
 98  
 99      chain.resize(length);
100      for (auto& block : chain) {
101          block = std::make_shared<CBlock>(CreateBlock(pindex, no_txns, coinbase_script_pub_key));
102          CBlockHeader header = block->GetBlockHeader();
103  
104          BlockValidationState state;
105          if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({header}, true, state, &pindex)) {
106              return false;
107          }
108      }
109  
110      return true;
111  }
112  
113  BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
114  {
115      BlockFilterIndex filter_index(interfaces::MakeChain(m_node), BlockFilterType::BASIC, 1 << 20, true);
116      BOOST_REQUIRE(filter_index.Init());
117  
118      uint256 last_header;
119  
120      // Filter should not be found in the index before it is started.
121      {
122          LOCK(cs_main);
123  
124          BlockFilter filter;
125          uint256 filter_header;
126          std::vector<BlockFilter> filters;
127          std::vector<uint256> filter_hashes;
128  
129          for (const CBlockIndex* block_index = m_node.chainman->ActiveChain().Genesis();
130               block_index != nullptr;
131               block_index = m_node.chainman->ActiveChain().Next(block_index)) {
132              BOOST_CHECK(!filter_index.LookupFilter(block_index, filter));
133              BOOST_CHECK(!filter_index.LookupFilterHeader(block_index, filter_header));
134              BOOST_CHECK(!filter_index.LookupFilterRange(block_index->nHeight, block_index, filters));
135              BOOST_CHECK(!filter_index.LookupFilterHashRange(block_index->nHeight, block_index,
136                                                              filter_hashes));
137          }
138      }
139  
140      // BlockUntilSyncedToCurrentChain should return false before index is started.
141      BOOST_CHECK(!filter_index.BlockUntilSyncedToCurrentChain());
142  
143      BOOST_REQUIRE(filter_index.StartBackgroundSync());
144  
145      // Allow filter index to catch up with the block index.
146      IndexWaitSynced(filter_index, *Assert(m_node.shutdown));
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  BOOST_AUTO_TEST_SUITE_END()