libp2p_swarm/behaviour/
external_addresses.rs

1use crate::behaviour::{ExternalAddrConfirmed, ExternalAddrExpired, FromSwarm};
2use libp2p_core::Multiaddr;
3
4/// The maximum number of local external addresses. When reached any
5/// further externally reported addresses are ignored. The behaviour always
6/// tracks all its listen addresses.
7const MAX_LOCAL_EXTERNAL_ADDRS: usize = 20;
8
9/// Utility struct for tracking the external addresses of a [`Swarm`](crate::Swarm).
10#[derive(Debug, Clone, Default)]
11pub struct ExternalAddresses {
12    addresses: Vec<Multiaddr>,
13}
14
15impl ExternalAddresses {
16    /// Returns an [`Iterator`] over all external addresses.
17    pub fn iter(&self) -> impl ExactSizeIterator<Item = &Multiaddr> {
18        self.addresses.iter()
19    }
20
21    pub fn as_slice(&self) -> &[Multiaddr] {
22        self.addresses.as_slice()
23    }
24
25    /// Feed a [`FromSwarm`] event to this struct.
26    ///
27    /// Returns whether the event changed our set of external addresses.
28    pub fn on_swarm_event(&mut self, event: &FromSwarm) -> bool {
29        match event {
30            FromSwarm::ExternalAddrConfirmed(ExternalAddrConfirmed { addr }) => {
31                if let Some(pos) = self
32                    .addresses
33                    .iter()
34                    .position(|candidate| candidate == *addr)
35                {
36                    // Refresh the existing confirmed address.
37                    self.addresses.remove(pos);
38                    self.push_front(addr);
39
40                    tracing::debug!(address=%addr, "Refreshed external address");
41
42                    return false; // No changes to our external addresses.
43                }
44
45                self.push_front(addr);
46
47                if self.addresses.len() > MAX_LOCAL_EXTERNAL_ADDRS {
48                    let expired = self.addresses.pop().expect("list to be not empty");
49
50                    tracing::debug!(
51                        external_address=%expired,
52                        address_limit=%MAX_LOCAL_EXTERNAL_ADDRS,
53                        "Removing previously confirmed external address because we reached the address limit"
54                    );
55                }
56
57                return true;
58            }
59            FromSwarm::ExternalAddrExpired(ExternalAddrExpired {
60                addr: expired_addr, ..
61            }) => {
62                let pos = match self
63                    .addresses
64                    .iter()
65                    .position(|candidate| candidate == *expired_addr)
66                {
67                    None => return false,
68                    Some(p) => p,
69                };
70
71                self.addresses.remove(pos);
72                return true;
73            }
74            _ => {}
75        }
76
77        false
78    }
79
80    fn push_front(&mut self, addr: &Multiaddr) {
81        self.addresses.insert(0, addr.clone()); // We have at most `MAX_LOCAL_EXTERNAL_ADDRS` so this isn't very expensive.
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use libp2p_core::multiaddr::Protocol;
89    use once_cell::sync::Lazy;
90    use rand::Rng;
91
92    #[test]
93    fn new_external_addr_returns_correct_changed_value() {
94        let mut addresses = ExternalAddresses::default();
95
96        let changed = addresses.on_swarm_event(&new_external_addr1());
97        assert!(changed);
98
99        let changed = addresses.on_swarm_event(&new_external_addr1());
100        assert!(!changed)
101    }
102
103    #[test]
104    fn expired_external_addr_returns_correct_changed_value() {
105        let mut addresses = ExternalAddresses::default();
106        addresses.on_swarm_event(&new_external_addr1());
107
108        let changed = addresses.on_swarm_event(&expired_external_addr1());
109        assert!(changed);
110
111        let changed = addresses.on_swarm_event(&expired_external_addr1());
112        assert!(!changed)
113    }
114
115    #[test]
116    fn more_recent_external_addresses_are_prioritized() {
117        let mut addresses = ExternalAddresses::default();
118
119        addresses.on_swarm_event(&new_external_addr1());
120        addresses.on_swarm_event(&new_external_addr2());
121
122        assert_eq!(
123            addresses.as_slice(),
124            &[(*MEMORY_ADDR_2000).clone(), (*MEMORY_ADDR_1000).clone()]
125        );
126    }
127
128    #[test]
129    fn when_pushing_more_than_max_addresses_oldest_is_evicted() {
130        let mut addresses = ExternalAddresses::default();
131
132        while addresses.as_slice().len() < MAX_LOCAL_EXTERNAL_ADDRS {
133            let random_address =
134                Multiaddr::empty().with(Protocol::Memory(rand::thread_rng().gen_range(0..1000)));
135            addresses.on_swarm_event(&FromSwarm::ExternalAddrConfirmed(ExternalAddrConfirmed {
136                addr: &random_address,
137            }));
138        }
139
140        addresses.on_swarm_event(&new_external_addr2());
141
142        assert_eq!(addresses.as_slice().len(), 20);
143        assert_eq!(addresses.as_slice()[0], (*MEMORY_ADDR_2000).clone());
144    }
145
146    #[test]
147    fn reporting_existing_external_address_moves_it_to_the_front() {
148        let mut addresses = ExternalAddresses::default();
149
150        addresses.on_swarm_event(&new_external_addr1());
151        addresses.on_swarm_event(&new_external_addr2());
152        addresses.on_swarm_event(&new_external_addr1());
153
154        assert_eq!(
155            addresses.as_slice(),
156            &[(*MEMORY_ADDR_1000).clone(), (*MEMORY_ADDR_2000).clone()]
157        );
158    }
159
160    fn new_external_addr1() -> FromSwarm<'static> {
161        FromSwarm::ExternalAddrConfirmed(ExternalAddrConfirmed {
162            addr: &MEMORY_ADDR_1000,
163        })
164    }
165
166    fn new_external_addr2() -> FromSwarm<'static> {
167        FromSwarm::ExternalAddrConfirmed(ExternalAddrConfirmed {
168            addr: &MEMORY_ADDR_2000,
169        })
170    }
171
172    fn expired_external_addr1() -> FromSwarm<'static> {
173        FromSwarm::ExternalAddrExpired(ExternalAddrExpired {
174            addr: &MEMORY_ADDR_1000,
175        })
176    }
177
178    static MEMORY_ADDR_1000: Lazy<Multiaddr> =
179        Lazy::new(|| Multiaddr::empty().with(Protocol::Memory(1000)));
180    static MEMORY_ADDR_2000: Lazy<Multiaddr> =
181        Lazy::new(|| Multiaddr::empty().with(Protocol::Memory(2000)));
182}