summaryrefslogtreecommitdiffstats
path: root/src/transport
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-29 00:00:47 +0100
committertv <tv@krebsco.de>2026-10-29 00:03:42 +0100
commitfc708a1c1dad15f268786d22cff883c16d251d65 (patch)
treeca9887455e7c41a1e3304f129fbfd4b0c5a7c2ee /src/transport
parent03a64a24a5795051fc24ce999a5b1ac0cf1affe8 (diff)
libp2p: support seeds that are only peer ID
Diffstat (limited to 'src/transport')
-rw-r--r--src/transport/libp2p.rs96
1 files changed, 85 insertions, 11 deletions
diff --git a/src/transport/libp2p.rs b/src/transport/libp2p.rs
index 9217c79..c3e2d98 100644
--- a/src/transport/libp2p.rs
+++ b/src/transport/libp2p.rs
@@ -698,6 +698,32 @@ impl Book {
.collect()
}
+ /// Where `peer` may be reached through the relays we know (named, then members that
+ /// relay): for a peer known by its ID only (a seed `/p2p/<peer ID>`), which may hold
+ /// a slot on one of them. `local`: this node, never its own relay.
+ fn through_relays(&self, peer: PeerId, local: PeerId) -> Vec<Multiaddr> {
+ let mut relays: Vec<(PeerId, Multiaddr)> = self
+ .named_relays
+ .iter()
+ .map(|(relay, named)| (*relay, named.at.clone()))
+ .collect();
+ relays.sort_by_key(|(_, address)| address.to_string());
+ let mut members: Vec<(PeerId, Multiaddr)> = self
+ .relays
+ .iter()
+ .filter(|(relay, _)| !self.named_relays.contains_key(relay))
+ .map(|(relay, address)| (*relay, address.clone()))
+ .collect();
+ members.sort_by_key(|(_, address)| address.to_string());
+ relays
+ .into_iter()
+ .chain(members)
+ .filter(|(relay, _)| *relay != local && *relay != peer)
+ .map(|(_, address)| address.with(Protocol::P2pCircuit).with(Protocol::P2p(peer)))
+ .take(ADDRESSES_PER_PEER)
+ .collect()
+ }
+
/// Whether all of `addresses` are known of `peer` (`remember`).
fn knows(&self, peer: PeerId, addresses: &[Multiaddr]) -> bool {
let Some(known) = self.addresses.get(&peer) else {
@@ -849,11 +875,19 @@ impl Libp2pTransport {
/// Connected to `peer`, if it can be had in time.
async fn connect(&self, peer: PeerId) -> Result<()> {
- let addresses = lock(&self.book)
- .addresses
- .get(&peer)
- .map(|known| dialable(known, self.peer_id))
- .unwrap_or_default();
+ let addresses = {
+ let book = lock(&self.book);
+ let known = book
+ .addresses
+ .get(&peer)
+ .map(|known| dialable(known, self.peer_id))
+ .unwrap_or_default();
+ if known.is_empty() {
+ book.through_relays(peer, self.peer_id)
+ } else {
+ known
+ }
+ };
let (done, connected) = oneshot::channel();
self.commands
.send(Command::Connect {
@@ -1728,7 +1762,9 @@ impl Transport for Libp2pTransport {
return Some(relay.to_string());
}
let peer = split_peer(&address)?;
- if peer != self.peer_id {
+ address.pop();
+ // A peer ID only says nothing about where.
+ if peer != self.peer_id && !address.is_empty() {
lock(&self.book).remember(peer, address);
}
Some(peer.to_string())
@@ -1798,14 +1834,26 @@ impl Transport for Libp2pTransport {
/// A multiaddr ending in a peer ID is that peer; one without is connected to, to
/// find out. A bare peer ID resolves if this transport knows where it is.
async fn resolve_seed(&self, seed: &str) -> Result<String> {
- if let Ok(peer) = PeerId::from_str(seed) {
- if lock(&self.book).addresses.contains_key(&peer) {
+ let address = match PeerId::from_str(seed) {
+ Ok(peer) => Multiaddr::empty().with(Protocol::P2p(peer)),
+ Err(_) => {
+ Multiaddr::from_str(seed).with_context(|| format!("{seed} is no multiaddr"))?
+ }
+ };
+ // A peer ID only (`/p2p/<peer ID>`): reached where it's known, or through the
+ // relays we know.
+ if let [Protocol::P2p(peer)] = address.iter().collect::<Vec<_>>()[..] {
+ let book = lock(&self.book);
+ if book.addresses.contains_key(&peer)
+ || !book.through_relays(peer, self.peer_id).is_empty()
+ {
return Ok(peer.to_string());
}
- bail!("no address known for libp2p peer {peer}; give a multiaddr");
+ bail!(
+ "no address known for libp2p peer {peer}, and no relay to reach it through; \
+ give a multiaddr, or a relay (libp2p-relays)"
+ );
}
- let address =
- Multiaddr::from_str(seed).with_context(|| format!("{seed} is no multiaddr"))?;
if let Some(peer) = self.locate(seed) {
return Ok(peer);
}
@@ -2171,6 +2219,32 @@ mod tests {
assert!(a.connection(b.local_id()).is_some());
}
+ /// A seed that's a peer ID only is reached through the relays the node knows.
+ #[tokio::test]
+ async fn a_peer_id_seed_is_reached_through_a_relay() {
+ let (_r, r_address) = memory_node_with(true, Vec::new(), true);
+ let (b, _) = memory_node_with(false, vec![r_address.clone()], false);
+ let b = Arc::new(b);
+ let mut at_b = received(Arc::clone(&b));
+ assert!(
+ eventually(|| !b.local_addresses().is_empty()).await,
+ "b got no slot on the relay"
+ );
+ let (a, _) = memory_node_with(true, vec![r_address.clone()], false);
+ let seed = format!("/p2p/{}", b.local_id());
+ assert_eq!(a.resolve_seed(&seed).await.unwrap(), b.local_id());
+ assert_eq!(a.resolve_seed(b.local_id()).await.unwrap(), b.local_id());
+ // Nothing to dial is remembered.
+ assert!(!lock(&a.book).addresses.contains_key(&b.peer_id));
+ a.send_message(b.local_id(), &status()).await.unwrap();
+ let (from, _) = at_b.recv().await.unwrap();
+ assert_eq!(from, a.local_id());
+
+ // Without a relay, there's nowhere to look.
+ let (c, _) = memory_node();
+ assert!(c.resolve_seed(&seed).await.is_err());
+ }
+
/// b listens nowhere; a, which can be dialled, reaches it through the relay r: b
/// then dials a directly (identify told it where), and both keep that.
#[tokio::test]