Fix chain_recovery to properly rebuild from checkpoint

This commit is contained in:
Matt Hess
2025-12-26 14:06:40 +00:00
parent 499cfad2d6
commit 694c672d0c
+108 -41
View File
@@ -645,9 +645,9 @@ bool SideChain::add_external_block(PoolBlock& block, std::vector<hash>& missing_
if (is_main_thread()) {
const uint64_t seed_height = p2pool::get_seed_height(block.m_txinGenHeight);
m_pool->fetch_mainchain_block(seed_height);
forget_incoming_block(block);
}
// If not on main thread, don't forget - block stays pending until mainchain data arrives via prefetch
// Always forget so block can be re-processed when mainchain data arrives
forget_incoming_block(block);
return false;
}
@@ -768,6 +768,12 @@ bool SideChain::add_block(const PoolBlock& block)
", mainchain height = " << block.m_txinGenHeight <<
", verified = " << (block.m_verified ? 1 : 0)
);
// Extra log for blocks near checkpoint height during recovery
const uint64_t cp_height = get_latest_checkpoint_height();
if (block.m_sidechainHeight >= cp_height && block.m_sidechainHeight <= cp_height + 5) {
LOGINFO(0, "RECOVERY DEBUG: add_block height " << block.m_sidechainHeight
<< " parent=" << block.m_parent);
}
PoolBlock* new_block = new PoolBlock(block);
{
@@ -1625,6 +1631,11 @@ void SideChain::verify_loop(PoolBlock* block)
on_block_rejected(block, "verification failed");
}
else {
// Extra logging for blocks near checkpoint during recovery
if (block->m_sidechainHeight <= get_latest_checkpoint_height() + 10) {
LOGINFO(0, "RECOVERY DEBUG: VERIFIED block " << block->m_sidechainHeight
<< " (parent " << (block->m_sidechainHeight > 0 ? block->m_sidechainHeight - 1 : 0) << " was verified)");
}
LOGINFO(3, "verified block at height = " << block->m_sidechainHeight <<
", depth = " << block->m_depth <<
", id = " << block->m_sidechainId <<
@@ -1752,7 +1763,12 @@ void SideChain::verify(PoolBlock* block)
return;
}
if (!it->second->m_verified) {
LOGWARN(3, "block at height = " << block->m_sidechainHeight << " parent " << block->m_parent << " found but NOT VERIFIED (parent height = " << it->second->m_sidechainHeight << ")");
// Only log for blocks close to checkpoint to reduce spam
if (block->m_sidechainHeight <= get_latest_checkpoint_height() + 10) {
LOGINFO(0, "RECOVERY DEBUG: block " << block->m_sidechainHeight << " waiting for parent "
<< it->second->m_sidechainHeight << " (parent verified=" << (it->second->m_verified ? 1 : 0)
<< " invalid=" << (it->second->m_invalid ? 1 : 0) << ")");
}
block->m_verified = false;
return;
}
@@ -1933,11 +1949,8 @@ void SideChain::verify(PoolBlock* block)
}
difficulty_type diff;
if (parent == m_chainTip) {
LOGINFO(6, "block " << block->m_sidechainId << " is built on top of the current chain tip, using current difficulty for verification");
diff = difficulty();
}
else if (!get_difficulty(parent, m_difficultyData, diff)) {
// Always use get_difficulty for verification - difficulty() may be stale after recovery
if (!get_difficulty(parent, m_difficultyData, diff)) {
// Don't mark as invalid - mainchain might not be synced yet
// Leave unverified so it can be retried later
LOGWARN(3, "block at height = " << block->m_sidechainHeight << ", id = " << block->m_sidechainId
@@ -2664,13 +2677,25 @@ void SideChain::get_missing_blocks(unordered_set<hash>& missing_blocks) const
ReadLock lock(m_sidechainLock);
const uint64_t cp_height = get_latest_checkpoint_height();
uint64_t lowest_unverified = UINT64_MAX;
for (auto& b : m_blocksById) {
if (b.second->m_verified) {
continue;
}
if (b.second->m_sidechainHeight < lowest_unverified) {
lowest_unverified = b.second->m_sidechainHeight;
}
if (!b.second->m_parent.empty() && (m_blocksById.find(b.second->m_parent) == m_blocksById.end())) {
missing_blocks.insert(b.second->m_parent);
// Log missing parents near checkpoint
if (b.second->m_sidechainHeight <= cp_height + 5) {
LOGINFO(0, "RECOVERY DEBUG: block " << b.second->m_sidechainHeight
<< " needs missing parent " << b.second->m_parent);
}
}
int num_missing_uncles = 0;
@@ -2688,6 +2713,11 @@ void SideChain::get_missing_blocks(unordered_set<hash>& missing_blocks) const
}
}
}
// Summary log for recovery debugging
if (!missing_blocks.empty() && lowest_unverified <= cp_height + 10) {
LOGINFO(0, "RECOVERY DEBUG: " << missing_blocks.size() << " missing blocks, lowest unverified=" << lowest_unverified);
}
}
void SideChain::retry_unverified_blocks()
@@ -3251,49 +3281,86 @@ void SideChain::reset_to_checkpoint(uint64_t checkpoint_height)
WriteLock lock(m_sidechainLock);
// SAFER APPROACH: Don't delete blocks - just mark them invalid and reset state
// Normal pruning will clean them up later. This avoids complex threading issues.
size_t invalidated_count = 0;
for (auto& kv : m_blocksById) {
if (kv.second->m_sidechainHeight > checkpoint_height) {
kv.second->m_invalid = true;
kv.second->m_verified = true;
++invalidated_count;
}
}
LOGINFO(0, "Marked " << invalidated_count << " blocks as invalid after checkpoint " << checkpoint_height);
// Reset chain tip to checkpoint block - find by checkpoint ID
// Find the nearest available checkpoint at or below target height
PoolBlock* checkpoint_block = nullptr;
uint64_t actual_checkpoint_height = 0;
{
ReadLock cpLock(m_checkpointsLock);
for (const Checkpoint& cp : m_checkpoints) {
if (cp.height == checkpoint_height) {
auto it = m_blocksById.find(cp.id);
if (it != m_blocksById.end()) {
checkpoint_block = it->second;
for (auto it = m_checkpoints.rbegin(); it != m_checkpoints.rend(); ++it) {
if (it->height <= checkpoint_height) {
auto block_it = m_blocksById.find(it->id);
if (block_it != m_blocksById.end()) {
checkpoint_block = block_it->second;
actual_checkpoint_height = it->height;
break;
}
break;
}
}
}
if (checkpoint_block) {
checkpoint_block->m_verified = true; // Mark verified for propagation
m_chainTip = checkpoint_block;
LOGINFO(0, "Chain tip reset to height " << checkpoint_height
<< ", id " << checkpoint_block->m_sidechainId);
} else {
m_chainTip = nullptr;
LOGWARN(0, "No block found at checkpoint height - chain tip set to null");
if (!checkpoint_block) {
LOGWARN(0, "No checkpoint found at or below height " << checkpoint_height << " - recovery aborted");
m_recoveryMode = false;
m_pendingRecoveryHeight = 0;
return;
}
if (actual_checkpoint_height != checkpoint_height) {
LOGINFO(0, "Using nearest checkpoint at height " << actual_checkpoint_height << " (requested " << checkpoint_height << ")");
}
// Remove blocks above checkpoint so fresh ones come from peers
// Collect block info to remove (can't modify map while iterating)
std::vector<std::pair<hash, PoolBlock::full_id>> blocks_to_remove;
for (const auto& kv : m_blocksById) {
if (kv.second->m_sidechainHeight > actual_checkpoint_height) {
blocks_to_remove.emplace_back(kv.first, kv.second->get_full_id());
}
}
// Remove from all indices
for (const auto& [id, full_id] : blocks_to_remove) {
auto it = m_blocksById.find(id);
if (it != m_blocksById.end()) {
PoolBlock* block = it->second;
// Remove from blocksByHeight vector
auto height_it = m_blocksByHeight.find(block->m_sidechainHeight);
if (height_it != m_blocksByHeight.end()) {
auto& vec = height_it->second;
vec.erase(std::remove(vec.begin(), vec.end(), block), vec.end());
if (vec.empty()) {
m_blocksByHeight.erase(height_it);
}
}
m_blocksById.erase(it);
delete block;
}
}
// Forget deleted blocks so they can be re-downloaded from peers
{
MutexLock lock(m_incomingBlocksLock);
for (const auto& [id, full_id] : blocks_to_remove) {
m_incomingBlocks.erase(full_id);
}
}
LOGINFO(0, "Removed " << blocks_to_remove.size() << " blocks above checkpoint " << actual_checkpoint_height);
LOGINFO(0, "Cleared " << blocks_to_remove.size() << " entries from incoming blocks tracking");
checkpoint_block->m_verified = true; // Mark verified for propagation
checkpoint_block->m_invalid = false; // Ensure not marked invalid
m_chainTip = checkpoint_block;
LOGINFO(0, "Chain tip reset to height " << actual_checkpoint_height
<< ", id " << checkpoint_block->m_sidechainId
<< ", verified=" << (checkpoint_block->m_verified ? 1 : 0));
// Clear checkpoints after this point
{
WriteLock cpLock(m_checkpointsLock);
while (!m_checkpoints.empty() && m_checkpoints.back().height > checkpoint_height) {
while (!m_checkpoints.empty() && m_checkpoints.back().height > actual_checkpoint_height) {
m_checkpoints.pop_back();
}
}
@@ -3302,11 +3369,11 @@ void SideChain::reset_to_checkpoint(uint64_t checkpoint_height)
m_externalBlockFailures = 0;
m_pendingRecoveryHeight = 0;
m_recoveryMode = false;
LOGINFO(0, "\n========================================");
LOGINFO(0, "RESET COMPLETE - RESYNCING FROM " << checkpoint_height);
LOGINFO(0, "========================================\n");
// Mining will be re-enabled after resync completes
}