1use std::collections::{BTreeMap, HashMap, HashSet};
23use indexmap::IndexMap;
4use rand::prelude::*;
56use cuprate_constants::block::MAX_BLOCK_HEIGHT_USIZE;
7use cuprate_p2p_core::{services::ZoneSpecificPeerListEntryBase, NetZoneAddress, NetworkZone};
8use cuprate_pruning::PruningSeed;
910#[cfg(test)]
11pub(crate) mod tests;
1213/// A Peer list in the address book.
14///
15/// This could either be the white list or gray list.
16#[derive(Debug)]
17pub(crate) struct PeerList<Z: NetworkZone> {
18/// The peers with their peer data.
19pub peers: IndexMap<Z::Addr, ZoneSpecificPeerListEntryBase<Z::Addr>>,
20/// An index of Pruning seed to address, so can quickly grab peers with the blocks
21 /// we want.
22 ///
23 /// Pruning seeds are sorted by first their `log_stripes` and then their stripe.
24 /// This means the first peers in this list will store more blocks than peers
25 /// later on. So when we need a peer with a certain block we look at the peers
26 /// storing more blocks first then work our way to the peers storing less.
27 ///
28pruning_seeds: BTreeMap<PruningSeed, Vec<Z::Addr>>,
29/// A hashmap linking `ban_ids` to addresses.
30ban_ids: HashMap<<Z::Addr as NetZoneAddress>::BanID, Vec<Z::Addr>>,
31}
3233impl<Z: NetworkZone> PeerList<Z> {
34/// Creates a new peer list.
35pub(crate) fn new(list: Vec<ZoneSpecificPeerListEntryBase<Z::Addr>>) -> Self {
36let mut peers = IndexMap::with_capacity(list.len());
37let mut pruning_seeds = BTreeMap::new();
38let mut ban_ids = HashMap::with_capacity(list.len());
3940for peer in list {
41 pruning_seeds
42 .entry(peer.pruning_seed)
43 .or_insert_with(Vec::new)
44 .push(peer.adr);
4546 ban_ids
47 .entry(peer.adr.ban_id())
48 .or_insert_with(Vec::new)
49 .push(peer.adr);
5051 peers.insert(peer.adr, peer);
52 }
53Self {
54 peers,
55 pruning_seeds,
56 ban_ids,
57 }
58 }
5960/// Gets the length of the peer list
61pub(crate) fn len(&self) -> usize {
62self.peers.len()
63 }
6465/// Adds a new peer to the peer list
66pub(crate) fn add_new_peer(&mut self, peer: ZoneSpecificPeerListEntryBase<Z::Addr>) {
67if self.peers.insert(peer.adr, peer).is_none() {
68#[expect(clippy::unwrap_or_default, reason = "It's more clear with this")]
69self.pruning_seeds
70 .entry(peer.pruning_seed)
71 .or_insert_with(Vec::new)
72 .push(peer.adr);
7374#[expect(clippy::unwrap_or_default)]
75self.ban_ids
76 .entry(peer.adr.ban_id())
77 .or_insert_with(Vec::new)
78 .push(peer.adr);
79 }
80 }
8182/// Returns a random peer.
83 /// If the pruning seed is specified then we will get a random peer with
84 /// that pruning seed otherwise we will just get a random peer in the whole
85 /// list.
86 ///
87 /// The given peer will be removed from the peer list.
88pub(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// Take a random peer and see if it's in the list of must_keep_peers, if it is try again.
95 // TODO: improve this
9697for _ in 0..3 {
98if let Some(needed_height) = block_needed {
99let (_, addresses_with_block) = self.pruning_seeds.iter().find(|(seed, _)| {
100// TODO: factor in peer blockchain height?
101seed.get_next_unpruned_block(needed_height, MAX_BLOCK_HEIGHT_USIZE)
102 .expect("Block needed is higher than max block allowed.")
103 == needed_height
104 })?;
105let n = r.gen_range(0..addresses_with_block.len());
106let peer = addresses_with_block[n];
107if must_keep_peers.contains(&peer) {
108continue;
109 }
110111return self.remove_peer(&peer);
112 }
113let len = self.len();
114115if len == 0 {
116return None;
117 }
118119let n = r.gen_range(0..len);
120121let (&key, _) = self.peers.get_index(n).unwrap();
122if !must_keep_peers.contains(&key) {
123return self.remove_peer(&key);
124 }
125 }
126127None
128}
129130pub(crate) fn get_random_peers<R: Rng>(
131&self,
132 r: &mut R,
133 len: usize,
134 ) -> Vec<ZoneSpecificPeerListEntryBase<Z::Addr>> {
135let mut peers = self.peers.values().copied().choose_multiple(r, len);
136// Order of the returned peers is not random, I am unsure of the impact of this, potentially allowing someone to make guesses about which peers
137 // were connected first.
138 // So to mitigate this shuffle the result.
139peers.shuffle(r);
140 peers.drain(len.min(peers.len())..peers.len());
141 peers
142 }
143144/// Returns a mutable reference to a peer.
145pub(crate) fn get_peer_mut(
146&mut self,
147 peer: &Z::Addr,
148 ) -> Option<&mut ZoneSpecificPeerListEntryBase<Z::Addr>> {
149self.peers.get_mut(peer)
150 }
151152/// Returns true if the list contains this peer.
153pub(crate) fn contains_peer(&self, peer: &Z::Addr) -> bool {
154self.peers.contains_key(peer)
155 }
156157/// Removes a peer from the pruning idx
158 ///
159 /// MUST NOT BE USED ALONE
160fn 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);
162if self
163.pruning_seeds
164 .get(&peer.pruning_seed)
165 .expect("There must be a peer with this id")
166 .is_empty()
167 {
168self.pruning_seeds.remove(&peer.pruning_seed);
169 }
170 }
171172/// Removes a peer from the ban idx
173 ///
174 /// MUST NOT BE USED ALONE
175fn 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);
177if self
178.ban_ids
179 .get(&peer.adr.ban_id())
180 .expect("There must be a peer with this id")
181 .is_empty()
182 {
183self.ban_ids.remove(&peer.adr.ban_id());
184 }
185 }
186187/// Removes a peer from all the indexes
188 ///
189 /// MUST NOT BE USED ALONE
190fn remove_peer_from_all_idxs(&mut self, peer: &ZoneSpecificPeerListEntryBase<Z::Addr>) {
191self.remove_peer_pruning_idx(peer);
192self.remove_peer_ban_idx(peer);
193 }
194195/// Removes a peer from the peer list
196pub(crate) fn remove_peer(
197&mut self,
198 peer: &Z::Addr,
199 ) -> Option<ZoneSpecificPeerListEntryBase<Z::Addr>> {
200let peer_eb = self.peers.swap_remove(peer)?;
201self.remove_peer_from_all_idxs(&peer_eb);
202Some(peer_eb)
203 }
204205/// Removes all peers with a specific ban id.
206pub(crate) fn remove_peers_with_ban_id(&mut self, ban_id: &<Z::Addr as NetZoneAddress>::BanID) {
207let Some(addresses) = self.ban_ids.get(ban_id) else {
208// No peers to ban
209return;
210 };
211212for addr in addresses.clone() {
213self.remove_peer(&addr);
214 }
215 }
216217/// Tries to reduce the peer list to `new_len`.
218 ///
219 /// This function could keep the list bigger than `new_len` if `must_keep_peers`s length
220 /// is larger than `new_len`, in that case we will remove as much as we can.
221pub(crate) fn reduce_list(&mut self, must_keep_peers: &HashSet<Z::Addr>, new_len: usize) {
222if new_len >= self.len() {
223return;
224 }
225226let target_removed = self.len() - new_len;
227let mut removed_count = 0;
228let mut peers_to_remove: Vec<Z::Addr> = Vec::with_capacity(target_removed);
229230for peer_adr in self.peers.keys() {
231if removed_count >= target_removed {
232break;
233 }
234if !must_keep_peers.contains(peer_adr) {
235 peers_to_remove.push(*peer_adr);
236 removed_count += 1;
237 }
238 }
239240for peer_adr in peers_to_remove {
241let _ = self.remove_peer(&peer_adr);
242 }
243 }
244}
245246/// Remove a peer from an index.
247fn remove_peer_idx<Z: NetworkZone>(peer_list: Option<&mut Vec<Z::Addr>>, addr: &Z::Addr) {
248if let Some(peer_list) = peer_list {
249if let Some(idx) = peer_list.iter().position(|peer_adr| peer_adr == addr) {
250 peer_list.swap_remove(idx);
251 } else {
252unreachable!("This function will only be called when the peer exists.");
253 }
254 } else {
255unreachable!("Index must exist if a peer has that index");
256 }
257}