|
| 1 | +//! Peerinfo example |
| 2 | +//! |
| 3 | +//! This example demonstrates the peerinfo protocol by creating two nodes |
| 4 | +//! that exchange peer information with each other using mDNS auto-discovery. |
| 5 | +//! |
| 6 | +//! Run with: |
| 7 | +//! ```sh |
| 8 | +//! cargo run --example peerinfo -p charon-peerinfo |
| 9 | +//! ``` |
| 10 | +//! |
| 11 | +//! Run two instances on different ports - they will auto-discover each other: |
| 12 | +//! |
| 13 | +//! Terminal 1: `cargo run --example peerinfo -p charon-peerinfo -- --port 4001` |
| 14 | +//! Terminal 2: `cargo run --example peerinfo -p charon-peerinfo -- --port 4002` |
| 15 | +#![allow(missing_docs)] |
| 16 | +use std::time::Duration; |
| 17 | + |
| 18 | +use charon_peerinfo::{Behaviour, Config, Event, LocalPeerInfo}; |
| 19 | +use clap::Parser; |
| 20 | +use libp2p::{ |
| 21 | + Multiaddr, Swarm, SwarmBuilder, |
| 22 | + futures::StreamExt, |
| 23 | + identify, mdns, noise, ping, |
| 24 | + swarm::{NetworkBehaviour, SwarmEvent}, |
| 25 | + tcp, yamux, |
| 26 | +}; |
| 27 | +use tokio::signal; |
| 28 | +use tracing_subscriber::EnvFilter; |
| 29 | + |
| 30 | +/// Command line arguments |
| 31 | +#[derive(Debug, Parser)] |
| 32 | +#[command(name = "peerinfo-example")] |
| 33 | +#[command(about = "Demonstrates the peerinfo protocol with mDNS discovery")] |
| 34 | +pub struct Args { |
| 35 | + /// The port to listen on |
| 36 | + #[arg(short, long, default_value = "4001")] |
| 37 | + pub port: u16, |
| 38 | + |
| 39 | + /// Optional address to dial |
| 40 | + #[arg(short, long)] |
| 41 | + pub dial: Option<Multiaddr>, |
| 42 | + |
| 43 | + /// Nickname for this node |
| 44 | + #[arg(short, long, default_value = "example-node")] |
| 45 | + pub nickname: String, |
| 46 | + |
| 47 | + /// Peer info exchange interval in seconds |
| 48 | + #[arg(short, long, default_value = "5")] |
| 49 | + pub interval: u64, |
| 50 | +} |
| 51 | + |
| 52 | +/// Combined behaviour with peerinfo, identify, ping, and mdns |
| 53 | +#[derive(NetworkBehaviour)] |
| 54 | +pub struct CombinedBehaviour { |
| 55 | + pub peer_info: Behaviour, |
| 56 | + pub identify: identify::Behaviour, |
| 57 | + pub ping: ping::Behaviour, |
| 58 | + pub mdns: mdns::tokio::Behaviour, |
| 59 | +} |
| 60 | + |
| 61 | +pub type CombinedEvent = CombinedBehaviourEvent; |
| 62 | + |
| 63 | +fn build_swarm(peerinfo_config: Config) -> anyhow::Result<Swarm<CombinedBehaviour>> { |
| 64 | + let swarm = SwarmBuilder::with_new_identity() |
| 65 | + .with_tokio() |
| 66 | + .with_tcp( |
| 67 | + tcp::Config::default(), |
| 68 | + noise::Config::new, |
| 69 | + yamux::Config::default, |
| 70 | + )? |
| 71 | + .with_behaviour(|key| { |
| 72 | + Ok(CombinedBehaviour { |
| 73 | + peer_info: Behaviour::new(peerinfo_config), |
| 74 | + identify: identify::Behaviour::new(identify::Config::new( |
| 75 | + "/peerinfo-example/1.0.0".to_string(), |
| 76 | + key.public(), |
| 77 | + )), |
| 78 | + ping: ping::Behaviour::new( |
| 79 | + ping::Config::new() |
| 80 | + .with_interval(Duration::from_secs(15)) |
| 81 | + .with_timeout(Duration::from_secs(10)), |
| 82 | + ), |
| 83 | + mdns: mdns::tokio::Behaviour::new( |
| 84 | + mdns::Config::default(), |
| 85 | + key.public().to_peer_id(), |
| 86 | + )?, |
| 87 | + }) |
| 88 | + })? |
| 89 | + .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(300))) |
| 90 | + .build(); |
| 91 | + |
| 92 | + Ok(swarm) |
| 93 | +} |
| 94 | + |
| 95 | +fn handle_event(event: SwarmEvent<CombinedEvent>, swarm: &mut Swarm<CombinedBehaviour>) { |
| 96 | + match event { |
| 97 | + SwarmEvent::NewListenAddr { address, .. } => { |
| 98 | + tracing::info!("Listening on {address}"); |
| 99 | + } |
| 100 | + SwarmEvent::ConnectionEstablished { |
| 101 | + peer_id, endpoint, .. |
| 102 | + } => { |
| 103 | + tracing::info!( |
| 104 | + "Connection established with {peer_id} via {}", |
| 105 | + endpoint.get_remote_address() |
| 106 | + ); |
| 107 | + } |
| 108 | + SwarmEvent::ConnectionClosed { peer_id, cause, .. } => { |
| 109 | + tracing::info!("Connection closed with {peer_id}: {cause:?}"); |
| 110 | + } |
| 111 | + SwarmEvent::Behaviour(CombinedEvent::PeerInfo(Event::Received { peer, info, .. })) => { |
| 112 | + tracing::info!( |
| 113 | + "📥 Received PeerInfo from {peer}:\n\ |
| 114 | + │ Version: {}\n\ |
| 115 | + │ Git Hash: {}\n\ |
| 116 | + │ Nickname: {}\n\ |
| 117 | + │ Builder API: {}\n\ |
| 118 | + │ Lock Hash: {:?}", |
| 119 | + info.charon_version, |
| 120 | + info.git_hash, |
| 121 | + info.nickname, |
| 122 | + info.builder_api_enabled, |
| 123 | + hex::encode(&info.lock_hash), |
| 124 | + ); |
| 125 | + } |
| 126 | + SwarmEvent::Behaviour(CombinedEvent::PeerInfo(Event::Error { peer, error, .. })) => { |
| 127 | + tracing::warn!("PeerInfo error with {peer}: {error}"); |
| 128 | + } |
| 129 | + SwarmEvent::Behaviour(CombinedEvent::Identify(identify::Event::Received { |
| 130 | + peer_id, |
| 131 | + info, |
| 132 | + .. |
| 133 | + })) => { |
| 134 | + tracing::debug!( |
| 135 | + "Identify received from {peer_id}: {} {}", |
| 136 | + info.protocol_version, |
| 137 | + info.agent_version |
| 138 | + ); |
| 139 | + } |
| 140 | + SwarmEvent::Behaviour(CombinedEvent::Ping(ping::Event { peer, result, .. })) => { |
| 141 | + match result { |
| 142 | + Ok(rtt) => tracing::debug!("Ping to {peer}: {rtt:?}"), |
| 143 | + Err(e) => tracing::debug!("Ping to {peer} failed: {e}"), |
| 144 | + } |
| 145 | + } |
| 146 | + SwarmEvent::Behaviour(CombinedEvent::Mdns(mdns::Event::Discovered(peers))) => { |
| 147 | + for (peer_id, addr) in peers { |
| 148 | + tracing::info!("🔍 mDNS discovered peer {peer_id} at {addr}"); |
| 149 | + if let Err(e) = swarm.dial(addr) { |
| 150 | + tracing::warn!("Failed to dial discovered peer: {e}"); |
| 151 | + } |
| 152 | + } |
| 153 | + } |
| 154 | + SwarmEvent::Behaviour(CombinedEvent::Mdns(mdns::Event::Expired(peers))) => { |
| 155 | + for (peer_id, addr) in peers { |
| 156 | + tracing::debug!("mDNS peer expired: {peer_id} at {addr}"); |
| 157 | + } |
| 158 | + } |
| 159 | + SwarmEvent::IncomingConnection { local_addr, .. } => { |
| 160 | + tracing::debug!("Incoming connection on {local_addr}"); |
| 161 | + } |
| 162 | + _ => {} |
| 163 | + } |
| 164 | +} |
| 165 | + |
| 166 | +#[tokio::main] |
| 167 | +async fn main() -> anyhow::Result<()> { |
| 168 | + // Initialize logging |
| 169 | + tracing_subscriber::fmt() |
| 170 | + .with_env_filter(EnvFilter::from_default_env().add_directive("debug".parse()?)) |
| 171 | + .init(); |
| 172 | + |
| 173 | + let args = Args::parse(); |
| 174 | + |
| 175 | + // Create local peer info |
| 176 | + let local_info = LocalPeerInfo::new( |
| 177 | + "v1.0.0", // charon_version |
| 178 | + vec![0xDE, 0xAD, 0xBE, 0xEF], // lock_hash (example) |
| 179 | + "abc1234", // git_hash |
| 180 | + false, // builder_api_enabled |
| 181 | + &args.nickname, // nickname |
| 182 | + ); |
| 183 | + |
| 184 | + // Create peerinfo config with custom interval for demonstration |
| 185 | + let peerinfo_config = Config::new(local_info) |
| 186 | + .with_interval(Duration::from_secs(args.interval)) |
| 187 | + .with_timeout(Duration::from_secs(10)); |
| 188 | + |
| 189 | + let mut swarm = build_swarm(peerinfo_config)?; |
| 190 | + |
| 191 | + let local_peer_id = *swarm.local_peer_id(); |
| 192 | + tracing::info!("Local peer id: {local_peer_id}"); |
| 193 | + tracing::info!("mDNS auto-discovery enabled"); |
| 194 | + |
| 195 | + // Listen on the specified port |
| 196 | + let listen_addr: Multiaddr = format!("/ip4/0.0.0.0/tcp/{}", args.port).parse()?; |
| 197 | + swarm.listen_on(listen_addr)?; |
| 198 | + |
| 199 | + // Dial the specified address if provided |
| 200 | + if let Some(dial_addr) = &args.dial { |
| 201 | + tracing::info!("Dialing {dial_addr}"); |
| 202 | + swarm.dial(dial_addr.clone())?; |
| 203 | + } |
| 204 | + |
| 205 | + tracing::info!( |
| 206 | + "Peerinfo example started with nickname '{}', interval {}s", |
| 207 | + args.nickname, |
| 208 | + args.interval |
| 209 | + ); |
| 210 | + tracing::info!("Press Ctrl+C to exit"); |
| 211 | + |
| 212 | + // Main event loop |
| 213 | + loop { |
| 214 | + tokio::select! { |
| 215 | + event = swarm.select_next_some() => handle_event(event, &mut swarm), |
| 216 | + _ = signal::ctrl_c() => { |
| 217 | + tracing::info!("Received Ctrl+C, shutting down..."); |
| 218 | + break; |
| 219 | + } |
| 220 | + } |
| 221 | + } |
| 222 | + |
| 223 | + Ok(()) |
| 224 | +} |
0 commit comments