Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 075b2d7

Browse files
mxindentomaka
andauthored
client/network: Add peers to DHT only if protocols match (#6549)
* client/network/src/discovery: Adjust to Kademlia API changes * client/network: Add peers to DHT only if protocols match With libp2p/rust-libp2p#1628 rust-libp2p allows manually controlling which peers are inserted into the routing table. Instead of adding each peer to the routing table automatically, insert them only if they support the local nodes protocol id (e.g. `dot`) retrieved via the `identify` behaviour. For now this works around libp2p/rust-libp2p#1611. In the future one might add more requirements. For example one might try to exclude light-clients. * Cargo.toml: Remove crates.io patch for libp2p * client/network/src/behaviour: Adjust to PeerInfo name change * client/network/src/discovery: Rework Kademlia event matching * client/network/discovery: Add trace on adding peer to DHT * client/network/discovery: Retrieve protocol name from kad behaviour * client/network/discovery: Fix formatting * client/network: Change DiscoveryBehaviour::add_self_reported signature * client/network: Document manual insertion strategy * client/network/discovery: Remove TODO for ignoring DHT address Co-authored-by: Pierre Krieger <[email protected]>
1 parent 075182e commit 075b2d7

File tree

2 files changed

+212
-54
lines changed

2 files changed

+212
-54
lines changed

client/network/src/behaviour.rs

+21-8
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use bytes::Bytes;
2525
use codec::Encode as _;
2626
use libp2p::NetworkBehaviour;
2727
use libp2p::core::{Multiaddr, PeerId, PublicKey};
28+
use libp2p::identify::IdentifyInfo;
2829
use libp2p::kad::record;
2930
use libp2p::swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters};
3031
use log::debug;
@@ -413,16 +414,28 @@ impl<B: BlockT, H: ExHashT> NetworkBehaviourEventProcess<finality_requests::Even
413414
impl<B: BlockT, H: ExHashT> NetworkBehaviourEventProcess<peer_info::PeerInfoEvent>
414415
for Behaviour<B, H> {
415416
fn inject_event(&mut self, event: peer_info::PeerInfoEvent) {
416-
let peer_info::PeerInfoEvent::Identified { peer_id, mut info } = event;
417-
if info.listen_addrs.len() > 30 {
418-
debug!(target: "sub-libp2p", "Node {:?} has reported more than 30 addresses; \
419-
it is identified by {:?} and {:?}", peer_id, info.protocol_version,
420-
info.agent_version
417+
let peer_info::PeerInfoEvent::Identified {
418+
peer_id,
419+
info: IdentifyInfo {
420+
protocol_version,
421+
agent_version,
422+
mut listen_addrs,
423+
protocols,
424+
..
425+
},
426+
} = event;
427+
428+
if listen_addrs.len() > 30 {
429+
debug!(
430+
target: "sub-libp2p",
431+
"Node {:?} has reported more than 30 addresses; it is identified by {:?} and {:?}",
432+
peer_id, protocol_version, agent_version
421433
);
422-
info.listen_addrs.truncate(30);
434+
listen_addrs.truncate(30);
423435
}
424-
for addr in &info.listen_addrs {
425-
self.discovery.add_self_reported_address(&peer_id, addr.clone());
436+
437+
for addr in listen_addrs {
438+
self.discovery.add_self_reported_address(&peer_id, protocols.iter(), addr);
426439
}
427440
self.substrate.add_discovered_nodes(iter::once(peer_id));
428441
}

client/network/src/discovery.rs

+191-46
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ use ip_network::IpNetwork;
5252
use libp2p::core::{connection::{ConnectionId, ListenerId}, ConnectedPoint, Multiaddr, PeerId, PublicKey};
5353
use libp2p::swarm::{NetworkBehaviour, NetworkBehaviourAction, PollParameters, ProtocolsHandler};
5454
use libp2p::swarm::protocols_handler::multi::MultiHandler;
55-
use libp2p::kad::{Kademlia, KademliaConfig, KademliaEvent, QueryResult, Quorum, Record};
55+
use libp2p::kad::{Kademlia, KademliaBucketInserts, KademliaConfig, KademliaEvent, QueryResult, Quorum, Record};
5656
use libp2p::kad::GetClosestPeersError;
5757
use libp2p::kad::handler::KademliaHandler;
5858
use libp2p::kad::QueryId;
@@ -137,17 +137,9 @@ impl DiscoveryConfig {
137137
}
138138

139139
/// Add discovery via Kademlia for the given protocol.
140-
pub fn add_protocol(&mut self, p: ProtocolId) -> &mut Self {
141-
// NB: If this protocol name derivation is changed, check if
142-
// `DiscoveryBehaviour::new_handler` is still correct.
143-
let proto_name = {
144-
let mut v = vec![b'/'];
145-
v.extend_from_slice(p.as_bytes());
146-
v.extend_from_slice(b"/kad");
147-
v
148-
};
149-
150-
self.add_kademlia(p, proto_name);
140+
pub fn add_protocol(&mut self, id: ProtocolId) -> &mut Self {
141+
let name = protocol_name_from_protocol_id(&id);
142+
self.add_kademlia(id, name);
151143
self
152144
}
153145

@@ -159,6 +151,10 @@ impl DiscoveryConfig {
159151

160152
let mut config = KademliaConfig::default();
161153
config.set_protocol_name(proto_name);
154+
// By default Kademlia attempts to insert all peers into its routing table once a dialing
155+
// attempt succeeds. In order to control which peer is added, disable the auto-insertion and
156+
// instead add peers manually.
157+
config.set_kbucket_inserts(KademliaBucketInserts::Manual);
162158

163159
let store = MemoryStore::new(self.local_peer_id.clone());
164160
let mut kad = Kademlia::with_config(self.local_peer_id.clone(), store, config);
@@ -259,17 +255,43 @@ impl DiscoveryBehaviour {
259255
}
260256
}
261257

262-
/// Call this method when a node reports an address for itself.
258+
/// Add a self-reported address of a remote peer to the k-buckets of the supported
259+
/// DHTs (`supported_protocols`).
263260
///
264-
/// **Note**: It is important that you call this method, otherwise the discovery mechanism will
265-
/// not properly work.
266-
pub fn add_self_reported_address(&mut self, peer_id: &PeerId, addr: Multiaddr) {
267-
if self.allow_non_globals_in_dht || self.can_add_to_dht(&addr) {
268-
for k in self.kademlias.values_mut() {
269-
k.add_address(peer_id, addr.clone());
261+
/// **Note**: It is important that you call this method. The discovery mechanism will not
262+
/// automatically add connecting peers to the Kademlia k-buckets.
263+
pub fn add_self_reported_address(
264+
&mut self,
265+
peer_id: &PeerId,
266+
supported_protocols: impl Iterator<Item = impl AsRef<[u8]>>,
267+
addr: Multiaddr
268+
) {
269+
if !self.allow_non_globals_in_dht && !self.can_add_to_dht(&addr) {
270+
log::trace!(target: "sub-libp2p", "Ignoring self-reported non-global address {} from {}.", addr, peer_id);
271+
return
272+
}
273+
274+
let mut added = false;
275+
for protocol in supported_protocols {
276+
for kademlia in self.kademlias.values_mut() {
277+
if protocol.as_ref() == kademlia.protocol_name() {
278+
log::trace!(
279+
target: "sub-libp2p",
280+
"Adding self-reported address {} from {} to Kademlia DHT {}.",
281+
addr, peer_id, String::from_utf8_lossy(kademlia.protocol_name()),
282+
);
283+
kademlia.add_address(peer_id, addr.clone());
284+
added = true;
285+
}
270286
}
271-
} else {
272-
log::trace!(target: "sub-libp2p", "Ignoring self-reported address {} from {}", addr, peer_id);
287+
}
288+
289+
if !added {
290+
log::trace!(
291+
target: "sub-libp2p",
292+
"Ignoring self-reported address {} from {} as remote node is not part of any \
293+
Kademlia DHTs supported by the local node.", addr, peer_id,
294+
);
273295
}
274296
}
275297

@@ -340,17 +362,21 @@ impl DiscoveryBehaviour {
340362
}
341363

342364
/// Event generated by the `DiscoveryBehaviour`.
365+
#[derive(Debug)]
343366
pub enum DiscoveryOut {
344-
/// The address of a peer has been added to the Kademlia routing table.
345-
///
346-
/// Can be called multiple times with the same identity.
367+
/// A connection to a peer has been established but the peer has not been
368+
/// added to the routing table because [`KademliaBucketInserts::Manual`] is
369+
/// configured. If the peer is to be included in the routing table, it must
370+
/// be explicitly added via
371+
/// [`DiscoveryBehaviour::add_self_reported_address`].
347372
Discovered(PeerId),
348373

349374
/// A peer connected to this node for whom no listen address is known.
350375
///
351376
/// In order for the peer to be added to the Kademlia routing table, a known
352-
/// listen address must be added via [`DiscoveryBehaviour::add_self_reported_address`],
353-
/// e.g. obtained through the `identify` protocol.
377+
/// listen address must be added via
378+
/// [`DiscoveryBehaviour::add_self_reported_address`], e.g. obtained through
379+
/// the `identify` protocol.
354380
UnroutablePeer(PeerId),
355381

356382
/// The DHT yielded results for the record request, grouped in (key, value) pairs.
@@ -569,8 +595,12 @@ impl NetworkBehaviour for DiscoveryBehaviour {
569595
let ev = DiscoveryOut::UnroutablePeer(peer);
570596
return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev));
571597
}
572-
KademliaEvent::RoutablePeer { .. } | KademliaEvent::PendingRoutablePeer { .. } => {
573-
// We are not interested in these events at the moment.
598+
KademliaEvent::RoutablePeer { peer, .. } => {
599+
let ev = DiscoveryOut::Discovered(peer);
600+
return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev));
601+
}
602+
KademliaEvent::PendingRoutablePeer { .. } => {
603+
// We are not interested in this event at the moment.
574604
}
575605
KademliaEvent::QueryResult { result: QueryResult::GetClosestPeers(res), .. } => {
576606
match res {
@@ -689,25 +719,36 @@ impl NetworkBehaviour for DiscoveryBehaviour {
689719
}
690720
}
691721

722+
// NB: If this protocol name derivation is changed, check if
723+
// `DiscoveryBehaviour::new_handler` is still correct.
724+
fn protocol_name_from_protocol_id(id: &ProtocolId) -> Vec<u8> {
725+
let mut v = vec![b'/'];
726+
v.extend_from_slice(id.as_bytes());
727+
v.extend_from_slice(b"/kad");
728+
v
729+
}
730+
692731
#[cfg(test)]
693732
mod tests {
694733
use crate::config::ProtocolId;
695734
use futures::prelude::*;
696735
use libp2p::identity::Keypair;
697-
use libp2p::Multiaddr;
736+
use libp2p::{Multiaddr, PeerId};
698737
use libp2p::core::upgrade;
699738
use libp2p::core::transport::{Transport, MemoryTransport};
700739
use libp2p::core::upgrade::{InboundUpgradeExt, OutboundUpgradeExt};
701740
use libp2p::swarm::Swarm;
702741
use std::{collections::HashSet, task::Poll};
703-
use super::{DiscoveryConfig, DiscoveryOut};
742+
use super::{DiscoveryConfig, DiscoveryOut, protocol_name_from_protocol_id};
704743

705744
#[test]
706745
fn discovery_working() {
707-
let mut user_defined = Vec::new();
746+
let mut first_swarm_peer_id_and_addr = None;
747+
let protocol_id = ProtocolId::from(b"dot".as_ref());
708748

709-
// Build swarms whose behaviour is `DiscoveryBehaviour`.
710-
let mut swarms = (0..25).map(|_| {
749+
// Build swarms whose behaviour is `DiscoveryBehaviour`, each aware of
750+
// the first swarm via `with_user_defined`.
751+
let mut swarms = (0..25).map(|i| {
711752
let keypair = Keypair::generate_ed25519();
712753
let keypair2 = keypair.clone();
713754

@@ -730,23 +771,21 @@ mod tests {
730771
});
731772

732773
let behaviour = {
733-
let protocol_id: &[u8] = b"/test/kad/1.0.0";
734-
735774
let mut config = DiscoveryConfig::new(keypair.public());
736-
config.with_user_defined(user_defined.clone())
775+
config.with_user_defined(first_swarm_peer_id_and_addr.clone())
737776
.allow_private_ipv4(true)
738777
.allow_non_globals_in_dht(true)
739778
.discovery_limit(50)
740-
.add_protocol(ProtocolId::from(protocol_id));
779+
.add_protocol(protocol_id.clone());
741780

742781
config.finish()
743782
};
744783

745784
let mut swarm = Swarm::new(transport, behaviour, keypair.public().into_peer_id());
746785
let listen_addr: Multiaddr = format!("/memory/{}", rand::random::<u64>()).parse().unwrap();
747786

748-
if user_defined.is_empty() {
749-
user_defined.push((keypair.public().into_peer_id(), listen_addr.clone()));
787+
if i == 0 {
788+
first_swarm_peer_id_and_addr = Some((keypair.public().into_peer_id(), listen_addr.clone()))
750789
}
751790

752791
Swarm::listen_on(&mut swarm, listen_addr.clone()).unwrap();
@@ -755,7 +794,10 @@ mod tests {
755794

756795
// Build a `Vec<HashSet<PeerId>>` with the list of nodes remaining to be discovered.
757796
let mut to_discover = (0..swarms.len()).map(|n| {
758-
(0..swarms.len()).filter(|p| *p != n)
797+
(0..swarms.len())
798+
// Skip the first swarm as all other swarms already know it.
799+
.skip(1)
800+
.filter(|p| *p != n)
759801
.map(|p| Swarm::local_peer_id(&swarms[p].0).clone())
760802
.collect::<HashSet<_>>()
761803
}).collect::<Vec<_>>();
@@ -766,7 +808,7 @@ mod tests {
766808
match swarms[swarm_n].0.poll_next_unpin(cx) {
767809
Poll::Ready(Some(e)) => {
768810
match e {
769-
DiscoveryOut::UnroutablePeer(other) => {
811+
DiscoveryOut::UnroutablePeer(other) | DiscoveryOut::Discovered(other) => {
770812
// Call `add_self_reported_address` to simulate identify happening.
771813
let addr = swarms.iter().find_map(|(s, a)|
772814
if s.local_peer_id == other {
@@ -775,12 +817,16 @@ mod tests {
775817
None
776818
})
777819
.unwrap();
778-
swarms[swarm_n].0.add_self_reported_address(&other, addr);
779-
},
780-
DiscoveryOut::Discovered(other) => {
820+
swarms[swarm_n].0.add_self_reported_address(
821+
&other,
822+
[protocol_name_from_protocol_id(&protocol_id)].iter(),
823+
addr,
824+
);
825+
781826
to_discover[swarm_n].remove(&other);
782-
}
783-
_ => {}
827+
},
828+
DiscoveryOut::RandomKademliaStarted(_) => {},
829+
e => {panic!("Unexpected event: {:?}", e)},
784830
}
785831
continue 'polling
786832
}
@@ -799,4 +845,103 @@ mod tests {
799845

800846
futures::executor::block_on(fut);
801847
}
848+
849+
#[test]
850+
fn discovery_ignores_peers_with_unknown_protocols() {
851+
let supported_protocol_id = ProtocolId::from(b"a".as_ref());
852+
let unsupported_protocol_id = ProtocolId::from(b"b".as_ref());
853+
854+
let mut discovery = {
855+
let keypair = Keypair::generate_ed25519();
856+
let mut config = DiscoveryConfig::new(keypair.public());
857+
config.allow_private_ipv4(true)
858+
.allow_non_globals_in_dht(true)
859+
.discovery_limit(50)
860+
.add_protocol(supported_protocol_id.clone());
861+
config.finish()
862+
};
863+
864+
let remote_peer_id = PeerId::random();
865+
let remote_addr: Multiaddr = format!("/memory/{}", rand::random::<u64>()).parse().unwrap();
866+
867+
// Add remote peer with unsupported protocol.
868+
discovery.add_self_reported_address(
869+
&remote_peer_id,
870+
[protocol_name_from_protocol_id(&unsupported_protocol_id)].iter(),
871+
remote_addr.clone(),
872+
);
873+
874+
for kademlia in discovery.kademlias.values_mut() {
875+
assert!(
876+
kademlia.kbucket(remote_peer_id.clone())
877+
.expect("Remote peer id not to be equal to local peer id.")
878+
.is_empty(),
879+
"Expect peer with unsupported protocol not to be added."
880+
);
881+
}
882+
883+
// Add remote peer with supported protocol.
884+
discovery.add_self_reported_address(
885+
&remote_peer_id,
886+
[protocol_name_from_protocol_id(&supported_protocol_id)].iter(),
887+
remote_addr.clone(),
888+
);
889+
890+
for kademlia in discovery.kademlias.values_mut() {
891+
assert_eq!(
892+
1,
893+
kademlia.kbucket(remote_peer_id.clone())
894+
.expect("Remote peer id not to be equal to local peer id.")
895+
.num_entries(),
896+
"Expect peer with supported protocol to be added."
897+
);
898+
}
899+
}
900+
901+
#[test]
902+
fn discovery_adds_peer_to_kademlia_of_same_protocol_only() {
903+
let protocol_a = ProtocolId::from(b"a".as_ref());
904+
let protocol_b = ProtocolId::from(b"b".as_ref());
905+
906+
let mut discovery = {
907+
let keypair = Keypair::generate_ed25519();
908+
let mut config = DiscoveryConfig::new(keypair.public());
909+
config.allow_private_ipv4(true)
910+
.allow_non_globals_in_dht(true)
911+
.discovery_limit(50)
912+
.add_protocol(protocol_a.clone())
913+
.add_protocol(protocol_b.clone());
914+
config.finish()
915+
};
916+
917+
let remote_peer_id = PeerId::random();
918+
let remote_addr: Multiaddr = format!("/memory/{}", rand::random::<u64>()).parse().unwrap();
919+
920+
// Add remote peer with `protocol_a` only.
921+
discovery.add_self_reported_address(
922+
&remote_peer_id,
923+
[protocol_name_from_protocol_id(&protocol_a)].iter(),
924+
remote_addr.clone(),
925+
);
926+
927+
assert_eq!(
928+
1,
929+
discovery.kademlias.get_mut(&protocol_a)
930+
.expect("Kademlia instance to exist.")
931+
.kbucket(remote_peer_id.clone())
932+
.expect("Remote peer id not to be equal to local peer id.")
933+
.num_entries(),
934+
"Expected remote peer to be added to `protocol_a` Kademlia instance.",
935+
936+
);
937+
938+
assert!(
939+
discovery.kademlias.get_mut(&protocol_b)
940+
.expect("Kademlia instance to exist.")
941+
.kbucket(remote_peer_id.clone())
942+
.expect("Remote peer id not to be equal to local peer id.")
943+
.is_empty(),
944+
"Expected remote peer not to be added to `protocol_b` Kademlia instance.",
945+
);
946+
}
802947
}

0 commit comments

Comments
 (0)