Skip to content

Commit

Permalink
examples/gossipsub-chat: Add mDNS peer discovery (#2996)
Browse files Browse the repository at this point in the history
Co-authored-by: João Oliveira <[email protected]>
Co-authored-by: Max Inden <[email protected]>
  • Loading branch information
3 people authored Oct 26, 2022
1 parent 437b387 commit 5bce6ed
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 85 deletions.
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))) => {
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

0 comments on commit 5bce6ed

Please sign in to comment.