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()