@@ -1073,7 +1073,8 @@ const RETARGET_BACKFILL: u64 = RETARGET_INTERVAL; // request a full retarget win
10731073 // Short-window tracker for peers repeatedly sending divergent parents at the same height
10741074 static DIVERGENT_PARENT_EVENTS : Lazy < Mutex < std:: collections:: HashMap < ( PeerId , u64 ) , ( std:: time:: Instant , u32 ) > > > = Lazy :: new ( || Mutex :: new ( std:: collections:: HashMap :: new ( ) ) ) ;
10751075 const DIVERGENT_WINDOW_SECS : u64 = 15 ;
1076- const DIVERGENT_THRESHOLD : u32 = 8 ;
1076+ const DIVERGENT_THRESHOLD : u32 = 5 ; // tighten threshold to penalize sooner
1077+ const DIVERGENT_SPAM_BAN_SECS : u64 = 60 * 60 ; // 1 hour ban for egregious spam
10771078
10781079 fn note_divergent_parent_event ( peer_id : & PeerId , height : u64 ) -> u32 {
10791080 let now = std:: time:: Instant :: now ( ) ;
@@ -2048,7 +2049,13 @@ const RETARGET_BACKFILL: u64 = RETARGET_INTERVAL; // request a full retarget win
20482049 } else {
20492050 net_log!( "🚫 Peer {} spamming divergent parent at height {} ({} in {}s) — penalizing" , peer_id, a. num, div_count, DIVERGENT_WINDOW_SECS ) ;
20502051 crate :: metrics:: VALIDATION_FAIL_ANCHOR . inc( ) ;
2052+ // Escalate penalties: multiple strikes and temporary ban
20512053 score. record_validation_failure( ) ;
2054+ score. record_validation_failure( ) ;
2055+ score. ban_for( DIVERGENT_SPAM_BAN_SECS ) ;
2056+ if score. is_banned( ) {
2057+ let _ = swarm. disconnect_peer_id( peer_id) ;
2058+ }
20522059 continue ;
20532060 }
20542061 }
@@ -2084,7 +2091,13 @@ const RETARGET_BACKFILL: u64 = RETARGET_INTERVAL; // request a full retarget win
20842091 } else {
20852092 net_log!( "🚫 Peer {} spamming parent-unknown at height {} ({} in {}s) — penalizing" , peer_id, a. num, div_count, DIVERGENT_WINDOW_SECS ) ;
20862093 crate :: metrics:: VALIDATION_FAIL_ANCHOR . inc( ) ;
2094+ // Escalate penalties: multiple strikes and temporary ban
2095+ score. record_validation_failure( ) ;
20872096 score. record_validation_failure( ) ;
2097+ score. ban_for( DIVERGENT_SPAM_BAN_SECS ) ;
2098+ if score. is_banned( ) {
2099+ let _ = swarm. disconnect_peer_id( peer_id) ;
2100+ }
20882101 continue ;
20892102 }
20902103 }
@@ -2093,6 +2106,7 @@ const RETARGET_BACKFILL: u64 = RETARGET_INTERVAL; // request a full retarget win
20932106 if score. check_rate_limit( ) { net_routine!( "⚓ Received anchor for epoch {} from peer: {}" , a. num, peer_id) ; }
20942107 match classify_anchor_ingress( & a, & db) {
20952108 AnchorClass :: Valid => {
2109+ // Only trust network progress on fully Valid anchors
20962110 if let Ok ( mut st) = sync_state. lock( ) {
20972111 if a. num > st. highest_seen_epoch { st. highest_seen_epoch = a. num; }
20982112 st. peer_confirmed_tip = true ;
@@ -2272,12 +2286,7 @@ const RETARGET_BACKFILL: u64 = RETARGET_INTERVAL; // request a full retarget win
22722286 }
22732287 }
22742288
2275- if let Ok ( mut state) = sync_state. lock( ) {
2276- if a. num > state. highest_seen_epoch {
2277- state. highest_seen_epoch = a. num;
2278- }
2279- state. peer_confirmed_tip = true ;
2280- }
2289+ // Do not treat MissingParent as confirmation of network tip; wait for Valid linkage
22812290 if a. num > 0 {
22822291 let retarget_gap = parent_h. saturating_add( 1 ) != a. num;
22832292 let backfill_window = if retarget_gap {
@@ -2342,10 +2351,7 @@ const RETARGET_BACKFILL: u64 = RETARGET_INTERVAL; // request a full retarget win
23422351 let cutoff = std:: time:: Instant :: now( ) - std:: time:: Duration :: from_secs( 60 ) ;
23432352 agg. retain( |_, v| v. first_seen >= cutoff) ;
23442353 }
2345- if let Ok ( mut st) = sync_state. lock( ) {
2346- if a. num > st. highest_seen_epoch { st. highest_seen_epoch = a. num; }
2347- st. peer_confirmed_tip = true ;
2348- }
2354+ // Do not mark peer-confirmed or advance highest_seen on alt-fork observations
23492355 needs_reorg_check = true ;
23502356
23512357 // Aggressively penalize consensus-parameter mismatches even when treated as alt-fork
@@ -3023,10 +3029,7 @@ const RETARGET_BACKFILL: u64 = RETARGET_INTERVAL; // request a full retarget win
30233029 if is_new {
30243030 net_routine!( "⏳ Buffered epoch {} from hash request (buffer size: {})" , a. num, orphan_buf. len( ) ) ;
30253031 }
3026- if let Ok ( mut st) = sync_state. lock( ) {
3027- if a. num > st. highest_seen_epoch { st. highest_seen_epoch = a. num; }
3028- st. peer_confirmed_tip = true ;
3029- }
3032+ // Do not update sync_state based solely on by-hash responses; wait for Valid classification path
30303033 needs_reorg_check = true ;
30313034 } ,
30323035 TOP_COIN_REQUEST => if let Ok ( id) = bincode:: deserialize:: <[ u8 ; 32 ] >( & message. data) {
0 commit comments