Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

examples/gossipsub-chat: Add mDNS peer discovery #2996

Merged
merged 6 commits into from
Oct 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ A set of examples showcasing how to use rust-libp2p.

- [Gossipsub chat](./gossipsub-chat.rs)

Same as the chat example but using the Gossipsub protocol.
Same as the chat example but using mDNS and the Gossipsub protocol.

- [Tokio based chat](./chat-tokio.rs)

Expand Down
166 changes: 82 additions & 84 deletions examples/gossipsub-chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,141 +18,139 @@
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

//! A basic chat application with logs demonstrating libp2p and the gossipsub protocol.
//! A basic chat application with logs demonstrating libp2p and the gossipsub protocol
//! combined with mDNS for the discovery of peers to gossip with.
//!
//! Using two terminal windows, start two instances. Type a message in either terminal and hit return: the
//! message is sent and printed in the other terminal. Close with Ctrl-c.
//!
//! You can of course open more terminal windows and add more participants.
//! Dialing any of the other peers will propagate the new participant to all
//! chat members and everyone will receive all messages.
//!
//! In order to get the nodes to connect, take note of the listening addresses of the first
//! instance and start the second with one of the addresses as the first argument. In the first
//! terminal window, run:
//! Using two terminal windows, start two instances, typing the following in each:
//!
//! ```sh
//! cargo run --example gossipsub-chat --features=full
//! ```
//!
//! It will print the [`PeerId`] and the listening addresses, e.g. `Listening on
//! "/ip4/0.0.0.0/tcp/24915"`
//!
//! In the second terminal window, start a new instance of the example with:
//! Mutual mDNS discovery may take a few seconds. When each peer does discover the other
//! it will print a message like:
//!
//! ```sh
//! cargo run --example gossipsub-chat --features=full -- /ip4/127.0.0.1/tcp/24915
//! mDNS discovered a new peer: {peerId}
//! ```
//!
//! The two nodes should then connect.
//! Type a message and hit return: the message is sent and printed in the other terminal.
//! Close with Ctrl-c.
//!
//! You can open more terminal windows and add more peers using the same line above.
//!
//! Once an additional peer is mDNS discovered it can participate in the conversation
//! and all peers will receive messages sent from it.
//!
//! If a participant exits (Control-C or otherwise) the other peers will receive an mDNS expired
//! event and remove the expired peer from the list of known peers.

use async_std::io;
use env_logger::{Builder, Env};
use futures::{prelude::*, select};
use libp2p::gossipsub::MessageId;
use libp2p::gossipsub::{
GossipsubEvent, GossipsubMessage, IdentTopic as Topic, MessageAuthenticity, ValidationMode,
Gossipsub, GossipsubEvent, GossipsubMessage, IdentTopic as Topic, MessageAuthenticity,
ValidationMode,
};
use libp2p::{
gossipsub, identity,
mdns::{Mdns, MdnsConfig, MdnsEvent},
swarm::SwarmEvent,
NetworkBehaviour, PeerId, Swarm,
};
use libp2p::{gossipsub, identity, swarm::SwarmEvent, Multiaddr, PeerId};
use std::collections::hash_map::DefaultHasher;
use std::error::Error;
use std::hash::{Hash, Hasher};
use std::time::Duration;

#[async_std::main]
async fn main() -> Result<(), Box<dyn Error>> {
Builder::from_env(Env::default().default_filter_or("info")).init();

// Create a random PeerId
let local_key = identity::Keypair::generate_ed25519();
let local_peer_id = PeerId::from(local_key.public());
println!("Local peer id: {:?}", local_peer_id);
println!("Local peer id: {}", local_peer_id);

// Set up an encrypted TCP Transport over the Mplex and Yamux protocols
// Set up an encrypted DNS-enabled TCP Transport over the Mplex protocol.
let transport = libp2p::development_transport(local_key.clone()).await?;

// Create a Gossipsub topic
let topic = Topic::new("test-net");
// We create a custom network behaviour that combines Gossipsub and Mdns.
#[derive(NetworkBehaviour)]
struct MyBehaviour {
gossipsub: Gossipsub,
mdns: Mdns,
}

// Create a Swarm to manage peers and events
let mut swarm = {
// To content-address message, we can take the hash of message and use it as an ID.
let message_id_fn = |message: &GossipsubMessage| {
let mut s = DefaultHasher::new();
message.data.hash(&mut s);
MessageId::from(s.finish().to_string())
};
// To content-address message, we can take the hash of message and use it as an ID.
let message_id_fn = |message: &GossipsubMessage| {
let mut s = DefaultHasher::new();
message.data.hash(&mut s);
MessageId::from(s.finish().to_string())
};

// Set a custom gossipsub
let gossipsub_config = gossipsub::GossipsubConfigBuilder::default()
.heartbeat_interval(Duration::from_secs(10)) // This is set to aid debugging by not cluttering the log space
.validation_mode(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::Gossipsub =
gossipsub::Gossipsub::new(MessageAuthenticity::Signed(local_key), gossipsub_config)
.expect("Correct configuration");
// Set a custom gossipsub configuration
let gossipsub_config = gossipsub::GossipsubConfigBuilder::default()
.heartbeat_interval(Duration::from_secs(10)) // This is set to aid debugging by not cluttering the log space
.validation_mode(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");

// subscribes to our topic
gossipsub.subscribe(&topic).unwrap();
// build a gossipsub network behaviour
let mut gossipsub = Gossipsub::new(MessageAuthenticity::Signed(local_key), gossipsub_config)
.expect("Correct configuration");

// add an explicit peer if one was provided
if let Some(explicit) = std::env::args().nth(2) {
match explicit.parse() {
Ok(id) => gossipsub.add_explicit_peer(&id),
Err(err) => println!("Failed to parse explicit peer id: {:?}", err),
}
}

// build the swarm
libp2p::Swarm::new(transport, gossipsub, local_peer_id)
};
// Create a Gossipsub topic
let topic = Topic::new("test-net");

// Listen on all interfaces and whatever port the OS assigns
swarm
.listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap())
.unwrap();
// subscribes to our topic
gossipsub.subscribe(&topic)?;

// Reach out to another node if specified
if let Some(to_dial) = std::env::args().nth(1) {
let address: Multiaddr = to_dial.parse().expect("User to provide valid address.");
match swarm.dial(address.clone()) {
Ok(_) => println!("Dialed {:?}", address),
Err(e) => println!("Dial {:?} failed: {:?}", address, e),
};
}
// Create a Swarm to manage peers and events
let mut swarm = {
let mdns = Mdns::new(MdnsConfig::default())?;
let behaviour = MyBehaviour { gossipsub, mdns };
Swarm::new(transport, behaviour, local_peer_id)
};

// 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/tcp/0".parse()?)?;

println!("Enter messages via STDIN and they will be sent to connected peers using Gossipsub");

// Kick it off
loop {
select! {
line = stdin.select_next_some() => {
if let Err(e) = swarm
.behaviour_mut()
.publish(topic.clone(), line.expect("Stdin not to close").as_bytes())
{
.behaviour_mut().gossipsub
.publish(topic.clone(), line.expect("Stdin not to close").as_bytes()) {
println!("Publish error: {:?}", e);
}
},
event = swarm.select_next_some() => match event {
SwarmEvent::Behaviour(GossipsubEvent::Message {
SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(MdnsEvent::Discovered(list))) => {
thomaseizinger marked this conversation as resolved.
Show resolved Hide resolved
for (peer_id, _multiaddr) in list {
println!("mDNS discovered a new peer: {}", peer_id);
swarm.behaviour_mut().gossipsub.add_explicit_peer(&peer_id);
}
},
SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(MdnsEvent::Expired(list))) => {
for (peer_id, _multiaddr) in list {
println!("mDNS discover peer has expired: {}", peer_id);
swarm.behaviour_mut().gossipsub.remove_explicit_peer(&peer_id);
}
},
SwarmEvent::Behaviour(MyBehaviourEvent::Gossipsub(GossipsubEvent::Message {
propagation_source: peer_id,
message_id: id,
message,
}) => println!(
"Got message: {} with id: {} from peer: {:?}",
String::from_utf8_lossy(&message.data),
id,
peer_id
),
SwarmEvent::NewListenAddr { address, .. } => {
println!("Listening on {:?}", address);
}
})) => println!(
"Got message: '{}' with id: {id} from peer: {peer_id}",
String::from_utf8_lossy(&message.data),
),
_ => {}
}
}
Expand Down