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<THandler>(&mut self, event: &FromSwarm<THandler>) -> 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                    log::debug!("Refreshed external address {addr}");
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                    log::debug!("Removing previously confirmed external address {expired} because we reached the limit of {MAX_LOCAL_EXTERNAL_ADDRS} addresses");
51                }
52
53                return true;
54            }
55            FromSwarm::ExternalAddrExpired(ExternalAddrExpired {
56                addr: expired_addr, ..
57            }) => {
58                let pos = match self
59                    .addresses
60                    .iter()
61                    .position(|candidate| candidate == *expired_addr)
62                {
63                    None => return false,
64                    Some(p) => p,
65                };
66
67                self.addresses.remove(pos);
68                return true;
69            }
70            _ => {}
71        }
72
73        false
74    }
75
76    fn push_front(&mut self, addr: &Multiaddr) {
77        self.addresses.insert(0, addr.clone()); // We have at most `MAX_LOCAL_EXTERNAL_ADDRS` so this isn't very expensive.
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use crate::dummy;
85    use libp2p_core::multiaddr::Protocol;
86    use once_cell::sync::Lazy;
87    use rand::Rng;
88
89    #[test]
90    fn new_external_addr_returns_correct_changed_value() {
91        let mut addresses = ExternalAddresses::default();
92
93        let changed = addresses.on_swarm_event(&new_external_addr1());
94        assert!(changed);
95
96        let changed = addresses.on_swarm_event(&new_external_addr1());
97        assert!(!changed)
98    }
99
100    #[test]
101    fn expired_external_addr_returns_correct_changed_value() {
102        let mut addresses = ExternalAddresses::default();
103        addresses.on_swarm_event(&new_external_addr1());
104
105        let changed = addresses.on_swarm_event(&expired_external_addr1());
106        assert!(changed);
107
108        let changed = addresses.on_swarm_event(&expired_external_addr1());
109        assert!(!changed)
110    }
111
112    #[test]
113    fn more_recent_external_addresses_are_prioritized() {
114        let mut addresses = ExternalAddresses::default();
115
116        addresses.on_swarm_event(&new_external_addr1());
117        addresses.on_swarm_event(&new_external_addr2());
118
119        assert_eq!(
120            addresses.as_slice(),
121            &[(*MEMORY_ADDR_2000).clone(), (*MEMORY_ADDR_1000).clone()]
122        );
123    }
124
125    #[test]
126    fn when_pushing_more_than_max_addresses_oldest_is_evicted() {
127        let mut addresses = ExternalAddresses::default();
128
129        while addresses.as_slice().len() < MAX_LOCAL_EXTERNAL_ADDRS {
130            let random_address =
131                Multiaddr::empty().with(Protocol::Memory(rand::thread_rng().gen_range(0..1000)));
132            addresses.on_swarm_event(
133                &FromSwarm::<'_, dummy::ConnectionHandler>::ExternalAddrConfirmed(
134                    ExternalAddrConfirmed {
135                        addr: &random_address,
136                    },
137                ),
138            );
139        }
140
141        addresses.on_swarm_event(&new_external_addr2());
142
143        assert_eq!(addresses.as_slice().len(), 20);
144        assert_eq!(addresses.as_slice()[0], (*MEMORY_ADDR_2000).clone());
145    }
146
147    #[test]
148    fn reporting_existing_external_address_moves_it_to_the_front() {
149        let mut addresses = ExternalAddresses::default();
150
151        addresses.on_swarm_event(&new_external_addr1());
152        addresses.on_swarm_event(&new_external_addr2());
153        addresses.on_swarm_event(&new_external_addr1());
154
155        assert_eq!(
156            addresses.as_slice(),
157            &[(*MEMORY_ADDR_1000).clone(), (*MEMORY_ADDR_2000).clone()]
158        );
159    }
160
161    fn new_external_addr1() -> FromSwarm<'static, dummy::ConnectionHandler> {
162        FromSwarm::ExternalAddrConfirmed(ExternalAddrConfirmed {
163            addr: &MEMORY_ADDR_1000,
164        })
165    }
166
167    fn new_external_addr2() -> FromSwarm<'static, dummy::ConnectionHandler> {
168        FromSwarm::ExternalAddrConfirmed(ExternalAddrConfirmed {
169            addr: &MEMORY_ADDR_2000,
170        })
171    }
172
173    fn expired_external_addr1() -> FromSwarm<'static, dummy::ConnectionHandler> {
174        FromSwarm::ExternalAddrExpired(ExternalAddrExpired {
175            addr: &MEMORY_ADDR_1000,
176        })
177    }
178
179    static MEMORY_ADDR_1000: Lazy<Multiaddr> =
180        Lazy::new(|| Multiaddr::empty().with(Protocol::Memory(1000)));
181    static MEMORY_ADDR_2000: Lazy<Multiaddr> =
182        Lazy::new(|| Multiaddr::empty().with(Protocol::Memory(2000)));
183}