From 7754adb834fda6ecc14bdd747562593e67b3925e Mon Sep 17 00:00:00 2001
From: Gary Yu <gairy.yu@gmail.com>
Date: Wed, 17 Oct 2018 00:14:16 +0800
Subject: [PATCH] kick stuck peer out of connected peers (#1746)

* kick stuck peer out of connected peers
* adjust kick time to 2 hours
* stuck detection take into account own difficulty compare
---
 core/src/global.rs   |  4 ++++
 p2p/src/handshake.rs |  2 ++
 p2p/src/peer.rs      | 14 +++++++++++++-
 p2p/src/peers.rs     |  8 ++++++++
 p2p/src/types.rs     |  4 ++++
 5 files changed, 31 insertions(+), 1 deletion(-)

diff --git a/core/src/global.rs b/core/src/global.rs
index 299476e2f..56b4327ee 100644
--- a/core/src/global.rs
+++ b/core/src/global.rs
@@ -69,6 +69,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;
+
 /// Types of chain a server can run with, dictates the genesis block and
 /// and mining parameters used.
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
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<Utc>,
+	pub stuck_detector: DateTime<Utc>,
 }
 
 /// 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()