cuprate_address_book/
peer_list.rs1use std::collections::{BTreeMap, HashMap, HashSet};
2
3use indexmap::IndexMap;
4use rand::prelude::*;
5
6use cuprate_constants::block::MAX_BLOCK_HEIGHT_USIZE;
7use cuprate_p2p_core::{services::ZoneSpecificPeerListEntryBase, NetZoneAddress, NetworkZone};
8use cuprate_pruning::PruningSeed;
9
10#[cfg(test)]
11pub(crate) mod tests;
12
13#[derive(Debug)]
17pub(crate) struct PeerList<Z: NetworkZone> {
18 pub peers: IndexMap<Z::Addr, ZoneSpecificPeerListEntryBase<Z::Addr>>,
20 pruning_seeds: BTreeMap<PruningSeed, Vec<Z::Addr>>,
29 ban_ids: HashMap<<Z::Addr as NetZoneAddress>::BanID, Vec<Z::Addr>>,
31}
32
33impl<Z: NetworkZone> PeerList<Z> {
34 pub(crate) fn new(list: Vec<ZoneSpecificPeerListEntryBase<Z::Addr>>) -> Self {
36 let mut peers = IndexMap::with_capacity(list.len());
37 let mut pruning_seeds = BTreeMap::new();
38 let mut ban_ids = HashMap::with_capacity(list.len());
39
40 for peer in list {
41 pruning_seeds
42 .entry(peer.pruning_seed)
43 .or_insert_with(Vec::new)
44 .push(peer.adr);
45
46 ban_ids
47 .entry(peer.adr.ban_id())
48 .or_insert_with(Vec::new)
49 .push(peer.adr);
50
51 peers.insert(peer.adr, peer);
52 }
53 Self {
54 peers,
55 pruning_seeds,
56 ban_ids,
57 }
58 }
59
60 pub(crate) fn len(&self) -> usize {
62 self.peers.len()
63 }
64
65 pub(crate) fn add_new_peer(&mut self, peer: ZoneSpecificPeerListEntryBase<Z::Addr>) {
67 if self.peers.insert(peer.adr, peer).is_none() {
68 #[expect(clippy::unwrap_or_default, reason = "It's more clear with this")]
69 self.pruning_seeds
70 .entry(peer.pruning_seed)
71 .or_insert_with(Vec::new)
72 .push(peer.adr);
73
74 #[expect(clippy::unwrap_or_default)]
75 self.ban_ids
76 .entry(peer.adr.ban_id())
77 .or_insert_with(Vec::new)
78 .push(peer.adr);
79 }
80 }
81
82 pub(crate) fn take_random_peer<R: Rng>(
89 &mut self,
90 r: &mut R,
91 block_needed: Option<usize>,
92 must_keep_peers: &HashSet<Z::Addr>,
93 ) -> Option<ZoneSpecificPeerListEntryBase<Z::Addr>> {
94 for _ in 0..3 {
98 if let Some(needed_height) = block_needed {
99 let (_, addresses_with_block) = self.pruning_seeds.iter().find(|(seed, _)| {
100 seed.get_next_unpruned_block(needed_height, MAX_BLOCK_HEIGHT_USIZE)
102 .expect("Block needed is higher than max block allowed.")
103 == needed_height
104 })?;
105 let n = r.gen_range(0..addresses_with_block.len());
106 let peer = addresses_with_block[n];
107 if must_keep_peers.contains(&peer) {
108 continue;
109 }
110
111 return self.remove_peer(&peer);
112 }
113 let len = self.len();
114
115 if len == 0 {
116 return None;
117 }
118
119 let n = r.gen_range(0..len);
120
121 let (&key, _) = self.peers.get_index(n).unwrap();
122 if !must_keep_peers.contains(&key) {
123 return self.remove_peer(&key);
124 }
125 }
126
127 None
128 }
129
130 pub(crate) fn get_random_peers<R: Rng>(
131 &self,
132 r: &mut R,
133 len: usize,
134 ) -> Vec<ZoneSpecificPeerListEntryBase<Z::Addr>> {
135 let mut peers = self.peers.values().copied().choose_multiple(r, len);
136 peers.shuffle(r);
140 peers.drain(len.min(peers.len())..peers.len());
141 peers
142 }
143
144 pub(crate) fn get_peer_mut(
146 &mut self,
147 peer: &Z::Addr,
148 ) -> Option<&mut ZoneSpecificPeerListEntryBase<Z::Addr>> {
149 self.peers.get_mut(peer)
150 }
151
152 pub(crate) fn contains_peer(&self, peer: &Z::Addr) -> bool {
154 self.peers.contains_key(peer)
155 }
156
157 fn remove_peer_pruning_idx(&mut self, peer: &ZoneSpecificPeerListEntryBase<Z::Addr>) {
161 remove_peer_idx::<Z>(self.pruning_seeds.get_mut(&peer.pruning_seed), &peer.adr);
162 if self
163 .pruning_seeds
164 .get(&peer.pruning_seed)
165 .expect("There must be a peer with this id")
166 .is_empty()
167 {
168 self.pruning_seeds.remove(&peer.pruning_seed);
169 }
170 }
171
172 fn remove_peer_ban_idx(&mut self, peer: &ZoneSpecificPeerListEntryBase<Z::Addr>) {
176 remove_peer_idx::<Z>(self.ban_ids.get_mut(&peer.adr.ban_id()), &peer.adr);
177 if self
178 .ban_ids
179 .get(&peer.adr.ban_id())
180 .expect("There must be a peer with this id")
181 .is_empty()
182 {
183 self.ban_ids.remove(&peer.adr.ban_id());
184 }
185 }
186
187 fn remove_peer_from_all_idxs(&mut self, peer: &ZoneSpecificPeerListEntryBase<Z::Addr>) {
191 self.remove_peer_pruning_idx(peer);
192 self.remove_peer_ban_idx(peer);
193 }
194
195 pub(crate) fn remove_peer(
197 &mut self,
198 peer: &Z::Addr,
199 ) -> Option<ZoneSpecificPeerListEntryBase<Z::Addr>> {
200 let peer_eb = self.peers.swap_remove(peer)?;
201 self.remove_peer_from_all_idxs(&peer_eb);
202 Some(peer_eb)
203 }
204
205 pub(crate) fn remove_peers_with_ban_id(&mut self, ban_id: &<Z::Addr as NetZoneAddress>::BanID) {
207 let Some(addresses) = self.ban_ids.get(ban_id) else {
208 return;
210 };
211
212 for addr in addresses.clone() {
213 self.remove_peer(&addr);
214 }
215 }
216
217 pub(crate) fn reduce_list(&mut self, must_keep_peers: &HashSet<Z::Addr>, new_len: usize) {
222 if new_len >= self.len() {
223 return;
224 }
225
226 let target_removed = self.len() - new_len;
227 let mut removed_count = 0;
228 let mut peers_to_remove: Vec<Z::Addr> = Vec::with_capacity(target_removed);
229
230 for peer_adr in self.peers.keys() {
231 if removed_count >= target_removed {
232 break;
233 }
234 if !must_keep_peers.contains(peer_adr) {
235 peers_to_remove.push(*peer_adr);
236 removed_count += 1;
237 }
238 }
239
240 for peer_adr in peers_to_remove {
241 let _ = self.remove_peer(&peer_adr);
242 }
243 }
244}
245
246fn remove_peer_idx<Z: NetworkZone>(peer_list: Option<&mut Vec<Z::Addr>>, addr: &Z::Addr) {
248 if let Some(peer_list) = peer_list {
249 if let Some(idx) = peer_list.iter().position(|peer_adr| peer_adr == addr) {
250 peer_list.swap_remove(idx);
251 } else {
252 unreachable!("This function will only be called when the peer exists.");
253 }
254 } else {
255 unreachable!("Index must exist if a peer has that index");
256 }
257}