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 <util/byte_units.h> 18 #include <validation.h> 19 20 #include <boost/test/unit_test.hpp> 21 #include <future> 22 23 using node::BlockAssembler; 24 using node::BlockManager; 25 using node::CBlockTemplate; 26 27 BOOST_AUTO_TEST_SUITE(blockfilter_index_tests) 28 29 struct BuildChainTestingSetup : public TestChain100Setup { 30 CBlock CreateBlock(const CBlockIndex* prev, const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey); 31 bool BuildChain(const CBlockIndex* pindex, const CScript& coinbase_script_pub_key, size_t length, std::vector<std::shared_ptr<CBlock>>& chain); 32 }; 33 34 static bool CheckFilterLookups(BlockFilterIndex& filter_index, const CBlockIndex* block_index, 35 uint256& last_header, const BlockManager& blockman) 36 { 37 BlockFilter expected_filter; 38 if (!ComputeFilter(filter_index.GetFilterType(), *block_index, expected_filter, blockman)) { 39 BOOST_ERROR("ComputeFilter failed on block " << block_index->nHeight); 40 return false; 41 } 42 43 BlockFilter filter; 44 uint256 filter_header; 45 std::vector<BlockFilter> filters; 46 std::vector<uint256> filter_hashes; 47 48 BOOST_CHECK(filter_index.LookupFilter(block_index, filter)); 49 BOOST_CHECK(filter_index.LookupFilterHeader(block_index, filter_header)); 50 BOOST_CHECK(filter_index.LookupFilterRange(block_index->nHeight, block_index, filters)); 51 BOOST_CHECK(filter_index.LookupFilterHashRange(block_index->nHeight, block_index, 52 filter_hashes)); 53 54 BOOST_CHECK_EQUAL(filters.size(), 1U); 55 BOOST_CHECK_EQUAL(filter_hashes.size(), 1U); 56 57 BOOST_CHECK_EQUAL(filter.GetHash(), expected_filter.GetHash()); 58 BOOST_CHECK_EQUAL(filter_header, expected_filter.ComputeHeader(last_header)); 59 BOOST_CHECK_EQUAL(filters[0].GetHash(), expected_filter.GetHash()); 60 BOOST_CHECK_EQUAL(filter_hashes[0], expected_filter.GetHash()); 61 62 filters.clear(); 63 filter_hashes.clear(); 64 last_header = filter_header; 65 return true; 66 } 67 68 CBlock BuildChainTestingSetup::CreateBlock(const CBlockIndex* prev, 69 const std::vector<CMutableTransaction>& txns, 70 const CScript& scriptPubKey) 71 { 72 BlockAssembler::Options options; 73 options.coinbase_output_script = scriptPubKey; 74 options.include_dummy_extranonce = true; 75 std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get(), options}.CreateNewBlock(); 76 CBlock& block = pblocktemplate->block; 77 block.hashPrevBlock = prev->GetBlockHash(); 78 block.nTime = prev->nTime + 1; 79 80 // Replace mempool-selected txns with just coinbase plus passed-in txns: 81 block.vtx.resize(1); 82 for (const CMutableTransaction& tx : txns) { 83 block.vtx.push_back(MakeTransactionRef(tx)); 84 } 85 { 86 CMutableTransaction tx_coinbase{*block.vtx.at(0)}; 87 tx_coinbase.nLockTime = static_cast<uint32_t>(prev->nHeight); 88 tx_coinbase.vin.at(0).scriptSig = CScript{} << prev->nHeight + 1; 89 block.vtx.at(0) = MakeTransactionRef(std::move(tx_coinbase)); 90 block.hashMerkleRoot = BlockMerkleRoot(block); 91 } 92 93 while (!CheckProofOfWork(block.GetHash(), block.nBits, m_node.chainman->GetConsensus())) ++block.nNonce; 94 95 return block; 96 } 97 98 bool BuildChainTestingSetup::BuildChain(const CBlockIndex* pindex, 99 const CScript& coinbase_script_pub_key, 100 size_t length, 101 std::vector<std::shared_ptr<CBlock>>& chain) 102 { 103 std::vector<CMutableTransaction> no_txns; 104 105 chain.resize(length); 106 for (auto& block : chain) { 107 block = std::make_shared<CBlock>(CreateBlock(pindex, no_txns, coinbase_script_pub_key)); 108 109 BlockValidationState state; 110 if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({{*block}}, true, state, &pindex)) { 111 return false; 112 } 113 } 114 115 return true; 116 } 117 118 BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) 119 { 120 BlockFilterIndex filter_index(interfaces::MakeChain(m_node), BlockFilterType::BASIC, 1_MiB, true); 121 BOOST_REQUIRE(filter_index.Init()); 122 123 uint256 last_header; 124 125 // Filter should not be found in the index before it is started. 126 { 127 LOCK(cs_main); 128 129 BlockFilter filter; 130 uint256 filter_header; 131 std::vector<BlockFilter> filters; 132 std::vector<uint256> filter_hashes; 133 134 for (const CBlockIndex* block_index = m_node.chainman->ActiveChain().Genesis(); 135 block_index != nullptr; 136 block_index = m_node.chainman->ActiveChain().Next(*block_index)) { 137 BOOST_CHECK(!filter_index.LookupFilter(block_index, filter)); 138 BOOST_CHECK(!filter_index.LookupFilterHeader(block_index, filter_header)); 139 BOOST_CHECK(!filter_index.LookupFilterRange(block_index->nHeight, block_index, filters)); 140 BOOST_CHECK(!filter_index.LookupFilterHashRange(block_index->nHeight, block_index, 141 filter_hashes)); 142 } 143 } 144 145 // BlockUntilSyncedToCurrentChain should return false before index is started. 146 BOOST_CHECK(!filter_index.BlockUntilSyncedToCurrentChain()); 147 148 filter_index.Sync(); 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_MiB, 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_MiB, 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_MiB, true, false)); 304 305 DestroyAllBlockFilterIndexes(); 306 307 filter_index = GetBlockFilterIndex(BlockFilterType::BASIC); 308 BOOST_CHECK(filter_index == nullptr); 309 } 310 311 class IndexReorgCrash : public BaseIndex 312 { 313 private: 314 std::unique_ptr<BaseIndex::DB> m_db; 315 std::shared_future<void> m_blocker; 316 int m_blocking_height; 317 318 public: 319 explicit IndexReorgCrash(std::unique_ptr<interfaces::Chain> chain, std::shared_future<void> blocker, 320 int blocking_height) : BaseIndex(std::move(chain), "test index"), m_blocker(blocker), 321 m_blocking_height(blocking_height) 322 { 323 const fs::path path = gArgs.GetDataDirNet() / "index"; 324 fs::create_directories(path); 325 m_db = std::make_unique<BaseIndex::DB>(path / "db", /*n_cache_size=*/0, /*f_memory=*/true, /*f_wipe=*/false); 326 } 327 328 bool AllowPrune() const override { return false; } 329 BaseIndex::DB& GetDB() const override { return *m_db; } 330 331 bool CustomAppend(const interfaces::BlockInfo& block) override 332 { 333 // Simulate a delay so new blocks can get connected during the initial sync 334 if (block.height == m_blocking_height) m_blocker.wait(); 335 336 // Move mock time forward so the best index gets updated only when we are not at the blocking height 337 if (block.height == m_blocking_height - 1 || block.height > m_blocking_height) { 338 SetMockTime(GetTime<std::chrono::seconds>() + 31s); 339 } 340 341 return true; 342 } 343 }; 344 345 BOOST_FIXTURE_TEST_CASE(index_reorg_crash, BuildChainTestingSetup) 346 { 347 // Enable mock time 348 SetMockTime(GetTime<std::chrono::minutes>()); 349 350 std::promise<void> promise; 351 std::shared_future<void> blocker(promise.get_future()); 352 int blocking_height = WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->nHeight); 353 354 IndexReorgCrash index(interfaces::MakeChain(m_node), blocker, blocking_height); 355 BOOST_REQUIRE(index.Init()); 356 BOOST_REQUIRE(index.StartBackgroundSync()); 357 358 auto func_wait_until = [&](int height, std::chrono::milliseconds timeout) { 359 auto deadline = std::chrono::steady_clock::now() + timeout; 360 while (index.GetSummary().best_block_height < height) { 361 if (std::chrono::steady_clock::now() > deadline) { 362 BOOST_FAIL(strprintf("Timeout waiting for index height %d (current: %d)", height, index.GetSummary().best_block_height)); 363 return; 364 } 365 std::this_thread::sleep_for(100ms); 366 } 367 }; 368 369 // Wait until the index is one block before the fork point 370 func_wait_until(blocking_height - 1, /*timeout=*/5s); 371 372 // Create a fork to trigger the reorg 373 std::vector<std::shared_ptr<CBlock>> fork; 374 const CBlockIndex* prev_tip = WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->pprev); 375 BOOST_REQUIRE(BuildChain(prev_tip, GetScriptForDestination(PKHash(GenerateRandomKey().GetPubKey())), 3, fork)); 376 377 for (const auto& block : fork) { 378 BOOST_REQUIRE(m_node.chainman->ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr)); 379 } 380 381 // Unblock the index thread so it can process the reorg 382 promise.set_value(); 383 // Wait for the index to reach the new tip 384 func_wait_until(blocking_height + 2, 5s); 385 index.Stop(); 386 } 387 388 BOOST_AUTO_TEST_SUITE_END()