@@ -15,7 +15,7 @@ pub const MIN_COMPATIBLE_VERSION: u32 = 1; // Minimum version we can work with
1515// PRODUCTION CONSTANTS - No hardcoded magic numbers!
1616const ROTATION_INTERVAL_BLOCKS: u64 = 30; // Producer rotation every 30 blocks
1717const MIN_BYZANTINE_NODES: usize = 4; // 3f+1 where f=1
18- const FAST_SYNC_THRESHOLD: u64 = 50 ; // Trigger fast sync if behind by 50 + blocks
18+ const FAST_SYNC_THRESHOLD: u64 = 10 ; // Trigger fast sync if behind by 10 + blocks (lowered from 50 for faster detection)
1919const FAST_SYNC_TIMEOUT_SECS: u64 = 60; // Fast sync timeout
2020const BACKGROUND_SYNC_TIMEOUT_SECS: u64 = 30; // Background sync timeout
2121const SNAPSHOT_FULL_INTERVAL: u64 = 10000; // Full snapshot every 10k blocks
@@ -69,11 +69,16 @@ pub fn set_emergency_producer_flag(block_height: u64, producer: String) {
6969}
7070
7171// CRITICAL: Global synchronization flags for API access
72- use std::sync::atomic::{AtomicBool, Ordering};
72+ use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
7373static SYNC_IN_PROGRESS: AtomicBool = AtomicBool::new(false);
7474static FAST_SYNC_IN_PROGRESS: AtomicBool = AtomicBool::new(false);
7575pub static NODE_IS_SYNCHRONIZED: AtomicBool = AtomicBool::new(false);
7676
77+ // CRITICAL FIX: Track last block production time globally for stall detection
78+ // This prevents network from getting stuck when all nodes stop producing
79+ static LAST_BLOCK_PRODUCED_TIME: AtomicU64 = AtomicU64::new(0);
80+ static LAST_BLOCK_PRODUCED_HEIGHT: AtomicU64 = AtomicU64::new(0);
81+
7782// NOTE: Removed ROTATION_NOTIFY - simple 1-second timing is more reliable
7883// Testing showed that natural timing without interrupts prevents race conditions
7984
@@ -1444,9 +1449,14 @@ impl BlockchainNode {
14441449 continue;
14451450 }
14461451
1447- // CRITICAL: Rate limit fork attempts (prevent DoS)
1452+ // CRITICAL: Rate limit fork attempts (prevent DoS)
1453+ // But allow immediate sync if it's our own fork
14481454 let last_attempt = *last_fork_attempt.read().await;
1449- if last_attempt.elapsed().as_secs() < FORK_ATTEMPT_COOLDOWN_SECS {
1455+ let own_fork = if let Some(p2p) = &unified_p2p {
1456+ fork_producer == p2p.get_node_id()
1457+ } else { false };
1458+
1459+ if !own_fork && last_attempt.elapsed().as_secs() < FORK_ATTEMPT_COOLDOWN_SECS {
14501460 println!("[REORG] 🛡️ Fork attempt too soon - rate limited ({}s cooldown)", FORK_ATTEMPT_COOLDOWN_SECS);
14511461
14521462 // CRITICAL FIX: If we detect our own blocks being rejected as forks,
@@ -1689,6 +1699,10 @@ impl BlockchainNode {
16891699 *height.write().await = received_block.height;
16901700 println!("[BLOCKS] 📊 Global height updated to {}", received_block.height);
16911701
1702+ // CRITICAL FIX: Update last block time for stall detection
1703+ LAST_BLOCK_PRODUCED_TIME.store(get_timestamp_safe(), Ordering::Relaxed);
1704+ LAST_BLOCK_PRODUCED_HEIGHT.store(received_block.height, Ordering::Relaxed);
1705+
16921706 // CRITICAL FIX: Update P2P local height for message filtering
16931707 crate::unified_p2p::LOCAL_BLOCKCHAIN_HEIGHT.store(
16941708 received_block.height,
@@ -2875,6 +2889,50 @@ impl BlockchainNode {
28752889 last_production_height = microblock_height;
28762890 last_production_time = std::time::Instant::now();
28772891 }
2892+
2893+ // CRITICAL FIX: Global network stall detection
2894+ // Check if ANY block has been produced recently (by us or others)
2895+ let last_block_time = LAST_BLOCK_PRODUCED_TIME.load(Ordering::Relaxed);
2896+ let last_block_height = LAST_BLOCK_PRODUCED_HEIGHT.load(Ordering::Relaxed);
2897+ let current_time = get_timestamp_safe();
2898+
2899+ if last_block_time > 0 && current_time > last_block_time {
2900+ let time_since_last_block = current_time - last_block_time;
2901+
2902+ // CRITICAL: Trigger emergency if no blocks for 10+ seconds
2903+ // This is GLOBAL stall detection, not just local
2904+ if time_since_last_block > 10 && microblock_height > 0 {
2905+ println!("[STALL] 🚨 NETWORK STALL DETECTED! No blocks for {} seconds", time_since_last_block);
2906+ println!("[STALL] 📊 Last block: #{} at timestamp {}", last_block_height, last_block_time);
2907+
2908+ // Force emergency producer selection if we're supposed to be producing
2909+ if let Some(p2p) = &unified_p2p {
2910+ let next_height = microblock_height + 1;
2911+ let expected_producer = Self::select_microblock_producer(
2912+ next_height, &unified_p2p, &node_id, node_type,
2913+ Some(&storage), &quantum_poh
2914+ ).await;
2915+
2916+ if time_since_last_block > 15 {
2917+ println!("[STALL] 🔥 Triggering emergency failover for producer: {}", expected_producer);
2918+
2919+ // Select emergency producer
2920+ let emergency_producer = Self::select_emergency_producer(
2921+ &expected_producer, next_height, &unified_p2p,
2922+ &node_id, node_type, Some(storage.clone())
2923+ ).await;
2924+
2925+ // Broadcast emergency change
2926+ if let Err(e) = p2p.broadcast_emergency_producer_change(
2927+ &expected_producer, &emergency_producer,
2928+ next_height, "network_stall"
2929+ ) {
2930+ println!("[STALL] ⚠️ Failed to broadcast emergency: {}", e);
2931+ }
2932+ }
2933+ }
2934+ }
2935+ }
28782936 }
28792937 // SYNC FIX: Fast catch-up mode for nodes that are far behind
28802938 // Using global flags defined at module level
@@ -2936,8 +2994,8 @@ impl BlockchainNode {
29362994 println!("[SYNC] ⚠️ Node is {} blocks behind network (local: {}, network: {})",
29372995 height_difference, microblock_height, network_height);
29382996
2939- // For extreme lag (>50 blocks), use fast sync
2940- if height_difference > 50 {
2997+ // For significant lag (>10 blocks), use fast sync
2998+ if height_difference > 10 {
29412999 // RACE CONDITION FIX: Only start fast sync if not already running
29423000 if !FAST_SYNC_IN_PROGRESS.swap(true, Ordering::SeqCst) {
29433001 println!("[SYNC] ⚡ FAST SYNC MODE: {} blocks behind, catching up...", height_difference);
@@ -4049,6 +4107,10 @@ impl BlockchainNode {
40494107 // This prevents phantom height where node claims height N without having block N
40504108 println!("[PRODUCER] ✅ Created and saved block #{}", microblock.height);
40514109
4110+ // CRITICAL FIX: Update global last block time for stall detection
4111+ LAST_BLOCK_PRODUCED_TIME.store(get_timestamp_safe(), Ordering::Relaxed);
4112+ LAST_BLOCK_PRODUCED_HEIGHT.store(microblock.height, Ordering::Relaxed);
4113+
40524114 // CRITICAL: Increment height for next iteration
40534115 // We only advance after successfully creating and storing the block
40544116 microblock_height = microblock.height; // Set to the block we just created
0 commit comments