mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-08-24 18:51:30 +02:00
Merge bitcoin/bitcoin#32878: index: fix wrong assert of current_tip == m_best_block_index
3aef38f44b
test: exercise index reorg assertion failure (furszy)acf50233cd
index: fix wrong assert of current_tip == m_best_block_index (Hao Xu) Pull request description: In BaseIndex::Sync(), pindex in `Rewind(pindex, pindex_next->pprev)` isn't always equal to m_best_block_index since m_best_block_index is updated every SYNC_LOCATOR_WRITE_INTERVAL seconds, during which multiple pindex update could happen. Thus the assert here is wrong. ACKs for top commit: achow101: ACK3aef38f44b
furszy: ACK3aef38f
mzumsande: Code Review ACK3aef38f44b
Tree-SHA512: 3ef9cc6dfdec10a9f95d7414c6a11aa216e4cf5974440d80ab19fc919abd2a3bd4c875718c9dc94523c33826f8582ec5a016374deb8fb2d35cd2fb7799b5c82e
This commit is contained in:
@@ -186,8 +186,8 @@ void BaseIndex::Sync()
|
|||||||
{
|
{
|
||||||
const CBlockIndex* pindex = m_best_block_index.load();
|
const CBlockIndex* pindex = m_best_block_index.load();
|
||||||
if (!m_synced) {
|
if (!m_synced) {
|
||||||
std::chrono::steady_clock::time_point last_log_time{0s};
|
auto last_log_time{NodeClock::now()};
|
||||||
std::chrono::steady_clock::time_point last_locator_write_time{0s};
|
auto last_locator_write_time{last_log_time};
|
||||||
while (true) {
|
while (true) {
|
||||||
if (m_interrupt) {
|
if (m_interrupt) {
|
||||||
LogInfo("%s: m_interrupt set; exiting ThreadSync", GetName());
|
LogInfo("%s: m_interrupt set; exiting ThreadSync", GetName());
|
||||||
@@ -229,14 +229,13 @@ void BaseIndex::Sync()
|
|||||||
|
|
||||||
if (!ProcessBlock(pindex)) return; // error logged internally
|
if (!ProcessBlock(pindex)) return; // error logged internally
|
||||||
|
|
||||||
auto current_time{std::chrono::steady_clock::now()};
|
auto current_time{NodeClock::now()};
|
||||||
if (last_log_time + SYNC_LOG_INTERVAL < current_time) {
|
if (current_time - last_log_time >= SYNC_LOG_INTERVAL) {
|
||||||
LogInfo("Syncing %s with block chain from height %d",
|
LogInfo("Syncing %s with block chain from height %d", GetName(), pindex->nHeight);
|
||||||
GetName(), pindex->nHeight);
|
|
||||||
last_log_time = current_time;
|
last_log_time = current_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (last_locator_write_time + SYNC_LOCATOR_WRITE_INTERVAL < current_time) {
|
if (current_time - last_locator_write_time >= SYNC_LOCATOR_WRITE_INTERVAL) {
|
||||||
SetBestBlockIndex(pindex);
|
SetBestBlockIndex(pindex);
|
||||||
last_locator_write_time = current_time;
|
last_locator_write_time = current_time;
|
||||||
// No need to handle errors in Commit. See rationale above.
|
// No need to handle errors in Commit. See rationale above.
|
||||||
@@ -274,7 +273,6 @@ bool BaseIndex::Commit()
|
|||||||
|
|
||||||
bool BaseIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip)
|
bool BaseIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip)
|
||||||
{
|
{
|
||||||
assert(current_tip == m_best_block_index);
|
|
||||||
assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip);
|
assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip);
|
||||||
|
|
||||||
CBlock block;
|
CBlock block;
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
#include <validation.h>
|
#include <validation.h>
|
||||||
|
|
||||||
#include <boost/test/unit_test.hpp>
|
#include <boost/test/unit_test.hpp>
|
||||||
|
#include <future>
|
||||||
|
|
||||||
using node::BlockAssembler;
|
using node::BlockAssembler;
|
||||||
using node::BlockManager;
|
using node::BlockManager;
|
||||||
@@ -305,4 +306,81 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_init_destroy, BasicTestingSetup)
|
|||||||
BOOST_CHECK(filter_index == nullptr);
|
BOOST_CHECK(filter_index == nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class IndexReorgCrash : public BaseIndex
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::unique_ptr<BaseIndex::DB> m_db;
|
||||||
|
std::shared_future<void> m_blocker;
|
||||||
|
int m_blocking_height;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit IndexReorgCrash(std::unique_ptr<interfaces::Chain> chain, std::shared_future<void> blocker,
|
||||||
|
int blocking_height) : BaseIndex(std::move(chain), "test index"), m_blocker(blocker),
|
||||||
|
m_blocking_height(blocking_height)
|
||||||
|
{
|
||||||
|
const fs::path path = gArgs.GetDataDirNet() / "index";
|
||||||
|
fs::create_directories(path);
|
||||||
|
m_db = std::make_unique<BaseIndex::DB>(path / "db", /*n_cache_size=*/0, /*f_memory=*/true, /*f_wipe=*/false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AllowPrune() const override { return false; }
|
||||||
|
BaseIndex::DB& GetDB() const override { return *m_db; }
|
||||||
|
|
||||||
|
bool CustomAppend(const interfaces::BlockInfo& block) override
|
||||||
|
{
|
||||||
|
// Simulate a delay so new blocks can get connected during the initial sync
|
||||||
|
if (block.height == m_blocking_height) m_blocker.wait();
|
||||||
|
|
||||||
|
// Move mock time forward so the best index gets updated only when we are not at the blocking height
|
||||||
|
if (block.height == m_blocking_height - 1 || block.height > m_blocking_height) {
|
||||||
|
SetMockTime(GetTime<std::chrono::seconds>() + 31s);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(index_reorg_crash, BuildChainTestingSetup)
|
||||||
|
{
|
||||||
|
// Enable mock time
|
||||||
|
SetMockTime(GetTime<std::chrono::minutes>());
|
||||||
|
|
||||||
|
std::promise<void> promise;
|
||||||
|
std::shared_future<void> blocker(promise.get_future());
|
||||||
|
int blocking_height = WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->nHeight);
|
||||||
|
|
||||||
|
IndexReorgCrash index(interfaces::MakeChain(m_node), blocker, blocking_height);
|
||||||
|
BOOST_REQUIRE(index.Init());
|
||||||
|
BOOST_REQUIRE(index.StartBackgroundSync());
|
||||||
|
|
||||||
|
auto func_wait_until = [&](int height, std::chrono::milliseconds timeout) {
|
||||||
|
auto deadline = std::chrono::steady_clock::now() + timeout;
|
||||||
|
while (index.GetSummary().best_block_height < height) {
|
||||||
|
if (std::chrono::steady_clock::now() > deadline) {
|
||||||
|
BOOST_FAIL(strprintf("Timeout waiting for index height %d (current: %d)", height, index.GetSummary().best_block_height));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(100ms);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wait until the index is one block before the fork point
|
||||||
|
func_wait_until(blocking_height - 1, /*timeout=*/5s);
|
||||||
|
|
||||||
|
// Create a fork to trigger the reorg
|
||||||
|
std::vector<std::shared_ptr<CBlock>> fork;
|
||||||
|
const CBlockIndex* prev_tip = WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->pprev);
|
||||||
|
BOOST_REQUIRE(BuildChain(prev_tip, GetScriptForDestination(PKHash(GenerateRandomKey().GetPubKey())), 3, fork));
|
||||||
|
|
||||||
|
for (const auto& block : fork) {
|
||||||
|
BOOST_REQUIRE(m_node.chainman->ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unblock the index thread so it can process the reorg
|
||||||
|
promise.set_value();
|
||||||
|
// Wait for the index to reach the new tip
|
||||||
|
func_wait_until(blocking_height + 2, 5s);
|
||||||
|
index.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
Reference in New Issue
Block a user