Add CAP exchange protocol, Deadlock fix, Sync stuck fix with retry mechanism

This commit is contained in:
Matt Hess
2025-12-17 00:29:41 +00:00
parent 940f1cabe8
commit 11b545e91b
10 changed files with 1101 additions and 117 deletions
+803 -59
View File
@@ -34,6 +34,7 @@
#include "json_parsers.h"
#include "crypto.h"
#include "hardforks/hardforks.h"
#include <fstream>
#if !defined(_MSC_VER) || !defined(__cppcheck__)
#include <rapidjson/document.h>
@@ -107,6 +108,8 @@ SideChain::SideChain(p2pool* pool, NetworkType type, const char* pool_name, cons
uv_rwlock_init_checked(&m_curDifficultyLock);
uv_rwlock_init_checked(&m_watchBlockLock);
uv_rwlock_init(&m_checkpointsLock);
m_difficultyData.reserve(m_chainWindowSize);
LOGINFO(1, "generating consensus ID");
@@ -220,10 +223,13 @@ SideChain::SideChain(p2pool* pool, NetworkType type, const char* pool_name, cons
m_uniquePrecalcInputs = new unordered_set<size_t>();
m_uniquePrecalcInputs->reserve(1 << 18);
load_checkpoints();
}
SideChain::~SideChain()
{
save_checkpoints();
finish_precalc();
uv_rwlock_destroy(&m_sidechainLock);
@@ -231,6 +237,8 @@ SideChain::~SideChain()
uv_rwlock_destroy(&m_curDifficultyLock);
uv_rwlock_destroy(&m_watchBlockLock);
uv_rwlock_destroy(&m_checkpointsLock);
for (const auto& it : m_blocksById) {
delete it.second;
}
@@ -727,24 +735,9 @@ bool SideChain::add_external_block(PoolBlock& block, std::vector<hash>& missing_
const bool added = add_block(block);
if (added && block.m_verified) {
if (block.m_invalid) {
++m_externalBlockFailures;
LOGWARN(3, "external block validation failed, failure count: " << m_externalBlockFailures << "/" << DIVERGENCE_THRESHOLD);
if (m_externalBlockFailures >= DIVERGENCE_THRESHOLD) {
LOGERR(0, log::LightRed() << "DIVERGENCE DETECTED: " << m_externalBlockFailures << " consecutive external block failures. Purging cache and restarting...");
// Delete cache file
const std::string cache_path = "p2pool.cache";
remove(cache_path.c_str());
// Signal shutdown - systemd will restart
if (m_pool) {
m_pool->stop();
}
}
on_block_rejected(&block, "external block validation failed");
} else {
// Valid external block - reset counter
if (m_externalBlockFailures > 0) {
LOGINFO(3, "external block validated successfully, resetting failure counter from " << m_externalBlockFailures);
m_externalBlockFailures = 0;
}
on_block_accepted();
}
}
return added;
@@ -1229,7 +1222,9 @@ void SideChain::print_status(bool obtain_sidechain_lock) const
"\nYour wallet address = " << m_pool->params().m_displayWallet <<
"\nYour shares = " << our_blocks_in_window_total << " blocks (+" << our_uncles_in_window_total << " uncles, " << our_orphans << " orphans)"
<< our_blocks_in_window_chart << our_uncles_in_window_chart <<
"\nBlock reward share = " << block_share << "% (" << log::XMRAmount(your_reward) << ')'
"\nBlock reward share = " << block_share << "% (" << log::XMRAmount(your_reward) << ')' <<
"\nAnchor point = " << get_latest_checkpoint_height() <<
"\nChain health = " << m_externalBlockFailures << " consecutive failures" << (m_recoveryMode.load() ? " [RECOVERY MODE]" : "")
);
}
@@ -1548,9 +1543,44 @@ void SideChain::add_onion_pubkeys(const std::vector<hash>& pubkeys)
void SideChain::verify_loop(PoolBlock* block)
{
// PoW is already checked at this point
// PoW is already checked at this point
std::vector<PoolBlock*> blocks_to_verify(1, block);
// Bootstrap: if no checkpoints and nothing verified yet, seed from oldest block
bool have_checkpoints = false;
{
ReadLock cpLock(m_checkpointsLock);
have_checkpoints = !m_checkpoints.empty();
}
if (!have_checkpoints) {
bool any_verified = false;
PoolBlock* oldest = nullptr;
for (auto& pair : m_blocksById) {
if (pair.second->m_verified) {
any_verified = true;
break;
}
if (!oldest || pair.second->m_sidechainHeight < oldest->m_sidechainHeight) {
oldest = pair.second;
}
}
if (!any_verified && oldest && oldest->m_sidechainHeight > 0) {
oldest->m_verified = true;
oldest->m_invalid = false;
LOGINFO(0, "Bootstrap: seeding chain from oldest block at height " << oldest->m_sidechainHeight);
// Find direct children to propagate verification
auto it = m_blocksByHeight.find(oldest->m_sidechainHeight + 1);
if (it != m_blocksByHeight.end()) {
for (PoolBlock* child : it->second) {
if (child->m_parent == oldest->m_sidechainId) {
blocks_to_verify.push_back(child);
}
}
}
}
}
std::vector<PoolBlock*> blocks_to_verify(1, block);
PoolBlock* highest_block = nullptr;
while (!blocks_to_verify.empty()) {
@@ -1570,20 +1600,19 @@ void SideChain::verify_loop(PoolBlock* block)
continue;
}
if (block->m_invalid) {
LOGWARN(3, "block at height = " << block->m_sidechainHeight <<
", id = " << block->m_sidechainId <<
", mainchain height = " << block->m_txinGenHeight << ", mined by " << block->m_minerWallet << " is invalid");
}
else {
LOGINFO(3, "verified block at height = " << block->m_sidechainHeight <<
", depth = " << block->m_depth <<
", id = " << block->m_sidechainId <<
", mainchain height = " << block->m_txinGenHeight);
if (block->m_invalid) {
on_block_rejected(block, "verification failed");
}
else {
LOGINFO(3, "verified block at height = " << block->m_sidechainHeight <<
", depth = " << block->m_depth <<
", id = " << block->m_sidechainId <<
", mainchain height = " << block->m_txinGenHeight);
// This block is now verified
// This block is now verified
bool is_alternative;
bool is_alternative;
if (is_longer_chain(highest_block, block, is_alternative)) {
highest_block = block;
}
@@ -1695,20 +1724,29 @@ void SideChain::verify(PoolBlock* block)
}
// Check parent
auto it = m_blocksById.find(block->m_parent);
if ((it == m_blocksById.end()) || !it->second->m_verified) {
block->m_verified = false;
return;
}
auto it = m_blocksById.find(block->m_parent);
if (it == m_blocksById.end()) {
LOGWARN(3, "block at height = " << block->m_sidechainHeight << " parent " << block->m_parent << " NOT FOUND in m_blocksById");
block->m_verified = false;
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 << ")");
block->m_verified = false;
return;
}
// If it's invalid then this block is also invalid
const PoolBlock* parent = it->second;
if (parent->m_invalid) {
LOGWARN(3, "block at height = " << block->m_sidechainHeight << ", id = " << block->m_sidechainId << ", mainchain height = " << block->m_txinGenHeight << ": get_shares failed");
block->m_verified = true;
block->m_invalid = true;
return;
}
LOGWARN(0, "DIAGNOSTIC: Block " << block->m_sidechainId << " (height=" << block->m_sidechainHeight
<< ") rejected because parent " << parent->m_sidechainId << " (height=" << parent->m_sidechainHeight
<< ") is marked invalid");
on_block_rejected(block, "parent block is invalid");
block->m_verified = true;
block->m_invalid = true;
return;
}
// Check m_txkeySecSeed
const hash h = (block->m_prevId == parent->m_prevId) ? parent->m_txkeySecSeed : parent->calculate_tx_key_seed();
@@ -1879,8 +1917,12 @@ void SideChain::verify(PoolBlock* block)
diff = difficulty();
}
else if (!get_difficulty(parent, m_difficultyData, diff)) {
LOGWARN(3, "block at height = " << block->m_sidechainHeight << ", id = " << block->m_sidechainId << ", mainchain height = " << block->m_txinGenHeight << ": get_difficulty failed for parent " << block->m_parent);
block->m_invalid = true;
// 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
<< ", mainchain height = " << block->m_txinGenHeight
<< ": get_difficulty failed for parent " << block->m_parent << " (mainchain may not be synced yet), leaving unverified");
block->m_verified = false;
return;
}
@@ -1898,10 +1940,14 @@ void SideChain::verify(PoolBlock* block)
}
if (shares.empty() && !get_shares(block, shares)) {
LOGWARN(3, "block at height = " << block->m_sidechainHeight << ", id = " << block->m_sidechainId << ", mainchain height = " << block->m_txinGenHeight << ": get_shares failed");
block->m_invalid = true;
return;
}
// 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
<< ", mainchain height = " << block->m_txinGenHeight
<< ": get_shares failed (mainchain may not be synced yet), leaving unverified");
block->m_verified = false;
return;
}
// Handle donation mode during verification - match block creation logic
const uint64_t donation_cycle = (s_networkType == NetworkType::Mainnet) ? DONATION_CYCLE_MAINNET : DONATION_CYCLE_TESTNET;
@@ -2037,7 +2083,11 @@ void SideChain::update_chain_tip(PoolBlock* block)
", next difficulty = " << log::Gray() << diff << log::NoColor() <<
", main chain height = " << log::Gray() << block->m_txinGenHeight);
block->m_wantBroadcast = true;
// Only broadcast after initial sync complete - avoid broadcasting stale blocks
if (m_readyToMine.load()) {
block->m_wantBroadcast = true;
}
if (m_pool) {
m_pool->update_block_template_async(is_alternative);
@@ -2054,6 +2104,99 @@ void SideChain::update_chain_tip(PoolBlock* block)
}
prune_old_blocks();
cleanup_incoming_blocks();
// Check if we're ready to mine (sync complete)
// For fresh/small chains that haven't reached prune threshold,
// we're ready if we have a verified tip and no unverified blocks pending
if (!m_readyToMine.load()) {
// Validate cached checkpoints before enabling mining
LOGINFO(3, "All blocks verified, checking m_checkpointsNeedValidation=" << (m_checkpointsNeedValidation ? "true" : "false"));
if (m_checkpointsNeedValidation) {
if (!validate_loaded_checkpoints()) {
LOGWARN(0, "Checkpoint validation deferred - waiting for sync, mining delayed");
return;
}
// If validation triggered recovery, don't enable mining yet
if (m_recoveryMode.load()) {
LOGWARN(0, "Checkpoint validation failed - recovery in progress, mining delayed");
return;
}
}
// Keep scanning until tip is verified
// Get checkpoint range for bulk verification
uint64_t oldest_cp = UINT64_MAX, newest_cp = 0;
{
ReadLock cpLock(m_checkpointsLock);
for (const auto& cp : m_checkpoints) {
if (cp.height < oldest_cp) oldest_cp = cp.height;
if (cp.height > newest_cp) newest_cp = cp.height;
}
}
bool have_checkpoints = (oldest_cp != UINT64_MAX && newest_cp > 0);
// Bootstrap: if no checkpoints, find oldest block and mark it verified as seed
if (!have_checkpoints && !m_blocksById.empty()) {
PoolBlock* oldest = nullptr;
for (auto& pair : m_blocksById) {
if (!oldest || pair.second->m_sidechainHeight < oldest->m_sidechainHeight) {
oldest = pair.second;
}
}
if (oldest) {
oldest->m_verified = true;
LOGINFO(1, "Bootstrap: seeding verification from height " << oldest->m_sidechainHeight);
}
}
for (int attempt = 0; attempt < 100; ++attempt) {
bool made_progress = true;
while (made_progress) {
made_progress = false;
for (auto& pair : m_blocksById) {
PoolBlock* b = pair.second;
if (!b->m_verified && b->m_sidechainHeight > 0) {
// If checkpoints exist, mark verified if within range
if (have_checkpoints && b->m_sidechainHeight >= oldest_cp && b->m_sidechainHeight <= newest_cp) {
b->m_verified = true;
made_progress = true;
}
// Always check parent propagation (works with or without checkpoints)
else {
auto parent_it = m_blocksById.find(b->m_parent);
if (parent_it != m_blocksById.end() && parent_it->second->m_verified) {
b->m_verified = true;
made_progress = true;
}
}
}
}
}
const PoolBlock* tip_check = m_chainTip.load();
if (tip_check && tip_check->m_verified) {
break; // Tip is verified, done
}
// Wait for more blocks to arrive
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
const PoolBlock* current_tip = m_chainTip.load();
bool have_checkpoint = false;
{
ReadLock cpLock(m_checkpointsLock);
have_checkpoint = !m_checkpoints.empty();
}
// Only enable mining when tip is verified at low depth (real-time, not catch-up)
if (current_tip && current_tip->m_verified && have_checkpoint && current_tip->m_depth < 10) {
m_readyToMine.store(true);
LOGINFO(0, log::LightGreen() << "########################################");
LOGINFO(0, log::LightGreen() << "SIDECHAIN LOADED - MINING IS NOW ENABLED");
LOGINFO(0, log::LightGreen() << "########################################");
}
}
}
}
else if (block->m_sidechainHeight > tip->m_sidechainHeight) {
@@ -2072,6 +2215,9 @@ void SideChain::update_chain_tip(PoolBlock* block)
block->m_broadcasted = true;
p2pServer()->broadcast(*block, get_parent(block));
}
// Update checkpoints if we crossed a boundary
update_checkpoints(block->m_sidechainHeight);
}
PoolBlock* SideChain::get_parent(const PoolBlock* block) const
@@ -2351,21 +2497,29 @@ void SideChain::prune_old_blocks()
const PoolBlock* tip = m_chainTip;
if (tip->m_sidechainHeight < prune_distance) {
return;
}
const uint64_t h = tip->m_sidechainHeight - prune_distance;
const uint64_t h = tip->m_sidechainHeight - prune_distance;
// Align prune boundary to CAP intervals for clean cache restoration
const uint64_t prune_boundary = (h / CHECKPOINT_INTERVAL) * CHECKPOINT_INTERVAL;
std::vector<PoolBlock*> blocks_to_prune;
for (auto it = m_blocksByHeight.begin(); (it != m_blocksByHeight.end()) && (it->first <= h);) {
for (auto it = m_blocksByHeight.begin(); (it != m_blocksByHeight.end()) && (it->first <= prune_boundary);) {
const uint64_t height = it->first;
std::vector<PoolBlock*>& v = it->second;
v.erase(std::remove_if(v.begin(), v.end(),
[this, prune_distance, cur_time, prune_delay, &blocks_to_prune, height](PoolBlock* block)
{
if ((block->m_depth >= prune_distance) || (cur_time >= block->m_localTimestamp + prune_delay)) {
[this, prune_distance, cur_time, prune_delay, &blocks_to_prune, height](PoolBlock* block)
{
// CAP: Never prune anchor point blocks
if (is_checkpoint_block(block)) {
LOGINFO(5, "Preserving anchor point block at height " << block->m_sidechainHeight);
return false;
}
if ((block->m_depth >= prune_distance) || (cur_time >= block->m_localTimestamp + prune_delay)) {
auto it2 = m_blocksById.find(block->m_sidechainId);
if (it2 != m_blocksById.end()) {
m_blocksById.erase(it2);
@@ -2397,7 +2551,7 @@ void SideChain::prune_old_blocks()
}
if (!blocks_to_prune.empty()) {
LOGINFO(4, "pruned " << blocks_to_prune.size() << " old blocks at heights <= " << h);
LOGINFO(4, "pruned " << blocks_to_prune.size() << " old blocks at heights <= " << prune_boundary);
// If side-chain started pruning blocks it means the initial sync is complete
// It's now safe to delete cached blocks
@@ -2451,10 +2605,28 @@ void SideChain::prune_old_blocks()
// If side-chain started pruning blocks it means the initial sync is complete
// It's now safe to delete cached blocks
if (!m_readyToMine.load()) {
m_readyToMine.store(true);
LOGINFO(0, log::LightGreen() << "########################################");
LOGINFO(0, log::LightGreen() << "SIDECHAIN LOADED - MINING IS NOW ENABLED");
LOGINFO(0, log::LightGreen() << "########################################");
// Validate cached checkpoints before enabling mining
LOGINFO(0, "Prune path: checking m_checkpointsNeedValidation=" << (m_checkpointsNeedValidation ? "true" : "false"));
if (m_checkpointsNeedValidation) {
if (!validate_loaded_checkpoints()) {
LOGWARN(0, "Checkpoint validation deferred - waiting for sync, mining delayed");
return;
}
// If validation triggered recovery, don't enable mining yet
if (m_recoveryMode.load()) {
LOGWARN(0, "Checkpoint validation failed - recovery in progress, mining delayed");
return;
}
}
const PoolBlock* current_tip = m_chainTip.load();
if (current_tip && current_tip->m_verified) {
m_readyToMine.store(true);
LOGINFO(0, log::LightGreen() << "########################################");
LOGINFO(0, log::LightGreen() << "SIDECHAIN LOADED - MINING IS NOW ENABLED");
LOGINFO(0, log::LightGreen() << "########################################");
}
}
}
@@ -2490,6 +2662,50 @@ void SideChain::get_missing_blocks(unordered_set<hash>& missing_blocks) const
}
}
void SideChain::retry_unverified_blocks()
{
WriteLock lock(m_sidechainLock);
// Scan for unverified blocks and retry them
// This is called when new mainchain data arrives that might allow verification
std::vector<PoolBlock*> blocks_to_retry;
for (auto& pair : m_blocksById) {
PoolBlock* block = pair.second;
if (!block->m_verified && !block->m_invalid) {
blocks_to_retry.push_back(block);
}
}
if (blocks_to_retry.empty()) {
return;
}
LOGINFO(4, "Retrying verification of " << blocks_to_retry.size() << " unverified blocks after mainchain update");
// Sort by height to process in order
std::sort(blocks_to_retry.begin(), blocks_to_retry.end(),
[](const PoolBlock* a, const PoolBlock* b) {
return a->m_sidechainHeight < b->m_sidechainHeight;
});
// Try to verify each block
uint32_t verified_count = 0;
for (PoolBlock* block : blocks_to_retry) {
if (block->m_verified) {
continue; // Already verified by earlier iteration
}
verify_loop(block);
if (block->m_verified) {
++verified_count;
}
}
if (verified_count > 0) {
LOGINFO(3, "Verified " << verified_count << " blocks after mainchain update");
}
}
bool SideChain::consider_peer_genesis(const hash& genesis_id, uint64_t timestamp, uint64_t height)
{
// Get our current genesis info for comparison
@@ -2821,4 +3037,532 @@ void SideChain::finish_precalc()
}
}
// ============================================================================
// CHECKPOINT SYSTEM
// ============================================================================
bool SideChain::is_checkpoint_block(const PoolBlock* block) const
{
ReadLock lock(m_checkpointsLock);
for (const Checkpoint& cp : m_checkpoints) {
if (cp.height == block->m_sidechainHeight && cp.id == block->m_sidechainId) {
return true;
}
}
return false;
}
void SideChain::update_checkpoints(uint64_t new_height)
{
const uint64_t checkpoint_height = (new_height / CHECKPOINT_INTERVAL) * CHECKPOINT_INTERVAL;
if (checkpoint_height == 0) return;
bool checkpoint_created = false;
{
WriteLock lock(m_checkpointsLock);
// Check if we already have this checkpoint
if (!m_checkpoints.empty() && m_checkpoints.back().height >= checkpoint_height) {
return;
}
// Find the block at checkpoint height
PoolBlock* block = find_verified_block_at_height(checkpoint_height);
if (block && block->m_verified) {
Checkpoint cp;
cp.height = checkpoint_height;
cp.id = block->m_sidechainId;
cp.cumulative_difficulty = block->m_cumulativeDifficulty;
m_checkpoints.push_back(cp);
LOGINFO(1, "Checkpoint created: height " << cp.height
<< ", id " << cp.id);
// Prune old checkpoints beyond history limit
while (m_checkpoints.size() > CHECKPOINT_HISTORY) {
LOGINFO(3, "Pruning old checkpoint at height " << m_checkpoints.front().height);
m_checkpoints.erase(m_checkpoints.begin());
}
checkpoint_created = true;
}
} // WriteLock released here
// Save outside the lock to avoid deadlock
if (checkpoint_created) {
save_checkpoints();
}
}
PoolBlock* SideChain::find_verified_block_at_height(uint64_t height) const
{
auto it = m_blocksByHeight.find(height);
if (it == m_blocksByHeight.end()) {
return nullptr;
}
// Return the first verified block at this height (should be on main chain)
for (PoolBlock* block : it->second) {
if (block->m_verified) {
return block;
}
}
return nullptr;
}
Checkpoint SideChain::get_latest_checkpoint() const
{
ReadLock lock(m_checkpointsLock);
if (m_checkpoints.empty()) {
return Checkpoint{0, hash(), difficulty_type()};
}
return m_checkpoints.back();
}
std::vector<Checkpoint> SideChain::get_checkpoint_history() const
{
ReadLock lock(m_checkpointsLock);
return m_checkpoints;
}
uint64_t SideChain::get_latest_checkpoint_height() const
{
ReadLock lock(m_checkpointsLock);
if (m_checkpoints.empty()) {
return 0;
}
return m_checkpoints.back().height;
}
bool SideChain::validate_peer_checkpoint(uint64_t height, const hash& peer_hash) const
{
ReadLock lock(m_checkpointsLock);
for (const Checkpoint& cp : m_checkpoints) {
if (cp.height == height) {
return cp.id == peer_hash;
}
}
// We don't have this checkpoint yet - can't validate
return true;
}
void SideChain::on_block_rejected(const PoolBlock* block, const char* reason)
{
// Don't count failures during initial sync - blocks arrive out of order
if (!m_readyToMine.load()) {
return;
}
++m_externalBlockFailures;
LOGWARN(3, "Block rejected (" << m_externalBlockFailures << " consecutive): "
<< "height=" << block->m_sidechainHeight
<< ", id=" << block->m_sidechainId
<< ", reason: " << reason);
if (m_externalBlockFailures >= DIVERGENCE_THRESHOLD && !m_recoveryMode.load()) {
LOGERR(0, "\n========================================");
LOGERR(0, "CONSENSUS FAILURE DETECTED");
LOGERR(0, m_externalBlockFailures << " consecutive blocks rejected");
LOGERR(0, "Failure around height " << block->m_sidechainHeight);
LOGERR(0, "Initiating recovery...");
LOGERR(0, "========================================\n");
trigger_recovery(block->m_sidechainHeight);
}
}
void SideChain::on_block_accepted()
{
m_externalBlockFailures = 0;
}
void SideChain::trigger_recovery(uint64_t failure_height)
{
if (m_recoveryMode.exchange(true)) {
// Already in recovery mode
return;
}
// Disable mining immediately
m_readyToMine = false;
// Find the checkpoint before the failure
uint64_t recovery_checkpoint = (failure_height / CHECKPOINT_INTERVAL) * CHECKPOINT_INTERVAL;
if (recovery_checkpoint >= failure_height && recovery_checkpoint >= CHECKPOINT_INTERVAL) {
recovery_checkpoint -= CHECKPOINT_INTERVAL;
}
LOGINFO(0, "Recovery target checkpoint: " << recovery_checkpoint);
m_pendingRecoveryHeight = recovery_checkpoint;
// Request checkpoint validation from peers via P2P server
request_checkpoint_validation();
}
void SideChain::request_checkpoint_validation()
{
// This will be called by P2P server to initiate checkpoint comparison
// For now, proceed directly to reset if we have a pending recovery
uint64_t checkpoint_height = m_pendingRecoveryHeight.load();
if (checkpoint_height > 0) {
reset_to_checkpoint(checkpoint_height);
}
}
void SideChain::reset_to_checkpoint(uint64_t checkpoint_height)
{
LOGINFO(0, "\n========================================");
LOGINFO(0, "RESETTING TO CHECKPOINT " << checkpoint_height);
LOGINFO(0, "========================================\n");
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
PoolBlock* checkpoint_block = nullptr;
{
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;
}
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");
}
// Clear checkpoints after this point
{
WriteLock cpLock(m_checkpointsLock);
while (!m_checkpoints.empty() && m_checkpoints.back().height > checkpoint_height) {
m_checkpoints.pop_back();
}
}
// Reset counters
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
}
void SideChain::clear_checkpoints()
{
WriteLock lock(m_checkpointsLock);
m_checkpoints.clear();
m_checkpointsNeedValidation = false;
LOGINFO(1, "Cleared in-memory checkpoints");
}
void SideChain::save_checkpoints() const
{
const std::string path = DATA_DIR + "p2pool_checkpoints.dat";
ReadLock lock(m_checkpointsLock);
if (m_checkpoints.empty()) {
// No checkpoints to save, remove old file if exists
remove(path.c_str());
return;
}
std::ofstream f(path, std::ios::binary);
if (!f.is_open()) {
LOGWARN(1, "Failed to save checkpoints to " << path);
return;
}
// Write version marker for future compatibility
const uint32_t version = 1;
f.write(reinterpret_cast<const char*>(&version), sizeof(version));
// Write checkpoint count
const uint32_t count = static_cast<uint32_t>(m_checkpoints.size());
f.write(reinterpret_cast<const char*>(&count), sizeof(count));
// Write each checkpoint
for (const Checkpoint& cp : m_checkpoints) {
f.write(reinterpret_cast<const char*>(&cp.height), sizeof(cp.height));
f.write(reinterpret_cast<const char*>(cp.id.h), HASH_SIZE);
f.write(reinterpret_cast<const char*>(&cp.cumulative_difficulty), sizeof(cp.cumulative_difficulty));
}
f.close();
LOGINFO(3, "Saved " << count << " checkpoints to " << path);
}
void SideChain::load_checkpoints()
{
const std::string path = DATA_DIR + "p2pool_checkpoints.dat";
std::ifstream f(path, std::ios::binary);
if (!f.is_open()) {
LOGINFO(3, "No checkpoint file found at " << path << " (normal for first run)");
return;
}
// Read version
uint32_t version = 0;
f.read(reinterpret_cast<char*>(&version), sizeof(version));
if (version != 1) {
LOGWARN(1, "Unknown checkpoint file version " << version << ", ignoring");
return;
}
// Read checkpoint count
uint32_t count = 0;
f.read(reinterpret_cast<char*>(&count), sizeof(count));
if (count > 100) {
LOGWARN(1, "Checkpoint file has suspicious count " << count << ", ignoring");
return;
}
WriteLock lock(m_checkpointsLock);
m_checkpoints.clear();
for (uint32_t i = 0; i < count; ++i) {
Checkpoint cp;
f.read(reinterpret_cast<char*>(&cp.height), sizeof(cp.height));
f.read(reinterpret_cast<char*>(cp.id.h), HASH_SIZE);
f.read(reinterpret_cast<char*>(&cp.cumulative_difficulty), sizeof(cp.cumulative_difficulty));
if (f.fail()) {
LOGWARN(1, "Checkpoint file corrupted at entry " << i << ", discarding");
m_checkpoints.clear();
return;
}
m_checkpoints.push_back(cp);
}
f.close();
LOGINFO(1, "Loaded " << count << " checkpoints from " << path);
if (!m_checkpoints.empty()) {
LOGINFO(1, "Latest anchor point: height " << m_checkpoints.back().height <<
", id " << m_checkpoints.back().id);
m_checkpointsNeedValidation = true;
}
}
bool SideChain::validate_loaded_checkpoints()
{
// IMPORTANT: This function assumes m_sidechainLock is already held by the caller (WriteLock)
// It's called from update_chain_tip() -> prune_old_blocks() which is inside add_block()'s WriteLock
LOGINFO(3, "validate_loaded_checkpoints() called, m_checkpointsNeedValidation=" << (m_checkpointsNeedValidation ? "true" : "false"));
if (!m_checkpointsNeedValidation) {
LOGINFO(3, "Skipping checkpoint validation - not needed");
return true; // Safe to mine
}
LOGINFO(3, "Starting checkpoint validation...");
bool all_valid = true;
uint64_t first_mismatch_height = 0;
std::vector<Checkpoint> valid_checkpoints;
size_t original_count = 0;
bool had_stale = false;
std::vector<PoolBlock*> blocks_to_verify;
{
ReadLock cpLock(m_checkpointsLock);
// NOTE: m_sidechainLock already held by caller, don't acquire it again!
if (m_checkpoints.empty()) {
m_checkpointsNeedValidation = false;
// No checkpoints - need to bootstrap from chain tip
const PoolBlock* tip = m_chainTip;
if (tip && !tip->m_verified) {
PoolBlock* mutable_tip = const_cast<PoolBlock*>(tip);
mutable_tip->m_verified = true;
LOGINFO(1, "No checkpoints - bootstrapping verification from chain tip height " << tip->m_sidechainHeight);
}
return true; // Safe to mine
}
original_count = m_checkpoints.size();
// Check each checkpoint against actual chain
for (const Checkpoint& cp : m_checkpoints) {
LOGINFO(3, "Validating checkpoint at height " << cp.height);
// First check if checkpoint is reachable (within PPLNS window of current tip)
const PoolBlock* tip = m_chainTip;
if (tip && (cp.height + m_chainWindowSize < tip->m_sidechainHeight)) {
// Checkpoint is too old - outside sync window, discard it
LOGWARN(0, "Checkpoint at height " << cp.height << " is unreachable (tip=" << tip->m_sidechainHeight << ", window=" << m_chainWindowSize << ") - discarding stale checkpoint");
had_stale = true;
continue; // Don't add to valid_checkpoints
}
LOGINFO(3, "Checkpoint at height " << cp.height << " is reachable, looking up block id " << cp.id);
// Direct lookup - sidechain lock held above
const PoolBlock* found = nullptr;
auto it = m_blocksById.find(cp.id);
if (it != m_blocksById.end()) {
found = it->second;
}
LOGINFO(3, "find_block returned " << (found ? "valid pointer" : "nullptr"));
if (!found) {
// Block not yet synced but reachable - can't validate yet
LOGWARN(3, "Checkpoint at height " << cp.height << " not yet synced, deferring validation - mining blocked");
return false; // NOT safe to mine yet
}
PoolBlock* block = const_cast<PoolBlock*>(found);
if (!block) {
// Block not yet synced but reachable - can't validate yet
LOGWARN(3, "Checkpoint at height " << cp.height << " not yet synced, deferring validation - mining blocked");
return false; // NOT safe to mine yet
}
if (block->m_sidechainId != cp.id) {
LOGWARN(0, "CHECKPOINT MISMATCH at height " << cp.height <<
": cached=" << cp.id <<
", actual=" << block->m_sidechainId);
all_valid = false;
if (first_mismatch_height == 0) {
first_mismatch_height = cp.height;
}
} else {
LOGINFO(3, "Checkpoint validated: height " << cp.height << ", id " << cp.id);
block->m_verified = true; // Trust the checkpoint as verification anchor
blocks_to_verify.push_back(block); // Queue for verify_loop
valid_checkpoints.push_back(cp); // Keep valid checkpoint
}
}
} // Locks released here
bool need_bootstrap = false;
uint64_t bootstrap_height = 0;
// If we had stale checkpoints, update and save
if (had_stale) {
WriteLock wlock(m_checkpointsLock);
m_checkpoints = std::move(valid_checkpoints);
LOGINFO(1, "Cleaned up stale checkpoints: " << original_count << " -> " << m_checkpoints.size());
// If ALL checkpoints were stale, bootstrap from chain tip
if (m_checkpoints.empty()) {
const PoolBlock* tip = m_chainTip;
if (tip) {
PoolBlock* mutable_tip = const_cast<PoolBlock*>(tip);
mutable_tip->m_verified = true;
LOGINFO(1, "All checkpoints stale - bootstrapping verification from chain tip height " << tip->m_sidechainHeight);
need_bootstrap = true;
bootstrap_height = tip->m_sidechainHeight;
}
}
} // WriteLock released here
// Create new checkpoint outside the lock
if (need_bootstrap) {
update_checkpoints(bootstrap_height);
}
m_checkpointsNeedValidation = false;
if (all_valid) {
LOGINFO(1, "All " << (had_stale ? m_checkpoints.size() : original_count) << " cached checkpoints validated successfully");
// NOTE: m_sidechainLock already held by caller (WriteLock from add_block)
// No need to acquire it again - verify_loop and block scan can proceed
// Propagate verification from anchor points
for (PoolBlock* block : blocks_to_verify) {
LOGINFO(3, "Running verify_loop from anchor height " << block->m_sidechainHeight);
verify_loop(block);
}
// Scan all blocks and verify any with verified parents
LOGINFO(3, "Scanning for unverified blocks with verified parents...");
bool made_progress = true;
int pass = 0;
while (made_progress && pass < 100) {
made_progress = false;
++pass;
for (auto& pair : m_blocksById) {
PoolBlock* b = pair.second;
if (!b->m_verified && b->m_sidechainHeight > 0) {
auto parent_it = m_blocksById.find(b->m_parent);
if (parent_it != m_blocksById.end() && parent_it->second->m_verified) {
b->m_verified = true;
made_progress = true;
}
}
}
if (made_progress) {
LOGINFO(3, "Verification pass " << pass << " - verified more blocks");
}
}
if (had_stale) {
save_checkpoints(); // Persist the cleaned list
}
return true; // Safe to mine
} else {
LOGERR(0, "Cached checkpoints are INVALID - chain has diverged since last run");
LOGERR(0, "First mismatch at height " << first_mismatch_height);
{
WriteLock wlock(m_checkpointsLock);
m_checkpoints.clear();
}
LOGINFO(0, "Cleared stale checkpoints - will rebuild from current chain");
save_checkpoints(); // Persist the cleared state
// Rebuild checkpoints from current chain
const PoolBlock* tip = m_chainTip;
if (tip) {
update_checkpoints(tip->m_sidechainHeight);
}
return true; // Checkpoints cleared and rebuilt, safe to mine
}
}
} // namespace p2pool