Skip to content

Commit 3aef38f

Browse files
furszyHao Xu
authored andcommitted
test: exercise index reorg assertion failure
1 parent acf5023 commit 3aef38f

File tree

2 files changed

+84
-7
lines changed

2 files changed

+84
-7
lines changed

src/index/base.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,8 @@ void BaseIndex::Sync()
186186
{
187187
const CBlockIndex* pindex = m_best_block_index.load();
188188
if (!m_synced) {
189-
std::chrono::steady_clock::time_point last_log_time{0s};
190-
std::chrono::steady_clock::time_point last_locator_write_time{0s};
189+
auto last_log_time{NodeClock::now()};
190+
auto last_locator_write_time{last_log_time};
191191
while (true) {
192192
if (m_interrupt) {
193193
LogInfo("%s: m_interrupt set; exiting ThreadSync", GetName());
@@ -229,14 +229,13 @@ void BaseIndex::Sync()
229229

230230
if (!ProcessBlock(pindex)) return; // error logged internally
231231

232-
auto current_time{std::chrono::steady_clock::now()};
233-
if (last_log_time + SYNC_LOG_INTERVAL < current_time) {
234-
LogInfo("Syncing %s with block chain from height %d",
235-
GetName(), pindex->nHeight);
232+
auto current_time{NodeClock::now()};
233+
if (current_time - last_log_time >= SYNC_LOG_INTERVAL) {
234+
LogInfo("Syncing %s with block chain from height %d", GetName(), pindex->nHeight);
236235
last_log_time = current_time;
237236
}
238237

239-
if (last_locator_write_time + SYNC_LOCATOR_WRITE_INTERVAL < current_time) {
238+
if (current_time - last_locator_write_time >= SYNC_LOCATOR_WRITE_INTERVAL) {
240239
SetBestBlockIndex(pindex);
241240
last_locator_write_time = current_time;
242241
// No need to handle errors in Commit. See rationale above.

src/test/blockfilter_index_tests.cpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <validation.h>
1717

1818
#include <boost/test/unit_test.hpp>
19+
#include <future>
1920

2021
using node::BlockAssembler;
2122
using node::BlockManager;
@@ -305,4 +306,81 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_init_destroy, BasicTestingSetup)
305306
BOOST_CHECK(filter_index == nullptr);
306307
}
307308

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+
308386
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)