Skip to content

Commit 25ddf91

Browse files
authored
Expose API for PublicAddresses (#212)
This PR exposes an object for handling public addresses. - `add_address` - adds an address to the given set, inserting the local peer ID or returning an error on different peer Ids - `remove_address` - removes an exact address from the set - `get_addresses` - list of actual addresses Users of litep2p can obtain the given list via litep2p::public_addresses method. The lists are exposed internally by a similar API on the transport handler (the handler that communicates with the transport manager). The identify protocol provides a concatenated list of: - user provided addresses via the config - listen addresses - external addresses This ensures that Substrate can use a custom heuristic to update the list of external addresses. ### Testing Done - Added an integration test with 2 litep2p nodes Closes: #191 cc @paritytech/networking --------- Signed-off-by: Alexandru Vasile <[email protected]>
1 parent e3a22d5 commit 25ddf91

File tree

9 files changed

+325
-68
lines changed

9 files changed

+325
-68
lines changed

src/addresses.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Copyright 2024 litep2p developers
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the "Software"),
5+
// to deal in the Software without restriction, including without limitation
6+
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7+
// and/or sell copies of the Software, and to permit persons to whom the
8+
// Software is furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19+
// DEALINGS IN THE SOFTWARE.
20+
21+
use std::{collections::HashSet, sync::Arc};
22+
23+
use multiaddr::{Multiaddr, Protocol};
24+
use parking_lot::RwLock;
25+
26+
use crate::PeerId;
27+
28+
/// Set of the public addresses of the local node.
29+
///
30+
/// The format of the addresses stored in the set contain the local peer ID.
31+
/// This requirement is enforced by the [`PublicAddresses::add_address`] method,
32+
/// that will add the local peer ID to the address if it is missing.
33+
///
34+
/// # Note
35+
///
36+
/// - The addresses are reported to the identify protocol and are used by other nodes
37+
/// to establish a connection with the local node.
38+
///
39+
/// - Users must ensure that the addresses are reachable from the network.
40+
#[derive(Debug, Clone)]
41+
pub struct PublicAddresses {
42+
pub(crate) inner: Arc<RwLock<HashSet<Multiaddr>>>,
43+
local_peer_id: PeerId,
44+
}
45+
46+
impl PublicAddresses {
47+
/// Creates new [`PublicAddresses`] from the given peer ID.
48+
pub(crate) fn new(local_peer_id: PeerId) -> Self {
49+
Self {
50+
inner: Arc::new(RwLock::new(HashSet::new())),
51+
local_peer_id,
52+
}
53+
}
54+
55+
/// Add a public address to the list of addresses.
56+
///
57+
/// The address must contain the local peer ID, otherwise an error is returned.
58+
/// In case the address does not contain any peer ID, it will be added.
59+
///
60+
/// Returns true if the address was added, false if it was already present.
61+
pub fn add_address(&self, address: Multiaddr) -> Result<bool, InsertionError> {
62+
let address = ensure_local_peer(address, self.local_peer_id)?;
63+
Ok(self.inner.write().insert(address))
64+
}
65+
66+
/// Remove the exact public address.
67+
///
68+
/// The provided address must contain the local peer ID.
69+
pub fn remove_address(&self, address: &Multiaddr) -> bool {
70+
self.inner.write().remove(address)
71+
}
72+
73+
/// Returns a vector of the available listen addresses.
74+
pub fn get_addresses(&self) -> Vec<Multiaddr> {
75+
self.inner.read().iter().cloned().collect()
76+
}
77+
}
78+
79+
/// Check if the address contains the local peer ID.
80+
///
81+
/// If the address does not contain any peer ID, it will be added.
82+
fn ensure_local_peer(
83+
mut address: Multiaddr,
84+
local_peer_id: PeerId,
85+
) -> Result<Multiaddr, InsertionError> {
86+
if address.is_empty() {
87+
return Err(InsertionError::EmptyAddress);
88+
}
89+
90+
// Verify the peer ID from the address corresponds to the local peer ID.
91+
if let Some(peer_id) = PeerId::try_from_multiaddr(&address) {
92+
if peer_id != local_peer_id {
93+
return Err(InsertionError::DifferentPeerId);
94+
}
95+
} else {
96+
address.push(Protocol::P2p(local_peer_id.into()));
97+
}
98+
99+
Ok(address)
100+
}
101+
102+
/// The error returned when an address cannot be inserted.
103+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104+
pub enum InsertionError {
105+
/// The address is empty.
106+
EmptyAddress,
107+
/// The address contains a different peer ID than the local peer ID.
108+
DifferentPeerId,
109+
}
110+
111+
#[cfg(test)]
112+
mod tests {
113+
use super::*;
114+
use std::str::FromStr;
115+
116+
#[test]
117+
fn add_remove_contains() {
118+
let peer_id = PeerId::random();
119+
let addresses = PublicAddresses::new(peer_id);
120+
let address = Multiaddr::from_str("/dns/domain1.com/tcp/30333").unwrap();
121+
let peer_address = Multiaddr::from_str("/dns/domain1.com/tcp/30333")
122+
.unwrap()
123+
.with(Protocol::P2p(peer_id.into()));
124+
125+
assert!(!addresses.get_addresses().contains(&address));
126+
127+
assert!(addresses.add_address(address.clone()).unwrap());
128+
// Adding the address a second time returns Ok(false).
129+
assert!(!addresses.add_address(address.clone()).unwrap());
130+
131+
assert!(!addresses.get_addresses().contains(&address));
132+
assert!(addresses.get_addresses().contains(&peer_address));
133+
134+
addresses.remove_address(&peer_address);
135+
assert!(!addresses.get_addresses().contains(&peer_address));
136+
}
137+
138+
#[test]
139+
fn get_addresses() {
140+
let peer_id = PeerId::random();
141+
let addresses = PublicAddresses::new(peer_id);
142+
let address1 = Multiaddr::from_str("/dns/domain1.com/tcp/30333").unwrap();
143+
let address2 = Multiaddr::from_str("/dns/domain2.com/tcp/30333").unwrap();
144+
// Addresses different than the local peer ID are ignored.
145+
let address3 = Multiaddr::from_str(
146+
"/dns/domain2.com/tcp/30333/p2p/12D3KooWSueCPH3puP2PcvqPJdNaDNF3jMZjtJtDiSy35pWrbt5h",
147+
)
148+
.unwrap();
149+
150+
assert!(addresses.add_address(address1.clone()).unwrap());
151+
assert!(addresses.add_address(address2.clone()).unwrap());
152+
addresses.add_address(address3.clone()).unwrap_err();
153+
154+
let addresses = addresses.get_addresses();
155+
assert_eq!(addresses.len(), 2);
156+
assert!(addresses.contains(&address1.with(Protocol::P2p(peer_id.into()))));
157+
assert!(addresses.contains(&address2.with(Protocol::P2p(peer_id.into()))));
158+
}
159+
}

src/lib.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
#![allow(clippy::match_like_matches_macro)]
3030

3131
use crate::{
32+
addresses::PublicAddresses,
3233
config::Litep2pConfig,
34+
error::DialError,
3335
protocol::{
3436
libp2p::{bitswap::Bitswap, identify::Identify, kademlia::Kademlia, ping::Ping},
3537
mdns::Mdns,
@@ -50,7 +52,6 @@ use crate::transport::webrtc::WebRtcTransport;
5052
#[cfg(feature = "websocket")]
5153
use crate::transport::websocket::WebSocketTransport;
5254

53-
use error::DialError;
5455
use multiaddr::{Multiaddr, Protocol};
5556
use multihash::Multihash;
5657
use transport::Endpoint;
@@ -65,6 +66,7 @@ pub use types::protocol::ProtocolName;
6566

6667
pub(crate) mod peer_id;
6768

69+
pub mod addresses;
6870
pub mod codec;
6971
pub mod config;
7072
pub mod crypto;
@@ -387,7 +389,7 @@ impl Litep2p {
387389
// if identify was enabled, give it the enabled protocols and listen addresses and start it
388390
if let Some((service, mut identify_config)) = identify_info.take() {
389391
identify_config.protocols = transport_manager.protocols().cloned().collect();
390-
let identify = Identify::new(service, identify_config, listen_addresses.clone());
392+
let identify = Identify::new(service, identify_config);
391393

392394
litep2p_config.executor.run(Box::pin(async move {
393395
let _ = identify.run().await;
@@ -450,7 +452,12 @@ impl Litep2p {
450452
&self.local_peer_id
451453
}
452454

453-
/// Get listen address of litep2p.
455+
/// Get the list of public addresses of the node.
456+
pub fn public_addresses(&self) -> PublicAddresses {
457+
self.transport_manager.public_addresses()
458+
}
459+
460+
/// Get the list of listen addresses of the node.
454461
pub fn listen_addresses(&self) -> impl Iterator<Item = &Multiaddr> {
455462
self.listen_addresses.iter()
456463
}
@@ -473,7 +480,7 @@ impl Litep2p {
473480
/// Add one ore more known addresses for peer.
474481
///
475482
/// Return value denotes how many addresses were added for the peer.
476-
// Addresses belonging to disabled/unsupported transports will be ignored.
483+
/// Addresses belonging to disabled/unsupported transports will be ignored.
477484
pub fn add_known_address(
478485
&mut self,
479486
peer: PeerId,

src/protocol/libp2p/identify.rs

Lines changed: 78 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,6 @@ pub struct Config {
7979
/// Protocols supported by the local node, filled by `Litep2p`.
8080
pub(crate) protocols: Vec<ProtocolName>,
8181

82-
/// Public addresses.
83-
pub(crate) public_addresses: Vec<Multiaddr>,
84-
8582
/// Protocol version.
8683
pub(crate) protocol_version: String,
8784

@@ -97,15 +94,13 @@ impl Config {
9794
pub fn new(
9895
protocol_version: String,
9996
user_agent: Option<String>,
100-
public_addresses: Vec<Multiaddr>,
10197
) -> (Self, Box<dyn Stream<Item = IdentifyEvent> + Send + Unpin>) {
10298
let (tx_event, rx_event) = channel(DEFAULT_CHANNEL_SIZE);
10399

104100
(
105101
Self {
106102
tx_event,
107103
public: None,
108-
public_addresses,
109104
protocol_version,
110105
user_agent,
111106
codec: ProtocolCodec::UnsignedVarint(Some(IDENTIFY_PAYLOAD_SIZE)),
@@ -182,9 +177,6 @@ pub(crate) struct Identify {
182177
/// User agent.
183178
user_agent: String,
184179

185-
/// Public addresses.
186-
listen_addresses: HashSet<Multiaddr>,
187-
188180
/// Protocols supported by the local node, filled by `Litep2p`.
189181
protocols: Vec<String>,
190182

@@ -200,16 +192,11 @@ pub(crate) struct Identify {
200192

201193
impl Identify {
202194
/// Create new [`Identify`] protocol.
203-
pub(crate) fn new(
204-
service: TransportService,
205-
config: Config,
206-
listen_addresses: Vec<Multiaddr>,
207-
) -> Self {
195+
pub(crate) fn new(service: TransportService, config: Config) -> Self {
208196
Self {
209197
service,
210198
tx: config.tx_event,
211199
peers: HashMap::new(),
212-
listen_addresses: config.public_addresses.into_iter().chain(listen_addresses).collect(),
213200
public: config.public.expect("public key to be supplied"),
214201
protocol_version: config.protocol_version,
215202
user_agent: config.user_agent.unwrap_or(DEFAULT_AGENT.to_string()),
@@ -265,15 +252,16 @@ impl Identify {
265252
}
266253
};
267254

255+
let mut listen_addr: HashSet<_> =
256+
self.service.listen_addresses().into_iter().map(|addr| addr.to_vec()).collect();
257+
listen_addr
258+
.extend(self.service.public_addresses().inner.read().iter().map(|addr| addr.to_vec()));
259+
268260
let identify = identify_schema::Identify {
269261
protocol_version: Some(self.protocol_version.clone()),
270262
agent_version: Some(self.user_agent.clone()),
271263
public_key: Some(self.public.to_protobuf_encoding()),
272-
listen_addrs: self
273-
.listen_addresses
274-
.iter()
275-
.map(|address| address.to_vec())
276-
.collect::<Vec<_>>(),
264+
listen_addrs: listen_addr.into_iter().collect(),
277265
observed_addr,
278266
protocols: self.protocols.clone(),
279267
};
@@ -413,3 +401,74 @@ impl Identify {
413401
}
414402
}
415403
}
404+
405+
#[cfg(test)]
406+
mod tests {
407+
use super::*;
408+
use crate::{config::ConfigBuilder, transport::tcp::config::Config as TcpConfig, Litep2p};
409+
use multiaddr::{Multiaddr, Protocol};
410+
411+
fn create_litep2p() -> (
412+
Litep2p,
413+
Box<dyn Stream<Item = IdentifyEvent> + Send + Unpin>,
414+
PeerId,
415+
) {
416+
let (identify_config, identify) =
417+
Config::new("1.0.0".to_string(), Some("litep2p/1.0.0".to_string()));
418+
419+
let keypair = crate::crypto::ed25519::Keypair::generate();
420+
let peer = PeerId::from_public_key(&crate::crypto::PublicKey::Ed25519(keypair.public()));
421+
let config = ConfigBuilder::new()
422+
.with_keypair(keypair)
423+
.with_tcp(TcpConfig {
424+
listen_addresses: vec!["/ip6/::1/tcp/0".parse().unwrap()],
425+
..Default::default()
426+
})
427+
.with_libp2p_identify(identify_config)
428+
.build();
429+
430+
(Litep2p::new(config).unwrap(), identify, peer)
431+
}
432+
433+
#[tokio::test]
434+
async fn update_identify_addresses() {
435+
// Create two instances of litep2p
436+
let (mut litep2p1, mut event_stream1, peer1) = create_litep2p();
437+
let (mut litep2p2, mut event_stream2, _peer2) = create_litep2p();
438+
let litep2p1_address = litep2p1.listen_addresses().into_iter().next().unwrap();
439+
440+
let multiaddr: Multiaddr = "/ip6/::9/tcp/111".parse().unwrap();
441+
// Litep2p1 is now reporting the new address.
442+
assert!(litep2p1.public_addresses().add_address(multiaddr.clone()).unwrap());
443+
444+
// Dial `litep2p1`
445+
litep2p2.dial_address(litep2p1_address.clone()).await.unwrap();
446+
447+
let expected_multiaddr = multiaddr.with(Protocol::P2p(peer1.into()));
448+
449+
tokio::spawn(async move {
450+
loop {
451+
tokio::select! {
452+
_ = litep2p1.next_event() => {}
453+
_event = event_stream1.next() => {}
454+
}
455+
}
456+
});
457+
458+
loop {
459+
tokio::select! {
460+
_ = litep2p2.next_event() => {}
461+
event = event_stream2.next() => match event {
462+
Some(IdentifyEvent::PeerIdentified {
463+
listen_addresses,
464+
..
465+
}) => {
466+
assert!(listen_addresses.iter().any(|address| address == &expected_multiaddr));
467+
break;
468+
}
469+
_ => {}
470+
}
471+
}
472+
}
473+
}
474+
}

0 commit comments

Comments
 (0)