snarkos_node_router/helpers/
cache.rs1use crate::messages::BlockRequest;
17use snarkvm::prelude::{Network, puzzle::SolutionID};
18
19use core::hash::Hash;
20use linked_hash_map::LinkedHashMap;
21#[cfg(feature = "locktick")]
22use locktick::parking_lot::RwLock;
23#[cfg(not(feature = "locktick"))]
24use parking_lot::RwLock;
25use std::{
26 collections::{HashMap, HashSet, VecDeque},
27 net::{IpAddr, SocketAddr},
28};
29use time::{Duration, OffsetDateTime};
30
31const MAX_CACHE_SIZE: usize = 1 << 17;
33
34type SolutionKey<N> = (SocketAddr, SolutionID<N>);
36type TransactionKey<N> = (SocketAddr, <N as Network>::TransactionID);
38
39#[derive(Debug)]
40pub struct Cache<N: Network> {
41 seen_inbound_connections: RwLock<HashMap<IpAddr, VecDeque<OffsetDateTime>>>,
43 seen_inbound_messages: RwLock<HashMap<SocketAddr, VecDeque<OffsetDateTime>>>,
45 seen_inbound_puzzle_requests: RwLock<HashMap<SocketAddr, VecDeque<OffsetDateTime>>>,
47 seen_inbound_block_requests: RwLock<HashMap<SocketAddr, VecDeque<OffsetDateTime>>>,
49 seen_inbound_unconfirmed_solutions: RwLock<HashMap<SocketAddr, VecDeque<OffsetDateTime>>>,
51 seen_inbound_solutions: RwLock<LinkedHashMap<SolutionKey<N>, OffsetDateTime>>,
53 seen_inbound_transactions: RwLock<LinkedHashMap<TransactionKey<N>, OffsetDateTime>>,
55 seen_outbound_block_requests: RwLock<HashMap<SocketAddr, HashSet<BlockRequest>>>,
57 seen_outbound_puzzle_requests: RwLock<HashMap<SocketAddr, u32>>,
59 seen_outbound_solutions: RwLock<LinkedHashMap<SolutionKey<N>, OffsetDateTime>>,
61 seen_outbound_transactions: RwLock<LinkedHashMap<TransactionKey<N>, OffsetDateTime>>,
63 seen_outbound_peer_requests: RwLock<HashMap<SocketAddr, u32>>,
65}
66
67impl<N: Network> Default for Cache<N> {
68 fn default() -> Self {
70 Self::new()
71 }
72}
73
74impl<N: Network> Cache<N> {
75 const INBOUND_BLOCK_REQUEST_INTERVAL: i64 = 60;
76 const INBOUND_PUZZLE_REQUEST_INTERVAL: i64 = 60;
77 const INBOUND_UNCONFIRMED_SOLUTION_INTERVAL: i64 = 60;
78
79 pub fn new() -> Self {
81 Self {
82 seen_inbound_connections: Default::default(),
83 seen_inbound_messages: Default::default(),
84 seen_inbound_puzzle_requests: Default::default(),
85 seen_inbound_block_requests: Default::default(),
86 seen_inbound_unconfirmed_solutions: Default::default(),
87 seen_inbound_solutions: RwLock::new(LinkedHashMap::with_capacity(MAX_CACHE_SIZE)),
88 seen_inbound_transactions: RwLock::new(LinkedHashMap::with_capacity(MAX_CACHE_SIZE)),
89 seen_outbound_block_requests: Default::default(),
90 seen_outbound_puzzle_requests: Default::default(),
91 seen_outbound_solutions: RwLock::new(LinkedHashMap::with_capacity(MAX_CACHE_SIZE)),
92 seen_outbound_transactions: RwLock::new(LinkedHashMap::with_capacity(MAX_CACHE_SIZE)),
93 seen_outbound_peer_requests: Default::default(),
94 }
95 }
96}
97
98impl<N: Network> Cache<N> {
99 pub fn insert_inbound_connection(&self, peer_ip: IpAddr, interval_in_secs: i64) -> usize {
101 Self::retain_and_insert(&self.seen_inbound_connections, peer_ip, interval_in_secs)
102 }
103
104 pub fn insert_inbound_message(&self, peer_ip: SocketAddr, interval_in_secs: i64) -> usize {
106 Self::retain_and_insert(&self.seen_inbound_messages, peer_ip, interval_in_secs)
107 }
108
109 pub fn insert_inbound_puzzle_request(&self, peer_ip: SocketAddr) -> usize {
111 Self::retain_and_insert(&self.seen_inbound_puzzle_requests, peer_ip, Self::INBOUND_PUZZLE_REQUEST_INTERVAL)
112 }
113
114 pub fn insert_inbound_block_request(&self, peer_ip: SocketAddr) -> usize {
116 Self::retain_and_insert(&self.seen_inbound_block_requests, peer_ip, Self::INBOUND_BLOCK_REQUEST_INTERVAL)
117 }
118
119 pub fn insert_inbound_unconfirmed_solution(&self, peer_ip: SocketAddr) -> usize {
121 Self::retain_and_insert(
122 &self.seen_inbound_unconfirmed_solutions,
123 peer_ip,
124 Self::INBOUND_UNCONFIRMED_SOLUTION_INTERVAL,
125 )
126 }
127
128 pub fn insert_inbound_solution(&self, peer_ip: SocketAddr, solution_id: SolutionID<N>) -> Option<OffsetDateTime> {
130 Self::refresh_and_insert(&self.seen_inbound_solutions, (peer_ip, solution_id))
131 }
132
133 pub fn insert_inbound_transaction(
135 &self,
136 peer_ip: SocketAddr,
137 transaction: N::TransactionID,
138 ) -> Option<OffsetDateTime> {
139 Self::refresh_and_insert(&self.seen_inbound_transactions, (peer_ip, transaction))
140 }
141}
142
143impl<N: Network> Cache<N> {
144 pub fn contains_inbound_block_request(&self, peer_ip: &SocketAddr) -> bool {
146 Self::retain(&self.seen_inbound_block_requests, *peer_ip, Self::INBOUND_BLOCK_REQUEST_INTERVAL) > 0
147 }
148
149 pub fn num_outbound_block_requests(&self, peer_ip: &SocketAddr) -> usize {
151 self.seen_outbound_block_requests.read().get(peer_ip).map(|r| r.len()).unwrap_or(0)
152 }
153
154 pub fn contains_outbound_block_request(&self, peer_ip: &SocketAddr, request: &BlockRequest) -> bool {
156 self.seen_outbound_block_requests.read().get(peer_ip).map(|r| r.contains(request)).unwrap_or(false)
157 }
158
159 pub fn insert_outbound_block_request(&self, peer_ip: SocketAddr, request: BlockRequest) -> usize {
161 let mut map_write = self.seen_outbound_block_requests.write();
162 let requests = map_write.entry(peer_ip).or_default();
163 requests.insert(request);
164 requests.len()
165 }
166
167 pub fn remove_outbound_block_request(&self, peer_ip: SocketAddr, request: &BlockRequest) -> bool {
169 let mut map_write = self.seen_outbound_block_requests.write();
170 if let Some(requests) = map_write.get_mut(&peer_ip) { requests.remove(request) } else { false }
171 }
172
173 pub fn contains_outbound_puzzle_request(&self, peer_ip: &SocketAddr) -> bool {
175 self.seen_outbound_puzzle_requests.read().get(peer_ip).map(|r| *r > 0).unwrap_or(false)
176 }
177
178 pub fn increment_outbound_puzzle_requests(&self, peer_ip: SocketAddr) -> u32 {
180 Self::increment_counter(&self.seen_outbound_puzzle_requests, peer_ip)
181 }
182
183 pub fn decrement_outbound_puzzle_requests(&self, peer_ip: SocketAddr) -> u32 {
185 Self::decrement_counter(&self.seen_outbound_puzzle_requests, peer_ip)
186 }
187
188 pub fn insert_outbound_solution(&self, peer_ip: SocketAddr, solution_id: SolutionID<N>) -> Option<OffsetDateTime> {
190 Self::refresh_and_insert(&self.seen_outbound_solutions, (peer_ip, solution_id))
191 }
192
193 pub fn insert_outbound_transaction(
195 &self,
196 peer_ip: SocketAddr,
197 transaction: N::TransactionID,
198 ) -> Option<OffsetDateTime> {
199 Self::refresh_and_insert(&self.seen_outbound_transactions, (peer_ip, transaction))
200 }
201
202 pub fn contains_outbound_peer_request(&self, peer_ip: SocketAddr) -> bool {
204 self.seen_outbound_peer_requests.read().get(&peer_ip).map(|r| *r > 0).unwrap_or(false)
205 }
206
207 pub fn increment_outbound_peer_requests(&self, peer_ip: SocketAddr) -> u32 {
209 Self::increment_counter(&self.seen_outbound_peer_requests, peer_ip)
210 }
211
212 pub fn decrement_outbound_peer_requests(&self, peer_ip: SocketAddr) -> u32 {
214 Self::decrement_counter(&self.seen_outbound_peer_requests, peer_ip)
215 }
216
217 pub fn clear_peer_entries(&self, peer_ip: SocketAddr) {
219 self.seen_outbound_block_requests.write().remove(&peer_ip);
220 }
221}
222
223impl<N: Network> Cache<N> {
224 fn retain_and_insert<K: Eq + Hash + Clone>(
226 map: &RwLock<HashMap<K, VecDeque<OffsetDateTime>>>,
227 key: K,
228 interval_in_secs: i64,
229 ) -> usize {
230 let now = OffsetDateTime::now_utc();
232
233 let mut map_write = map.write();
234 let timestamps = map_write.entry(key).or_default();
236 timestamps.push_back(now);
238 while timestamps.front().is_some_and(|t| now - *t > Duration::seconds(interval_in_secs)) {
240 timestamps.pop_front();
241 }
242 timestamps.len()
244 }
245
246 fn retain<K: Eq + Hash + Clone>(
248 map: &RwLock<HashMap<K, VecDeque<OffsetDateTime>>>,
249 key: K,
250 interval_in_secs: i64,
251 ) -> usize {
252 let now = OffsetDateTime::now_utc();
254
255 let mut map_write = map.write();
256 let timestamps = map_write.entry(key).or_default();
258 while timestamps.front().is_some_and(|t| now - *t > Duration::seconds(interval_in_secs)) {
260 timestamps.pop_front();
261 }
262 timestamps.len()
264 }
265
266 fn increment_counter<K: Hash + Eq>(map: &RwLock<HashMap<K, u32>>, key: K) -> u32 {
268 let mut map_write = map.write();
269 let entry = map_write.entry(key).or_default();
271 *entry = entry.saturating_add(1);
272 *entry
274 }
275
276 fn decrement_counter<K: Copy + Hash + Eq>(map: &RwLock<HashMap<K, u32>>, key: K) -> u32 {
278 let mut map_write = map.write();
279 let entry = map_write.entry(key).or_default();
281 let value = entry.saturating_sub(1);
282 if *entry == 0 {
284 map_write.remove(&key);
285 } else {
286 *entry = value;
287 }
288 value
290 }
291
292 fn refresh<K: Eq + Hash, V>(map: &RwLock<LinkedHashMap<K, V>>) {
294 let mut map_write = map.write();
295 while map_write.len() >= MAX_CACHE_SIZE {
296 map_write.pop_front();
297 }
298 }
299
300 fn refresh_and_insert<K: Eq + Hash>(
303 map: &RwLock<LinkedHashMap<K, OffsetDateTime>>,
304 key: K,
305 ) -> Option<OffsetDateTime> {
306 let previous_timestamp = map.write().insert(key, OffsetDateTime::now_utc());
308 Self::refresh(map);
310 previous_timestamp
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use snarkvm::prelude::MainnetV0;
319
320 use std::net::Ipv4Addr;
321
322 type CurrentNetwork = MainnetV0;
323
324 #[test]
325 fn test_inbound_block_request() {
326 let cache = Cache::<CurrentNetwork>::default();
327 let peer_ip = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1234);
328
329 assert_eq!(cache.seen_inbound_block_requests.read().len(), 0);
331
332 assert_eq!(cache.insert_inbound_block_request(peer_ip), 1);
334
335 assert!(cache.contains_inbound_block_request(&peer_ip));
337
338 assert_eq!(cache.insert_inbound_block_request(peer_ip), 2);
340
341 assert!(cache.contains_inbound_block_request(&peer_ip));
343 }
344
345 #[test]
346 fn test_inbound_unconfirmed_solution() {
347 let cache = Cache::<CurrentNetwork>::default();
348 let peer_ip = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1234);
349
350 assert_eq!(cache.insert_inbound_unconfirmed_solution(peer_ip), 1);
351 assert_eq!(cache.insert_inbound_unconfirmed_solution(peer_ip), 2);
352 }
353
354 #[test]
355 fn test_inbound_solution() {
356 let cache = Cache::<CurrentNetwork>::default();
357 let peer_ip = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1234);
358 let solution_id = SolutionID::<CurrentNetwork>::from(123456789);
359
360 assert_eq!(cache.seen_inbound_solutions.read().len(), 0);
362
363 assert!(cache.insert_inbound_solution(peer_ip, solution_id).is_none());
365
366 assert_eq!(cache.seen_inbound_solutions.read().len(), 1);
368
369 assert!(cache.insert_inbound_solution(peer_ip, solution_id).is_some());
371
372 assert_eq!(cache.seen_inbound_solutions.read().len(), 1);
374 }
375
376 #[test]
377 fn test_inbound_transaction() {
378 let cache = Cache::<CurrentNetwork>::default();
379 let peer_ip = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1234);
380 let transaction = Default::default();
381
382 assert_eq!(cache.seen_inbound_transactions.read().len(), 0);
384
385 assert!(cache.insert_inbound_transaction(peer_ip, transaction).is_none());
387
388 assert_eq!(cache.seen_inbound_transactions.read().len(), 1);
390
391 assert!(cache.insert_inbound_transaction(peer_ip, transaction).is_some());
393
394 assert_eq!(cache.seen_inbound_transactions.read().len(), 1);
396 }
397
398 #[test]
399 fn test_outbound_solution() {
400 let cache = Cache::<CurrentNetwork>::default();
401 let peer_ip = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1234);
402 let solution_id = SolutionID::<CurrentNetwork>::from(123456789);
403
404 assert_eq!(cache.seen_outbound_solutions.read().len(), 0);
406
407 assert!(cache.insert_outbound_solution(peer_ip, solution_id).is_none());
409
410 assert_eq!(cache.seen_outbound_solutions.read().len(), 1);
412
413 assert!(cache.insert_outbound_solution(peer_ip, solution_id).is_some());
415
416 assert_eq!(cache.seen_outbound_solutions.read().len(), 1);
418 }
419
420 #[test]
421 fn test_outbound_transaction() {
422 let cache = Cache::<CurrentNetwork>::default();
423 let peer_ip = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1234);
424 let transaction = Default::default();
425
426 assert_eq!(cache.seen_outbound_transactions.read().len(), 0);
428
429 assert!(cache.insert_outbound_transaction(peer_ip, transaction).is_none());
431
432 assert_eq!(cache.seen_outbound_transactions.read().len(), 1);
434
435 assert!(cache.insert_outbound_transaction(peer_ip, transaction).is_some());
437
438 assert_eq!(cache.seen_outbound_transactions.read().len(), 1);
440 }
441
442 #[test]
443 fn test_outbound_peer_request() {
444 let cache = Cache::<CurrentNetwork>::default();
445 let peer_ip = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1234);
446
447 assert!(cache.seen_outbound_peer_requests.read().is_empty());
449 assert!(!cache.contains_outbound_peer_request(peer_ip));
450
451 assert_eq!(cache.increment_outbound_peer_requests(peer_ip), 1);
453
454 assert!(cache.contains_outbound_peer_request(peer_ip));
456
457 assert_eq!(cache.increment_outbound_peer_requests(peer_ip), 2);
459
460 assert!(cache.contains_outbound_peer_request(peer_ip));
462
463 assert_eq!(cache.decrement_outbound_peer_requests(peer_ip), 1);
465
466 assert_eq!(cache.decrement_outbound_peer_requests(peer_ip), 0);
468
469 assert!(!cache.contains_outbound_peer_request(peer_ip));
471 }
472}