1use crate::{
2 ip::{bucket::NodeBucket, sample::SampleHashMap, util::SubnetType},
3 node_database::NodeDatabase,
4 node_table::NodeId,
5};
6use std::{
7 collections::{HashMap, HashSet},
8 net::IpAddr,
9 time::Duration,
10};
11
12const DEFAULT_EVICT_TIMEOUT: Duration = Duration::from_secs(7 * 24 * 3600); #[derive(Debug, PartialEq)]
18pub enum ValidateInsertResult {
19 AlreadyExists,
21 OccupyIp(NodeId),
25 QuotaEnough,
28 Evict(NodeId),
31 QuotaNotEnough,
35}
36
37#[derive(Debug)]
45pub struct NodeIpLimit {
46 subnet_type: SubnetType,
47 subnet_quota: usize, evict_timeout: Duration, trusted_buckets: SampleHashMap<u32, NodeBucket>,
52 untrusted_buckets: SampleHashMap<u32, NodeBucket>,
54
55 ip_index: HashMap<IpAddr, NodeId>,
57 node_index: HashMap<NodeId, IpAddr>,
58}
59
60impl NodeIpLimit {
61 pub fn new(subnet_quota: usize) -> Self {
62 NodeIpLimit {
63 subnet_type: SubnetType::C,
64 subnet_quota,
65 evict_timeout: DEFAULT_EVICT_TIMEOUT,
66 trusted_buckets: Default::default(),
67 untrusted_buckets: Default::default(),
68 ip_index: HashMap::new(),
69 node_index: HashMap::new(),
70 }
71 }
72
73 #[inline]
74 pub fn is_enabled(&self) -> bool { self.subnet_quota > 0 }
75
76 pub fn subnet(&self, id: &NodeId) -> Option<u32> {
78 let ip = self.node_index.get(id)?;
79 Some(self.subnet_type.subnet(ip))
80 }
81
82 pub fn remove(&mut self, id: &NodeId) -> bool {
85 if !self.is_enabled() {
86 return true;
87 }
88
89 let ip = match self.node_index.remove(id) {
90 Some(ip) => ip,
91 None => return false,
92 };
93
94 self.ip_index.remove(&ip);
95
96 let subnet = self.subnet_type.subnet(&ip);
97 if !Self::remove_with_buckets(&mut self.trusted_buckets, subnet, id) {
98 Self::remove_with_buckets(&mut self.untrusted_buckets, subnet, id);
99 }
100
101 true
102 }
103
104 fn remove_with_buckets(
106 buckets: &mut SampleHashMap<u32, NodeBucket>, subnet: u32, id: &NodeId,
107 ) -> bool {
108 let bucket = match buckets.get_mut(&subnet) {
109 Some(bucket) => bucket,
110 None => return false,
111 };
112
113 if !bucket.remove(id) {
114 return false;
115 }
116
117 if bucket.count() == 0 {
119 buckets.remove(&subnet);
120 }
121
122 true
123 }
124
125 pub fn sample_trusted(&self, n: u32) -> HashSet<NodeId> {
129 if !self.is_enabled() {
130 return HashSet::new();
131 }
132
133 let mut sampled = HashSet::new();
134 if self.trusted_buckets.is_empty() {
135 return sampled;
136 }
137
138 let mut rng = rand::rng();
139
140 for _ in 0..n {
141 if let Some(bucket) = self.trusted_buckets.sample(&mut rng) {
142 if let Some(id) = bucket.sample(&mut rng) {
143 sampled.insert(id);
144 }
145 }
146 }
147
148 sampled
149 }
150
151 pub fn validate_insertion(
164 &self, id: &NodeId, ip: &IpAddr, db: &NodeDatabase,
165 ) -> ValidateInsertResult {
166 if !self.is_enabled() {
167 return ValidateInsertResult::QuotaEnough;
168 }
169
170 let maybe_cur_ip = self.node_index.get(id);
172 if let Some(cur_ip) = maybe_cur_ip {
173 if cur_ip == ip {
174 return ValidateInsertResult::AlreadyExists;
175 }
176 }
177
178 if let Some(old_id) = self.ip_index.get(ip) {
180 return ValidateInsertResult::OccupyIp(*old_id);
181 }
182
183 if self.is_quota_allowed(ip) {
185 return ValidateInsertResult::QuotaEnough;
186 }
187
188 if let Some(cur_ip) = maybe_cur_ip {
191 let cur_subnet = self.subnet_type.subnet(cur_ip);
192 let new_subnet = self.subnet_type.subnet(ip);
193 if cur_subnet == new_subnet {
194 return ValidateInsertResult::Evict(*id);
195 }
196 }
197
198 if let Some(evictee) = self.select_evictee(ip, db) {
200 return ValidateInsertResult::Evict(evictee);
201 }
202
203 ValidateInsertResult::QuotaNotEnough
204 }
205
206 pub fn insert(
210 &mut self, id: NodeId, ip: IpAddr, trusted: bool,
211 evictee: Option<NodeId>,
212 ) -> bool {
213 if !self.is_enabled() {
214 return true;
215 }
216
217 if let Some(cur_ip) = self.node_index.get(&id) {
219 if *cur_ip == ip {
220 return false;
221 }
222 }
223
224 if let Some(id) = evictee {
226 self.remove(&id);
227 }
228
229 if self.ip_index.contains_key(&ip) {
231 return false;
232 }
233
234 if self.is_quota_allowed(&ip) {
235 self.add_or_update(ip, id, trusted);
236 return true;
237 }
238
239 false
240 }
241
242 pub fn demote(&mut self, id: &NodeId) {
244 if let Some(ip) = self.node_index.get(id) {
245 let subnet = self.subnet_type.subnet(ip);
246 if let Some(b) = self.trusted_buckets.get_mut(&subnet) {
247 if b.remove(id) {
248 if let Some(b) = self.untrusted_buckets.get_mut(&subnet) {
249 b.add(*id);
250 }
251 }
252 }
253 }
254 }
255
256 fn add_or_update(&mut self, ip: IpAddr, id: NodeId, trusted: bool) {
259 self.remove(&id);
261
262 self.node_index.insert(id, ip);
263 self.ip_index.insert(ip, id);
264
265 let subnet = self.subnet_type.subnet(&ip);
266 if trusted {
267 self.trusted_buckets
268 .get_mut_or_insert_with(subnet, NodeBucket::default)
269 .add(id);
270 } else {
271 self.untrusted_buckets
272 .get_mut_or_insert_with(subnet, NodeBucket::default)
273 .add(id);
274 }
275 }
276
277 fn is_quota_allowed(&self, ip: &IpAddr) -> bool {
279 let subnet = self.subnet_type.subnet(ip);
280
281 let num_trusted = self
282 .trusted_buckets
283 .get(&subnet)
284 .map_or(0, |bucket| bucket.count());
285
286 let num_untrusted = self
287 .untrusted_buckets
288 .get(&subnet)
289 .map_or(0, |bucket| bucket.count());
290
291 num_trusted + num_untrusted < self.subnet_quota
292 }
293
294 fn select_evictee(&self, ip: &IpAddr, db: &NodeDatabase) -> Option<NodeId> {
296 let subnet = self.subnet_type.subnet(ip);
297
298 self.untrusted_buckets
300 .get(&subnet)
301 .and_then(|bucket| bucket.select_evictee(db, self.evict_timeout))
302 .or_else(|| {
303 self.trusted_buckets.get(&subnet).and_then(|bucket| {
304 bucket.select_evictee(db, self.evict_timeout)
305 })
306 })
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::{NodeDatabase, NodeId, NodeIpLimit, ValidateInsertResult};
313 use std::{net::IpAddr, str::FromStr};
314
315 fn new_ip(ip: &'static str) -> IpAddr { IpAddr::from_str(ip).unwrap() }
316
317 #[test]
318 fn test_remove() {
319 let mut limit = NodeIpLimit::new(2);
320
321 assert_eq!(limit.remove(&NodeId::random()), false);
323
324 let n1 = NodeId::random();
326 let ip1 = new_ip("127.0.0.1");
327 assert_eq!(limit.insert(n1.clone(), ip1, true, None), true);
328
329 let n2 = NodeId::random();
330 let ip2 = new_ip("127.0.0.2");
331 assert_eq!(limit.insert(n2.clone(), ip2, true, None), true);
332
333 validate_node(&limit, &n1, &ip1, true );
335 assert_eq!(limit.remove(&n1), true);
336 validate_node(&limit, &n1, &ip1, false );
337
338 validate_node(&limit, &n2, &ip2, true );
339 assert_eq!(limit.remove(&n2), true);
340 validate_node(&limit, &n2, &ip2, false );
341 }
342
343 #[test]
344 fn test_sample() {
345 let mut limit = NodeIpLimit::new(2);
346
347 assert_eq!(limit.sample_trusted(3).is_empty(), true);
349
350 let n1 = NodeId::random();
352 let ip1 = new_ip("127.0.0.1");
353 assert_eq!(limit.insert(n1, ip1, false, None), true);
354 assert_eq!(limit.sample_trusted(3).is_empty(), true);
355
356 let n2 = NodeId::random();
358 let ip2 = new_ip("127.0.0.2");
359 assert_eq!(limit.insert(n2, ip2, true, None), true);
360 assert_eq!(limit.sample_trusted(0).len(), 0);
361 assert_eq!(limit.sample_trusted(1).len(), 1);
362 assert_eq!(limit.sample_trusted(3).len(), 1);
363 }
364
365 fn validate_node(
366 limit: &NodeIpLimit, id: &NodeId, ip: &IpAddr, exists: bool,
367 ) {
368 if !exists {
369 assert_eq!(limit.node_index.contains_key(id), false);
370 } else {
371 assert_eq!(limit.node_index.contains_key(id), true);
372 assert_eq!(limit.node_index[id], *ip);
373 }
374 }
375
376 #[test]
377 fn test_insert_duplicate_id_ip() {
378 let mut limit = NodeIpLimit::new(2);
379 let db = NodeDatabase::new(None, 2);
380
381 let n = NodeId::random();
383 let ip = new_ip("127.0.0.1");
384 assert_eq!(
385 limit.validate_insertion(&n, &ip, &db),
386 ValidateInsertResult::QuotaEnough
387 );
388 assert_eq!(limit.insert(n.clone(), ip, true, None), true);
389 validate_node(&limit, &n, &ip, true );
390
391 assert_eq!(
393 limit.validate_insertion(&n, &ip, &db),
394 ValidateInsertResult::AlreadyExists
395 );
396 assert_eq!(limit.insert(n.clone(), ip, true, None), false);
397 assert_eq!(limit.insert(n.clone(), ip, false, None), false);
398 validate_node(&limit, &n, &ip, true );
399 }
400
401 #[test]
402 fn test_insert_occupy_ip_new_node() {
403 let mut limit = NodeIpLimit::new(2);
404 let db = NodeDatabase::new(None, 2);
405
406 let n1 = NodeId::random();
408 let ip = new_ip("127.0.0.1");
409 assert_eq!(
410 limit.validate_insertion(&n1, &ip, &db),
411 ValidateInsertResult::QuotaEnough
412 );
413 assert_eq!(limit.insert(n1.clone(), ip, true, None), true);
414 validate_node(&limit, &n1, &ip, true );
415
416 let n2 = NodeId::random();
418 assert_eq!(
419 limit.validate_insertion(&n2, &ip, &db),
420 ValidateInsertResult::OccupyIp(n1.clone())
421 );
422
423 assert_eq!(limit.insert(n2.clone(), ip, true, None), false);
425 validate_node(&limit, &n1, &ip, true ); validate_node(&limit, &n2, &ip, false ); assert_eq!(limit.insert(n2.clone(), ip, true, Some(n1.clone())), true);
430 validate_node(&limit, &n1, &ip, false ); validate_node(&limit, &n2, &ip, true ); }
433
434 #[test]
435 fn test_insert_occupy_ip_update_node() {
436 let mut limit = NodeIpLimit::new(2);
437 let db = NodeDatabase::new(None, 2);
438
439 let n1 = NodeId::random();
441 let ip1 = new_ip("127.0.0.1");
442 assert_eq!(
443 limit.validate_insertion(&n1, &ip1, &db),
444 ValidateInsertResult::QuotaEnough
445 );
446 assert_eq!(limit.insert(n1.clone(), ip1, true, None), true);
447 validate_node(&limit, &n1, &ip1, true );
448
449 let n2 = NodeId::random();
450 let ip2 = new_ip("127.0.0.2");
451 assert_eq!(
452 limit.validate_insertion(&n2, &ip2, &db),
453 ValidateInsertResult::QuotaEnough
454 );
455 assert_eq!(limit.insert(n2.clone(), ip2, true, None), true);
456 validate_node(&limit, &n2, &ip2, true );
457
458 assert_eq!(
460 limit.validate_insertion(&n2, &ip1, &db),
461 ValidateInsertResult::OccupyIp(n1.clone())
462 );
463
464 assert_eq!(limit.insert(n2.clone(), ip1, true, None), false);
466 validate_node(&limit, &n1, &ip1, true ); validate_node(&limit, &n2, &ip2, true ); assert_eq!(limit.insert(n2.clone(), ip1, true, Some(n1.clone())), true);
471 validate_node(&limit, &n1, &ip1, false ); validate_node(&limit, &n2, &ip1, true ); }
474
475 #[test]
476 fn test_is_quota_allowed() {
477 let mut limit = NodeIpLimit::new(2);
478
479 let n1 = NodeId::random();
481 let ip1 = new_ip("127.0.0.1");
482 assert_eq!(limit.insert(n1, ip1, true, None), true);
483
484 let n2 = NodeId::random();
486 let ip2 = new_ip("127.0.0.2");
487 assert_eq!(limit.insert(n2, ip2, true, None), true);
488
489 assert_eq!(limit.is_quota_allowed(&new_ip("127.0.0.3")), false);
491
492 assert_eq!(limit.is_quota_allowed(&new_ip("127.0.1.1")), true);
494 }
495
496 #[test]
497 fn test_select_evictee() {
498 let limit = NodeIpLimit::new(2);
499 let db = NodeDatabase::new(None, 2);
500
501 assert_eq!(limit.select_evictee(&new_ip("127.0.0.1"), &db), None);
503 }
504}