I posted this question on StackOverflow ipfs - Kademlia get_providers() fails to find nodes that start_providing() in Rust libp2p - Stack Overflow, but perhaps this forum is a better place to ask:
I tried to combine two Rust examples (ipfs-kad, GitHub - mxinden/libp2p-lookup: Lookup a peer by its id or address. and chat-example) that use Kademlia to locate their peers to create a shared message channel.
However when I run 2 nodes that try to locate each other, they don’t find each other:
Enter messages via STDIN and they will be sent to connected peers using Gossipsub
Other Kademlia event: RoutingUpdated { peer: PeerId("QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"), is_new_peer: true, addresses: ["/dnsaddr/bootstrap.libp2p.io"], bucket_range: (Distance(57896044618658097711785492504343953926634992332820282019728792003956564819968), Distance(115792089237316195423570985008687907853269984665640564039457584007913129639935)), old_peer: None }
Other Kademlia event: RoutingUpdated { peer: PeerId("QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa"), is_new_peer: true, addresses: ["/dnsaddr/bootstrap.libp2p.io"], bucket_range: (Distance(14474011154664524427946373126085988481658748083205070504932198000989141204992), Distance(28948022309329048855892746252171976963317496166410141009864396001978282409983)), old_peer: None }
Other Kademlia event: RoutingUpdated { peer: PeerId("QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb"), is_new_peer: true, addresses: ["/dnsaddr/bootstrap.libp2p.io"], bucket_range: (Distance(57896044618658097711785492504343953926634992332820282019728792003956564819968), Distance(115792089237316195423570985008687907853269984665640564039457584007913129639935)), old_peer: None }
Other Kademlia event: RoutingUpdated { peer: PeerId("QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt"), is_new_peer: true, addresses: ["/dnsaddr/bootstrap.libp2p.io"], bucket_range: (Distance(14474011154664524427946373126085988481658748083205070504932198000989141204992), Distance(28948022309329048855892746252171976963317496166410141009864396001978282409983)), old_peer: None }
Finished getting providers, waiting for other peers to connect; closest peers: []
Published this node as a provider for key
Local node is listening on /ip4/...
Local node is listening on /ip6/...
...
test message
Publish error: InsufficientPeers
It seems that it fails because it’s unable to find closest peers.
My code:
use async_std::io;
use futures::{prelude::*, select};
use libp2p::{
core::muxing::StreamMuxerBox,
gossipsub, identity,
kad::{
record::store::MemoryStore, GetProvidersOk, Kademlia, KademliaConfig, KademliaEvent,
QueryResult, RecordKey,
},
swarm::NetworkBehaviour,
swarm::{SwarmBuilder, SwarmEvent},
PeerId, Swarm, Transport,
};
use libp2p_quic as quic;
use std::collections::hash_map::DefaultHasher;
use std::error::Error;
use std::hash::{Hash, Hasher};
use std::time::Duration;
// We create a custom network behaviour that combines Gossipsub and Kademlia.
#[derive(NetworkBehaviour)]
struct MyBehaviour {
gossipsub: gossipsub::Behaviour,
kademlia: Kademlia<MemoryStore>,
}
#[async_std::main]
async fn main() -> Result<(), Box<dyn Error>> {
env_logger::init();
// Create a random PeerId
let local_peer = identity::Keypair::generate_ed25519();
let local_peer_id = PeerId::from(local_peer.public());
println!("Local peer id: {local_peer_id}");
let shared_dht_key =
RecordKey::new(&"12D3KooWD3mFq2ijumTBcnuG5jQh3qC7ChjLNwf5WaPjh3aAWJr9".as_bytes());
let quic_transport = quic::async_std::Transport::new(quic::Config::new(&local_peer));
let transport = quic_transport
.map(|output, _| match output {
(peer_id, muxer) => (peer_id, StreamMuxerBox::new(muxer)),
})
.boxed();
// To content-address message, we can take the hash of message and use it as an ID.
let message_id_fn = |message: &gossipsub::Message| {
let mut s = DefaultHasher::new();
message.data.hash(&mut s);
gossipsub::MessageId::from(s.finish().to_string())
};
// Set a custom gossipsub configuration
let gossipsub_config = gossipsub::ConfigBuilder::default()
.heartbeat_interval(Duration::from_secs(10)) // This is set to aid debugging by not cluttering the log space
.validation_mode(gossipsub::ValidationMode::Strict) // This sets the kind of message validation. The default is Strict (enforce message signing)
.message_id_fn(message_id_fn) // content-address messages. No two messages of the same content will be propagated.
.build()
.expect("Valid config");
// build a gossipsub network behaviour
let mut gossipsub = gossipsub::Behaviour::new(
gossipsub::MessageAuthenticity::Signed(local_peer),
gossipsub_config,
)
.expect("Correct configuration");
// Create a Gossipsub topic
let topic = gossipsub::IdentTopic::new("test-net");
// subscribes to our topic
gossipsub.subscribe(&topic)?;
// Create a Kademlia behaviour.
let mut kademlia = Kademlia::with_config(
local_peer_id,
MemoryStore::new(local_peer_id),
KademliaConfig::default(),
);
for peer in &BOOTNODES {
kademlia.add_address(&peer.parse()?, "/dnsaddr/bootstrap.libp2p.io".parse()?);
}
// Create a Swarm to manage peers and events
let mut swarm = {
let behaviour = MyBehaviour {
gossipsub,
kademlia,
};
SwarmBuilder::with_async_std_executor(transport, behaviour, local_peer_id).build()
};
// Read full lines from stdin
let mut stdin = io::BufReader::new(io::stdin()).lines().fuse();
// Listen on all interfaces and whatever port the OS assigns
swarm.listen_on("/ip4/0.0.0.0/udp/0/quic-v1".parse()?)?;
swarm.listen_on("/ip6/::0/udp/0/quic-v1".parse()?)?;
println!("Enter messages via STDIN and they will be sent to connected peers using Gossipsub");
// Kick it off
let kademlia = &mut swarm.behaviour_mut().kademlia;
kademlia.start_providing(shared_dht_key.clone())?;
kademlia.get_providers(shared_dht_key.clone());
loop {
select! {
line = stdin.select_next_some() => {
if let Err(e) = swarm
.behaviour_mut().gossipsub
.publish(topic.clone(), line.expect("Stdin not to close").as_bytes()) {
println!("Publish error: {e:?}");
// Let's try to fetch providers again.
swarm.behaviour_mut().kademlia.get_providers(shared_dht_key.clone());
}
},
event = swarm.select_next_some() => match event {
SwarmEvent::Behaviour(MyBehaviourEvent::Kademlia(kademlia_event)) =>
match kademlia_event {
KademliaEvent::OutboundQueryProgressed {
result: QueryResult::Bootstrap(result),
..
} => {
result?;
panic!("Unexpected bootstrap");
}
KademliaEvent::OutboundQueryProgressed {
result: QueryResult::GetProviders(Ok(GetProvidersOk::FoundProviders{ providers, .. })),
..
} => {
for peer_id in providers {
if peer_id != local_peer_id && !Swarm::is_connected(&swarm, &peer_id) {
println!("Kademlia discovered a new peer: {peer_id}");
// TODO: Kademlia might not be caching the address of the peer.
Swarm::dial(&mut swarm, peer_id)?;
swarm.behaviour_mut().gossipsub.add_explicit_peer(&peer_id);
}
}
},
KademliaEvent::OutboundQueryProgressed {
result: QueryResult::GetProviders(Ok(GetProvidersOk::FinishedWithNoAdditionalRecord{ closest_peers })),
..
} => {
println!("Finished getting providers, waiting for other peers to connect; closest peers: {closest_peers:?}");
},
KademliaEvent::OutboundQueryProgressed {
result: QueryResult::StartProviding(add_provider),
..
} => {
add_provider?;
println!("Published this node as a provider for key");
},
ev => {
println!("Other Kademlia event: {ev:?}");
},
},
SwarmEvent::Behaviour(MyBehaviourEvent::Gossipsub(gossipsub::Event::Message {
propagation_source: peer_id,
message_id: id,
message,
})) => println!("Got message: '{}' with id: {id} from peer: {peer_id}", String::from_utf8_lossy(&message.data)),
SwarmEvent::NewListenAddr { address, .. } => {
println!("Local node is listening on {address}");
}
_ => {}
}
}
}
}
const BOOTNODES: [&str; 4] = [
"QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
"QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
"QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb",
"QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt",
];