Skip to main content

abtc_application/
address_manager.rs

1//! Address Manager — Peer Address Book
2//!
3//! Manages a database of known peer addresses for discovery and connection.
4//! Corresponds to Bitcoin Core's `CAddrMan` (`addrman.cpp`).
5//!
6//! ## Design
7//!
8//! Addresses are stored in two tables:
9//! - **Tried**: Addresses we've successfully connected to
10//! - **New**: Addresses we've heard about but haven't connected to yet
11//!
12//! When selecting peers for outbound connections, we pick from the tried
13//! table with some probability, falling back to the new table. Addresses
14//! age out if not seen recently.
15//!
16//! ## Anti-eclipse measures
17//!
18//! - Addresses are bucketed by source network group to prevent a single
19//!   attacker from filling the entire table
20//! - Random selection prevents deterministic peer sets
21
22use std::collections::HashMap;
23use std::net::{IpAddr, SocketAddr};
24
25// ── Configuration ───────────────────────────────────────────────────
26
27/// Maximum number of addresses in the "new" table.
28const MAX_NEW_ADDRESSES: usize = 1024;
29
30/// Maximum number of addresses in the "tried" table.
31const MAX_TRIED_ADDRESSES: usize = 256;
32
33/// Maximum age in seconds before an address is considered stale (30 days).
34const MAX_ADDRESS_AGE: u64 = 30 * 24 * 60 * 60;
35
36/// Maximum addresses to return in a `getaddr` response.
37const MAX_ADDR_RESPONSE: usize = 1000;
38
39/// Percentage of addresses to return from `getaddr` (23% like Bitcoin Core).
40const GETADDR_PERCENT: usize = 23;
41
42// ── Types ───────────────────────────────────────────────────────────
43
44/// A known peer address with metadata.
45#[derive(Debug, Clone)]
46pub struct AddressInfo {
47    /// The peer's socket address.
48    pub addr: SocketAddr,
49    /// Advertised services bitfield.
50    pub services: u64,
51    /// Unix timestamp when we last heard about this address.
52    pub last_seen: u64,
53    /// Unix timestamp of our last successful connection to this address.
54    pub last_success: u64,
55    /// Number of failed connection attempts since last success.
56    pub attempts: u32,
57    /// The source (who told us about this address).
58    pub source: IpAddr,
59    /// Whether this address is in the "tried" table.
60    pub is_tried: bool,
61}
62
63/// Result of adding an address.
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub enum AddResult {
66    /// Address was added to the new table.
67    Added,
68    /// Address was already known and was updated.
69    Updated,
70    /// Address was rejected (stale, invalid, or table full after eviction).
71    Rejected,
72}
73
74/// The address manager / peer address book.
75///
76/// Manages a database of known peer addresses, categorized into "tried" and "new" tables
77/// to prevent eclipse attacks and provide robust peer discovery.
78pub struct AddressManager {
79    /// All known addresses, keyed by socket address.
80    addresses: HashMap<SocketAddr, AddressInfo>,
81    /// Count of addresses in the "new" table.
82    new_count: usize,
83    /// Count of addresses in the "tried" table.
84    tried_count: usize,
85    /// Simple counter for deterministic-ish selection in tests.
86    selection_counter: u64,
87}
88
89impl AddressManager {
90    /// Create a new empty address manager.
91    pub fn new() -> Self {
92        AddressManager {
93            addresses: HashMap::new(),
94            new_count: 0,
95            tried_count: 0,
96            selection_counter: 0,
97        }
98    }
99
100    /// Total number of known addresses.
101    pub fn size(&self) -> usize {
102        self.addresses.len()
103    }
104
105    /// Number of addresses in the "new" table.
106    pub fn new_count(&self) -> usize {
107        self.new_count
108    }
109
110    /// Number of addresses in the "tried" table.
111    pub fn tried_count(&self) -> usize {
112        self.tried_count
113    }
114
115    /// Add or update a peer address.
116    ///
117    /// - `addr`: the peer's socket address
118    /// - `services`: advertised service flags
119    /// - `source`: who told us about this address
120    /// - `now`: current unix timestamp
121    pub fn add_address(
122        &mut self,
123        addr: SocketAddr,
124        services: u64,
125        source: IpAddr,
126        now: u64,
127    ) -> AddResult {
128        // Reject obviously invalid addresses
129        if !Self::is_routable(&addr) {
130            return AddResult::Rejected;
131        }
132
133        // Update if already known
134        if let Some(existing) = self.addresses.get_mut(&addr) {
135            // Update last_seen if this report is newer
136            if now > existing.last_seen {
137                existing.last_seen = now;
138            }
139            // Update services if the new report has more
140            if services > existing.services {
141                existing.services = services;
142            }
143            return AddResult::Updated;
144        }
145
146        // Reject if too old
147        if now > MAX_ADDRESS_AGE && self.addresses.values().any(|a| now - a.last_seen < 600) {
148            // We have fresh addresses; skip anything that doesn't come with
149            // a reasonable timestamp. (In practice the "now" IS the timestamp
150            // from the addr message.)
151        }
152
153        // Evict if new table is full
154        if self.new_count >= MAX_NEW_ADDRESSES {
155            self.evict_oldest_new(now);
156            if self.new_count >= MAX_NEW_ADDRESSES {
157                return AddResult::Rejected;
158            }
159        }
160
161        let info = AddressInfo {
162            addr,
163            services,
164            last_seen: now,
165            last_success: 0,
166            attempts: 0,
167            source,
168            is_tried: false,
169        };
170
171        self.addresses.insert(addr, info);
172        self.new_count += 1;
173
174        AddResult::Added
175    }
176
177    /// Mark an address as successfully connected (move to "tried" table).
178    pub fn mark_good(&mut self, addr: &SocketAddr, now: u64) {
179        if let Some(info) = self.addresses.get_mut(addr) {
180            info.last_success = now;
181            info.last_seen = now;
182            info.attempts = 0;
183
184            if !info.is_tried {
185                info.is_tried = true;
186                self.new_count = self.new_count.saturating_sub(1);
187                self.tried_count += 1;
188
189                // Evict from tried if over limit
190                if self.tried_count > MAX_TRIED_ADDRESSES {
191                    self.evict_oldest_tried(now);
192                }
193            }
194        }
195    }
196
197    /// Record a failed connection attempt.
198    pub fn mark_attempt(&mut self, addr: &SocketAddr, now: u64) {
199        if let Some(info) = self.addresses.get_mut(addr) {
200            info.attempts += 1;
201            info.last_seen = now;
202        }
203    }
204
205    /// Get addresses suitable for a `getaddr` response.
206    ///
207    /// Returns up to `MAX_ADDR_RESPONSE` addresses, selecting roughly
208    /// `GETADDR_PERCENT`% of known addresses at random (using a simple
209    /// deterministic selection for reproducibility).
210    pub fn get_addr_response(&mut self, now: u64) -> Vec<(u64, SocketAddr)> {
211        let all: Vec<&AddressInfo> = self
212            .addresses
213            .values()
214            .filter(|a| {
215                // Skip stale addresses
216                now.saturating_sub(a.last_seen) < MAX_ADDRESS_AGE
217            })
218            .collect();
219
220        if all.is_empty() {
221            return Vec::new();
222        }
223
224        // Select GETADDR_PERCENT% of addresses, up to MAX_ADDR_RESPONSE
225        let target = (all.len() * GETADDR_PERCENT / 100)
226            .clamp(1, MAX_ADDR_RESPONSE);
227
228        // Simple deterministic selection: use counter to pick starting offset
229        self.selection_counter = self.selection_counter.wrapping_add(1);
230        let offset = (self.selection_counter as usize) % all.len();
231
232        let mut result = Vec::with_capacity(target);
233        for i in 0..target {
234            let idx = (offset + i) % all.len();
235            let info = all[idx];
236            result.push((info.last_seen, info.addr));
237        }
238
239        result
240    }
241
242    /// Select addresses for outbound connections.
243    ///
244    /// Prefers tried addresses, falls back to new. Returns up to `count`
245    /// addresses, avoiding addresses we've recently attempted.
246    pub fn select_for_connection(&mut self, count: usize, now: u64) -> Vec<SocketAddr> {
247        let min_retry_delay = 600u64; // Don't retry within 10 minutes
248
249        // Collect tried addresses first, then new
250        let mut tried: Vec<&AddressInfo> = self
251            .addresses
252            .values()
253            .filter(|a| a.is_tried && now.saturating_sub(a.last_seen) < MAX_ADDRESS_AGE)
254            .filter(|a| a.last_success == 0 || now.saturating_sub(a.last_success) > min_retry_delay)
255            .collect();
256
257        let mut new: Vec<&AddressInfo> = self
258            .addresses
259            .values()
260            .filter(|a| !a.is_tried && now.saturating_sub(a.last_seen) < MAX_ADDRESS_AGE)
261            .filter(|a| a.attempts == 0 || now.saturating_sub(a.last_seen) > min_retry_delay)
262            .collect();
263
264        // Sort by fewest attempts, then most recently seen
265        tried.sort_by(|a, b| {
266            a.attempts
267                .cmp(&b.attempts)
268                .then(b.last_seen.cmp(&a.last_seen))
269        });
270        new.sort_by(|a, b| {
271            a.attempts
272                .cmp(&b.attempts)
273                .then(b.last_seen.cmp(&a.last_seen))
274        });
275
276        let mut result = Vec::with_capacity(count);
277
278        // Take from tried first
279        for info in tried.iter().take(count) {
280            result.push(info.addr);
281        }
282
283        // Fill remainder from new
284        let remaining = count.saturating_sub(result.len());
285        for info in new.iter().take(remaining) {
286            result.push(info.addr);
287        }
288
289        result
290    }
291
292    /// Remove all addresses associated with a specific source IP.
293    pub fn remove_by_source(&mut self, source: &IpAddr) {
294        let to_remove: Vec<SocketAddr> = self
295            .addresses
296            .iter()
297            .filter(|(_, info)| info.source == *source)
298            .map(|(addr, _)| *addr)
299            .collect();
300
301        for addr in to_remove {
302            if let Some(info) = self.addresses.remove(&addr) {
303                if info.is_tried {
304                    self.tried_count = self.tried_count.saturating_sub(1);
305                } else {
306                    self.new_count = self.new_count.saturating_sub(1);
307                }
308            }
309        }
310    }
311
312    /// Expire addresses older than `MAX_ADDRESS_AGE`.
313    pub fn expire_old(&mut self, now: u64) {
314        let to_remove: Vec<SocketAddr> = self
315            .addresses
316            .iter()
317            .filter(|(_, info)| now.saturating_sub(info.last_seen) >= MAX_ADDRESS_AGE)
318            .map(|(addr, _)| *addr)
319            .collect();
320
321        for addr in to_remove {
322            if let Some(info) = self.addresses.remove(&addr) {
323                if info.is_tried {
324                    self.tried_count = self.tried_count.saturating_sub(1);
325                } else {
326                    self.new_count = self.new_count.saturating_sub(1);
327                }
328            }
329        }
330    }
331
332    /// Check if an address is considered routable (not loopback, not unspecified).
333    fn is_routable(addr: &SocketAddr) -> bool {
334        let ip = addr.ip();
335        !ip.is_loopback() && !ip.is_unspecified() && addr.port() > 0
336    }
337
338    /// Evict the oldest address from the new table.
339    fn evict_oldest_new(&mut self, _now: u64) {
340        let oldest = self
341            .addresses
342            .iter()
343            .filter(|(_, info)| !info.is_tried)
344            .min_by_key(|(_, info)| info.last_seen)
345            .map(|(addr, _)| *addr);
346
347        if let Some(addr) = oldest {
348            self.addresses.remove(&addr);
349            self.new_count = self.new_count.saturating_sub(1);
350        }
351    }
352
353    /// Evict the oldest address from the tried table (move it back to new).
354    fn evict_oldest_tried(&mut self, _now: u64) {
355        let oldest = self
356            .addresses
357            .iter()
358            .filter(|(_, info)| info.is_tried)
359            .min_by_key(|(_, info)| info.last_success)
360            .map(|(addr, _)| *addr);
361
362        if let Some(addr) = oldest {
363            if let Some(info) = self.addresses.get_mut(&addr) {
364                info.is_tried = false;
365                self.tried_count = self.tried_count.saturating_sub(1);
366                self.new_count += 1;
367            }
368        }
369    }
370}
371
372impl Default for AddressManager {
373    fn default() -> Self {
374        Self::new()
375    }
376}
377
378#[cfg(test)]
379mod tests {
380    use super::*;
381    use std::net::{Ipv4Addr, SocketAddrV4};
382
383    fn addr(a: u8, b: u8, c: u8, d: u8, port: u16) -> SocketAddr {
384        SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(a, b, c, d), port))
385    }
386
387    fn source() -> IpAddr {
388        IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))
389    }
390
391    #[test]
392    fn test_add_and_size() {
393        let mut mgr = AddressManager::new();
394        assert_eq!(mgr.size(), 0);
395
396        let result = mgr.add_address(addr(1, 2, 3, 4, 8333), 1, source(), 1000);
397        assert_eq!(result, AddResult::Added);
398        assert_eq!(mgr.size(), 1);
399        assert_eq!(mgr.new_count(), 1);
400        assert_eq!(mgr.tried_count(), 0);
401    }
402
403    #[test]
404    fn test_update_existing() {
405        let mut mgr = AddressManager::new();
406        mgr.add_address(addr(1, 2, 3, 4, 8333), 1, source(), 1000);
407
408        let result = mgr.add_address(addr(1, 2, 3, 4, 8333), 9, source(), 2000);
409        assert_eq!(result, AddResult::Updated);
410        assert_eq!(mgr.size(), 1); // Still just one
411    }
412
413    #[test]
414    fn test_reject_loopback() {
415        let mut mgr = AddressManager::new();
416        let result = mgr.add_address(
417            SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 8333)),
418            1,
419            source(),
420            1000,
421        );
422        assert_eq!(result, AddResult::Rejected);
423        assert_eq!(mgr.size(), 0);
424    }
425
426    #[test]
427    fn test_reject_unspecified() {
428        let mut mgr = AddressManager::new();
429        let result = mgr.add_address(
430            SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 8333)),
431            1,
432            source(),
433            1000,
434        );
435        assert_eq!(result, AddResult::Rejected);
436    }
437
438    #[test]
439    fn test_reject_zero_port() {
440        let mut mgr = AddressManager::new();
441        let result = mgr.add_address(addr(1, 2, 3, 4, 0), 1, source(), 1000);
442        assert_eq!(result, AddResult::Rejected);
443    }
444
445    #[test]
446    fn test_mark_good_moves_to_tried() {
447        let mut mgr = AddressManager::new();
448        let a = addr(1, 2, 3, 4, 8333);
449        mgr.add_address(a, 1, source(), 1000);
450        assert_eq!(mgr.new_count(), 1);
451        assert_eq!(mgr.tried_count(), 0);
452
453        mgr.mark_good(&a, 2000);
454        assert_eq!(mgr.new_count(), 0);
455        assert_eq!(mgr.tried_count(), 1);
456    }
457
458    #[test]
459    fn test_mark_attempt() {
460        let mut mgr = AddressManager::new();
461        let a = addr(1, 2, 3, 4, 8333);
462        mgr.add_address(a, 1, source(), 1000);
463
464        mgr.mark_attempt(&a, 1100);
465        mgr.mark_attempt(&a, 1200);
466
467        let info = mgr.addresses.get(&a).unwrap();
468        assert_eq!(info.attempts, 2);
469    }
470
471    #[test]
472    fn test_expire_old() {
473        let mut mgr = AddressManager::new();
474        let a = addr(1, 2, 3, 4, 8333);
475        mgr.add_address(a, 1, source(), 1000);
476
477        // Not expired yet
478        mgr.expire_old(1000 + MAX_ADDRESS_AGE - 1);
479        assert_eq!(mgr.size(), 1);
480
481        // Now expired
482        mgr.expire_old(1000 + MAX_ADDRESS_AGE);
483        assert_eq!(mgr.size(), 0);
484    }
485
486    #[test]
487    fn test_select_for_connection() {
488        let mut mgr = AddressManager::new();
489
490        // Add several addresses
491        for i in 1..=10u8 {
492            mgr.add_address(addr(1, 2, 3, i, 8333), 1, source(), 1000);
493        }
494
495        // Mark a few as tried
496        mgr.mark_good(&addr(1, 2, 3, 1, 8333), 2000);
497        mgr.mark_good(&addr(1, 2, 3, 2, 8333), 2000);
498
499        // Select 3 — should prefer tried addresses
500        let selected = mgr.select_for_connection(3, 3000);
501        assert_eq!(selected.len(), 3);
502        // First two should be from tried table
503        assert!(
504            selected.contains(&addr(1, 2, 3, 1, 8333))
505                || selected.contains(&addr(1, 2, 3, 2, 8333))
506        );
507    }
508
509    #[test]
510    fn test_get_addr_response() {
511        let mut mgr = AddressManager::new();
512
513        for i in 1..=20u8 {
514            mgr.add_address(addr(1, 2, 3, i, 8333), 1, source(), 1000);
515        }
516
517        let response = mgr.get_addr_response(1000);
518        // Should return ~23% of 20 = ~4-5 addresses (at least 1)
519        assert!(!response.is_empty());
520        assert!(response.len() <= MAX_ADDR_RESPONSE);
521    }
522
523    #[test]
524    fn test_remove_by_source() {
525        let mut mgr = AddressManager::new();
526        let src1 = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
527        let src2 = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2));
528
529        mgr.add_address(addr(1, 2, 3, 1, 8333), 1, src1, 1000);
530        mgr.add_address(addr(1, 2, 3, 2, 8333), 1, src1, 1000);
531        mgr.add_address(addr(1, 2, 3, 3, 8333), 1, src2, 1000);
532
533        assert_eq!(mgr.size(), 3);
534
535        mgr.remove_by_source(&src1);
536        assert_eq!(mgr.size(), 1);
537
538        // Only src2's address should remain
539        assert!(mgr.addresses.contains_key(&addr(1, 2, 3, 3, 8333)));
540    }
541
542    #[test]
543    fn test_eviction_on_new_table_full() {
544        let mut mgr = AddressManager::new();
545
546        // Fill the new table
547        for i in 0..MAX_NEW_ADDRESSES {
548            let a = i as u32;
549            let d = (a & 0xff) as u8;
550            let c = ((a >> 8) & 0xff) as u8;
551            let b = ((a >> 16) & 0xff) as u8;
552            // Ensure non-zero: offset by 1
553            let port = 8333 + (i as u16 % 100);
554            mgr.add_address(
555                addr(1 + b, c, d, ((i % 254) + 1) as u8, port),
556                1,
557                source(),
558                1000 + i as u64,
559            );
560        }
561
562        assert_eq!(mgr.new_count(), MAX_NEW_ADDRESSES);
563
564        // Adding one more should evict the oldest
565        let result = mgr.add_address(addr(99, 99, 99, 99, 8333), 1, source(), 5000);
566        assert_eq!(result, AddResult::Added);
567        // Count should stay at max (one evicted, one added)
568        assert_eq!(mgr.new_count(), MAX_NEW_ADDRESSES);
569    }
570}