|
1 | | -#![allow(missing_docs)] |
2 | | - |
3 | | -use std::net::Ipv4Addr; |
| 1 | +//! P2P example |
| 2 | +//! |
| 3 | +//! This example creates a Pluto P2P node and connects to a relay. |
| 4 | +//! Also, it discovers other Pluto nodes using mDNS (requires the `mdns` |
| 5 | +//! feature). |
4 | 6 |
|
5 | 7 | use anyhow::Result; |
6 | | -use charon_eth2::enr::{Record, with_ip_impl, with_tcp_impl, with_udp_impl}; |
| 8 | +use charon_eth2::enr::Record; |
7 | 9 | use charon_p2p::{ |
| 10 | + behaviours::pluto::{PlutoBehaviourEvent}, |
| 11 | + behaviours::pluto_mdns::{PlutoMdnsBehaviour, PlutoMdnsBehaviourEvent}, |
8 | 12 | config::P2PConfig, |
9 | 13 | gater::ConnGater, |
10 | | - peer::peer_id_from_key, |
11 | | - p2p::{Node, NodeType, PlutoBehavior, PlutoBehaviorEvent}, |
| 14 | + p2p::{Node, NodeType}, |
12 | 15 | }; |
| 16 | +use clap::Parser; |
13 | 17 | use k256::elliptic_curve::rand_core::OsRng; |
14 | | -use libp2p::{Multiaddr, futures::StreamExt, identify, swarm::SwarmEvent}; |
| 18 | +use libp2p::{Multiaddr, futures::StreamExt, identify, multiaddr::Protocol, swarm::SwarmEvent}; |
15 | 19 | use tokio::signal; |
16 | 20 |
|
| 21 | +/// Command line arguments |
| 22 | +#[derive(Debug, Parser)] |
| 23 | +pub struct Args { |
| 24 | + /// The port to listen on |
| 25 | + #[arg(short, long, default_value = "1050")] |
| 26 | + pub port: u16, |
| 27 | + /// The ENRs to listen on |
| 28 | + #[arg(short, long)] |
| 29 | + pub enrs: Vec<String>, |
| 30 | + /// The relay URL to dial |
| 31 | + #[arg(short, long)] |
| 32 | + pub relay_url: Option<Multiaddr>, |
| 33 | +} |
| 34 | + |
17 | 35 | #[tokio::main] |
18 | 36 | async fn main() -> Result<()> { |
19 | 37 | let key = k256::SecretKey::random(&mut OsRng); |
20 | | - let mut p2p: Node<PlutoBehavior> = Node::new( |
| 38 | + let mut p2p: Node<_> = Node::new( |
21 | 39 | P2PConfig::default(), |
22 | 40 | key.clone(), |
23 | 41 | ConnGater, |
24 | 42 | false, |
25 | 43 | NodeType::QUIC, |
26 | | - PlutoBehavior::new, |
27 | | - ); |
28 | | - |
29 | | - let swarm = &mut p2p.swarm; |
30 | | - |
31 | | - // Get port from environment variable or default to 1050 |
32 | | - let port = std::env::var("PORT") |
33 | | - .ok() |
34 | | - .and_then(|p| p.parse::<u16>().ok()) |
35 | | - .unwrap_or(1050); |
36 | | - |
37 | | - let enr = Record::new( |
38 | | - key.clone(), |
39 | | - vec![ |
40 | | - with_tcp_impl(port), |
41 | | - with_udp_impl(port), |
42 | | - with_ip_impl(Ipv4Addr::new(0, 0, 0, 0)), |
43 | | - ], |
44 | | - ) |
45 | | - .unwrap(); |
46 | | - |
47 | | - println!("ENR: {}", enr); |
48 | | - |
49 | | - swarm.listen_on(format!("/ip4/0.0.0.0/udp/{}/quic-v1", port).parse()?)?; |
50 | | - swarm.listen_on(format!("/ip4/0.0.0.0/tcp/{}", port).parse()?)?; |
51 | | - |
52 | | - // Fetch peers from CLI arguments (ENR strings) |
53 | | - // Usage: cargo run --example p2p -- <enr1> <enr2> ... |
54 | | - for enr_str in std::env::args().skip(1) { |
55 | | - match Record::try_from(enr_str.as_str()) { |
56 | | - Ok(enr) => { |
57 | | - println!("Adding peer: {:?}", enr); |
58 | | - // Extract public key and convert to PeerId |
59 | | - let Some(public_key) = enr.public_key else { |
60 | | - eprintln!("ENR missing public key"); |
61 | | - continue; |
62 | | - }; |
63 | | - |
64 | | - let peer_id = match peer_id_from_key(public_key) { |
65 | | - Ok(peer_id) => peer_id, |
66 | | - Err(e) => { |
67 | | - eprintln!("Failed to convert ENR public key to PeerId: {}", e); |
68 | | - continue; |
69 | | - } |
70 | | - }; |
| 44 | + PlutoMdnsBehaviour::new, |
| 45 | + )?; |
71 | 46 |
|
72 | | - // Extract IP and ports from ENR |
73 | | - let ip = enr.ip().unwrap_or(Ipv4Addr::new(0, 0, 0, 0)); |
| 47 | + let args = Args::parse(); |
74 | 48 |
|
75 | | - // Try to add TCP address if available |
76 | | - let tcp_port = enr.tcp().unwrap_or(3610); |
77 | | - let udp_port = enr.udp().unwrap_or(3610); |
| 49 | + let swarm = &mut p2p.swarm; |
78 | 50 |
|
79 | | - if enr.tcp().is_none() && enr.udp().is_none() { |
80 | | - eprintln!("ENR missing both TCP and UDP ports"); |
| 51 | + let enr = Record::new(key.clone(), vec![])?; |
| 52 | + |
| 53 | + if let Some(relay_url) = &args.relay_url { |
| 54 | + swarm.dial(relay_url.clone())?; |
| 55 | + println!("Dialed relay"); |
| 56 | + let mut learned_observed_addr = false; |
| 57 | + let mut told_relay_observed_addr = false; |
| 58 | + |
| 59 | + loop { |
| 60 | + match swarm |
| 61 | + .next() |
| 62 | + .await |
| 63 | + .ok_or(anyhow::anyhow!("Swarm event is None"))? |
| 64 | + { |
| 65 | + SwarmEvent::NewListenAddr { .. } => {} |
| 66 | + SwarmEvent::Dialing { .. } => {} |
| 67 | + SwarmEvent::ConnectionEstablished { .. } => {} |
| 68 | + SwarmEvent::Behaviour(PlutoMdnsBehaviourEvent::Pluto( |
| 69 | + PlutoBehaviourEvent::Ping(_), |
| 70 | + )) => {} |
| 71 | + SwarmEvent::Behaviour(PlutoMdnsBehaviourEvent::Pluto( |
| 72 | + PlutoBehaviourEvent::Identify(identify::Event::Sent { .. }), |
| 73 | + )) => { |
| 74 | + println!("Told relay its public address"); |
| 75 | + told_relay_observed_addr = true; |
81 | 76 | } |
82 | | - |
83 | | - swarm.add_peer_address(peer_id, format!("/ip4/{}/udp/{}", ip, udp_port).parse().unwrap()); |
84 | | - swarm.add_peer_address(peer_id, format!("/ip4/{}/tcp/{}", ip, tcp_port).parse().unwrap()); |
| 77 | + SwarmEvent::Behaviour(PlutoMdnsBehaviourEvent::Pluto( |
| 78 | + PlutoBehaviourEvent::Identify(identify::Event::Received { |
| 79 | + info: identify::Info { observed_addr, .. }, |
| 80 | + .. |
| 81 | + }), |
| 82 | + )) => { |
| 83 | + println!("Relay told us our observed address: {}", observed_addr); |
| 84 | + learned_observed_addr = true; |
| 85 | + } |
| 86 | + event => panic!("{event:?}"), |
85 | 87 | } |
86 | | - Err(e) => { |
87 | | - eprintln!("Failed to parse ENR: {} (error: {})", enr_str, e); |
| 88 | + if learned_observed_addr && told_relay_observed_addr { |
| 89 | + break; |
88 | 90 | } |
89 | 91 | } |
90 | 92 | } |
91 | 93 |
|
| 94 | + println!("ENR: {}", enr); |
| 95 | + |
| 96 | + swarm.listen_on(format!("/ip4/0.0.0.0/udp/{}/quic-v1", args.port).parse()?)?; |
| 97 | + swarm.listen_on(format!("/ip4/0.0.0.0/tcp/{}", args.port).parse()?)?; |
| 98 | + if let Some(relay_url) = args.relay_url { |
| 99 | + swarm.listen_on(relay_url.with(Protocol::P2pCircuit))?; |
| 100 | + } |
| 101 | + |
92 | 102 | loop { |
93 | 103 | tokio::select! { |
94 | 104 | event = swarm.select_next_some() => match event { |
95 | | - SwarmEvent::Behaviour(PlutoBehaviorEvent::Relay(event)) => { |
96 | | - println!("Got relay event: {:?}", event); |
97 | | - }, |
98 | | - SwarmEvent::Behaviour(PlutoBehaviorEvent::Identify(identify::Event::Received { |
99 | | - info: identify::Info { observed_addr, ..}, .. |
100 | | - })) => { |
| 105 | + SwarmEvent::Behaviour(PlutoMdnsBehaviourEvent::Pluto(PlutoBehaviourEvent::Identify(identify::Event::Received { info: identify::Info { observed_addr, .. }, .. }))) => { |
| 106 | + swarm.add_external_address(observed_addr.clone()); |
101 | 107 | println!("Address observed {}", observed_addr); |
102 | 108 | } |
103 | | - SwarmEvent::Behaviour(PlutoBehaviorEvent::Mdns(libp2p::mdns::Event::Discovered(nodes))) => { |
| 109 | + SwarmEvent::Behaviour(PlutoMdnsBehaviourEvent::Pluto(PlutoBehaviourEvent::Relay(event))) => { |
| 110 | + println!("Got relay event: {:?}", event); |
| 111 | + }, |
| 112 | + SwarmEvent::Behaviour(PlutoMdnsBehaviourEvent::Mdns(libp2p::mdns::Event::Discovered(nodes))) => { |
104 | 113 | for node in nodes { |
105 | 114 | println!("Discovered node: {:?}", node); |
106 | | - swarm.dial(node.1).unwrap(); |
| 115 | + swarm.dial(node.1)?; |
107 | 116 | } |
108 | 117 | } |
109 | 118 | SwarmEvent::NewListenAddr { address, .. } => { |
110 | 119 | println!("Local node is listening on {address}"); |
111 | 120 | } |
112 | | - SwarmEvent::Behaviour(PlutoBehaviorEvent::Ping(ping_event)) => { |
| 121 | + SwarmEvent::Behaviour(PlutoMdnsBehaviourEvent::Pluto(PlutoBehaviourEvent::Ping(ping_event))) => { |
113 | 122 | println!("Got ping event: {:?}", ping_event); |
114 | 123 | } |
115 | 124 | SwarmEvent::IncomingConnection { connection_id, local_addr, send_back_addr } => { |
|
0 commit comments