quic_p2p/
bootstrap_cache.rs1use crate::dirs::Dirs;
11use crate::utils;
12use crate::{QuicP2pError, R};
13use log::info;
14use std::{
15 collections::{HashSet, VecDeque},
16 fs, io,
17 net::SocketAddr,
18 path::PathBuf,
19};
20
21const MAX_CACHE_SIZE: usize = 200;
23
24pub struct BootstrapCache {
26 peers: VecDeque<SocketAddr>,
27 cache_path: PathBuf,
28 add_count: u8,
29 hard_coded_contacts: HashSet<SocketAddr>,
30}
31
32impl BootstrapCache {
33 pub fn new(
41 hard_coded_contacts: HashSet<SocketAddr>,
42 user_override: Option<&Dirs>,
43 ) -> R<BootstrapCache> {
44 let path = |dir: &Dirs| {
45 let path = dir.cache_dir();
46 path.join("bootstrap_cache")
47 };
48
49 let cache_path = user_override.map_or_else(
50 || Ok::<_, QuicP2pError>(path(&utils::project_dir()?)),
51 |d| Ok(path(d)),
52 )?;
53
54 let peers = if cache_path.exists() {
55 utils::read_from_disk(&cache_path)?
56 } else {
57 let cache_dir = cache_path
58 .parent()
59 .ok_or_else(|| io::ErrorKind::NotFound.into())
60 .map_err(QuicP2pError::Io)?;
61 fs::create_dir_all(&cache_dir)?;
62 Default::default()
63 };
64
65 Ok(BootstrapCache {
66 peers,
67 cache_path,
68 add_count: 0u8,
69 hard_coded_contacts,
70 })
71 }
72
73 pub fn peers_mut(&mut self) -> &mut VecDeque<SocketAddr> {
74 &mut self.peers
75 }
76
77 pub fn peers(&self) -> &VecDeque<SocketAddr> {
78 &self.peers
79 }
80
81 pub fn hard_coded_contacts(&self) -> &HashSet<SocketAddr> {
82 &self.hard_coded_contacts
83 }
84
85 pub fn add_peer(&mut self, peer: SocketAddr) {
87 if self.hard_coded_contacts.contains(&peer) {
88 return;
89 }
90
91 if self.peers.contains(&peer) {
92 self.move_to_cache_top(peer);
93 } else {
94 self.insert_new(peer);
95 }
96 }
97
98 fn insert_new(&mut self, peer: SocketAddr) {
99 self.peers.push_back(peer);
100 self.add_count += 1;
101 if self.peers.len() > MAX_CACHE_SIZE {
102 let _ = self.peers.pop_front();
103 }
104 self.try_sync_to_disk();
105 }
106
107 fn move_to_cache_top(&mut self, peer: SocketAddr) {
108 if let Some(pos) = self.peers.iter().position(|p| *p == peer) {
109 let _ = self.peers.remove(pos);
110 self.peers.push_back(peer);
111 }
112 }
113
114 fn try_sync_to_disk(&mut self) {
116 if self.add_count > 9 {
117 if let Err(e) = utils::write_to_disk(&self.cache_path, &self.peers) {
118 info!("Failed to write bootstrap cache to disk: {}", e);
119 }
120 self.add_count = 0;
121 }
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use crate::test_utils::{make_node_addr, rand_node_addr, test_dirs};
129 use unwrap::unwrap;
130
131 mod add_peer {
132 use super::*;
133
134 #[test]
135 fn when_10_peers_are_added_they_are_synced_to_disk() {
136 let dirs = test_dirs();
137 let mut cache = unwrap!(BootstrapCache::new(Default::default(), Some(&dirs)));
138
139 for _ in 0..10 {
140 cache.add_peer(rand_node_addr());
141 }
142
143 assert_eq!(cache.peers.len(), 10);
144
145 let cache = unwrap!(BootstrapCache::new(Default::default(), Some(&dirs)));
146 assert_eq!(cache.peers.len(), 10);
147 }
148
149 #[test]
150 fn when_given_peer_is_in_hard_coded_contacts_it_is_not_cached() {
151 let peer1 = rand_node_addr();
152 let peer2 = rand_node_addr();
153 let mut hard_coded: HashSet<_> = Default::default();
154 assert!(hard_coded.insert(peer1));
155
156 let dirs = test_dirs();
157 let mut cache = BootstrapCache::new(hard_coded, Some(&dirs)).unwrap();
158
159 cache.add_peer(peer1);
160 cache.add_peer(peer2);
161
162 let peers: Vec<_> = cache.peers.iter().cloned().collect();
163 assert_eq!(peers, vec![peer2]);
164 }
165
166 #[test]
167 fn it_caps_cache_size() {
168 let dirs = test_dirs();
169 let port_base = 5000;
170
171 let mut cache = unwrap!(BootstrapCache::new(Default::default(), Some(&dirs)));
172
173 for i in 0..MAX_CACHE_SIZE {
174 cache.add_peer(make_node_addr(port_base + i as u16));
175 }
176 assert_eq!(cache.peers.len(), MAX_CACHE_SIZE);
177
178 cache.add_peer(make_node_addr(port_base + MAX_CACHE_SIZE as u16));
179 assert_eq!(cache.peers.len(), MAX_CACHE_SIZE);
180 }
181 }
182
183 mod move_to_cache_top {
184 use super::*;
185
186 #[test]
187 fn it_moves_given_node_to_the_top_of_the_list() {
188 let dirs = test_dirs();
189 let mut cache = BootstrapCache::new(Default::default(), Some(&dirs)).unwrap();
190 let peer1 = rand_node_addr();
191 let peer2 = rand_node_addr();
192 let peer3 = rand_node_addr();
193 cache.add_peer(peer1);
194 cache.add_peer(peer2);
195 cache.add_peer(peer3);
196
197 cache.move_to_cache_top(peer2);
198
199 let peers: Vec<_> = cache.peers.iter().cloned().collect();
200 assert_eq!(peers, vec![peer1, peer3, peer2]);
201 }
202 }
203}