sc_network_sync/strategy/
disconnected_peers.rs1use crate::types::BadPeer;
20use sc_network::ReputationChange as Rep;
21use sc_network_types::PeerId;
22use schnellru::{ByLength, LruMap};
23
24const LOG_TARGET: &str = "sync::disconnected_peers";
25
26const MAX_DISCONNECTED_PEERS_STATE: u32 = 512;
32
33const DISCONNECTED_PEER_BACKOFF_SECONDS: u64 = 60;
42
43const MAX_NUM_DISCONNECTS: u64 = 3;
45
46pub const REPUTATION_REPORT: Rep = Rep::new_fatal("Peer disconnected with inflight after backoffs");
51
52#[derive(Debug)]
54struct DisconnectedState {
55 num_disconnects: u64,
57 last_disconnect: std::time::Instant,
59}
60
61impl DisconnectedState {
62 pub fn new() -> Self {
64 Self { num_disconnects: 1, last_disconnect: std::time::Instant::now() }
65 }
66
67 pub fn increment(&mut self) {
69 self.num_disconnects = self.num_disconnects.saturating_add(1);
70 self.last_disconnect = std::time::Instant::now();
71 }
72
73 pub fn num_disconnects(&self) -> u64 {
75 self.num_disconnects
76 }
77
78 pub fn last_disconnect(&self) -> std::time::Instant {
80 self.last_disconnect
81 }
82}
83
84pub struct DisconnectedPeers {
89 disconnected_peers: LruMap<PeerId, DisconnectedState>,
91 backoff_seconds: u64,
93}
94
95impl DisconnectedPeers {
96 pub fn new() -> Self {
98 Self {
99 disconnected_peers: LruMap::new(ByLength::new(MAX_DISCONNECTED_PEERS_STATE)),
100 backoff_seconds: DISCONNECTED_PEER_BACKOFF_SECONDS,
101 }
102 }
103
104 pub fn on_disconnect_during_request(&mut self, peer: PeerId) -> Option<BadPeer> {
108 if let Some(state) = self.disconnected_peers.get(&peer) {
109 state.increment();
110
111 let should_ban = state.num_disconnects() >= MAX_NUM_DISCONNECTS;
112 log::debug!(
113 target: LOG_TARGET,
114 "Disconnected known peer {peer} state: {state:?}, should ban: {should_ban}",
115 );
116
117 should_ban.then(|| {
118 self.disconnected_peers.remove(&peer);
124 BadPeer(peer, REPUTATION_REPORT)
125 })
126 } else {
127 log::debug!(
128 target: LOG_TARGET,
129 "Added peer {peer} for the first time"
130 );
131 self.disconnected_peers.insert(peer, DisconnectedState::new());
133 None
134 }
135 }
136
137 pub fn is_peer_available(&mut self, peer_id: &PeerId) -> bool {
139 let Some(state) = self.disconnected_peers.get(peer_id) else {
140 return true;
141 };
142
143 let elapsed = state.last_disconnect().elapsed();
144 if elapsed.as_secs() >= self.backoff_seconds * state.num_disconnects {
145 log::debug!(target: LOG_TARGET, "Peer {peer_id} is available for queries");
146 self.disconnected_peers.remove(peer_id);
147 true
148 } else {
149 log::debug!(target: LOG_TARGET,"Peer {peer_id} is backedoff");
150 false
151 }
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use std::time::Duration;
159
160 #[test]
161 fn test_disconnected_peer_state() {
162 let mut state = DisconnectedPeers::new();
163 let peer = PeerId::random();
164
165 assert_eq!(state.is_peer_available(&peer), true);
167
168 for _ in 0..MAX_NUM_DISCONNECTS - 1 {
169 assert!(state.on_disconnect_during_request(peer).is_none());
170 assert_eq!(state.is_peer_available(&peer), false);
171 }
172
173 assert!(state.on_disconnect_during_request(peer).is_some());
174 assert!(state.disconnected_peers.get(&peer).is_none());
177 }
178
179 #[test]
180 fn ensure_backoff_time() {
181 const TEST_BACKOFF_SECONDS: u64 = 2;
182 let mut state = DisconnectedPeers {
183 disconnected_peers: LruMap::new(ByLength::new(1)),
184 backoff_seconds: TEST_BACKOFF_SECONDS,
185 };
186 let peer = PeerId::random();
187
188 assert!(state.on_disconnect_during_request(peer).is_none());
189 assert_eq!(state.is_peer_available(&peer), false);
190
191 std::thread::sleep(Duration::from_secs(TEST_BACKOFF_SECONDS + 1));
193
194 assert_eq!(state.is_peer_available(&peer), true);
195 }
196}