Skip to content

Commit a82bbc7

Browse files
committed
feat: [wip] add relay/ping
1 parent 65d3a49 commit a82bbc7

7 files changed

Lines changed: 309 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ license = "Apache-2.0" # TODO(template) updat
2323
publish = false
2424

2525
[workspace.dependencies]
26+
anyhow = "*" # for testing purposes only
2627
axum = "0.8.6"
2728
chrono = { version = "0.4", features = ["serde"] }
2829
hex = { version = "^0.4.3" }

crates/charon-p2p/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ tokio.workspace = true
1818
[dev-dependencies]
1919
charon-testutil.workspace = true
2020
vise-exporter.workspace = true
21+
anyhow.workspace = true
2122

2223
[lints]
2324
workspace = true
25+
26+
[features]
27+
mdns = ["libp2p/mdns"]

crates/charon-p2p/examples/p2p.rs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#![allow(missing_docs)]
2+
3+
use std::net::Ipv4Addr;
4+
5+
use anyhow::Result;
6+
use charon_eth2::enr::{Record, with_ip_impl, with_tcp_impl, with_udp_impl};
7+
use charon_p2p::{
8+
config::P2PConfig,
9+
gater::ConnGater,
10+
peer::peer_id_from_key,
11+
p2p::{Node, NodeType, PlutoBehavior, PlutoBehaviorEvent},
12+
};
13+
use k256::elliptic_curve::rand_core::OsRng;
14+
use libp2p::{Multiaddr, futures::StreamExt, identify, swarm::SwarmEvent};
15+
use tokio::signal;
16+
17+
#[tokio::main]
18+
async fn main() -> Result<()> {
19+
let key = k256::SecretKey::random(&mut OsRng);
20+
let mut p2p: Node<PlutoBehavior> = Node::new(
21+
P2PConfig::default(),
22+
key.clone(),
23+
ConnGater,
24+
false,
25+
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+
};
71+
72+
// Extract IP and ports from ENR
73+
let ip = enr.ip().unwrap_or(Ipv4Addr::new(0, 0, 0, 0));
74+
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);
78+
79+
if enr.tcp().is_none() && enr.udp().is_none() {
80+
eprintln!("ENR missing both TCP and UDP ports");
81+
}
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());
85+
}
86+
Err(e) => {
87+
eprintln!("Failed to parse ENR: {} (error: {})", enr_str, e);
88+
}
89+
}
90+
}
91+
92+
loop {
93+
tokio::select! {
94+
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+
})) => {
101+
println!("Address observed {}", observed_addr);
102+
}
103+
SwarmEvent::Behaviour(PlutoBehaviorEvent::Mdns(libp2p::mdns::Event::Discovered(nodes))) => {
104+
for node in nodes {
105+
println!("Discovered node: {:?}", node);
106+
swarm.dial(node.1).unwrap();
107+
}
108+
}
109+
SwarmEvent::NewListenAddr { address, .. } => {
110+
println!("Local node is listening on {address}");
111+
}
112+
SwarmEvent::Behaviour(PlutoBehaviorEvent::Ping(ping_event)) => {
113+
println!("Got ping event: {:?}", ping_event);
114+
}
115+
SwarmEvent::IncomingConnection { connection_id, local_addr, send_back_addr } => {
116+
println!("Incoming connection (id={connection_id}) from {:?} (send on {:?})", local_addr, send_back_addr);
117+
}
118+
SwarmEvent::IncomingConnectionError {peer_id,connection_id,error, local_addr, send_back_addr } => {
119+
println!("Incoming connection (id={connection_id}) error from {:?} (send on {:?} to {:?}): {:?}", peer_id, local_addr, send_back_addr, error);
120+
}
121+
event => {
122+
println!("{:?}", event);
123+
}
124+
},
125+
_ = signal::ctrl_c() => {
126+
println!("\nReceived Ctrl+C, shutting down gracefully...");
127+
128+
// Perform cleanup
129+
let _ = swarm;
130+
drop(p2p);
131+
132+
println!("Shutdown complete");
133+
break;
134+
}
135+
}
136+
}
137+
138+
Ok(())
139+
}

crates/charon-p2p/src/gater.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
//! Gater is responsible for whitelisting / blacklisting peers
2+
3+
/// todo: Temporary empty
4+
pub struct ConnGater;

crates/charon-p2p/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@ pub mod config;
1616

1717
/// Metrics.
1818
pub mod metrics;
19+
20+
/// P2P.
21+
pub mod p2p;
22+
23+
/// Gater
24+
pub mod gater;

crates/charon-p2p/src/p2p.rs

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#![allow(missing_docs)]
2+
#![allow(dead_code)]
3+
#![allow(unused)]
4+
5+
//! P2P core concepts
6+
7+
use std::{sync::Once, time::Duration};
8+
9+
use libp2p::{
10+
Swarm, SwarmBuilder, identify,
11+
identity::Keypair,
12+
noise, ping, relay,
13+
swarm::{NetworkBehaviour, SwarmEvent},
14+
tcp, yamux,
15+
};
16+
17+
use libp2p::mdns;
18+
19+
use crate::{config::P2PConfig, gater::ConnGater};
20+
21+
pub enum NodeType {
22+
TCP,
23+
QUIC,
24+
}
25+
26+
#[derive(NetworkBehaviour)]
27+
pub struct PlutoBehavior {
28+
pub relay: relay::Behaviour,
29+
pub identify: identify::Behaviour,
30+
pub ping: ping::Behaviour,
31+
pub mdns: mdns::tokio::Behaviour,
32+
}
33+
34+
pub trait LoopBehavior {
35+
fn spawn_loop(&self) -> impl Future<Output = ()>;
36+
}
37+
38+
impl LoopBehavior for Node<PlutoBehavior> {
39+
async fn spawn_loop(&self) {}
40+
}
41+
42+
impl PlutoBehavior {
43+
pub fn new(key: &Keypair) -> Self {
44+
Self {
45+
relay: relay::Behaviour::new(key.public().to_peer_id(), Default::default()),
46+
identify: identify::Behaviour::new(identify::Config::new(
47+
"/pluto/1.0.0-alpha".into(),
48+
key.public(),
49+
)),
50+
ping: ping::Behaviour::new(
51+
ping::Config::new()
52+
.with_interval(Duration::from_secs(1))
53+
.with_timeout(Duration::from_secs(2)),
54+
),
55+
mdns: mdns::tokio::Behaviour::new(mdns::Config::default(), key.public().to_peer_id())
56+
.unwrap(),
57+
}
58+
}
59+
}
60+
61+
pub struct Node<B: NetworkBehaviour> {
62+
pub swarm: Swarm<B>,
63+
pub callbacks: Vec<Box<dyn Fn(&SwarmEvent<B>) + Send + Sync>>,
64+
}
65+
66+
impl<B: NetworkBehaviour> Node<B> {
67+
pub fn new<F>(
68+
cfg: P2PConfig,
69+
key: k256::SecretKey,
70+
conn_gater: ConnGater,
71+
filter_private_addrs: bool,
72+
node_type: NodeType,
73+
behavior_fn: F,
74+
) -> Self
75+
where
76+
F: Fn(&Keypair) -> B,
77+
{
78+
match node_type {
79+
NodeType::TCP => {
80+
Self::new_with_tcp(cfg, key, conn_gater, filter_private_addrs, behavior_fn)
81+
}
82+
NodeType::QUIC => {
83+
Self::new_with_quic(cfg, key, conn_gater, filter_private_addrs, behavior_fn)
84+
}
85+
}
86+
}
87+
88+
pub fn new_with_quic<F>(
89+
cfg: P2PConfig,
90+
key: k256::SecretKey,
91+
conn_gater: ConnGater,
92+
filter_private_addrs: bool,
93+
behavior_fn: F,
94+
) -> Self
95+
where
96+
F: Fn(&Keypair) -> B,
97+
{
98+
let mut der = key.to_sec1_der().unwrap();
99+
let keypair = Keypair::secp256k1_from_der(&mut der).unwrap();
100+
101+
let mut swarm = SwarmBuilder::with_existing_identity(keypair.clone())
102+
.with_tokio()
103+
.with_tcp(
104+
tcp::Config::default(),
105+
noise::Config::new,
106+
yamux::Config::default,
107+
)
108+
.unwrap()
109+
.with_quic()
110+
.with_behaviour(behavior_fn)
111+
.unwrap()
112+
.with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(300)))
113+
.build();
114+
115+
Node {
116+
swarm,
117+
callbacks: vec![],
118+
}
119+
}
120+
121+
pub fn new_with_tcp<F>(
122+
cfg: P2PConfig,
123+
key: k256::SecretKey,
124+
conn_gater: ConnGater,
125+
filter_private_addrs: bool,
126+
behavior_fn: F,
127+
) -> Self
128+
where
129+
F: Fn(&Keypair) -> B,
130+
{
131+
let mut der = key.to_sec1_der().unwrap();
132+
let keypair = Keypair::secp256k1_from_der(&mut der).unwrap();
133+
134+
let mut swarm = SwarmBuilder::with_existing_identity(keypair.clone())
135+
.with_tokio()
136+
.with_tcp(
137+
tcp::Config::default(),
138+
noise::Config::new,
139+
yamux::Config::default,
140+
)
141+
.unwrap()
142+
.with_behaviour(behavior_fn)
143+
.unwrap()
144+
.with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(300)))
145+
.build();
146+
147+
Node {
148+
swarm,
149+
callbacks: vec![],
150+
}
151+
}
152+
153+
pub fn add_callback(&mut self, callback: Box<dyn Fn(&SwarmEvent<B>) + Send + Sync>) {}
154+
}

0 commit comments

Comments
 (0)