|
16 | 16 | #include <validation.h> |
17 | 17 |
|
18 | 18 | #include <boost/test/unit_test.hpp> |
| 19 | +#include <future> |
19 | 20 |
|
20 | 21 | using node::BlockAssembler; |
21 | 22 | using node::BlockManager; |
@@ -305,4 +306,81 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_init_destroy, BasicTestingSetup) |
305 | 306 | BOOST_CHECK(filter_index == nullptr); |
306 | 307 | } |
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 | + |
308 | 386 | BOOST_AUTO_TEST_SUITE_END() |
0 commit comments