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