diff --git a/src/side_chain.cpp b/src/side_chain.cpp index 098ad6e..24f3c04 100644 --- a/src/side_chain.cpp +++ b/src/side_chain.cpp @@ -645,9 +645,9 @@ bool SideChain::add_external_block(PoolBlock& block, std::vector& 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& 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& 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> 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 }