Skip to content

Commit 517189d

Browse files
michaelgptclaude
andcommitted
fix(sync): resolve state machine deadlock when target_height=0
When no peer has a higher chain (target_height=0), the sync state machine was stuck in RequestingBlocks state indefinitely, blocking all block production. This occurred because: 1. Transition to RequestingBlocks didn't check if already synced 2. Sync completion check required target_height > 0 Fixed by: - Checking sync status before transitioning to RequestingBlocks - If target_height=0 or current >= target, transition to Synced - Updated periodic completion check to handle target_height=0 Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 5da3044 commit 517189d

File tree

1 file changed

+76
-22
lines changed

1 file changed

+76
-22
lines changed

app/src/actors_v2/network/sync_actor.rs

Lines changed: 76 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -409,23 +409,39 @@ impl Actor for SyncActor {
409409

410410
// Check if sync is complete
411411
const SYNC_THRESHOLD: u64 = 2;
412-
let is_complete = s.is_running
412+
413+
// Sync is complete when:
414+
// 1. Sync is running and in active sync state
415+
// 2. No pending requests or blocks
416+
// 3. Either: target_height == 0 (no peer has higher chain, already synced)
417+
// OR: we've reached target_height within threshold
418+
let in_sync_state = s.is_running
413419
&& (s.sync_state == SyncState::RequestingBlocks
414-
|| s.sync_state == SyncState::ProcessingBlocks)
415-
&& s.target_height > 0
416-
&& s.current_height + SYNC_THRESHOLD >= s.target_height
417-
&& s.active_requests.is_empty()
418-
&& s.block_queue.is_empty();
420+
|| s.sync_state == SyncState::ProcessingBlocks);
421+
422+
let no_pending_work = s.active_requests.is_empty() && s.block_queue.is_empty();
423+
424+
let reached_target = s.target_height == 0
425+
|| s.current_height + SYNC_THRESHOLD >= s.target_height;
426+
427+
let is_complete = in_sync_state && no_pending_work && reached_target;
419428

420429
if is_complete {
421430
let current = s.current_height;
422431
let target = s.target_height;
423432

424-
tracing::info!(
425-
current_height = current,
426-
target_height = target,
427-
"Sync complete - reached target height"
428-
);
433+
if target == 0 {
434+
tracing::info!(
435+
current_height = current,
436+
"Sync complete - no higher chain discovered (already synced)"
437+
);
438+
} else {
439+
tracing::info!(
440+
current_height = current,
441+
target_height = target,
442+
"Sync complete - reached target height"
443+
);
444+
}
429445

430446
s.transition_to_state(SyncState::Synced);
431447
s.is_running = false;
@@ -545,10 +561,31 @@ impl Handler<SyncMessage> for SyncActor {
545561
"Retrieved peers from NetworkActor"
546562
);
547563

548-
// Transition based on peer availability
564+
// Transition based on peer availability and sync status
549565
if !s.sync_peers.is_empty() {
550-
s.transition_to_state(SyncState::RequestingBlocks);
551-
tracing::info!("Peers available - transitioning to RequestingBlocks");
566+
// Check if we're already synced (target_height == 0 means no peer has a higher chain)
567+
const SYNC_THRESHOLD: u64 = 2;
568+
let already_synced = s.target_height == 0
569+
|| s.current_height + SYNC_THRESHOLD >= s.target_height;
570+
571+
if already_synced {
572+
let height = s.current_height;
573+
tracing::info!(
574+
current_height = height,
575+
target_height = s.target_height,
576+
"Already synced (no higher chain discovered) - completing sync"
577+
);
578+
s.transition_to_state(SyncState::Synced);
579+
s.is_running = false;
580+
s.metrics.record_sync_complete(height);
581+
} else {
582+
s.transition_to_state(SyncState::RequestingBlocks);
583+
tracing::info!(
584+
current_height = s.current_height,
585+
target_height = s.target_height,
586+
"Peers available, behind target - transitioning to RequestingBlocks"
587+
);
588+
}
552589
} else {
553590
tracing::info!("No peers yet - staying in DiscoveringPeers");
554591
}
@@ -952,16 +989,33 @@ impl Handler<SyncMessage> for SyncActor {
952989
s.discovery_time_accumulated = Duration::ZERO;
953990
s.state_entered_at = SystemTime::now();
954991

955-
// Transition to RequestingBlocks if we're ready to sync
992+
// Transition based on sync status
956993
if s.is_running && s.sync_state == SyncState::DiscoveringPeers {
957-
s.transition_to_state(SyncState::RequestingBlocks);
994+
// Check if we're already synced (target_height == 0 means no peer has a higher chain)
995+
const SYNC_THRESHOLD: u64 = 2;
996+
let already_synced = s.target_height == 0
997+
|| s.current_height + SYNC_THRESHOLD >= s.target_height;
958998

959-
tracing::info!(
960-
current_height = s.current_height,
961-
target_height = s.target_height,
962-
peer_count = s.sync_peers.len(),
963-
"Peers discovered - transitioning to RequestingBlocks"
964-
);
999+
if already_synced {
1000+
let height = s.current_height;
1001+
tracing::info!(
1002+
current_height = height,
1003+
target_height = s.target_height,
1004+
peer_count = s.sync_peers.len(),
1005+
"Already synced (no higher chain discovered) - completing sync"
1006+
);
1007+
s.transition_to_state(SyncState::Synced);
1008+
s.is_running = false;
1009+
s.metrics.record_sync_complete(height);
1010+
} else {
1011+
s.transition_to_state(SyncState::RequestingBlocks);
1012+
tracing::info!(
1013+
current_height = s.current_height,
1014+
target_height = s.target_height,
1015+
peer_count = s.sync_peers.len(),
1016+
"Peers discovered, behind target - transitioning to RequestingBlocks"
1017+
);
1018+
}
9651019
}
9661020
}
9671021

0 commit comments

Comments
 (0)