diff --git a/core/src/global.rs b/core/src/global.rs index e97e43b69..c3ad5650c 100644 --- a/core/src/global.rs +++ b/core/src/global.rs @@ -66,6 +66,10 @@ pub const TESTNET2_INITIAL_DIFFICULTY: u64 = 1000; /// a 30x Cuckoo adjustment factor pub const TESTNET3_INITIAL_DIFFICULTY: u64 = 30000; +/// If a peer's last updated difficulty is 2 hours ago and its difficulty's lower than ours, +/// we're sure this peer is a stuck node, and we will kick out such kind of stuck peers. +pub const STUCK_PEER_KICK_TIME: i64 = 2 * 3600 * 1000; + /// Testnet 4 initial block difficulty /// 1_000 times natural scale factor for cuckatoo29 pub const TESTNET4_INITIAL_DIFFICULTY: u64 = 1_000 * (2<<(29-24)) * 29; diff --git a/p2p/src/handshake.rs b/p2p/src/handshake.rs index 3fa9f879b..c384356b8 100644 --- a/p2p/src/handshake.rs +++ b/p2p/src/handshake.rs @@ -102,6 +102,7 @@ impl Handshake { total_difficulty: shake.total_difficulty, height: 0, last_seen: Utc::now(), + stuck_detector: Utc::now(), })), direction: Direction::Outbound, }; @@ -161,6 +162,7 @@ impl Handshake { total_difficulty: hand.total_difficulty, height: 0, last_seen: Utc::now(), + stuck_detector: Utc::now(), })), direction: Direction::Inbound, }; diff --git a/p2p/src/peer.rs b/p2p/src/peer.rs index d89a08ed9..00eaf9a14 100644 --- a/p2p/src/peer.rs +++ b/p2p/src/peer.rs @@ -18,9 +18,9 @@ use std::sync::{Arc, RwLock}; use chrono::prelude::{DateTime, Utc}; use conn; -use core::core; use core::core::hash::{Hash, Hashed}; use core::pow::Difficulty; +use core::{core, global}; use handshake::Handshake; use msg::{self, BanReason, GetPeerAddrs, Locator, Ping, TxHashSetRequest}; use protocol::Protocol; @@ -140,6 +140,18 @@ impl Peer { State::Banned == *self.state.read().unwrap() } + /// Whether this peer is stuck on sync. + pub fn is_stuck(&self) -> (bool, Difficulty) { + let peer_live_info = self.info.live_info.read().unwrap(); + let now = Utc::now().timestamp_millis(); + // if last updated difficulty is 2 hours ago, we're sure this peer is a stuck node. + if now > peer_live_info.stuck_detector.timestamp_millis() + global::STUCK_PEER_KICK_TIME { + (true, peer_live_info.total_difficulty) + } else { + (false, peer_live_info.total_difficulty) + } + } + /// Set this peer status to banned pub fn set_banned(&self) { *self.state.write().unwrap() = State::Banned; diff --git a/p2p/src/peers.rs b/p2p/src/peers.rs index c260f8776..5dde96c54 100644 --- a/p2p/src/peers.rs +++ b/p2p/src/peers.rs @@ -424,6 +424,14 @@ impl Peers { } else if !peer.is_connected() { debug!(LOGGER, "clean_peers {:?}, not connected", peer.info.addr); rm.push(peer.clone()); + } else { + let (stuck, diff) = peer.is_stuck(); + if stuck && diff < self.adapter.total_difficulty() { + debug!(LOGGER, "clean_peers {:?}, stuck peer", peer.info.addr); + peer.stop(); + let _ = self.update_state(peer.info.addr, State::Defunct); + rm.push(peer.clone()); + } } } diff --git a/p2p/src/types.rs b/p2p/src/types.rs index eaee5dd39..f7e94d175 100644 --- a/p2p/src/types.rs +++ b/p2p/src/types.rs @@ -241,6 +241,7 @@ pub struct PeerLiveInfo { pub total_difficulty: Difficulty, pub height: u64, pub last_seen: DateTime, + pub stuck_detector: DateTime, } /// General information about a connected peer that's useful to other modules. @@ -274,6 +275,9 @@ impl PeerInfo { /// Takes a write lock on the live_info. pub fn update(&self, height: u64, total_difficulty: Difficulty) { let mut live_info = self.live_info.write().unwrap(); + if total_difficulty != live_info.total_difficulty { + live_info.stuck_detector = Utc::now(); + } live_info.height = height; live_info.total_difficulty = total_difficulty; live_info.last_seen = Utc::now()