From d605255fecd9ae31cdb2a5e5da8b6722fd3b4bd7 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 10 Oct 2023 08:55:14 +0200 Subject: [PATCH] feat(libp2p): add `SwarmBuilder` Introduce the new `libp2p::SwarmBuilder`. Users should use the new `libp2p::SwarmBuilder` instead of the now deprecated `libp2p::swarm::SwarmBuilder`. See `libp2p::SwarmBuilder` docs on how to use the new builder. Fixes #3657. Fixes #3563. Fixes #3179. Pull-Request: #4120. --- Cargo.lock | 8 +- Cargo.toml | 2 +- examples/autonat/src/bin/autonat_client.rs | 26 +- examples/autonat/src/bin/autonat_server.rs | 28 +- examples/browser-webrtc/src/lib.rs | 20 +- examples/browser-webrtc/src/main.rs | 32 +- examples/chat/src/main.rs | 88 ++--- examples/dcutr/Cargo.toml | 3 +- examples/dcutr/src/main.rs | 82 ++--- .../distributed-key-value-store/Cargo.toml | 2 +- .../distributed-key-value-store/src/main.rs | 46 +-- examples/file-sharing/src/network.rs | 35 +- examples/identify/src/main.rs | 39 +-- examples/ipfs-kad/Cargo.toml | 2 +- examples/ipfs-kad/src/main.rs | 57 ++-- examples/ipfs-private/src/main.rs | 134 +++----- examples/metrics/src/main.rs | 33 +- examples/ping/src/main.rs | 34 +- examples/relay-server/src/main.rs | 62 ++-- examples/rendezvous/src/bin/rzv-discover.rs | 35 +- examples/rendezvous/src/bin/rzv-identify.rs | 37 +- examples/rendezvous/src/bin/rzv-register.rs | 35 +- examples/rendezvous/src/main.rs | 39 ++- examples/upnp/Cargo.toml | 2 +- examples/upnp/src/main.rs | 32 +- interop-tests/Cargo.toml | 4 +- interop-tests/src/arch.rs | 307 +++++++++-------- interop-tests/src/lib.rs | 53 ++- libp2p/CHANGELOG.md | 5 + libp2p/Cargo.toml | 5 + libp2p/src/builder.rs | 322 ++++++++++++++++++ libp2p/src/builder/phase.rs | 134 ++++++++ libp2p/src/builder/phase/bandwidth_logging.rs | 70 ++++ libp2p/src/builder/phase/behaviour.rs | 90 +++++ libp2p/src/builder/phase/build.rs | 31 ++ libp2p/src/builder/phase/dns.rs | 68 ++++ libp2p/src/builder/phase/identity.rs | 21 ++ libp2p/src/builder/phase/other_transport.rs | 208 +++++++++++ libp2p/src/builder/phase/provider.rs | 46 +++ libp2p/src/builder/phase/quic.rs | 239 +++++++++++++ libp2p/src/builder/phase/relay.rs | 192 +++++++++++ libp2p/src/builder/phase/swarm.rs | 60 ++++ libp2p/src/builder/phase/tcp.rs | 226 ++++++++++++ libp2p/src/builder/phase/websocket.rs | 188 ++++++++++ libp2p/src/builder/select_security.rs | 113 ++++++ libp2p/src/lib.rs | 9 +- libp2p/src/tutorials/ping.rs | 160 ++++----- misc/server/src/main.rs | 59 ++-- protocols/dcutr/tests/lib.rs | 6 +- protocols/kad/src/behaviour/test.rs | 9 +- protocols/perf/src/bin/perf.rs | 16 +- protocols/relay/tests/lib.rs | 10 +- swarm-test/Cargo.toml | 2 +- swarm-test/src/lib.rs | 12 +- swarm/CHANGELOG.md | 9 + swarm/Cargo.toml | 4 +- swarm/src/connection.rs | 4 +- swarm/src/lib.rs | 293 ++++++++++++---- transports/pnet/tests/smoke.rs | 11 +- transports/tcp/src/provider/async_io.rs | 4 +- transports/tls/Cargo.toml | 2 +- transports/tls/tests/smoke.rs | 11 +- 62 files changed, 2973 insertions(+), 943 deletions(-) create mode 100644 libp2p/src/builder.rs create mode 100644 libp2p/src/builder/phase.rs create mode 100644 libp2p/src/builder/phase/bandwidth_logging.rs create mode 100644 libp2p/src/builder/phase/behaviour.rs create mode 100644 libp2p/src/builder/phase/build.rs create mode 100644 libp2p/src/builder/phase/dns.rs create mode 100644 libp2p/src/builder/phase/identity.rs create mode 100644 libp2p/src/builder/phase/other_transport.rs create mode 100644 libp2p/src/builder/phase/provider.rs create mode 100644 libp2p/src/builder/phase/quic.rs create mode 100644 libp2p/src/builder/phase/relay.rs create mode 100644 libp2p/src/builder/phase/swarm.rs create mode 100644 libp2p/src/builder/phase/tcp.rs create mode 100644 libp2p/src/builder/phase/websocket.rs create mode 100644 libp2p/src/builder/select_security.rs diff --git a/Cargo.lock b/Cargo.lock index ff024cbc90b..96f5b8e73c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1236,6 +1236,7 @@ dependencies = [ "futures-timer", "libp2p", "log", + "tokio", ] [[package]] @@ -2205,6 +2206,8 @@ dependencies = [ "instant", "libp2p", "libp2p-mplex", + "libp2p-noise", + "libp2p-tls", "libp2p-webrtc", "libp2p-webrtc-websys", "log", @@ -2386,6 +2389,7 @@ dependencies = [ "libp2p-mdns", "libp2p-memory-connection-limits", "libp2p-metrics", + "libp2p-mplex", "libp2p-noise", "libp2p-ping", "libp2p-plaintext", @@ -2406,6 +2410,8 @@ dependencies = [ "libp2p-yamux", "multiaddr", "pin-project", + "rw-stream-sink", + "thiserror", "tokio", ] @@ -3045,7 +3051,7 @@ dependencies = [ [[package]] name = "libp2p-swarm" -version = "0.43.5" +version = "0.43.6" dependencies = [ "async-std", "either", diff --git a/Cargo.toml b/Cargo.toml index a0871d4a912..5334d279963 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,7 +101,7 @@ libp2p-rendezvous = { version = "0.13.0", path = "protocols/rendezvous" } libp2p-upnp = { version = "0.1.1", path = "protocols/upnp" } libp2p-request-response = { version = "0.25.1", path = "protocols/request-response" } libp2p-server = { version = "0.12.3", path = "misc/server" } -libp2p-swarm = { version = "0.43.5", path = "swarm" } +libp2p-swarm = { version = "0.43.6", path = "swarm" } libp2p-swarm-derive = { version = "0.33.0", path = "swarm-derive" } libp2p-swarm-test = { version = "0.2.0", path = "swarm-test" } libp2p-tcp = { version = "0.40.0", path = "transports/tcp" } diff --git a/examples/autonat/src/bin/autonat_client.rs b/examples/autonat/src/bin/autonat_client.rs index eeb39ec52de..e92be18c279 100644 --- a/examples/autonat/src/bin/autonat_client.rs +++ b/examples/autonat/src/bin/autonat_client.rs @@ -21,10 +21,10 @@ #![doc = include_str!("../../README.md")] use clap::Parser; -use futures::prelude::*; +use futures::StreamExt; use libp2p::core::multiaddr::Protocol; -use libp2p::core::{upgrade::Version, Multiaddr, Transport}; -use libp2p::swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}; +use libp2p::core::Multiaddr; +use libp2p::swarm::{NetworkBehaviour, SwarmEvent}; use libp2p::{autonat, identify, identity, noise, tcp, yamux, PeerId}; use std::error::Error; use std::net::Ipv4Addr; @@ -49,18 +49,16 @@ async fn main() -> Result<(), Box> { let opt = Opt::parse(); - let local_key = identity::Keypair::generate_ed25519(); - let local_peer_id = PeerId::from(local_key.public()); + let mut swarm = libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_behaviour(|key| Behaviour::new(key.public()))? + .build(); - let transport = tcp::tokio::Transport::default() - .upgrade(Version::V1Lazy) - .authenticate(noise::Config::new(&local_key)?) - .multiplex(yamux::Config::default()) - .boxed(); - - let behaviour = Behaviour::new(local_key.public()); - - let mut swarm = SwarmBuilder::with_tokio_executor(transport, behaviour, local_peer_id).build(); swarm.listen_on( Multiaddr::empty() .with(Protocol::Ip4(Ipv4Addr::UNSPECIFIED)) diff --git a/examples/autonat/src/bin/autonat_server.rs b/examples/autonat/src/bin/autonat_server.rs index 065708f745f..00ccd641da8 100644 --- a/examples/autonat/src/bin/autonat_server.rs +++ b/examples/autonat/src/bin/autonat_server.rs @@ -21,10 +21,10 @@ #![doc = include_str!("../../README.md")] use clap::Parser; -use futures::prelude::*; -use libp2p::core::{multiaddr::Protocol, upgrade::Version, Multiaddr, Transport}; -use libp2p::swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}; -use libp2p::{autonat, identify, identity, noise, tcp, yamux, PeerId}; +use futures::StreamExt; +use libp2p::core::{multiaddr::Protocol, Multiaddr}; +use libp2p::swarm::{NetworkBehaviour, SwarmEvent}; +use libp2p::{autonat, identify, identity, noise, tcp, yamux}; use std::error::Error; use std::net::Ipv4Addr; @@ -41,18 +41,16 @@ async fn main() -> Result<(), Box> { let opt = Opt::parse(); - let local_key = identity::Keypair::generate_ed25519(); - let local_peer_id = PeerId::from(local_key.public()); + let mut swarm = libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_behaviour(|key| Behaviour::new(key.public()))? + .build(); - let transport = tcp::tokio::Transport::default() - .upgrade(Version::V1Lazy) - .authenticate(noise::Config::new(&local_key)?) - .multiplex(yamux::Config::default()) - .boxed(); - - let behaviour = Behaviour::new(local_key.public()); - - let mut swarm = SwarmBuilder::with_tokio_executor(transport, behaviour, local_peer_id).build(); swarm.listen_on( Multiaddr::empty() .with(Protocol::Ip4(Ipv4Addr::UNSPECIFIED)) diff --git a/examples/browser-webrtc/src/lib.rs b/examples/browser-webrtc/src/lib.rs index 1a9856dadcc..ef0fd1d0b58 100644 --- a/examples/browser-webrtc/src/lib.rs +++ b/examples/browser-webrtc/src/lib.rs @@ -6,6 +6,7 @@ use libp2p::core::Multiaddr; use libp2p::identity::{Keypair, PeerId}; use libp2p::ping; use libp2p::swarm::{keep_alive, NetworkBehaviour, SwarmBuilder, SwarmEvent}; +use libp2p::webrtc_websys; use std::convert::From; use std::io; use wasm_bindgen::prelude::*; @@ -18,19 +19,16 @@ pub async fn run(libp2p_endpoint: String) -> Result<(), JsError> { let body = Body::from_current_window()?; body.append_p("Let's ping the WebRTC Server!")?; - let local_key = Keypair::generate_ed25519(); - let local_peer_id = PeerId::from(local_key.public()); - let mut swarm = SwarmBuilder::with_wasm_executor( - libp2p_webrtc_websys::Transport::new(libp2p_webrtc_websys::Config::new(&local_key)).boxed(), - Behaviour { + let swarm = libp2p::SwarmBuilder::with_new_identity() + .with_wasm_bindgen() + .with_other_transport(|key| { + webrtc_websys::Transport::new(webrtc_websys::Config::new(&key)) + })? + .with_behaviour(|_| Behaviour { ping: ping::Behaviour::new(ping::Config::new()), keep_alive: keep_alive::Behaviour, - }, - local_peer_id, - ) - .build(); - - log::info!("Initialize swarm with identity: {local_peer_id}"); + })? + .build(); let addr = libp2p_endpoint.parse::()?; log::info!("Dialing {addr}"); diff --git a/examples/browser-webrtc/src/main.rs b/examples/browser-webrtc/src/main.rs index 8a4034a436e..4ee86cd1229 100644 --- a/examples/browser-webrtc/src/main.rs +++ b/examples/browser-webrtc/src/main.rs @@ -10,10 +10,9 @@ use futures::StreamExt; use libp2p::{ core::muxing::StreamMuxerBox, core::Transport, - identity, multiaddr::{Multiaddr, Protocol}, ping, - swarm::{SwarmBuilder, SwarmEvent}, + swarm::SwarmEvent, }; use libp2p_webrtc as webrtc; use rand::thread_rng; @@ -28,19 +27,22 @@ async fn main() -> anyhow::Result<()> { .parse_default_env() .init(); - let id_keys = identity::Keypair::generate_ed25519(); - let local_peer_id = id_keys.public().to_peer_id(); - let transport = webrtc::tokio::Transport::new( - id_keys, - webrtc::tokio::Certificate::generate(&mut thread_rng())?, - ) - .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) - .boxed(); - - let mut swarm = - SwarmBuilder::with_tokio_executor(transport, ping::Behaviour::default(), local_peer_id) - .idle_connection_timeout(Duration::from_secs(30)) // Allows us to observe the pings. - .build(); + let mut swarm = libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_other_transport(|id_keys| { + Ok(webrtc::tokio::Transport::new( + id_keys.clone(), + webrtc::tokio::Certificate::generate(&mut thread_rng())?, + ) + .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn)))) + })? + .with_behaviour(|_| ping::Behaviour::default())? + .with_swarm_config(|cfg| { + cfg.with_idle_connection_timeout( + Duration::from_secs(30), // Allows us to observe the pings. + ) + }) + .build(); let address_webrtc = Multiaddr::from(Ipv4Addr::UNSPECIFIED) .with(Protocol::Udp(0)) diff --git a/examples/chat/src/main.rs b/examples/chat/src/main.rs index 312ca2d3087..0a261873f35 100644 --- a/examples/chat/src/main.rs +++ b/examples/chat/src/main.rs @@ -20,14 +20,8 @@ #![doc = include_str!("../README.md")] -use futures::{future::Either, stream::StreamExt}; -use libp2p::{ - core::{muxing::StreamMuxerBox, transport::OrTransport, upgrade}, - gossipsub, identity, mdns, noise, quic, - swarm::NetworkBehaviour, - swarm::{SwarmBuilder, SwarmEvent}, - tcp, yamux, PeerId, Transport, -}; +use futures::stream::StreamExt; +use libp2p::{gossipsub, mdns, noise, swarm::NetworkBehaviour, swarm::SwarmEvent, tcp, yamux}; use std::collections::hash_map::DefaultHasher; use std::error::Error; use std::hash::{Hash, Hasher}; @@ -43,58 +37,46 @@ struct MyBehaviour { #[tokio::main] async fn main() -> Result<(), Box> { - // Create a random PeerId - env_logger::init(); - let id_keys = identity::Keypair::generate_ed25519(); - let local_peer_id = PeerId::from(id_keys.public()); + let mut swarm = libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_quic() + .with_behaviour(|key| { + // To content-address message, we can take the hash of message and use it as an ID. + let message_id_fn = |message: &gossipsub::Message| { + let mut s = DefaultHasher::new(); + message.data.hash(&mut s); + gossipsub::MessageId::from(s.finish().to_string()) + }; - // Set up an encrypted DNS-enabled TCP Transport over the yamux protocol. - let tcp_transport = tcp::tokio::Transport::new(tcp::Config::default().nodelay(true)) - .upgrade(upgrade::Version::V1Lazy) - .authenticate(noise::Config::new(&id_keys).expect("signing libp2p-noise static keypair")) - .multiplex(yamux::Config::default()) - .timeout(std::time::Duration::from_secs(20)) - .boxed(); - let quic_transport = quic::tokio::Transport::new(quic::Config::new(&id_keys)); - let transport = OrTransport::new(quic_transport, tcp_transport) - .map(|either_output, _| match either_output { - Either::Left((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), - Either::Right((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), - }) - .boxed(); + // Set a custom gossipsub configuration + let gossipsub_config = gossipsub::ConfigBuilder::default() + .heartbeat_interval(Duration::from_secs(10)) // This is set to aid debugging by not cluttering the log space + .validation_mode(gossipsub::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() + .map_err(|msg| io::Error::new(io::ErrorKind::Other, msg))?; // Temporary hack because `build` does not return a proper `std::error::Error`. - // To content-address message, we can take the hash of message and use it as an ID. - let message_id_fn = |message: &gossipsub::Message| { - let mut s = DefaultHasher::new(); - message.data.hash(&mut s); - gossipsub::MessageId::from(s.finish().to_string()) - }; + // build a gossipsub network behaviour + let gossipsub = gossipsub::Behaviour::new( + gossipsub::MessageAuthenticity::Signed(key.clone()), + gossipsub_config, + )?; - // Set a custom gossipsub configuration - let gossipsub_config = gossipsub::ConfigBuilder::default() - .heartbeat_interval(Duration::from_secs(10)) // This is set to aid debugging by not cluttering the log space - .validation_mode(gossipsub::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"); + let mdns = + mdns::tokio::Behaviour::new(mdns::Config::default(), key.public().to_peer_id())?; + Ok(MyBehaviour { gossipsub, mdns }) + })? + .build(); - // build a gossipsub network behaviour - let mut gossipsub = gossipsub::Behaviour::new( - gossipsub::MessageAuthenticity::Signed(id_keys), - gossipsub_config, - ) - .expect("Correct configuration"); // Create a Gossipsub topic let topic = gossipsub::IdentTopic::new("test-net"); // subscribes to our topic - gossipsub.subscribe(&topic)?; - - // Create a Swarm to manage peers and events - let mut swarm = { - let mdns = mdns::tokio::Behaviour::new(mdns::Config::default(), local_peer_id)?; - let behaviour = MyBehaviour { gossipsub, mdns }; - SwarmBuilder::with_tokio_executor(transport, behaviour, local_peer_id).build() - }; + swarm.behaviour_mut().gossipsub.subscribe(&topic)?; // Read full lines from stdin let mut stdin = io::BufReader::new(io::stdin()).lines(); diff --git a/examples/dcutr/Cargo.toml b/examples/dcutr/Cargo.toml index 49e1ada2e96..7c35fefcaf9 100644 --- a/examples/dcutr/Cargo.toml +++ b/examples/dcutr/Cargo.toml @@ -10,8 +10,9 @@ clap = { version = "4.3.23", features = ["derive"] } env_logger = "0.10.0" futures = "0.3.28" futures-timer = "3.0" -libp2p = { path = "../../libp2p", features = [ "async-std", "dns", "dcutr", "identify", "macros", "noise", "ping", "quic", "relay", "rendezvous", "tcp", "tokio", "yamux"] } +libp2p = { path = "../../libp2p", features = [ "dns", "dcutr", "identify", "macros", "noise", "ping", "quic", "relay", "rendezvous", "tcp", "tokio", "yamux"] } log = "0.4" +tokio = { version = "1.29", features = ["macros", "net", "rt", "signal"] } [lints] workspace = true diff --git a/examples/dcutr/src/main.rs b/examples/dcutr/src/main.rs index 099867df744..f9ddb1e2ef1 100644 --- a/examples/dcutr/src/main.rs +++ b/examples/dcutr/src/main.rs @@ -21,20 +21,11 @@ #![doc = include_str!("../README.md")] use clap::Parser; -use futures::{ - executor::{block_on, ThreadPool}, - future::{Either, FutureExt}, - stream::StreamExt, -}; +use futures::{executor::block_on, future::FutureExt, stream::StreamExt}; use libp2p::{ - core::{ - multiaddr::{Multiaddr, Protocol}, - muxing::StreamMuxerBox, - transport::Transport, - upgrade, - }, - dcutr, dns, identify, identity, noise, ping, quic, relay, - swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}, + core::multiaddr::{Multiaddr, Protocol}, + dcutr, identify, identity, noise, ping, relay, + swarm::{NetworkBehaviour, SwarmEvent}, tcp, yamux, PeerId, }; use log::info; @@ -78,37 +69,12 @@ impl FromStr for Mode { } } -fn main() -> Result<(), Box> { +#[tokio::main] +async fn main() -> Result<(), Box> { env_logger::init(); let opts = Opts::parse(); - let local_key = generate_ed25519(opts.secret_key_seed); - let local_peer_id = PeerId::from(local_key.public()); - - let (relay_transport, client) = relay::client::new(local_peer_id); - - let transport = { - let relay_tcp_quic_transport = relay_transport - .or_transport(tcp::async_io::Transport::new( - tcp::Config::default().port_reuse(true), - )) - .upgrade(upgrade::Version::V1) - .authenticate(noise::Config::new(&local_key).unwrap()) - .multiplex(yamux::Config::default()) - .or_transport(quic::async_std::Transport::new(quic::Config::new( - &local_key, - ))); - - block_on(dns::async_std::Transport::system(relay_tcp_quic_transport)) - .unwrap() - .map(|either_output, _| match either_output { - Either::Left((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), - Either::Right((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), - }) - .boxed() - }; - #[derive(NetworkBehaviour)] struct Behaviour { relay_client: relay::client::Behaviour, @@ -117,21 +83,27 @@ fn main() -> Result<(), Box> { dcutr: dcutr::Behaviour, } - let behaviour = Behaviour { - relay_client: client, - ping: ping::Behaviour::new(ping::Config::new()), - identify: identify::Behaviour::new(identify::Config::new( - "/TODO/0.0.1".to_string(), - local_key.public(), - )), - dcutr: dcutr::Behaviour::new(local_peer_id), - }; - - let mut swarm = match ThreadPool::new() { - Ok(tp) => SwarmBuilder::with_executor(transport, behaviour, local_peer_id, tp), - Err(_) => SwarmBuilder::without_executor(transport, behaviour, local_peer_id), - } - .build(); + let mut swarm = + libp2p::SwarmBuilder::with_existing_identity(generate_ed25519(opts.secret_key_seed)) + .with_tokio() + .with_tcp( + tcp::Config::default().port_reuse(true).nodelay(true), + noise::Config::new, + yamux::Config::default, + )? + .with_quic() + .with_dns()? + .with_relay_client(noise::Config::new, yamux::Config::default)? + .with_behaviour(|keypair, relay_behaviour| Behaviour { + relay_client: relay_behaviour, + ping: ping::Behaviour::new(ping::Config::new()), + identify: identify::Behaviour::new(identify::Config::new( + "/TODO/0.0.1".to_string(), + keypair.public(), + )), + dcutr: dcutr::Behaviour::new(keypair.public().to_peer_id()), + })? + .build(); swarm .listen_on("/ip4/0.0.0.0/udp/0/quic-v1".parse().unwrap()) diff --git a/examples/distributed-key-value-store/Cargo.toml b/examples/distributed-key-value-store/Cargo.toml index aa9a875be5a..fd1f92dc410 100644 --- a/examples/distributed-key-value-store/Cargo.toml +++ b/examples/distributed-key-value-store/Cargo.toml @@ -10,7 +10,7 @@ async-std = { version = "1.12", features = ["attributes"] } async-trait = "0.1" env_logger = "0.10" futures = "0.3.28" -libp2p = { path = "../../libp2p", features = [ "async-std", "dns", "kad", "mdns", "noise", "macros", "tcp", "websocket", "yamux"] } +libp2p = { path = "../../libp2p", features = [ "async-std", "dns", "kad", "mdns", "noise", "macros", "tcp", "yamux"] } [lints] workspace = true diff --git a/examples/distributed-key-value-store/src/main.rs b/examples/distributed-key-value-store/src/main.rs index cd9857b1482..81bc1c2ec90 100644 --- a/examples/distributed-key-value-store/src/main.rs +++ b/examples/distributed-key-value-store/src/main.rs @@ -22,14 +22,13 @@ use async_std::io; use futures::{prelude::*, select}; -use libp2p::core::upgrade::Version; use libp2p::kad; use libp2p::kad::record::store::MemoryStore; use libp2p::kad::Mode; use libp2p::{ - identity, mdns, noise, - swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}, - tcp, yamux, PeerId, Transport, + mdns, noise, + swarm::{NetworkBehaviour, SwarmEvent}, + tcp, yamux, }; use std::error::Error; @@ -37,16 +36,6 @@ use std::error::Error; async fn main() -> Result<(), Box> { env_logger::init(); - // Create a random key for ourselves. - let local_key = identity::Keypair::generate_ed25519(); - let local_peer_id = PeerId::from(local_key.public()); - - let transport = tcp::async_io::Transport::default() - .upgrade(Version::V1Lazy) - .authenticate(noise::Config::new(&local_key)?) - .multiplex(yamux::Config::default()) - .boxed(); - // We create a custom network behaviour that combines Kademlia and mDNS. #[derive(NetworkBehaviour)] struct Behaviour { @@ -54,15 +43,26 @@ async fn main() -> Result<(), Box> { mdns: mdns::async_io::Behaviour, } - // Create a swarm to manage peers and events. - let mut swarm = { - // Create a Kademlia behaviour. - let store = MemoryStore::new(local_peer_id); - let kademlia = kad::Behaviour::new(local_peer_id, store); - let mdns = mdns::async_io::Behaviour::new(mdns::Config::default(), local_peer_id)?; - let behaviour = Behaviour { kademlia, mdns }; - SwarmBuilder::with_async_std_executor(transport, behaviour, local_peer_id).build() - }; + let mut swarm = libp2p::SwarmBuilder::with_new_identity() + .with_async_std() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_behaviour(|key| { + Ok(Behaviour { + kademlia: kad::Behaviour::new( + key.public().to_peer_id(), + MemoryStore::new(key.public().to_peer_id()), + ), + mdns: mdns::async_io::Behaviour::new( + mdns::Config::default(), + key.public().to_peer_id(), + )?, + }) + })? + .build(); swarm.behaviour_mut().kademlia.set_mode(Some(Mode::Server)); diff --git a/examples/file-sharing/src/network.rs b/examples/file-sharing/src/network.rs index f13e72f0000..d6adea0ccd6 100644 --- a/examples/file-sharing/src/network.rs +++ b/examples/file-sharing/src/network.rs @@ -9,11 +9,10 @@ use libp2p::{ multiaddr::Protocol, noise, request_response::{self, ProtocolSupport, RequestId, ResponseChannel}, - swarm::{NetworkBehaviour, Swarm, SwarmBuilder, SwarmEvent}, - tcp, yamux, PeerId, Transport, + swarm::{NetworkBehaviour, Swarm, SwarmEvent}, + tcp, yamux, PeerId, }; -use libp2p::core::upgrade::Version; use libp2p::StreamProtocol; use serde::{Deserialize, Serialize}; use std::collections::{hash_map, HashMap, HashSet}; @@ -41,18 +40,18 @@ pub(crate) async fn new( }; let peer_id = id_keys.public().to_peer_id(); - let transport = tcp::async_io::Transport::default() - .upgrade(Version::V1Lazy) - .authenticate(noise::Config::new(&id_keys)?) - .multiplex(yamux::Config::default()) - .boxed(); - - // Build the Swarm, connecting the lower layer transport logic with the - // higher layer network behaviour logic. - let mut swarm = SwarmBuilder::with_async_std_executor( - transport, - Behaviour { - kademlia: kad::Behaviour::new(peer_id, kad::record::store::MemoryStore::new(peer_id)), + let mut swarm = libp2p::SwarmBuilder::with_existing_identity(id_keys) + .with_async_std() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_behaviour(|key| Behaviour { + kademlia: kad::Behaviour::new( + peer_id, + kad::store::MemoryStore::new(key.public().to_peer_id()), + ), request_response: request_response::cbor::Behaviour::new( [( StreamProtocol::new("/file-exchange/1"), @@ -60,10 +59,8 @@ pub(crate) async fn new( )], request_response::Config::default(), ), - }, - peer_id, - ) - .build(); + })? + .build(); swarm .behaviour_mut() diff --git a/examples/identify/src/main.rs b/examples/identify/src/main.rs index dc98fb58295..d6be9cb9435 100644 --- a/examples/identify/src/main.rs +++ b/examples/identify/src/main.rs @@ -20,35 +20,28 @@ #![doc = include_str!("../README.md")] -use futures::prelude::*; -use libp2p::{ - core::{multiaddr::Multiaddr, upgrade::Version}, - identify, identity, noise, - swarm::{SwarmBuilder, SwarmEvent}, - tcp, yamux, PeerId, Transport, -}; +use futures::StreamExt; +use libp2p::{core::multiaddr::Multiaddr, identify, noise, swarm::SwarmEvent, tcp, yamux}; use std::error::Error; #[async_std::main] async fn main() -> Result<(), Box> { env_logger::init(); - let local_key = identity::Keypair::generate_ed25519(); - let local_peer_id = PeerId::from(local_key.public()); - let transport = tcp::async_io::Transport::default() - .upgrade(Version::V1Lazy) - .authenticate(noise::Config::new(&local_key).unwrap()) - .multiplex(yamux::Config::default()) - .boxed(); - - // Create a identify network behaviour. - let behaviour = identify::Behaviour::new(identify::Config::new( - "/ipfs/id/1.0.0".to_string(), - local_key.public(), - )); - - let mut swarm = - SwarmBuilder::with_async_std_executor(transport, behaviour, local_peer_id).build(); + let mut swarm = libp2p::SwarmBuilder::with_new_identity() + .with_async_std() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_behaviour(|key| { + identify::Behaviour::new(identify::Config::new( + "/ipfs/id/1.0.0".to_string(), + key.public(), + )) + })? + .build(); // Tell the swarm to listen on all interfaces and a random, OS-assigned // port. diff --git a/examples/ipfs-kad/Cargo.toml b/examples/ipfs-kad/Cargo.toml index 025cf2598fc..075d80754c1 100644 --- a/examples/ipfs-kad/Cargo.toml +++ b/examples/ipfs-kad/Cargo.toml @@ -12,7 +12,7 @@ clap = { version = "4.3.23", features = ["derive"] } env_logger = "0.10" futures = "0.3.28" anyhow = "1.0.75" -libp2p = { path = "../../libp2p", features = [ "tokio", "dns", "kad", "noise", "tcp", "websocket", "yamux", "rsa"] } +libp2p = { path = "../../libp2p", features = [ "tokio", "dns", "kad", "noise", "tcp", "yamux", "rsa"] } [lints] workspace = true diff --git a/examples/ipfs-kad/src/main.rs b/examples/ipfs-kad/src/main.rs index f912bd0565a..fc55a107929 100644 --- a/examples/ipfs-kad/src/main.rs +++ b/examples/ipfs-kad/src/main.rs @@ -27,12 +27,7 @@ use std::time::{Duration, Instant}; use anyhow::{bail, Result}; use clap::Parser; use futures::StreamExt; -use libp2p::{ - bytes::BufMut, - identity, kad, - swarm::{SwarmBuilder, SwarmEvent}, - tokio_development_transport, PeerId, -}; +use libp2p::{bytes::BufMut, identity, kad, noise, swarm::SwarmEvent, tcp, yamux, PeerId}; const BOOTNODES: [&str; 4] = [ "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", @@ -47,28 +42,32 @@ async fn main() -> Result<()> { // Create a random key for ourselves. let local_key = identity::Keypair::generate_ed25519(); - let local_peer_id = PeerId::from(local_key.public()); - - // Set up a an encrypted DNS-enabled TCP Transport over the yamux protocol - let transport = tokio_development_transport(local_key.clone())?; - - // Create a swarm to manage peers and events. - let mut swarm = { - // Create a Kademlia behaviour. - let mut cfg = kad::Config::default(); - cfg.set_query_timeout(Duration::from_secs(5 * 60)); - let store = kad::store::MemoryStore::new(local_peer_id); - let mut behaviour = kad::Behaviour::with_config(local_peer_id, store, cfg); - - // Add the bootnodes to the local routing table. `libp2p-dns` built - // into the `transport` resolves the `dnsaddr` when Kademlia tries - // to dial these nodes. - for peer in &BOOTNODES { - behaviour.add_address(&peer.parse()?, "/dnsaddr/bootstrap.libp2p.io".parse()?); - } - SwarmBuilder::with_tokio_executor(transport, behaviour, local_peer_id).build() - }; + let mut swarm = libp2p::SwarmBuilder::with_existing_identity(local_key.clone()) + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_dns()? + .with_behaviour(|key| { + // Create a Kademlia behaviour. + let mut cfg = kad::Config::default(); + cfg.set_query_timeout(Duration::from_secs(5 * 60)); + let store = kad::store::MemoryStore::new(key.public().to_peer_id()); + kad::Behaviour::with_config(key.public().to_peer_id(), store, cfg) + })? + .build(); + + // Add the bootnodes to the local routing table. `libp2p-dns` built + // into the `transport` resolves the `dnsaddr` when Kademlia tries + // to dial these nodes. + for peer in &BOOTNODES { + swarm + .behaviour_mut() + .add_address(&peer.parse()?, "/dnsaddr/bootstrap.libp2p.io".parse()?); + } let cli_opt = Opt::parse(); @@ -83,11 +82,11 @@ async fn main() -> Result<()> { let mut pk_record_key = vec![]; pk_record_key.put_slice("/pk/".as_bytes()); - pk_record_key.put_slice(local_peer_id.to_bytes().as_slice()); + pk_record_key.put_slice(swarm.local_peer_id().to_bytes().as_slice()); let mut pk_record = kad::Record::new(pk_record_key, local_key.public().encode_protobuf()); - pk_record.publisher = Some(local_peer_id); + pk_record.publisher = Some(*swarm.local_peer_id()); pk_record.expires = Some(Instant::now().add(Duration::from_secs(60))); swarm diff --git a/examples/ipfs-private/src/main.rs b/examples/ipfs-private/src/main.rs index fe83e891cfc..861648fecdd 100644 --- a/examples/ipfs-private/src/main.rs +++ b/examples/ipfs-private/src/main.rs @@ -23,40 +23,17 @@ use either::Either; use futures::prelude::*; use libp2p::{ - core::{muxing::StreamMuxerBox, transport, transport::upgrade::Version}, - gossipsub, identify, identity, + core::transport::upgrade::Version, + gossipsub, identify, multiaddr::Protocol, noise, ping, pnet::{PnetConfig, PreSharedKey}, - swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}, - tcp, yamux, Multiaddr, PeerId, Transport, + swarm::{NetworkBehaviour, SwarmEvent}, + tcp, yamux, Multiaddr, Transport, }; -use std::{env, error::Error, fs, path::Path, str::FromStr, time::Duration}; +use std::{env, error::Error, fs, path::Path, str::FromStr}; use tokio::{io, io::AsyncBufReadExt, select}; -/// Builds the transport that serves as a common ground for all connections. -pub fn build_transport( - key_pair: identity::Keypair, - psk: Option, -) -> transport::Boxed<(PeerId, StreamMuxerBox)> { - let noise_config = noise::Config::new(&key_pair).unwrap(); - let yamux_config = yamux::Config::default(); - - let base_transport = tcp::tokio::Transport::new(tcp::Config::default().nodelay(true)); - let maybe_encrypted = match psk { - Some(psk) => Either::Left( - base_transport.and_then(move |socket, _| PnetConfig::new(psk).handshake(socket)), - ), - None => Either::Right(base_transport), - }; - maybe_encrypted - .upgrade(Version::V1Lazy) - .authenticate(noise_config) - .multiplex(yamux_config) - .timeout(Duration::from_secs(20)) - .boxed() -} - /// Get the current ipfs repo path, either from the IPFS_PATH environment variable or /// from the default $HOME/.ipfs fn get_ipfs_path() -> Box { @@ -118,76 +95,67 @@ async fn main() -> Result<(), Box> { .map(|text| PreSharedKey::from_str(&text)) .transpose()?; - // Create a random PeerId - let local_key = identity::Keypair::generate_ed25519(); - let local_peer_id = PeerId::from(local_key.public()); - println!("using random peer id: {local_peer_id:?}"); if let Some(psk) = psk { println!("using swarm key with fingerprint: {}", psk.fingerprint()); } - // Set up a an encrypted DNS-enabled TCP Transport over and Yamux protocol - let transport = build_transport(local_key.clone(), psk); - // Create a Gosspipsub topic let gossipsub_topic = gossipsub::IdentTopic::new("chat"); // We create a custom network behaviour that combines gossipsub, ping and identify. #[derive(NetworkBehaviour)] - #[behaviour(to_swarm = "MyBehaviourEvent")] struct MyBehaviour { gossipsub: gossipsub::Behaviour, identify: identify::Behaviour, ping: ping::Behaviour, } - enum MyBehaviourEvent { - Gossipsub(gossipsub::Event), - Identify(identify::Event), - Ping(ping::Event), - } - - impl From for MyBehaviourEvent { - fn from(event: gossipsub::Event) -> Self { - MyBehaviourEvent::Gossipsub(event) - } - } - - impl From for MyBehaviourEvent { - fn from(event: identify::Event) -> Self { - MyBehaviourEvent::Identify(event) - } - } - - impl From for MyBehaviourEvent { - fn from(event: ping::Event) -> Self { - MyBehaviourEvent::Ping(event) - } - } - - // Create a Swarm to manage peers and events - let mut swarm = { - let gossipsub_config = gossipsub::ConfigBuilder::default() - .max_transmit_size(262144) - .build() - .expect("valid config"); - let mut behaviour = MyBehaviour { - gossipsub: gossipsub::Behaviour::new( - gossipsub::MessageAuthenticity::Signed(local_key.clone()), - gossipsub_config, - ) - .expect("Valid configuration"), - identify: identify::Behaviour::new(identify::Config::new( - "/ipfs/0.1.0".into(), - local_key.public(), - )), - ping: ping::Behaviour::new(ping::Config::new()), - }; - - println!("Subscribing to {gossipsub_topic:?}"); - behaviour.gossipsub.subscribe(&gossipsub_topic).unwrap(); - SwarmBuilder::with_tokio_executor(transport, behaviour, local_peer_id).build() - }; + let mut swarm = libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_other_transport(|key| { + let noise_config = noise::Config::new(key).unwrap(); + let yamux_config = yamux::Config::default(); + + let base_transport = tcp::tokio::Transport::new(tcp::Config::default().nodelay(true)); + let maybe_encrypted = match psk { + Some(psk) => Either::Left( + base_transport + .and_then(move |socket, _| PnetConfig::new(psk).handshake(socket)), + ), + None => Either::Right(base_transport), + }; + maybe_encrypted + .upgrade(Version::V1Lazy) + .authenticate(noise_config) + .multiplex(yamux_config) + })? + .with_dns()? + .with_behaviour(|key| { + let gossipsub_config = gossipsub::ConfigBuilder::default() + .max_transmit_size(262144) + .build() + .map_err(|msg| io::Error::new(io::ErrorKind::Other, msg))?; // Temporary hack because `build` does not return a proper `std::error::Error`. + Ok(MyBehaviour { + gossipsub: gossipsub::Behaviour::new( + gossipsub::MessageAuthenticity::Signed(key.clone()), + gossipsub_config, + ) + .expect("Valid configuration"), + identify: identify::Behaviour::new(identify::Config::new( + "/ipfs/0.1.0".into(), + key.public(), + )), + ping: ping::Behaviour::new(ping::Config::new()), + }) + })? + .build(); + + println!("Subscribing to {gossipsub_topic:?}"); + swarm + .behaviour_mut() + .gossipsub + .subscribe(&gossipsub_topic) + .unwrap(); // Reach out to other nodes if specified for to_dial in std::env::args().skip(1) { diff --git a/examples/metrics/src/main.rs b/examples/metrics/src/main.rs index b28abaee941..09d4f7a5941 100644 --- a/examples/metrics/src/main.rs +++ b/examples/metrics/src/main.rs @@ -21,12 +21,10 @@ #![doc = include_str!("../README.md")] use env_logger::Env; -use futures::executor::block_on; -use futures::stream::StreamExt; -use libp2p::core::{upgrade::Version, Multiaddr, Transport}; -use libp2p::identity::PeerId; +use futures::{executor::block_on, StreamExt}; +use libp2p::core::Multiaddr; use libp2p::metrics::{Metrics, Recorder}; -use libp2p::swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}; +use libp2p::swarm::{NetworkBehaviour, SwarmEvent}; use libp2p::{identify, identity, noise, ping, tcp, yamux}; use log::info; use prometheus_client::registry::Registry; @@ -39,21 +37,16 @@ mod http_service; fn main() -> Result<(), Box> { env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); - let local_key = identity::Keypair::generate_ed25519(); - let local_peer_id = PeerId::from(local_key.public()); - let local_pub_key = local_key.public(); - - let mut swarm = SwarmBuilder::without_executor( - tcp::async_io::Transport::default() - .upgrade(Version::V1Lazy) - .authenticate(noise::Config::new(&local_key)?) - .multiplex(yamux::Config::default()) - .boxed(), - Behaviour::new(local_pub_key), - local_peer_id, - ) - .idle_connection_timeout(Duration::from_secs(60)) - .build(); + let mut swarm = libp2p::SwarmBuilder::with_new_identity() + .with_async_std() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_behaviour(|key| Behaviour::new(key.public()))? + .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(u64::MAX))) + .build(); swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?; diff --git a/examples/ping/src/main.rs b/examples/ping/src/main.rs index 25939a132c1..d89415132e5 100644 --- a/examples/ping/src/main.rs +++ b/examples/ping/src/main.rs @@ -21,31 +21,21 @@ #![doc = include_str!("../README.md")] use futures::prelude::*; -use libp2p::core::upgrade::Version; -use libp2p::{ - identity, noise, ping, - swarm::{SwarmBuilder, SwarmEvent}, - tcp, yamux, Multiaddr, PeerId, Transport, -}; -use std::error::Error; -use std::time::Duration; +use libp2p::{noise, ping, swarm::SwarmEvent, tcp, yamux, Multiaddr}; +use std::{error::Error, time::Duration}; #[tokio::main] async fn main() -> Result<(), Box> { - env_logger::init(); - let local_key = identity::Keypair::generate_ed25519(); - let local_peer_id = PeerId::from(local_key.public()); - - let transport = tcp::tokio::Transport::default() - .upgrade(Version::V1Lazy) - .authenticate(noise::Config::new(&local_key)?) - .multiplex(yamux::Config::default()) - .boxed(); - - let mut swarm = - SwarmBuilder::with_tokio_executor(transport, ping::Behaviour::default(), local_peer_id) - .idle_connection_timeout(Duration::from_secs(60)) // For illustrative purposes, keep idle connections alive for a minute so we can observe a few pings. - .build(); + let mut swarm = libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_behaviour(|_| ping::Behaviour::default())? + .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(u64::MAX))) + .build(); // Tell the swarm to listen on all interfaces and a random, OS-assigned // port. diff --git a/examples/relay-server/src/main.rs b/examples/relay-server/src/main.rs index ab87615d74e..2f86f9b938e 100644 --- a/examples/relay-server/src/main.rs +++ b/examples/relay-server/src/main.rs @@ -22,18 +22,14 @@ #![doc = include_str!("../README.md")] use clap::Parser; +use futures::executor::block_on; use futures::stream::StreamExt; -use futures::{executor::block_on, future::Either}; use libp2p::{ core::multiaddr::Protocol, - core::muxing::StreamMuxerBox, - core::upgrade, - core::{Multiaddr, Transport}, - identify, identity, - identity::PeerId, - noise, ping, quic, relay, - swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}, - tcp, + core::Multiaddr, + identify, identity, noise, ping, relay, + swarm::{NetworkBehaviour, SwarmEvent}, + tcp, yamux, }; use std::error::Error; use std::net::{Ipv4Addr, Ipv6Addr}; @@ -42,41 +38,27 @@ fn main() -> Result<(), Box> { env_logger::init(); let opt = Opt::parse(); - println!("opt: {opt:?}"); // Create a static known PeerId based on given secret let local_key: identity::Keypair = generate_ed25519(opt.secret_key_seed); - let local_peer_id = PeerId::from(local_key.public()); - let tcp_transport = tcp::async_io::Transport::default(); - - let tcp_transport = tcp_transport - .upgrade(upgrade::Version::V1Lazy) - .authenticate( - noise::Config::new(&local_key).expect("Signing libp2p-noise static DH keypair failed."), - ) - .multiplex(libp2p::yamux::Config::default()); - - let quic_transport = quic::async_std::Transport::new(quic::Config::new(&local_key)); - - let transport = quic_transport - .or_transport(tcp_transport) - .map(|either_output, _| match either_output { - Either::Left((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), - Either::Right((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), - }) - .boxed(); - - let behaviour = Behaviour { - relay: relay::Behaviour::new(local_peer_id, Default::default()), - ping: ping::Behaviour::new(ping::Config::new()), - identify: identify::Behaviour::new(identify::Config::new( - "/TODO/0.0.1".to_string(), - local_key.public(), - )), - }; - - let mut swarm = SwarmBuilder::without_executor(transport, behaviour, local_peer_id).build(); + let mut swarm = libp2p::SwarmBuilder::with_existing_identity(local_key) + .with_async_std() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_quic() + .with_behaviour(|key| Behaviour { + relay: relay::Behaviour::new(key.public().to_peer_id(), Default::default()), + ping: ping::Behaviour::new(ping::Config::new()), + identify: identify::Behaviour::new(identify::Config::new( + "/TODO/0.0.1".to_string(), + key.public(), + )), + })? + .build(); // Listen on all interfaces let listen_addr_tcp = Multiaddr::empty() diff --git a/examples/rendezvous/src/bin/rzv-discover.rs b/examples/rendezvous/src/bin/rzv-discover.rs index ac45afae840..42a5a20b6ad 100644 --- a/examples/rendezvous/src/bin/rzv-discover.rs +++ b/examples/rendezvous/src/bin/rzv-discover.rs @@ -20,41 +20,38 @@ use futures::StreamExt; use libp2p::{ - core::transport::upgrade::Version, - identity, multiaddr::Protocol, noise, ping, rendezvous, - swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}, - tcp, yamux, Multiaddr, PeerId, Transport, + swarm::{NetworkBehaviour, SwarmEvent}, + tcp, yamux, Multiaddr, }; +use std::error::Error; use std::time::Duration; const NAMESPACE: &str = "rendezvous"; #[tokio::main] -async fn main() { +async fn main() -> Result<(), Box> { env_logger::init(); - let key_pair = identity::Keypair::generate_ed25519(); let rendezvous_point_address = "/ip4/127.0.0.1/tcp/62649".parse::().unwrap(); let rendezvous_point = "12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN" .parse() .unwrap(); - let mut swarm = SwarmBuilder::with_tokio_executor( - tcp::tokio::Transport::default() - .upgrade(Version::V1Lazy) - .authenticate(noise::Config::new(&key_pair).unwrap()) - .multiplex(yamux::Config::default()) - .boxed(), - MyBehaviour { - rendezvous: rendezvous::client::Behaviour::new(key_pair.clone()), + let mut swarm = libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_behaviour(|key| MyBehaviour { + rendezvous: rendezvous::client::Behaviour::new(key.clone()), ping: ping::Behaviour::new(ping::Config::new().with_interval(Duration::from_secs(1))), - }, - PeerId::from(key_pair.public()), - ) - .idle_connection_timeout(Duration::from_secs(5)) - .build(); + })? + .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(5))) + .build(); swarm.dial(rendezvous_point_address.clone()).unwrap(); diff --git a/examples/rendezvous/src/bin/rzv-identify.rs b/examples/rendezvous/src/bin/rzv-identify.rs index 95ed7a5ccd8..be644dbb9f8 100644 --- a/examples/rendezvous/src/bin/rzv-identify.rs +++ b/examples/rendezvous/src/bin/rzv-identify.rs @@ -20,10 +20,9 @@ use futures::StreamExt; use libp2p::{ - core::transport::upgrade::Version, - identify, identity, noise, ping, rendezvous, - swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}, - tcp, yamux, Multiaddr, PeerId, Transport, + identify, noise, ping, rendezvous, + swarm::{NetworkBehaviour, SwarmEvent}, + tcp, yamux, Multiaddr, }; use std::time::Duration; @@ -31,30 +30,30 @@ use std::time::Duration; async fn main() { env_logger::init(); - let key_pair = identity::Keypair::generate_ed25519(); let rendezvous_point_address = "/ip4/127.0.0.1/tcp/62649".parse::().unwrap(); let rendezvous_point = "12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN" .parse() .unwrap(); - let mut swarm = SwarmBuilder::with_tokio_executor( - tcp::tokio::Transport::default() - .upgrade(Version::V1Lazy) - .authenticate(noise::Config::new(&key_pair).unwrap()) - .multiplex(yamux::Config::default()) - .boxed(), - MyBehaviour { + let mut swarm = libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + ) + .unwrap() + .with_behaviour(|key| MyBehaviour { identify: identify::Behaviour::new(identify::Config::new( "rendezvous-example/1.0.0".to_string(), - key_pair.public(), + key.public(), )), - rendezvous: rendezvous::client::Behaviour::new(key_pair.clone()), + rendezvous: rendezvous::client::Behaviour::new(key.clone()), ping: ping::Behaviour::new(ping::Config::new().with_interval(Duration::from_secs(1))), - }, - PeerId::from(key_pair.public()), - ) - .idle_connection_timeout(Duration::from_secs(5)) - .build(); + }) + .unwrap() + .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(5))) + .build(); let _ = swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()); diff --git a/examples/rendezvous/src/bin/rzv-register.rs b/examples/rendezvous/src/bin/rzv-register.rs index 51acfee2a71..928dcdd1625 100644 --- a/examples/rendezvous/src/bin/rzv-register.rs +++ b/examples/rendezvous/src/bin/rzv-register.rs @@ -20,10 +20,9 @@ use futures::StreamExt; use libp2p::{ - core::transport::upgrade::Version, - identity, noise, ping, rendezvous, - swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}, - tcp, yamux, Multiaddr, PeerId, Transport, + noise, ping, rendezvous, + swarm::{NetworkBehaviour, SwarmEvent}, + tcp, yamux, Multiaddr, }; use std::time::Duration; @@ -31,26 +30,26 @@ use std::time::Duration; async fn main() { env_logger::init(); - let key_pair = identity::Keypair::generate_ed25519(); let rendezvous_point_address = "/ip4/127.0.0.1/tcp/62649".parse::().unwrap(); let rendezvous_point = "12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN" .parse() .unwrap(); - let mut swarm = SwarmBuilder::with_tokio_executor( - tcp::tokio::Transport::default() - .upgrade(Version::V1Lazy) - .authenticate(noise::Config::new(&key_pair).unwrap()) - .multiplex(yamux::Config::default()) - .boxed(), - MyBehaviour { - rendezvous: rendezvous::client::Behaviour::new(key_pair.clone()), + let mut swarm = libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + ) + .unwrap() + .with_behaviour(|key| MyBehaviour { + rendezvous: rendezvous::client::Behaviour::new(key.clone()), ping: ping::Behaviour::new(ping::Config::new().with_interval(Duration::from_secs(1))), - }, - PeerId::from(key_pair.public()), - ) - .idle_connection_timeout(Duration::from_secs(5)) - .build(); + }) + .unwrap() + .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(5))) + .build(); // In production the external address should be the publicly facing IP address of the rendezvous point. // This address is recorded in the registration entry by the rendezvous point. diff --git a/examples/rendezvous/src/main.rs b/examples/rendezvous/src/main.rs index a3ed3c0fce5..e40507c3fc3 100644 --- a/examples/rendezvous/src/main.rs +++ b/examples/rendezvous/src/main.rs @@ -22,37 +22,34 @@ use futures::StreamExt; use libp2p::{ - core::transport::upgrade::Version, - identify, identity, noise, ping, rendezvous, - swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}, - tcp, yamux, PeerId, Transport, + identify, noise, ping, rendezvous, + swarm::{NetworkBehaviour, SwarmEvent}, + tcp, yamux, }; +use std::error::Error; use std::time::Duration; #[tokio::main] -async fn main() { +async fn main() -> Result<(), Box> { env_logger::init(); - let key_pair = identity::Keypair::generate_ed25519(); - - let mut swarm = SwarmBuilder::with_tokio_executor( - tcp::tokio::Transport::default() - .upgrade(Version::V1Lazy) - .authenticate(noise::Config::new(&key_pair).unwrap()) - .multiplex(yamux::Config::default()) - .boxed(), - MyBehaviour { + let mut swarm = libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_behaviour(|key| MyBehaviour { identify: identify::Behaviour::new(identify::Config::new( "rendezvous-example/1.0.0".to_string(), - key_pair.public(), + key.public(), )), rendezvous: rendezvous::server::Behaviour::new(rendezvous::server::Config::default()), ping: ping::Behaviour::new(ping::Config::new().with_interval(Duration::from_secs(1))), - }, - PeerId::from(key_pair.public()), - ) - .idle_connection_timeout(Duration::from_secs(5)) - .build(); + })? + .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(5))) + .build(); let _ = swarm.listen_on("/ip4/0.0.0.0/tcp/62649".parse().unwrap()); @@ -90,6 +87,8 @@ async fn main() { } } } + + Ok(()) } #[derive(NetworkBehaviour)] diff --git a/examples/upnp/Cargo.toml b/examples/upnp/Cargo.toml index 74e88bf82bd..dc55507a021 100644 --- a/examples/upnp/Cargo.toml +++ b/examples/upnp/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" [dependencies] tokio = { version = "1", features = ["rt-multi-thread", "macros"] } futures = "0.3.28" -libp2p = { path = "../../libp2p", features = ["tokio", "dns", "macros", "noise", "ping", "tcp", "websocket", "yamux", "upnp"] } +libp2p = { path = "../../libp2p", features = ["tokio", "dns", "macros", "noise", "ping", "tcp", "yamux", "upnp"] } [lints] workspace = true diff --git a/examples/upnp/src/main.rs b/examples/upnp/src/main.rs index b4350dc82ad..c602a687db7 100644 --- a/examples/upnp/src/main.rs +++ b/examples/upnp/src/main.rs @@ -21,32 +21,20 @@ #![doc = include_str!("../README.md")] use futures::prelude::*; -use libp2p::core::upgrade::Version; -use libp2p::{ - identity, noise, - swarm::{SwarmBuilder, SwarmEvent}, - tcp, upnp, yamux, Multiaddr, PeerId, Transport, -}; +use libp2p::{noise, swarm::SwarmEvent, upnp, yamux, Multiaddr}; use std::error::Error; #[tokio::main] async fn main() -> Result<(), Box> { - let local_key = identity::Keypair::generate_ed25519(); - let local_peer_id = PeerId::from(local_key.public()); - println!("Local peer id: {local_peer_id:?}"); - - let transport = tcp::tokio::Transport::default() - .upgrade(Version::V1Lazy) - .authenticate(noise::Config::new(&local_key)?) - .multiplex(yamux::Config::default()) - .boxed(); - - let mut swarm = SwarmBuilder::with_tokio_executor( - transport, - upnp::tokio::Behaviour::default(), - local_peer_id, - ) - .build(); + let mut swarm = libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + Default::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_behaviour(|_| upnp::tokio::Behaviour::default())? + .build(); // Tell the swarm to listen on all interfaces and a random, OS-assigned // port. diff --git a/interop-tests/Cargo.toml b/interop-tests/Cargo.toml index c76aa12f8a0..2383499b835 100644 --- a/interop-tests/Cargo.toml +++ b/interop-tests/Cargo.toml @@ -20,8 +20,10 @@ rand = "0.8.5" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] axum = "0.6" libp2p = { path = "../libp2p", features = [ "ping", "noise", "tls", "rsa", "macros", "websocket", "tokio", "yamux", "tcp", "dns", "identify", "quic"] } -libp2p-webrtc = { workspace = true, features = ["tokio"] } libp2p-mplex = { path = "../muxers/mplex" } +libp2p-noise = { workspace = true } +libp2p-tls = { workspace = true } +libp2p-webrtc = { workspace = true, features = ["tokio"] } mime_guess = "2.0" redis = { version = "0.23.3", default-features = false, features = [ "tokio-comp", diff --git a/interop-tests/src/arch.rs b/interop-tests/src/arch.rs index c1adb1199ad..d90af53abb1 100644 --- a/interop-tests/src/arch.rs +++ b/interop-tests/src/arch.rs @@ -1,16 +1,10 @@ -use libp2p::core::muxing::StreamMuxerBox; -use libp2p::core::transport::Boxed; -use libp2p::PeerId; - // Native re-exports #[cfg(not(target_arch = "wasm32"))] -pub(crate) use native::{build_transport, init_logger, sleep, swarm_builder, Instant, RedisClient}; +pub(crate) use native::{build_swarm, init_logger, sleep, Instant, RedisClient}; // Wasm re-exports #[cfg(target_arch = "wasm32")] -pub(crate) use wasm::{build_transport, init_logger, sleep, swarm_builder, Instant, RedisClient}; - -type BoxedTransport = Boxed<(PeerId, StreamMuxerBox)>; +pub(crate) use wasm::{build_swarm, init_logger, sleep, Instant, RedisClient}; #[cfg(not(target_arch = "wasm32"))] pub(crate) mod native { @@ -20,20 +14,15 @@ pub(crate) mod native { use env_logger::{Env, Target}; use futures::future::BoxFuture; use futures::FutureExt; - use libp2p::core::muxing::StreamMuxerBox; - use libp2p::core::upgrade::Version; use libp2p::identity::Keypair; - use libp2p::swarm::{NetworkBehaviour, SwarmBuilder}; - use libp2p::websocket::WsConfig; - use libp2p::{noise, quic, tcp, tls, yamux, PeerId, Transport as _}; + use libp2p::swarm::{NetworkBehaviour, Swarm}; + use libp2p::{noise, tcp, tls, yamux}; use libp2p_mplex as mplex; use libp2p_webrtc as webrtc; use redis::AsyncCommands; use crate::{Muxer, SecProtocol, Transport}; - use super::BoxedTransport; - pub(crate) type Instant = std::time::Instant; pub(crate) fn init_logger() { @@ -46,128 +35,132 @@ pub(crate) mod native { tokio::time::sleep(duration).boxed() } - pub(crate) fn build_transport( - local_key: Keypair, + pub(crate) async fn build_swarm( ip: &str, transport: Transport, sec_protocol: Option, muxer: Option, - ) -> Result<(BoxedTransport, String)> { - let (transport, addr) = match (transport, sec_protocol, muxer) { - (Transport::QuicV1, _, _) => ( - quic::tokio::Transport::new(quic::Config::new(&local_key)) - .map(|(p, c), _| (p, StreamMuxerBox::new(c))) - .boxed(), + behaviour_constructor: impl FnOnce(&Keypair) -> B, + ) -> Result<(Swarm, String)> { + let (swarm, addr) = match (transport, sec_protocol, muxer) { + (Transport::QuicV1, None, None) => ( + libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_quic() + .with_behaviour(behaviour_constructor)? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) + .build(), format!("/ip4/{ip}/udp/0/quic-v1"), ), (Transport::Tcp, Some(SecProtocol::Tls), Some(Muxer::Mplex)) => ( - tcp::tokio::Transport::new(tcp::Config::new()) - .upgrade(Version::V1Lazy) - .authenticate(tls::Config::new(&local_key).context("failed to initialise tls")?) - .multiplex(mplex::MplexConfig::new()) - .timeout(Duration::from_secs(5)) - .boxed(), + libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + tls::Config::new, + mplex::MplexConfig::default, + )? + .with_behaviour(behaviour_constructor)? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) + .build(), format!("/ip4/{ip}/tcp/0"), ), (Transport::Tcp, Some(SecProtocol::Tls), Some(Muxer::Yamux)) => ( - tcp::tokio::Transport::new(tcp::Config::new()) - .upgrade(Version::V1Lazy) - .authenticate(tls::Config::new(&local_key).context("failed to initialise tls")?) - .multiplex(yamux::Config::default()) - .timeout(Duration::from_secs(5)) - .boxed(), + libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + tls::Config::new, + yamux::Config::default, + )? + .with_behaviour(behaviour_constructor)? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) + .build(), format!("/ip4/{ip}/tcp/0"), ), (Transport::Tcp, Some(SecProtocol::Noise), Some(Muxer::Mplex)) => ( - tcp::tokio::Transport::new(tcp::Config::new()) - .upgrade(Version::V1Lazy) - .authenticate( - noise::Config::new(&local_key).context("failed to intialise noise")?, - ) - .multiplex(mplex::MplexConfig::new()) - .timeout(Duration::from_secs(5)) - .boxed(), + libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + mplex::MplexConfig::default, + )? + .with_behaviour(behaviour_constructor)? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) + .build(), format!("/ip4/{ip}/tcp/0"), ), (Transport::Tcp, Some(SecProtocol::Noise), Some(Muxer::Yamux)) => ( - tcp::tokio::Transport::new(tcp::Config::new()) - .upgrade(Version::V1Lazy) - .authenticate( - noise::Config::new(&local_key).context("failed to intialise noise")?, - ) - .multiplex(yamux::Config::default()) - .timeout(Duration::from_secs(5)) - .boxed(), + libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_behaviour(behaviour_constructor)? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) + .build(), format!("/ip4/{ip}/tcp/0"), ), (Transport::Ws, Some(SecProtocol::Tls), Some(Muxer::Mplex)) => ( - WsConfig::new(tcp::tokio::Transport::new(tcp::Config::new())) - .upgrade(Version::V1Lazy) - .authenticate(tls::Config::new(&local_key).context("failed to initialise tls")?) - .multiplex(mplex::MplexConfig::new()) - .timeout(Duration::from_secs(5)) - .boxed(), + libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_websocket(tls::Config::new, mplex::MplexConfig::default) + .await? + .with_behaviour(behaviour_constructor)? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) + .build(), format!("/ip4/{ip}/tcp/0/ws"), ), (Transport::Ws, Some(SecProtocol::Tls), Some(Muxer::Yamux)) => ( - WsConfig::new(tcp::tokio::Transport::new(tcp::Config::new())) - .upgrade(Version::V1Lazy) - .authenticate( - tls::Config::new(&local_key).context("failed to intialise noise")?, - ) - .multiplex(yamux::Config::default()) - .timeout(Duration::from_secs(5)) - .boxed(), + libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_websocket(tls::Config::new, yamux::Config::default) + .await? + .with_behaviour(behaviour_constructor)? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) + .build(), format!("/ip4/{ip}/tcp/0/ws"), ), (Transport::Ws, Some(SecProtocol::Noise), Some(Muxer::Mplex)) => ( - WsConfig::new(tcp::tokio::Transport::new(tcp::Config::new())) - .upgrade(Version::V1Lazy) - .authenticate( - noise::Config::new(&local_key).context("failed to initialise tls")?, - ) - .multiplex(mplex::MplexConfig::new()) - .timeout(Duration::from_secs(5)) - .boxed(), + libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_websocket(noise::Config::new, mplex::MplexConfig::default) + .await? + .with_behaviour(behaviour_constructor)? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) + .build(), format!("/ip4/{ip}/tcp/0/ws"), ), (Transport::Ws, Some(SecProtocol::Noise), Some(Muxer::Yamux)) => ( - WsConfig::new(tcp::tokio::Transport::new(tcp::Config::new())) - .upgrade(Version::V1Lazy) - .authenticate( - noise::Config::new(&local_key).context("failed to intialise noise")?, - ) - .multiplex(yamux::Config::default()) - .timeout(Duration::from_secs(5)) - .boxed(), + libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_websocket(noise::Config::new, yamux::Config::default) + .await? + .with_behaviour(behaviour_constructor)? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) + .build(), format!("/ip4/{ip}/tcp/0/ws"), ), - (Transport::WebRtcDirect, _, _) => ( - webrtc::tokio::Transport::new( - local_key, - webrtc::tokio::Certificate::generate(&mut rand::thread_rng())?, - ) - .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))) - .boxed(), + (Transport::WebRtcDirect, None, None) => ( + libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_other_transport(|key| { + Ok(webrtc::tokio::Transport::new( + key.clone(), + webrtc::tokio::Certificate::generate(&mut rand::thread_rng())?, + )) + })? + .with_behaviour(behaviour_constructor)? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) + .build(), format!("/ip4/{ip}/udp/0/webrtc-direct"), ), - (Transport::Webtransport, _, _) => bail!("Webtransport can only be used with wasm"), - (Transport::Tcp | Transport::Ws, None, _) => { - bail!("Missing security protocol for {transport:?}") - } - (Transport::Tcp | Transport::Ws, _, None) => { - bail!("Missing muxer protocol for {transport:?}") - } + (t, s, m) => bail!("Unsupported combination: {t:?} {s:?} {m:?}"), }; - Ok((transport, addr)) - } - - pub(crate) fn swarm_builder( - transport: BoxedTransport, - behaviour: TBehaviour, - peer_id: PeerId, - ) -> SwarmBuilder { - SwarmBuilder::with_tokio_executor(transport, behaviour, peer_id) + Ok((swarm, addr)) } pub(crate) struct RedisClient(redis::Client); @@ -198,16 +191,14 @@ pub(crate) mod wasm { use futures::future::{BoxFuture, FutureExt}; use libp2p::core::upgrade::Version; use libp2p::identity::Keypair; - use libp2p::swarm::{NetworkBehaviour, SwarmBuilder}; - use libp2p::{noise, yamux, PeerId, Transport as _}; + use libp2p::swarm::{NetworkBehaviour, Swarm}; + use libp2p::{noise, websocket_websys, webtransport_websys, yamux, Transport as _}; use libp2p_mplex as mplex; - use libp2p_webrtc_websys as webrtc; + use libp2p_webrtc_websys as webrtc_websys; use std::time::Duration; use crate::{BlpopRequest, Muxer, SecProtocol, Transport}; - use super::BoxedTransport; - pub(crate) type Instant = instant::Instant; pub(crate) fn init_logger() { @@ -219,70 +210,76 @@ pub(crate) mod wasm { futures_timer::Delay::new(duration).boxed() } - pub(crate) fn build_transport( - local_key: Keypair, + pub(crate) async fn build_swarm( ip: &str, transport: Transport, sec_protocol: Option, muxer: Option, - ) -> Result<(BoxedTransport, String)> { + behaviour_constructor: impl FnOnce(&Keypair) -> B, + ) -> Result<(Swarm, String)> { Ok(match (transport, sec_protocol, muxer) { - (Transport::Webtransport, _, _) => ( - libp2p::webtransport_websys::Transport::new( - libp2p::webtransport_websys::Config::new(&local_key), - ) - .boxed(), + (Transport::Webtransport, None, None) => ( + libp2p::SwarmBuilder::with_new_identity() + .with_wasm_bindgen() + .with_other_transport(|local_key| { + webtransport_websys::Transport::new(webtransport_websys::Config::new( + &local_key, + )) + })? + .with_behaviour(behaviour_constructor)? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) + .build(), format!("/ip4/{ip}/udp/0/quic/webtransport"), ), (Transport::Ws, Some(SecProtocol::Noise), Some(Muxer::Mplex)) => ( - libp2p::websocket_websys::Transport::default() - .upgrade(Version::V1Lazy) - .authenticate( - noise::Config::new(&local_key).context("failed to initialise noise")?, - ) - .multiplex(mplex::MplexConfig::new()) - .timeout(Duration::from_secs(5)) - .boxed(), + libp2p::SwarmBuilder::with_new_identity() + .with_wasm_bindgen() + .with_other_transport(|local_key| { + Ok(websocket_websys::Transport::default() + .upgrade(Version::V1Lazy) + .authenticate( + noise::Config::new(&local_key) + .context("failed to initialise noise")?, + ) + .multiplex(mplex::MplexConfig::new())) + })? + .with_behaviour(behaviour_constructor)? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) + .build(), format!("/ip4/{ip}/tcp/0/wss"), ), (Transport::Ws, Some(SecProtocol::Noise), Some(Muxer::Yamux)) => ( - libp2p::websocket_websys::Transport::default() - .upgrade(Version::V1Lazy) - .authenticate( - noise::Config::new(&local_key).context("failed to initialise noise")?, - ) - .multiplex(yamux::Config::default()) - .timeout(Duration::from_secs(5)) - .boxed(), + libp2p::SwarmBuilder::with_new_identity() + .with_wasm_bindgen() + .with_other_transport(|local_key| { + Ok(websocket_websys::Transport::default() + .upgrade(Version::V1Lazy) + .authenticate( + noise::Config::new(&local_key) + .context("failed to initialise noise")?, + ) + .multiplex(yamux::Config::default())) + })? + .with_behaviour(behaviour_constructor)? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) + .build(), format!("/ip4/{ip}/tcp/0/wss"), ), - (Transport::Ws, None, _) => { - bail!("Missing security protocol for WS") - } - (Transport::Ws, Some(SecProtocol::Tls), _) => { - bail!("TLS not supported in WASM") - } - (Transport::Ws, _, None) => { - bail!("Missing muxer protocol for WS") - } - (Transport::WebRtcDirect, _, _) => ( - webrtc::Transport::new(webrtc::Config::new(&local_key)).boxed(), + (Transport::WebRtcDirect, None, None) => ( + libp2p::SwarmBuilder::with_new_identity() + .with_wasm_bindgen() + .with_other_transport(|local_key| { + webrtc_websys::Transport::new(webrtc_websys::Config::new(&local_key)) + })? + .with_behaviour(behaviour_constructor)? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) + .build(), format!("/ip4/{ip}/udp/0/webrtc-direct"), ), - (Transport::QuicV1 | Transport::Tcp, _, _) => { - bail!("{transport:?} is not supported in WASM") - } + (t, s, m) => bail!("Unsupported combination: {t:?} {s:?} {m:?}"), }) } - pub(crate) fn swarm_builder( - transport: BoxedTransport, - behaviour: TBehaviour, - peer_id: PeerId, - ) -> SwarmBuilder { - SwarmBuilder::with_wasm_executor(transport, behaviour, peer_id) - } - pub(crate) struct RedisClient(String); impl RedisClient { diff --git a/interop-tests/src/lib.rs b/interop-tests/src/lib.rs index 43a0b643ef8..d48fc289d4b 100644 --- a/interop-tests/src/lib.rs +++ b/interop-tests/src/lib.rs @@ -3,14 +3,15 @@ use std::time::Duration; use anyhow::{bail, Context, Result}; use futures::{FutureExt, StreamExt}; +use libp2p::identity::Keypair; use libp2p::swarm::SwarmEvent; -use libp2p::{identify, identity, ping, swarm::NetworkBehaviour, Multiaddr, PeerId}; +use libp2p::{identify, ping, swarm::NetworkBehaviour, Multiaddr}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; mod arch; -use arch::{build_transport, init_logger, swarm_builder, Instant, RedisClient}; +use arch::{build_swarm, init_logger, Instant, RedisClient}; pub async fn run_test( transport: &str, @@ -40,37 +41,23 @@ pub async fn run_test( }) .transpose()?; - let local_key = identity::Keypair::generate_ed25519(); - let local_peer_id = PeerId::from(local_key.public()); let redis_client = RedisClient::new(redis_addr).context("Could not connect to redis")?; // Build the transport from the passed ENV var. - let (boxed_transport, local_addr) = - build_transport(local_key.clone(), ip, transport, sec_protocol, muxer)?; - let mut swarm = swarm_builder( - boxed_transport, - Behaviour { - ping: ping::Behaviour::new(ping::Config::new().with_interval(Duration::from_secs(10))), - // Need to include identify until https://github.com/status-im/nim-libp2p/issues/924 is resolved. - identify: identify::Behaviour::new(identify::Config::new( - "/interop-tests".to_owned(), - local_key.public(), - )), - }, - local_peer_id, - ) - .idle_connection_timeout(Duration::from_secs(5)) - .build(); + let (mut swarm, local_addr) = + build_swarm(ip, transport, sec_protocol, muxer, build_behaviour).await?; log::info!("Running ping test: {}", swarm.local_peer_id()); - let mut maybe_id = None; - // See https://github.com/libp2p/rust-libp2p/issues/4071. #[cfg(not(target_arch = "wasm32"))] - if transport == Transport::WebRtcDirect { - maybe_id = Some(swarm.listen_on(local_addr.parse()?)?); - } + let maybe_id = if transport == Transport::WebRtcDirect { + Some(swarm.listen_on(local_addr.parse()?)?) + } else { + None + }; + #[cfg(target_arch = "wasm32")] + let maybe_id = None; // Run a ping interop test. Based on `is_dialer`, either dial the address // retrieved via `listenAddr` key over the redis connection. Or wait to be pinged and have @@ -129,9 +116,8 @@ pub async fn run_test( continue; } if listener_id == id { - let ma = format!("{address}/p2p/{local_peer_id}"); + let ma = format!("{address}/p2p/{}", swarm.local_peer_id()); redis_client.rpush("listenerAddr", ma.clone()).await?; - break; } } @@ -271,7 +257,18 @@ impl FromStr for SecProtocol { } #[derive(NetworkBehaviour)] -struct Behaviour { +pub(crate) struct Behaviour { ping: ping::Behaviour, identify: identify::Behaviour, } + +pub(crate) fn build_behaviour(key: &Keypair) -> Behaviour { + Behaviour { + ping: ping::Behaviour::new(ping::Config::new().with_interval(Duration::from_secs(1))), + // Need to include identify until https://github.com/status-im/nim-libp2p/issues/924 is resolved. + identify: identify::Behaviour::new(identify::Config::new( + "/interop-tests".to_owned(), + key.public(), + )), + } +} diff --git a/libp2p/CHANGELOG.md b/libp2p/CHANGELOG.md index f60ccc07ab3..085268c52dd 100644 --- a/libp2p/CHANGELOG.md +++ b/libp2p/CHANGELOG.md @@ -4,7 +4,12 @@ This supersedes the existing `libp2p::wasm_ext` module which is now deprecated. See [PR 3679]. +- Introduce a new `libp2p::SwarmBuilder` in favor of the now deprecated `libp2p::swarm::SwarmBuilder`. + See `libp2p::SwarmBuilder` docs on how to use the new builder. + Also see [PR 4120]. + [PR 3679]: https://github.com/libp2p/rust-libp2p/pull/3679 +[PR 4120]: https://github.com/libp2p/rust-libp2p/pull/4120 ## 0.52.3 diff --git a/libp2p/Cargo.toml b/libp2p/Cargo.toml index d57653bcd40..36c987ee9f2 100644 --- a/libp2p/Cargo.toml +++ b/libp2p/Cargo.toml @@ -97,10 +97,13 @@ upnp = ["dep:libp2p-upnp"] [dependencies] bytes = "1" +either = "1.9.0" futures = "0.3.26" futures-timer = "3.0.2" # Explicit dependency to be used in `wasm-bindgen` feature getrandom = "0.2.3" # Explicit dependency to be used in `wasm-bindgen` feature instant = "0.1.12" # Explicit dependency to be used in `wasm-bindgen` feature +# TODO feature flag? +rw-stream-sink = { workspace = true } libp2p-allow-block-list = { workspace = true } libp2p-autonat = { workspace = true, optional = true } @@ -127,6 +130,7 @@ libp2p-webtransport-websys = { workspace = true, optional = true } libp2p-yamux = { workspace = true, optional = true } multiaddr = { workspace = true } pin-project = "1.0.0" +thiserror = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] libp2p-deflate = { workspace = true, optional = true } @@ -148,6 +152,7 @@ env_logger = "0.10.0" clap = { version = "4.1.6", features = ["derive"] } tokio = { version = "1.15", features = [ "io-util", "io-std", "macros", "rt", "rt-multi-thread"] } +libp2p-mplex = { workspace = true } libp2p-noise = { workspace = true } libp2p-tcp = { workspace = true, features = ["tokio"] } diff --git a/libp2p/src/builder.rs b/libp2p/src/builder.rs new file mode 100644 index 00000000000..1b07689d2f6 --- /dev/null +++ b/libp2p/src/builder.rs @@ -0,0 +1,322 @@ +use std::marker::PhantomData; + +mod phase; +mod select_security; + +/// Build a [`Swarm`](libp2p_swarm::Swarm) by combining an identity, a set of +/// [`Transport`](libp2p_core::Transport)s and a +/// [`NetworkBehaviour`](libp2p_swarm::NetworkBehaviour). +/// +/// ``` +/// # use libp2p::{swarm::NetworkBehaviour, SwarmBuilder}; +/// # use libp2p::core::transport::dummy::DummyTransport; +/// # use libp2p::core::muxing::StreamMuxerBox; +/// # use libp2p::identity::PeerId; +/// # use std::error::Error; +/// # +/// # #[cfg(all( +/// # not(target_arch = "wasm32"), +/// # feature = "tokio", +/// # feature = "tcp", +/// # feature = "tls", +/// # feature = "noise", +/// # feature = "quic", +/// # feature = "dns", +/// # feature = "relay", +/// # feature = "websocket", +/// # ))] +/// # async fn build_swarm() -> Result<(), Box> { +/// # #[derive(NetworkBehaviour)] +/// # #[behaviour(prelude = "libp2p_swarm::derive_prelude")] +/// # struct MyBehaviour { +/// # relay: libp2p_relay::client::Behaviour, +/// # } +/// +/// let swarm = SwarmBuilder::with_new_identity() +/// .with_tokio() +/// .with_tcp( +/// Default::default(), +/// (libp2p_tls::Config::new, libp2p_noise::Config::new), +/// libp2p_yamux::Config::default, +/// )? +/// .with_quic() +/// .with_other_transport(|_key| DummyTransport::<(PeerId, StreamMuxerBox)>::new())? +/// .with_dns()? +/// .with_relay_client( +/// (libp2p_tls::Config::new, libp2p_noise::Config::new), +/// libp2p_yamux::Config::default, +/// )? +/// .with_websocket( +/// (libp2p_tls::Config::new, libp2p_noise::Config::new), +/// libp2p_yamux::Config::default, +/// ) +/// .await? +/// .with_behaviour(|_key, relay| MyBehaviour { relay })? +/// .build(); +/// # +/// # Ok(()) +/// # } +/// ``` +pub struct SwarmBuilder { + keypair: libp2p_identity::Keypair, + phantom: PhantomData, + phase: Phase, +} + +#[cfg(test)] +mod tests { + use crate::SwarmBuilder; + use libp2p_swarm::{NetworkBehaviour, Swarm}; + + #[test] + #[cfg(all(feature = "tokio", feature = "tcp", feature = "tls", feature = "noise"))] + fn tcp() { + let _ = SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + Default::default(), + libp2p_tls::Config::new, + libp2p_yamux::Config::default, + ) + .unwrap() + .with_behaviour(|_| libp2p_swarm::dummy::Behaviour) + .unwrap() + .build(); + } + + #[test] + #[cfg(all( + feature = "async-std", + feature = "tcp", + feature = "tls", + feature = "noise" + ))] + fn async_std_tcp() { + let _ = SwarmBuilder::with_new_identity() + .with_async_std() + .with_tcp( + Default::default(), + libp2p_tls::Config::new, + libp2p_yamux::Config::default, + ) + .unwrap() + .with_behaviour(|_| libp2p_swarm::dummy::Behaviour) + .unwrap() + .build(); + } + + #[test] + #[cfg(all(feature = "tokio", feature = "tcp", feature = "tls", feature = "mplex"))] + fn tcp_yamux_mplex() { + let _ = SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + Default::default(), + libp2p_tls::Config::new, + ( + libp2p_yamux::Config::default(), + libp2p_mplex::MplexConfig::default(), + ), + ) + .unwrap() + .with_behaviour(|_| libp2p_swarm::dummy::Behaviour) + .unwrap() + .build(); + } + + #[test] + #[cfg(all(feature = "tokio", feature = "tcp", feature = "tls", feature = "noise"))] + fn tcp_tls_noise() { + let _ = SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + Default::default(), + (libp2p_tls::Config::new, libp2p_noise::Config::new), + ( + libp2p_yamux::Config::default, + libp2p_mplex::MplexConfig::default, + ), + ) + .unwrap() + .with_behaviour(|_| libp2p_swarm::dummy::Behaviour) + .unwrap() + .build(); + } + + #[test] + #[cfg(all( + feature = "tokio", + feature = "tcp", + feature = "tls", + feature = "noise", + feature = "quic" + ))] + fn tcp_quic() { + let _ = SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + Default::default(), + (libp2p_tls::Config::new, libp2p_noise::Config::new), + libp2p_yamux::Config::default, + ) + .unwrap() + .with_quic() + .with_behaviour(|_| libp2p_swarm::dummy::Behaviour) + .unwrap() + .build(); + } + + #[test] + #[cfg(all( + feature = "tokio", + feature = "tcp", + feature = "tls", + feature = "noise", + feature = "relay" + ))] + fn tcp_relay() { + #[derive(libp2p_swarm::NetworkBehaviour)] + #[behaviour(prelude = "libp2p_swarm::derive_prelude")] + struct Behaviour { + dummy: libp2p_swarm::dummy::Behaviour, + relay: libp2p_relay::client::Behaviour, + } + + let _ = SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + Default::default(), + libp2p_tls::Config::new, + libp2p_yamux::Config::default, + ) + .unwrap() + .with_relay_client(libp2p_tls::Config::new, libp2p_yamux::Config::default) + .unwrap() + .with_behaviour(|_, relay| Behaviour { + dummy: libp2p_swarm::dummy::Behaviour, + relay, + }) + .unwrap() + .build(); + } + + #[tokio::test] + #[cfg(all( + feature = "tokio", + feature = "tcp", + feature = "tls", + feature = "noise", + feature = "dns" + ))] + async fn tcp_dns() { + SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + Default::default(), + (libp2p_tls::Config::new, libp2p_noise::Config::new), + libp2p_yamux::Config::default, + ) + .unwrap() + .with_dns() + .unwrap() + .with_behaviour(|_| libp2p_swarm::dummy::Behaviour) + .unwrap() + .build(); + } + + /// Showcases how to provide custom transports unknown to the libp2p crate, e.g. WebRTC. + #[test] + #[cfg(feature = "tokio")] + fn other_transport() -> Result<(), Box> { + use libp2p_core::{muxing::StreamMuxerBox, transport::dummy::DummyTransport}; + use libp2p_identity::PeerId; + + let _ = SwarmBuilder::with_new_identity() + .with_tokio() + // Closure can either return a Transport directly. + .with_other_transport(|_| DummyTransport::<(PeerId, StreamMuxerBox)>::new())? + // Or a Result containing a Transport. + .with_other_transport(|_| { + if true { + Ok(DummyTransport::<(PeerId, StreamMuxerBox)>::new()) + } else { + Err(Box::from("test")) + } + })? + .with_behaviour(|_| libp2p_swarm::dummy::Behaviour) + .unwrap() + .build(); + + Ok(()) + } + + #[tokio::test] + #[cfg(all( + feature = "tokio", + feature = "tcp", + feature = "tls", + feature = "noise", + feature = "dns", + feature = "websocket", + ))] + async fn tcp_websocket() { + let _ = SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + Default::default(), + (libp2p_tls::Config::new, libp2p_noise::Config::new), + libp2p_yamux::Config::default, + ) + .unwrap() + .with_websocket( + (libp2p_tls::Config::new, libp2p_noise::Config::new), + libp2p_yamux::Config::default, + ) + .await + .unwrap() + .with_behaviour(|_| libp2p_swarm::dummy::Behaviour) + .unwrap() + .build(); + } + + #[tokio::test] + #[cfg(all( + feature = "tokio", + feature = "tcp", + feature = "tls", + feature = "noise", + feature = "quic", + feature = "dns", + feature = "relay", + feature = "websocket", + ))] + async fn all() { + #[derive(NetworkBehaviour)] + #[behaviour(prelude = "libp2p_swarm::derive_prelude")] + struct MyBehaviour { + relay: libp2p_relay::client::Behaviour, + } + + let (builder, _bandwidth_sinks) = SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + Default::default(), + libp2p_tls::Config::new, + libp2p_yamux::Config::default, + ) + .unwrap() + .with_quic() + .with_dns() + .unwrap() + .with_relay_client(libp2p_tls::Config::new, libp2p_yamux::Config::default) + .unwrap() + .with_websocket(libp2p_tls::Config::new, libp2p_yamux::Config::default) + .await + .unwrap() + .with_bandwidth_logging(); + let _: Swarm = builder + .with_behaviour(|_key, relay| MyBehaviour { relay }) + .unwrap() + .build(); + } +} diff --git a/libp2p/src/builder/phase.rs b/libp2p/src/builder/phase.rs new file mode 100644 index 00000000000..dbf9eb883ae --- /dev/null +++ b/libp2p/src/builder/phase.rs @@ -0,0 +1,134 @@ +#![allow(unused_imports)] + +mod bandwidth_logging; +mod behaviour; +mod build; +mod dns; +mod identity; +mod other_transport; +mod provider; +mod quic; +mod relay; +mod swarm; +mod tcp; +mod websocket; + +use bandwidth_logging::*; +use behaviour::*; +use build::*; +use dns::*; +use other_transport::*; +use provider::*; +use quic::*; +use relay::*; +use swarm::*; +use tcp::*; +use websocket::*; + +use super::select_security::SelectSecurityUpgrade; +use super::SwarmBuilder; + +use libp2p_core::{muxing::StreamMuxerBox, upgrade::SelectUpgrade, Transport}; +use libp2p_identity::Keypair; + +pub trait IntoSecurityUpgrade { + type Upgrade; + type Error; + + fn into_security_upgrade(self, keypair: &Keypair) -> Result; +} + +impl IntoSecurityUpgrade for F +where + F: for<'a> FnOnce(&'a Keypair) -> Result, +{ + type Upgrade = T; + type Error = E; + + fn into_security_upgrade(self, keypair: &Keypair) -> Result { + (self)(keypair) + } +} + +impl IntoSecurityUpgrade for (F1, F2) +where + F1: IntoSecurityUpgrade, + F2: IntoSecurityUpgrade, +{ + type Upgrade = SelectSecurityUpgrade; + type Error = either::Either; + + fn into_security_upgrade(self, keypair: &Keypair) -> Result { + let (f1, f2) = self; + + let u1 = f1 + .into_security_upgrade(keypair) + .map_err(either::Either::Left)?; + let u2 = f2 + .into_security_upgrade(keypair) + .map_err(either::Either::Right)?; + + Ok(SelectSecurityUpgrade::new(u1, u2)) + } +} + +pub trait IntoMultiplexerUpgrade { + type Upgrade; + + fn into_multiplexer_upgrade(self) -> Self::Upgrade; +} + +impl IntoMultiplexerUpgrade for F +where + F: FnOnce() -> U, +{ + type Upgrade = U; + + fn into_multiplexer_upgrade(self) -> Self::Upgrade { + (self)() + } +} + +impl IntoMultiplexerUpgrade for (U1, U2) +where + U1: IntoMultiplexerUpgrade, + U2: IntoMultiplexerUpgrade, +{ + type Upgrade = SelectUpgrade; + + fn into_multiplexer_upgrade(self) -> Self::Upgrade { + let (f1, f2) = self; + + let u1 = f1.into_multiplexer_upgrade(); + let u2 = f2.into_multiplexer_upgrade(); + + SelectUpgrade::new(u1, u2) + } +} + +pub trait AuthenticatedMultiplexedTransport: + Transport< + Error = Self::E, + Dial = Self::D, + ListenerUpgrade = Self::U, + Output = (libp2p_identity::PeerId, StreamMuxerBox), + > + Send + + Unpin + + 'static +{ + type E: Send + Sync + 'static; + type D: Send; + type U: Send; +} + +impl AuthenticatedMultiplexedTransport for T +where + T: Transport + Send + Unpin + 'static, + ::Error: Send + Sync + 'static, + ::Dial: Send, + ::ListenerUpgrade: Send, +{ + type E = T::Error; + type D = T::Dial; + type U = T::ListenerUpgrade; +} diff --git a/libp2p/src/builder/phase/bandwidth_logging.rs b/libp2p/src/builder/phase/bandwidth_logging.rs new file mode 100644 index 00000000000..3f3142d31a2 --- /dev/null +++ b/libp2p/src/builder/phase/bandwidth_logging.rs @@ -0,0 +1,70 @@ +use super::*; +use crate::bandwidth::BandwidthSinks; +use crate::transport_ext::TransportExt; +use crate::SwarmBuilder; +use std::marker::PhantomData; +use std::sync::Arc; + +pub struct BandwidthLoggingPhase { + pub(crate) relay_behaviour: R, + pub(crate) transport: T, +} + +impl + SwarmBuilder> +{ + pub fn with_bandwidth_logging( + self, + ) -> ( + SwarmBuilder>, + Arc, + ) { + let (transport, sinks) = self.phase.transport.with_bandwidth_logging(); + ( + SwarmBuilder { + phase: BehaviourPhase { + relay_behaviour: self.phase.relay_behaviour, + transport, + }, + keypair: self.keypair, + phantom: PhantomData, + }, + sinks, + ) + } + + pub fn without_bandwidth_logging(self) -> SwarmBuilder> { + SwarmBuilder { + phase: BehaviourPhase { + relay_behaviour: self.phase.relay_behaviour, + transport: self.phase.transport, + }, + keypair: self.keypair, + phantom: PhantomData, + } + } +} + +// Shortcuts +#[cfg(feature = "relay")] +impl + SwarmBuilder> +{ + pub fn with_behaviour>( + self, + constructor: impl FnOnce(&libp2p_identity::Keypair, libp2p_relay::client::Behaviour) -> R, + ) -> Result>, R::Error> { + self.without_bandwidth_logging().with_behaviour(constructor) + } +} + +impl + SwarmBuilder> +{ + pub fn with_behaviour>( + self, + constructor: impl FnOnce(&libp2p_identity::Keypair) -> R, + ) -> Result>, R::Error> { + self.without_bandwidth_logging().with_behaviour(constructor) + } +} diff --git a/libp2p/src/builder/phase/behaviour.rs b/libp2p/src/builder/phase/behaviour.rs new file mode 100644 index 00000000000..939db935c80 --- /dev/null +++ b/libp2p/src/builder/phase/behaviour.rs @@ -0,0 +1,90 @@ +use super::*; +use crate::SwarmBuilder; +use libp2p_swarm::NetworkBehaviour; +use std::convert::Infallible; +use std::marker::PhantomData; + +pub struct BehaviourPhase { + pub(crate) relay_behaviour: R, + pub(crate) transport: T, +} + +#[cfg(feature = "relay")] +impl SwarmBuilder> { + pub fn with_behaviour>( + self, + constructor: impl FnOnce(&libp2p_identity::Keypair, libp2p_relay::client::Behaviour) -> R, + ) -> Result>, R::Error> { + Ok(SwarmBuilder { + phase: SwarmPhase { + behaviour: constructor(&self.keypair, self.phase.relay_behaviour) + .try_into_behaviour()?, + transport: self.phase.transport, + }, + keypair: self.keypair, + phantom: PhantomData, + }) + } +} + +impl SwarmBuilder> { + pub fn with_behaviour>( + self, + constructor: impl FnOnce(&libp2p_identity::Keypair) -> R, + ) -> Result>, R::Error> { + // Discard `NoRelayBehaviour`. + let _ = self.phase.relay_behaviour; + + Ok(SwarmBuilder { + phase: SwarmPhase { + behaviour: constructor(&self.keypair).try_into_behaviour()?, + transport: self.phase.transport, + }, + keypair: self.keypair, + phantom: PhantomData, + }) + } +} + +pub trait TryIntoBehaviour: private::Sealed { + type Error; + + fn try_into_behaviour(self) -> Result; +} + +impl TryIntoBehaviour for B +where + B: NetworkBehaviour, +{ + type Error = Infallible; + + fn try_into_behaviour(self) -> Result { + Ok(self) + } +} + +impl TryIntoBehaviour for Result> +where + B: NetworkBehaviour, +{ + type Error = BehaviourError; + + fn try_into_behaviour(self) -> Result { + self.map_err(BehaviourError) + } +} + +mod private { + pub trait Sealed {} +} + +impl private::Sealed for B {} + +impl private::Sealed + for Result> +{ +} + +#[derive(Debug, thiserror::Error)] +#[error("failed to build behaviour: {0}")] +pub struct BehaviourError(Box); diff --git a/libp2p/src/builder/phase/build.rs b/libp2p/src/builder/phase/build.rs new file mode 100644 index 00000000000..80a83994eeb --- /dev/null +++ b/libp2p/src/builder/phase/build.rs @@ -0,0 +1,31 @@ +#[allow(unused_imports)] +use super::*; + +use crate::SwarmBuilder; +use libp2p_core::Transport; +use libp2p_swarm::Swarm; + +pub struct BuildPhase { + pub(crate) behaviour: B, + pub(crate) transport: T, + pub(crate) swarm_config: libp2p_swarm::Config, +} + +const CONNECTION_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); + +impl + SwarmBuilder> +{ + pub fn build(self) -> Swarm { + Swarm::new( + libp2p_core::transport::timeout::TransportTimeout::new( + self.phase.transport, + CONNECTION_TIMEOUT, + ) + .boxed(), + self.phase.behaviour, + self.keypair.public().to_peer_id(), + self.phase.swarm_config, + ) + } +} diff --git a/libp2p/src/builder/phase/dns.rs b/libp2p/src/builder/phase/dns.rs new file mode 100644 index 00000000000..1d20cacd26d --- /dev/null +++ b/libp2p/src/builder/phase/dns.rs @@ -0,0 +1,68 @@ +use super::*; +use crate::SwarmBuilder; +use std::marker::PhantomData; + +pub struct DnsPhase { + pub(crate) transport: T, +} + +#[cfg(all(not(target_arch = "wasm32"), feature = "async-std", feature = "dns"))] +impl SwarmBuilder> { + pub async fn with_dns( + self, + ) -> Result< + SwarmBuilder>, + std::io::Error, + > { + Ok(SwarmBuilder { + keypair: self.keypair, + phantom: PhantomData, + phase: RelayPhase { + transport: libp2p_dns::async_std::Transport::system(self.phase.transport).await?, + }, + }) + } +} + +#[cfg(all(not(target_arch = "wasm32"), feature = "tokio", feature = "dns"))] +impl SwarmBuilder> { + pub fn with_dns( + self, + ) -> Result< + SwarmBuilder>, + std::io::Error, + > { + Ok(SwarmBuilder { + keypair: self.keypair, + phantom: PhantomData, + phase: RelayPhase { + transport: libp2p_dns::tokio::Transport::system(self.phase.transport)?, + }, + }) + } +} + +impl SwarmBuilder> { + pub(crate) fn without_dns(self) -> SwarmBuilder> { + SwarmBuilder { + keypair: self.keypair, + phantom: PhantomData, + phase: RelayPhase { + transport: self.phase.transport, + }, + } + } +} + +// Shortcuts +impl SwarmBuilder> { + pub fn with_behaviour>( + self, + constructor: impl FnOnce(&libp2p_identity::Keypair) -> R, + ) -> Result>, R::Error> { + self.without_dns() + .without_relay() + .without_websocket() + .with_behaviour(constructor) + } +} diff --git a/libp2p/src/builder/phase/identity.rs b/libp2p/src/builder/phase/identity.rs new file mode 100644 index 00000000000..ceb86819dc7 --- /dev/null +++ b/libp2p/src/builder/phase/identity.rs @@ -0,0 +1,21 @@ +use super::*; +use crate::SwarmBuilder; +use std::marker::PhantomData; + +pub struct IdentityPhase {} + +impl SwarmBuilder { + pub fn with_new_identity() -> SwarmBuilder { + SwarmBuilder::with_existing_identity(libp2p_identity::Keypair::generate_ed25519()) + } + + pub fn with_existing_identity( + keypair: libp2p_identity::Keypair, + ) -> SwarmBuilder { + SwarmBuilder { + keypair, + phantom: PhantomData, + phase: ProviderPhase {}, + } + } +} diff --git a/libp2p/src/builder/phase/other_transport.rs b/libp2p/src/builder/phase/other_transport.rs new file mode 100644 index 00000000000..6a050f4f2de --- /dev/null +++ b/libp2p/src/builder/phase/other_transport.rs @@ -0,0 +1,208 @@ +use std::convert::Infallible; +use std::marker::PhantomData; +use std::sync::Arc; + +use libp2p_core::Transport; +#[cfg(feature = "relay")] +use libp2p_core::{InboundUpgrade, Negotiated, OutboundUpgrade, UpgradeInfo}; +#[cfg(feature = "relay")] +use libp2p_identity::PeerId; + +use crate::bandwidth::BandwidthSinks; +use crate::SwarmBuilder; + +use super::*; + +pub struct OtherTransportPhase { + pub(crate) transport: T, +} + +impl + SwarmBuilder> +{ + pub fn with_other_transport< + Muxer: libp2p_core::muxing::StreamMuxer + Send + 'static, + OtherTransport: Transport + Send + Unpin + 'static, + R: TryIntoTransport, + >( + self, + constructor: impl FnOnce(&libp2p_identity::Keypair) -> R, + ) -> Result< + SwarmBuilder>, + R::Error, + > + where + ::Error: Send + Sync + 'static, + ::Dial: Send, + ::ListenerUpgrade: Send, + ::Substream: Send, + ::Error: Send + Sync, + { + Ok(SwarmBuilder { + phase: OtherTransportPhase { + transport: self + .phase + .transport + .or_transport( + constructor(&self.keypair) + .try_into_transport()? + .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn))), + ) + .map(|either, _| either.into_inner()), + }, + keypair: self.keypair, + phantom: PhantomData, + }) + } + + pub(crate) fn without_any_other_transports(self) -> SwarmBuilder> { + SwarmBuilder { + keypair: self.keypair, + phantom: PhantomData, + phase: DnsPhase { + transport: self.phase.transport, + }, + } + } +} + +// Shortcuts +#[cfg(all(not(target_arch = "wasm32"), feature = "async-std", feature = "dns"))] +impl + SwarmBuilder> +{ + pub async fn with_dns( + self, + ) -> Result< + SwarmBuilder>, + std::io::Error, + > { + self.without_any_other_transports().with_dns().await + } +} +#[cfg(all(not(target_arch = "wasm32"), feature = "tokio", feature = "dns"))] +impl + SwarmBuilder> +{ + pub fn with_dns( + self, + ) -> Result< + SwarmBuilder>, + std::io::Error, + > { + self.without_any_other_transports().with_dns() + } +} +#[cfg(feature = "relay")] +impl + SwarmBuilder> +{ + /// See [`SwarmBuilder::with_relay_client`]. + pub fn with_relay_client( + self, + security_upgrade: SecUpgrade, + multiplexer_upgrade: MuxUpgrade, + ) -> Result< + SwarmBuilder< + Provider, + WebsocketPhase, + >, + SecUpgrade::Error, + > where + + SecStream: futures::AsyncRead + futures::AsyncWrite + Unpin + Send + 'static, + SecError: std::error::Error + Send + Sync + 'static, + SecUpgrade: IntoSecurityUpgrade, + SecUpgrade::Upgrade: InboundUpgrade, Output = (PeerId, SecStream), Error = SecError> + OutboundUpgrade, Output = (PeerId, SecStream), Error = SecError> + Clone + Send + 'static, + >>::Future: Send, + >>::Future: Send, + <<>::Upgrade as UpgradeInfo>::InfoIter as IntoIterator>::IntoIter: Send, + <>::Upgrade as UpgradeInfo>::Info: Send, + + MuxStream: libp2p_core::muxing::StreamMuxer + Send + 'static, + MuxStream::Substream: Send + 'static, + MuxStream::Error: Send + Sync + 'static, + MuxUpgrade: IntoMultiplexerUpgrade, + MuxUpgrade::Upgrade: InboundUpgrade, Output = MuxStream, Error = MuxError> + OutboundUpgrade, Output = MuxStream, Error = MuxError> + Clone + Send + 'static, + >>::Future: Send, + >>::Future: Send, + MuxError: std::error::Error + Send + Sync + 'static, + <<>::Upgrade as UpgradeInfo>::InfoIter as IntoIterator>::IntoIter: Send, + <>::Upgrade as UpgradeInfo>::Info: Send, + { + self.without_any_other_transports() + .without_dns() + .with_relay_client(security_upgrade, multiplexer_upgrade) + } +} +impl + SwarmBuilder> +{ + pub fn with_bandwidth_logging( + self, + ) -> ( + SwarmBuilder< + Provider, + BehaviourPhase, + >, + Arc, + ) { + self.without_any_other_transports() + .without_dns() + .without_relay() + .without_websocket() + .with_bandwidth_logging() + } +} +impl + SwarmBuilder> +{ + pub fn with_behaviour>( + self, + constructor: impl FnOnce(&libp2p_identity::Keypair) -> R, + ) -> Result>, R::Error> { + self.without_any_other_transports() + .without_dns() + .without_relay() + .without_websocket() + .without_bandwidth_logging() + .with_behaviour(constructor) + } +} + +pub trait TryIntoTransport: private::Sealed { + type Error; + + fn try_into_transport(self) -> Result; +} + +impl TryIntoTransport for T { + type Error = Infallible; + + fn try_into_transport(self) -> Result { + Ok(self) + } +} + +impl TryIntoTransport for Result> { + type Error = TransportError; + + fn try_into_transport(self) -> Result { + self.map_err(TransportError) + } +} + +mod private { + pub trait Sealed {} +} + +impl private::Sealed for T {} + +impl private::Sealed + for Result> +{ +} + +#[derive(Debug, thiserror::Error)] +#[error("failed to build transport: {0}")] +pub struct TransportError(Box); diff --git a/libp2p/src/builder/phase/provider.rs b/libp2p/src/builder/phase/provider.rs new file mode 100644 index 00000000000..32321442689 --- /dev/null +++ b/libp2p/src/builder/phase/provider.rs @@ -0,0 +1,46 @@ +#[allow(unused_imports)] +use super::*; + +use crate::SwarmBuilder; + +pub struct ProviderPhase {} + +impl SwarmBuilder { + #[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))] + pub fn with_async_std(self) -> SwarmBuilder { + SwarmBuilder { + keypair: self.keypair, + phantom: std::marker::PhantomData, + phase: TcpPhase {}, + } + } + + #[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))] + pub fn with_tokio(self) -> SwarmBuilder { + SwarmBuilder { + keypair: self.keypair, + phantom: std::marker::PhantomData, + phase: TcpPhase {}, + } + } + + #[cfg(feature = "wasm-bindgen")] + pub fn with_wasm_bindgen(self) -> SwarmBuilder { + SwarmBuilder { + keypair: self.keypair, + phantom: std::marker::PhantomData, + phase: TcpPhase {}, + } + } +} + +pub enum NoProviderSpecified {} + +#[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))] +pub enum AsyncStd {} + +#[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))] +pub enum Tokio {} + +#[cfg(feature = "wasm-bindgen")] +pub enum WasmBindgen {} diff --git a/libp2p/src/builder/phase/quic.rs b/libp2p/src/builder/phase/quic.rs new file mode 100644 index 00000000000..6ef056aeaf0 --- /dev/null +++ b/libp2p/src/builder/phase/quic.rs @@ -0,0 +1,239 @@ +use super::*; +use crate::SwarmBuilder; +#[cfg(all(not(target_arch = "wasm32"), feature = "websocket"))] +use libp2p_core::muxing::StreamMuxer; +#[cfg(any( + feature = "relay", + all(not(target_arch = "wasm32"), feature = "websocket") +))] +use libp2p_core::{InboundUpgrade, Negotiated, OutboundUpgrade, UpgradeInfo}; +use std::marker::PhantomData; + +pub struct QuicPhase { + pub(crate) transport: T, +} + +macro_rules! impl_quic_builder { + ($providerKebabCase:literal, $providerPascalCase:ty, $quic:ident) => { + #[cfg(all(not(target_arch = "wasm32"), feature = "quic", feature = $providerKebabCase))] + impl SwarmBuilder<$providerPascalCase, QuicPhase> { + pub fn with_quic( + self, + ) -> SwarmBuilder< + $providerPascalCase, + OtherTransportPhase, + > { + self.with_quic_config(std::convert::identity) + } + + pub fn with_quic_config( + self, + constructor: impl FnOnce(libp2p_quic::Config) -> libp2p_quic::Config, + ) -> SwarmBuilder< + $providerPascalCase, + OtherTransportPhase, + > { + SwarmBuilder { + phase: OtherTransportPhase { + transport: self + .phase + .transport + .or_transport( + libp2p_quic::$quic::Transport::new(constructor( + libp2p_quic::Config::new(&self.keypair), + )) + .map(|(peer_id, muxer), _| { + (peer_id, libp2p_core::muxing::StreamMuxerBox::new(muxer)) + }), + ) + .map(|either, _| either.into_inner()), + }, + keypair: self.keypair, + phantom: PhantomData, + } + } + } + }; +} + +impl_quic_builder!("async-std", AsyncStd, async_std); +impl_quic_builder!("tokio", super::provider::Tokio, tokio); + +impl SwarmBuilder> { + pub(crate) fn without_quic(self) -> SwarmBuilder> { + SwarmBuilder { + keypair: self.keypair, + phantom: PhantomData, + phase: OtherTransportPhase { + transport: self.phase.transport, + }, + } + } +} + +// Shortcuts +impl SwarmBuilder> { + /// See [`SwarmBuilder::with_relay_client`]. + #[cfg(feature = "relay")] + pub fn with_relay_client( + self, + security_upgrade: SecUpgrade, + multiplexer_upgrade: MuxUpgrade, + ) -> Result< + SwarmBuilder< + Provider, + super::websocket::WebsocketPhase, + >, + SecUpgrade::Error, + > where + + SecStream: futures::AsyncRead + futures::AsyncWrite + Unpin + Send + 'static, + SecError: std::error::Error + Send + Sync + 'static, + SecUpgrade: IntoSecurityUpgrade, + SecUpgrade::Upgrade: InboundUpgrade, Output = (libp2p_identity::PeerId, SecStream), Error = SecError> + OutboundUpgrade, Output = (libp2p_identity::PeerId, SecStream), Error = SecError> + Clone + Send + 'static, + >>::Future: Send, + >>::Future: Send, + <<>::Upgrade as UpgradeInfo>::InfoIter as IntoIterator>::IntoIter: Send, + <>::Upgrade as UpgradeInfo>::Info: Send, + + MuxStream: libp2p_core::muxing::StreamMuxer + Send + 'static, + MuxStream::Substream: Send + 'static, + MuxStream::Error: Send + Sync + 'static, + MuxUpgrade: IntoMultiplexerUpgrade, + MuxUpgrade::Upgrade: InboundUpgrade, Output = MuxStream, Error = MuxError> + OutboundUpgrade, Output = MuxStream, Error = MuxError> + Clone + Send + 'static, + >>::Future: Send, + >>::Future: Send, + MuxError: std::error::Error + Send + Sync + 'static, + <<>::Upgrade as UpgradeInfo>::InfoIter as IntoIterator>::IntoIter: Send, + <>::Upgrade as UpgradeInfo>::Info: Send, + { + self.without_quic() + .with_relay_client(security_upgrade, multiplexer_upgrade) + } + + pub fn with_other_transport< + Muxer: libp2p_core::muxing::StreamMuxer + Send + 'static, + OtherTransport: Transport + Send + Unpin + 'static, + R: TryIntoTransport, + >( + self, + constructor: impl FnOnce(&libp2p_identity::Keypair) -> R, + ) -> Result< + SwarmBuilder>, + R::Error, + > + where + ::Error: Send + Sync + 'static, + ::Dial: Send, + ::ListenerUpgrade: Send, + ::Substream: Send, + ::Error: Send + Sync, + { + self.without_quic().with_other_transport(constructor) + } + + pub fn with_behaviour>( + self, + constructor: impl FnOnce(&libp2p_identity::Keypair) -> R, + ) -> Result>, R::Error> { + self.without_quic() + .without_any_other_transports() + .without_dns() + .without_relay() + .without_websocket() + .with_behaviour(constructor) + } +} +#[cfg(all(not(target_arch = "wasm32"), feature = "async-std", feature = "dns"))] +impl SwarmBuilder> { + pub async fn with_dns( + self, + ) -> Result< + SwarmBuilder>, + std::io::Error, + > { + self.without_quic() + .without_any_other_transports() + .with_dns() + .await + } +} +#[cfg(all(not(target_arch = "wasm32"), feature = "tokio", feature = "dns"))] +impl SwarmBuilder> { + pub fn with_dns( + self, + ) -> Result< + SwarmBuilder>, + std::io::Error, + > { + self.without_quic() + .without_any_other_transports() + .with_dns() + } +} +macro_rules! impl_quic_phase_with_websocket { + ($providerKebabCase:literal, $providerPascalCase:ty, $websocketStream:ty) => { + #[cfg(all(feature = $providerKebabCase, not(target_arch = "wasm32"), feature = "websocket"))] + impl SwarmBuilder<$providerPascalCase, QuicPhase> { + /// See [`SwarmBuilder::with_websocket`]. + pub async fn with_websocket < + SecUpgrade, + SecStream, + SecError, + MuxUpgrade, + MuxStream, + MuxError, + > ( + self, + security_upgrade: SecUpgrade, + multiplexer_upgrade: MuxUpgrade, + ) -> Result< + SwarmBuilder< + $providerPascalCase, + BandwidthLoggingPhase, + >, + super::websocket::WebsocketError, + > + where + SecStream: futures::AsyncRead + futures::AsyncWrite + Unpin + Send + 'static, + SecError: std::error::Error + Send + Sync + 'static, + SecUpgrade: IntoSecurityUpgrade<$websocketStream>, + SecUpgrade::Upgrade: InboundUpgrade, Output = (libp2p_identity::PeerId, SecStream), Error = SecError> + OutboundUpgrade, Output = (libp2p_identity::PeerId, SecStream), Error = SecError> + Clone + Send + 'static, + >>::Future: Send, + >>::Future: Send, + <<>::Upgrade as UpgradeInfo>::InfoIter as IntoIterator>::IntoIter: Send, + <>::Upgrade as UpgradeInfo>::Info: Send, + + MuxStream: StreamMuxer + Send + 'static, + MuxStream::Substream: Send + 'static, + MuxStream::Error: Send + Sync + 'static, + MuxUpgrade: IntoMultiplexerUpgrade, + MuxUpgrade::Upgrade: InboundUpgrade, Output = MuxStream, Error = MuxError> + OutboundUpgrade, Output = MuxStream, Error = MuxError> + Clone + Send + 'static, + >>::Future: Send, + >>::Future: Send, + MuxError: std::error::Error + Send + Sync + 'static, + <<>::Upgrade as UpgradeInfo>::InfoIter as IntoIterator>::IntoIter: Send, + <>::Upgrade as UpgradeInfo>::Info: Send, + { + self.without_quic() + .without_any_other_transports() + .without_dns() + .without_relay() + .with_websocket(security_upgrade, multiplexer_upgrade) + .await + } + } + } +} +impl_quic_phase_with_websocket!( + "async-std", + super::provider::AsyncStd, + rw_stream_sink::RwStreamSink< + libp2p_websocket::BytesConnection, + > +); +impl_quic_phase_with_websocket!( + "tokio", + super::provider::Tokio, + rw_stream_sink::RwStreamSink> +); diff --git a/libp2p/src/builder/phase/relay.rs b/libp2p/src/builder/phase/relay.rs new file mode 100644 index 00000000000..abfc58ec064 --- /dev/null +++ b/libp2p/src/builder/phase/relay.rs @@ -0,0 +1,192 @@ +use std::marker::PhantomData; + +#[cfg(feature = "relay")] +use libp2p_core::muxing::StreamMuxerBox; +#[cfg(feature = "relay")] +use libp2p_core::Transport; +#[cfg(any(feature = "relay", feature = "websocket"))] +use libp2p_core::{InboundUpgrade, Negotiated, OutboundUpgrade, StreamMuxer, UpgradeInfo}; +#[cfg(feature = "relay")] +use libp2p_identity::PeerId; + +use crate::SwarmBuilder; + +use super::*; + +pub struct RelayPhase { + pub(crate) transport: T, +} + +#[cfg(feature = "relay")] +impl SwarmBuilder> { + /// Adds a relay client transport. + /// + /// Note that both `security_upgrade` and `multiplexer_upgrade` take function pointers, + /// i.e. they take the function themselves (without the invocation via `()`), not the + /// result of the function invocation. See example below. + /// + /// ``` rust + /// # use libp2p::SwarmBuilder; + /// # use std::error::Error; + /// # async fn build_swarm() -> Result<(), Box> { + /// let swarm = SwarmBuilder::with_new_identity() + /// .with_tokio() + /// .with_tcp( + /// Default::default(), + /// (libp2p_tls::Config::new, libp2p_noise::Config::new), + /// libp2p_yamux::Config::default, + /// )? + /// .with_relay_client( + /// (libp2p_tls::Config::new, libp2p_noise::Config::new), + /// libp2p_yamux::Config::default, + /// )? + /// # ; + /// # Ok(()) + /// # } + /// ``` + pub fn with_relay_client( + self, + security_upgrade: SecUpgrade, + multiplexer_upgrade: MuxUpgrade, + ) -> Result< + SwarmBuilder< + Provider, + WebsocketPhase, + >, + SecUpgrade::Error, + > where + + SecStream: futures::AsyncRead + futures::AsyncWrite + Unpin + Send + 'static, + SecError: std::error::Error + Send + Sync + 'static, + SecUpgrade: IntoSecurityUpgrade, + SecUpgrade::Upgrade: InboundUpgrade, Output = (PeerId, SecStream), Error = SecError> + OutboundUpgrade, Output = (PeerId, SecStream), Error = SecError> + Clone + Send + 'static, + >>::Future: Send, + >>::Future: Send, + <<>::Upgrade as UpgradeInfo>::InfoIter as IntoIterator>::IntoIter: Send, + <>::Upgrade as UpgradeInfo>::Info: Send, + + MuxStream: StreamMuxer + Send + 'static, + MuxStream::Substream: Send + 'static, + MuxStream::Error: Send + Sync + 'static, + MuxUpgrade: IntoMultiplexerUpgrade, + MuxUpgrade::Upgrade: InboundUpgrade, Output = MuxStream, Error = MuxError> + OutboundUpgrade, Output = MuxStream, Error = MuxError> + Clone + Send + 'static, + >>::Future: Send, + >>::Future: Send, + MuxError: std::error::Error + Send + Sync + 'static, + <<>::Upgrade as UpgradeInfo>::InfoIter as IntoIterator>::IntoIter: Send, + <>::Upgrade as UpgradeInfo>::Info: Send, + { + let (relay_transport, relay_behaviour) = + libp2p_relay::client::new(self.keypair.public().to_peer_id()); + + Ok(SwarmBuilder { + phase: WebsocketPhase { + relay_behaviour, + transport: self + .phase + .transport + .or_transport( + relay_transport + .upgrade(libp2p_core::upgrade::Version::V1Lazy) + .authenticate(security_upgrade.into_security_upgrade(&self.keypair)?) + .multiplex(multiplexer_upgrade.into_multiplexer_upgrade()) + .map(|(p, c), _| (p, StreamMuxerBox::new(c))), + ) + .map(|either, _| either.into_inner()), + }, + keypair: self.keypair, + phantom: PhantomData, + }) + } +} + +pub struct NoRelayBehaviour; + +impl SwarmBuilder> { + pub(crate) fn without_relay( + self, + ) -> SwarmBuilder> { + SwarmBuilder { + keypair: self.keypair, + phantom: PhantomData, + phase: WebsocketPhase { + transport: self.phase.transport, + relay_behaviour: NoRelayBehaviour, + }, + } + } +} + +// Shortcuts +impl SwarmBuilder> { + pub fn with_behaviour>( + self, + constructor: impl FnOnce(&libp2p_identity::Keypair) -> R, + ) -> Result>, R::Error> { + self.without_relay() + .without_websocket() + .with_behaviour(constructor) + } +} +macro_rules! impl_relay_phase_with_websocket { + ($providerKebabCase:literal, $providerPascalCase:ty, $websocketStream:ty) => { + #[cfg(all(feature = $providerKebabCase, not(target_arch = "wasm32"), feature = "websocket"))] + impl SwarmBuilder<$providerPascalCase, RelayPhase> { + pub async fn with_websocket < + SecUpgrade, + SecStream, + SecError, + MuxUpgrade, + MuxStream, + MuxError, + > ( + self, + security_upgrade: SecUpgrade, + multiplexer_upgrade: MuxUpgrade, + ) -> Result< + SwarmBuilder< + $providerPascalCase, + BandwidthLoggingPhase, + >, + super::websocket::WebsocketError, + > + where + SecStream: futures::AsyncRead + futures::AsyncWrite + Unpin + Send + 'static, + SecError: std::error::Error + Send + Sync + 'static, + SecUpgrade: IntoSecurityUpgrade<$websocketStream>, + SecUpgrade::Upgrade: InboundUpgrade, Output = (libp2p_identity::PeerId, SecStream), Error = SecError> + OutboundUpgrade, Output = (libp2p_identity::PeerId, SecStream), Error = SecError> + Clone + Send + 'static, + >>::Future: Send, + >>::Future: Send, + <<>::Upgrade as UpgradeInfo>::InfoIter as IntoIterator>::IntoIter: Send, + <>::Upgrade as UpgradeInfo>::Info: Send, + + MuxStream: StreamMuxer + Send + 'static, + MuxStream::Substream: Send + 'static, + MuxStream::Error: Send + Sync + 'static, + MuxUpgrade: IntoMultiplexerUpgrade, + MuxUpgrade::Upgrade: InboundUpgrade, Output = MuxStream, Error = MuxError> + OutboundUpgrade, Output = MuxStream, Error = MuxError> + Clone + Send + 'static, + >>::Future: Send, + >>::Future: Send, + MuxError: std::error::Error + Send + Sync + 'static, + <<>::Upgrade as UpgradeInfo>::InfoIter as IntoIterator>::IntoIter: Send, + <>::Upgrade as UpgradeInfo>::Info: Send, + { + self.without_relay() + .with_websocket(security_upgrade, multiplexer_upgrade) + .await + } + } + } +} +impl_relay_phase_with_websocket!( + "async-std", + super::provider::AsyncStd, + rw_stream_sink::RwStreamSink< + libp2p_websocket::BytesConnection, + > +); +impl_relay_phase_with_websocket!( + "tokio", + super::provider::Tokio, + rw_stream_sink::RwStreamSink> +); diff --git a/libp2p/src/builder/phase/swarm.rs b/libp2p/src/builder/phase/swarm.rs new file mode 100644 index 00000000000..ee456ced927 --- /dev/null +++ b/libp2p/src/builder/phase/swarm.rs @@ -0,0 +1,60 @@ +#[allow(unused_imports)] +use super::*; + +#[allow(dead_code)] +pub struct SwarmPhase { + pub(crate) behaviour: B, + pub(crate) transport: T, +} + +macro_rules! impl_with_swarm_config { + ($providerKebabCase:literal, $providerPascalCase:ty, $config:expr) => { + #[cfg(feature = $providerKebabCase)] + impl SwarmBuilder<$providerPascalCase, SwarmPhase> { + pub fn with_swarm_config( + self, + constructor: impl FnOnce(libp2p_swarm::Config) -> libp2p_swarm::Config, + ) -> SwarmBuilder<$providerPascalCase, BuildPhase> { + SwarmBuilder { + phase: BuildPhase { + behaviour: self.phase.behaviour, + transport: self.phase.transport, + swarm_config: constructor($config), + }, + keypair: self.keypair, + phantom: std::marker::PhantomData, + } + } + + // Shortcuts + pub fn build(self) -> libp2p_swarm::Swarm + where + B: libp2p_swarm::NetworkBehaviour, + T: AuthenticatedMultiplexedTransport, + { + self.with_swarm_config(std::convert::identity).build() + } + } + }; +} + +#[cfg(not(target_arch = "wasm32"))] +impl_with_swarm_config!( + "async-std", + super::provider::AsyncStd, + libp2p_swarm::Config::with_async_std_executor() +); + +#[cfg(not(target_arch = "wasm32"))] +impl_with_swarm_config!( + "tokio", + super::provider::Tokio, + libp2p_swarm::Config::with_tokio_executor() +); + +#[cfg(target_arch = "wasm32")] +impl_with_swarm_config!( + "wasm-bindgen", + super::provider::WasmBindgen, + libp2p_swarm::Config::with_wasm_executor() +); diff --git a/libp2p/src/builder/phase/tcp.rs b/libp2p/src/builder/phase/tcp.rs new file mode 100644 index 00000000000..d2b69798252 --- /dev/null +++ b/libp2p/src/builder/phase/tcp.rs @@ -0,0 +1,226 @@ +use super::*; +use crate::SwarmBuilder; +#[cfg(all( + not(target_arch = "wasm32"), + any(feature = "tcp", feature = "websocket") +))] +use libp2p_core::muxing::{StreamMuxer, StreamMuxerBox}; +#[cfg(all(feature = "websocket", not(target_arch = "wasm32")))] +use libp2p_core::Transport; +#[cfg(all( + not(target_arch = "wasm32"), + any(feature = "tcp", feature = "websocket") +))] +use libp2p_core::{InboundUpgrade, Negotiated, OutboundUpgrade, UpgradeInfo}; +use std::marker::PhantomData; + +pub struct TcpPhase {} + +macro_rules! impl_tcp_builder { + ($providerKebabCase:literal, $providerPascalCase:ty, $path:ident) => { + #[cfg(all( + not(target_arch = "wasm32"), + feature = "tcp", + feature = $providerKebabCase, + ))] + impl SwarmBuilder<$providerPascalCase, TcpPhase> { + /// Adds a TCP based transport. + /// + /// Note that both `security_upgrade` and `multiplexer_upgrade` take function pointers, + /// i.e. they take the function themselves (without the invocation via `()`), not the + /// result of the function invocation. See example below. + /// + /// ``` rust + /// # use libp2p::SwarmBuilder; + /// # use std::error::Error; + /// # async fn build_swarm() -> Result<(), Box> { + /// let swarm = SwarmBuilder::with_new_identity() + /// .with_tokio() + /// .with_tcp( + /// Default::default(), + /// (libp2p_tls::Config::new, libp2p_noise::Config::new), + /// libp2p_yamux::Config::default, + /// )? + /// # ; + /// # Ok(()) + /// # } + /// ``` + pub fn with_tcp( + self, + tcp_config: libp2p_tcp::Config, + security_upgrade: SecUpgrade, + multiplexer_upgrade: MuxUpgrade, + ) -> Result< + SwarmBuilder<$providerPascalCase, QuicPhase>, + SecUpgrade::Error, + > + where + SecStream: futures::AsyncRead + futures::AsyncWrite + Unpin + Send + 'static, + SecError: std::error::Error + Send + Sync + 'static, + SecUpgrade: IntoSecurityUpgrade, + SecUpgrade::Upgrade: InboundUpgrade, Output = (libp2p_identity::PeerId, SecStream), Error = SecError> + OutboundUpgrade, Output = (libp2p_identity::PeerId, SecStream), Error = SecError> + Clone + Send + 'static, + >>::Future: Send, + >>::Future: Send, + <<>::Upgrade as UpgradeInfo>::InfoIter as IntoIterator>::IntoIter: Send, + <>::Upgrade as UpgradeInfo>::Info: Send, + + MuxStream: StreamMuxer + Send + 'static, + MuxStream::Substream: Send + 'static, + MuxStream::Error: Send + Sync + 'static, + MuxUpgrade: IntoMultiplexerUpgrade, + MuxUpgrade::Upgrade: InboundUpgrade, Output = MuxStream, Error = MuxError> + OutboundUpgrade, Output = MuxStream, Error = MuxError> + Clone + Send + 'static, + >>::Future: Send, + >>::Future: Send, + MuxError: std::error::Error + Send + Sync + 'static, + <<>::Upgrade as UpgradeInfo>::InfoIter as IntoIterator>::IntoIter: Send, + <>::Upgrade as UpgradeInfo>::Info: Send, + { + Ok(SwarmBuilder { + phase: QuicPhase { + transport: libp2p_tcp::$path::Transport::new(tcp_config) + .upgrade(libp2p_core::upgrade::Version::V1Lazy) + .authenticate( + security_upgrade.into_security_upgrade(&self.keypair)?, + ) + .multiplex(multiplexer_upgrade.into_multiplexer_upgrade()) + .map(|(p, c), _| (p, StreamMuxerBox::new(c))), + }, + keypair: self.keypair, + phantom: PhantomData, + }) + } + } + }; +} + +impl_tcp_builder!("async-std", super::provider::AsyncStd, async_io); +impl_tcp_builder!("tokio", super::provider::Tokio, tokio); + +impl SwarmBuilder { + pub(crate) fn without_tcp( + self, + ) -> SwarmBuilder> { + SwarmBuilder { + keypair: self.keypair, + phantom: PhantomData, + phase: QuicPhase { + transport: libp2p_core::transport::dummy::DummyTransport::new(), + }, + } + } +} + +// Shortcuts +#[cfg(all(not(target_arch = "wasm32"), feature = "quic", feature = "async-std"))] +impl SwarmBuilder { + pub fn with_quic( + self, + ) -> SwarmBuilder< + super::provider::AsyncStd, + OtherTransportPhase, + > { + self.without_tcp().with_quic() + } +} +#[cfg(all(not(target_arch = "wasm32"), feature = "quic", feature = "tokio"))] +impl SwarmBuilder { + pub fn with_quic( + self, + ) -> SwarmBuilder< + super::provider::Tokio, + OtherTransportPhase, + > { + self.without_tcp().with_quic() + } +} +impl SwarmBuilder { + pub fn with_other_transport< + Muxer: libp2p_core::muxing::StreamMuxer + Send + 'static, + OtherTransport: Transport + Send + Unpin + 'static, + R: TryIntoTransport, + >( + self, + constructor: impl FnOnce(&libp2p_identity::Keypair) -> R, + ) -> Result< + SwarmBuilder>, + R::Error, + > + where + ::Error: Send + Sync + 'static, + ::Dial: Send, + ::ListenerUpgrade: Send, + ::Substream: Send, + ::Error: Send + Sync, + { + self.without_tcp() + .without_quic() + .with_other_transport(constructor) + } +} +macro_rules! impl_tcp_phase_with_websocket { + ($providerKebabCase:literal, $providerPascalCase:ty, $websocketStream:ty) => { + #[cfg(all(feature = $providerKebabCase, not(target_arch = "wasm32"), feature = "websocket"))] + impl SwarmBuilder<$providerPascalCase, TcpPhase> { + /// See [`SwarmBuilder::with_websocket`]. + pub async fn with_websocket < + SecUpgrade, + SecStream, + SecError, + MuxUpgrade, + MuxStream, + MuxError, + > ( + self, + security_upgrade: SecUpgrade, + multiplexer_upgrade: MuxUpgrade, + ) -> Result< + SwarmBuilder< + $providerPascalCase, + BandwidthLoggingPhase, + >, + WebsocketError, + > + where + SecStream: futures::AsyncRead + futures::AsyncWrite + Unpin + Send + 'static, + SecError: std::error::Error + Send + Sync + 'static, + SecUpgrade: IntoSecurityUpgrade<$websocketStream>, + SecUpgrade::Upgrade: InboundUpgrade, Output = (libp2p_identity::PeerId, SecStream), Error = SecError> + OutboundUpgrade, Output = (libp2p_identity::PeerId, SecStream), Error = SecError> + Clone + Send + 'static, + >>::Future: Send, + >>::Future: Send, + <<>::Upgrade as UpgradeInfo>::InfoIter as IntoIterator>::IntoIter: Send, + <>::Upgrade as UpgradeInfo>::Info: Send, + + MuxStream: StreamMuxer + Send + 'static, + MuxStream::Substream: Send + 'static, + MuxStream::Error: Send + Sync + 'static, + MuxUpgrade: IntoMultiplexerUpgrade, + MuxUpgrade::Upgrade: InboundUpgrade, Output = MuxStream, Error = MuxError> + OutboundUpgrade, Output = MuxStream, Error = MuxError> + Clone + Send + 'static, + >>::Future: Send, + >>::Future: Send, + MuxError: std::error::Error + Send + Sync + 'static, + <<>::Upgrade as UpgradeInfo>::InfoIter as IntoIterator>::IntoIter: Send, + <>::Upgrade as UpgradeInfo>::Info: Send, + { + self.without_tcp() + .without_quic() + .without_any_other_transports() + .without_dns() + .without_relay() + .with_websocket(security_upgrade, multiplexer_upgrade) + .await + } + } + } +} +impl_tcp_phase_with_websocket!( + "async-std", + super::provider::AsyncStd, + rw_stream_sink::RwStreamSink< + libp2p_websocket::BytesConnection, + > +); +impl_tcp_phase_with_websocket!( + "tokio", + super::provider::Tokio, + rw_stream_sink::RwStreamSink> +); diff --git a/libp2p/src/builder/phase/websocket.rs b/libp2p/src/builder/phase/websocket.rs new file mode 100644 index 00000000000..bc536b4a7d1 --- /dev/null +++ b/libp2p/src/builder/phase/websocket.rs @@ -0,0 +1,188 @@ +use super::*; +use crate::SwarmBuilder; +#[cfg(all(not(target_arch = "wasm32"), feature = "websocket"))] +use libp2p_core::muxing::{StreamMuxer, StreamMuxerBox}; +#[cfg(all(not(target_arch = "wasm32"), feature = "websocket"))] +use libp2p_core::Transport; +#[cfg(all(not(target_arch = "wasm32"), feature = "websocket"))] +use libp2p_core::{InboundUpgrade, Negotiated, OutboundUpgrade, UpgradeInfo}; +#[cfg(all(not(target_arch = "wasm32"), feature = "websocket"))] +use libp2p_identity::PeerId; +use std::marker::PhantomData; + +pub struct WebsocketPhase { + pub(crate) transport: T, + pub(crate) relay_behaviour: R, +} + +macro_rules! impl_websocket_builder { + ($providerKebabCase:literal, $providerPascalCase:ty, $dnsTcp:expr, $websocketStream:ty) => { + /// Adds a websocket client transport. + /// + /// Note that both `security_upgrade` and `multiplexer_upgrade` take function pointers, + /// i.e. they take the function themselves (without the invocation via `()`), not the + /// result of the function invocation. See example below. + /// + /// ``` rust + /// # use libp2p::SwarmBuilder; + /// # use std::error::Error; + /// # async fn build_swarm() -> Result<(), Box> { + /// let swarm = SwarmBuilder::with_new_identity() + /// .with_tokio() + /// .with_websocket( + /// (libp2p_tls::Config::new, libp2p_noise::Config::new), + /// libp2p_yamux::Config::default, + /// ) + /// .await? + /// # ; + /// # Ok(()) + /// # } + /// ``` + #[cfg(all(not(target_arch = "wasm32"), feature = $providerKebabCase, feature = "websocket"))] + impl SwarmBuilder<$providerPascalCase, WebsocketPhase> { + pub async fn with_websocket< + SecUpgrade, + SecStream, + SecError, + MuxUpgrade, + MuxStream, + MuxError, + >( + self, + security_upgrade: SecUpgrade, + multiplexer_upgrade: MuxUpgrade, + ) -> Result< + SwarmBuilder< + $providerPascalCase, + BandwidthLoggingPhase, + >, + WebsocketError, + > + + where + T: AuthenticatedMultiplexedTransport, + + SecStream: futures::AsyncRead + futures::AsyncWrite + Unpin + Send + 'static, + SecError: std::error::Error + Send + Sync + 'static, + SecUpgrade: IntoSecurityUpgrade<$websocketStream>, + SecUpgrade::Upgrade: InboundUpgrade, Output = (PeerId, SecStream), Error = SecError> + OutboundUpgrade, Output = (PeerId, SecStream), Error = SecError> + Clone + Send + 'static, + >>::Future: Send, + >>::Future: Send, + <<>::Upgrade as UpgradeInfo>::InfoIter as IntoIterator>::IntoIter: Send, + <>::Upgrade as UpgradeInfo>::Info: Send, + + MuxStream: StreamMuxer + Send + 'static, + MuxStream::Substream: Send + 'static, + MuxStream::Error: Send + Sync + 'static, + MuxUpgrade: IntoMultiplexerUpgrade, + MuxUpgrade::Upgrade: InboundUpgrade, Output = MuxStream, Error = MuxError> + OutboundUpgrade, Output = MuxStream, Error = MuxError> + Clone + Send + 'static, + >>::Future: Send, + >>::Future: Send, + MuxError: std::error::Error + Send + Sync + 'static, + <<>::Upgrade as UpgradeInfo>::InfoIter as IntoIterator>::IntoIter: Send, + <>::Upgrade as UpgradeInfo>::Info: Send, + + { + let security_upgrade = security_upgrade.into_security_upgrade(&self.keypair) + .map_err(WebsocketErrorInner::SecurityUpgrade)?; + let websocket_transport = libp2p_websocket::WsConfig::new( + $dnsTcp.await.map_err(WebsocketErrorInner::Dns)?, + ) + .upgrade(libp2p_core::upgrade::Version::V1Lazy) + .authenticate(security_upgrade) + .multiplex(multiplexer_upgrade.into_multiplexer_upgrade()) + .map(|(p, c), _| (p, StreamMuxerBox::new(c))); + + Ok(SwarmBuilder { + keypair: self.keypair, + phantom: PhantomData, + phase: BandwidthLoggingPhase { + transport: websocket_transport + .or_transport(self.phase.transport) + .map(|either, _| either.into_inner()), + relay_behaviour: self.phase.relay_behaviour, + }, + }) + } + } + }; +} + +impl_websocket_builder!( + "async-std", + super::provider::AsyncStd, + libp2p_dns::async_std::Transport::system(libp2p_tcp::async_io::Transport::new( + libp2p_tcp::Config::default(), + )), + rw_stream_sink::RwStreamSink< + libp2p_websocket::BytesConnection, + > +); +impl_websocket_builder!( + "tokio", + super::provider::Tokio, + // Note this is an unnecessary await for Tokio Websocket (i.e. tokio dns) in order to be consistent + // with above AsyncStd construction. + futures::future::ready(libp2p_dns::tokio::Transport::system( + libp2p_tcp::tokio::Transport::new(libp2p_tcp::Config::default()) + )), + rw_stream_sink::RwStreamSink> +); + +impl + SwarmBuilder> +{ + pub(crate) fn without_websocket(self) -> SwarmBuilder> { + SwarmBuilder { + keypair: self.keypair, + phantom: PhantomData, + phase: BandwidthLoggingPhase { + relay_behaviour: self.phase.relay_behaviour, + transport: self.phase.transport, + }, + } + } +} + +// Shortcuts +#[cfg(feature = "relay")] +impl + SwarmBuilder> +{ + pub fn with_behaviour>( + self, + constructor: impl FnOnce(&libp2p_identity::Keypair, libp2p_relay::client::Behaviour) -> R, + ) -> Result>, R::Error> { + self.without_websocket() + .without_bandwidth_logging() + .with_behaviour(constructor) + } +} + +impl + SwarmBuilder> +{ + pub fn with_behaviour>( + self, + constructor: impl FnOnce(&libp2p_identity::Keypair) -> R, + ) -> Result>, R::Error> { + self.without_websocket() + .without_bandwidth_logging() + .with_behaviour(constructor) + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +#[cfg(all(not(target_arch = "wasm32"), feature = "websocket"))] +pub struct WebsocketError(#[from] WebsocketErrorInner); + +#[derive(Debug, thiserror::Error)] +#[cfg(all(not(target_arch = "wasm32"), feature = "websocket"))] +enum WebsocketErrorInner { + #[error("SecurityUpgrade")] + SecurityUpgrade(Sec), + #[cfg(feature = "dns")] + #[error("Dns")] + Dns(#[from] std::io::Error), +} diff --git a/libp2p/src/builder/select_security.rs b/libp2p/src/builder/select_security.rs new file mode 100644 index 00000000000..91dbae869c6 --- /dev/null +++ b/libp2p/src/builder/select_security.rs @@ -0,0 +1,113 @@ +// Copyright 2023 Protocol Labs. +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use either::Either; +use futures::future::MapOk; +use futures::{future, TryFutureExt}; +use libp2p_core::either::EitherFuture; +use libp2p_core::upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; +use libp2p_identity::PeerId; +use std::iter::{Chain, Map}; + +/// Upgrade that combines two upgrades into one. Supports all the protocols supported by either +/// sub-upgrade. +/// +/// The protocols supported by the first element have a higher priority. +#[derive(Debug, Clone)] +pub struct SelectSecurityUpgrade(A, B); + +impl SelectSecurityUpgrade { + /// Combines two upgrades into an `SelectUpgrade`. + /// + /// The protocols supported by the first element have a higher priority. + pub fn new(a: A, b: B) -> Self { + SelectSecurityUpgrade(a, b) + } +} + +impl UpgradeInfo for SelectSecurityUpgrade +where + A: UpgradeInfo, + B: UpgradeInfo, +{ + type Info = Either; + type InfoIter = Chain< + Map<::IntoIter, fn(A::Info) -> Self::Info>, + Map<::IntoIter, fn(B::Info) -> Self::Info>, + >; + + fn protocol_info(&self) -> Self::InfoIter { + let a = self + .0 + .protocol_info() + .into_iter() + .map(Either::Left as fn(A::Info) -> _); + let b = self + .1 + .protocol_info() + .into_iter() + .map(Either::Right as fn(B::Info) -> _); + + a.chain(b) + } +} + +impl InboundUpgrade for SelectSecurityUpgrade +where + A: InboundUpgrade, + B: InboundUpgrade, +{ + type Output = (PeerId, future::Either); + type Error = Either; + type Future = MapOk< + EitherFuture, + fn(future::Either<(PeerId, TA), (PeerId, TB)>) -> (PeerId, future::Either), + >; + + fn upgrade_inbound(self, sock: C, info: Self::Info) -> Self::Future { + match info { + Either::Left(info) => EitherFuture::First(self.0.upgrade_inbound(sock, info)), + Either::Right(info) => EitherFuture::Second(self.1.upgrade_inbound(sock, info)), + } + .map_ok(future::Either::factor_first) + } +} + +impl OutboundUpgrade for SelectSecurityUpgrade +where + A: OutboundUpgrade, + B: OutboundUpgrade, +{ + type Output = (PeerId, future::Either); + type Error = Either; + type Future = MapOk< + EitherFuture, + fn(future::Either<(PeerId, TA), (PeerId, TB)>) -> (PeerId, future::Either), + >; + + fn upgrade_outbound(self, sock: C, info: Self::Info) -> Self::Future { + match info { + Either::Left(info) => EitherFuture::First(self.0.upgrade_outbound(sock, info)), + Either::Right(info) => EitherFuture::Second(self.1.upgrade_outbound(sock, info)), + } + .map_ok(future::Either::factor_first) + } +} diff --git a/libp2p/src/lib.rs b/libp2p/src/lib.rs index bcfdd41ef76..e3135ef3cca 100644 --- a/libp2p/src/lib.rs +++ b/libp2p/src/lib.rs @@ -22,8 +22,9 @@ //! //! To learn more about the general libp2p multi-language framework visit . //! -//! To get started with this libp2p implementation in Rust, please take a look at the [`tutorials`]. -//! Further examples can be found in the [examples] directory. +//! To get started with this libp2p implementation in Rust, please take a look +//! at the [`tutorials`]. Further examples can be found in the +//! [examples] directory. //! //! [examples]: https://github.com/libp2p/rust-libp2p/tree/master/examples @@ -157,6 +158,7 @@ pub use libp2p_webtransport_websys as webtransport_websys; #[doc(inline)] pub use libp2p_yamux as yamux; +mod builder; mod transport_ext; pub mod bandwidth; @@ -164,6 +166,7 @@ pub mod bandwidth; #[cfg(doc)] pub mod tutorials; +pub use self::builder::SwarmBuilder; pub use self::core::{ transport::TransportError, upgrade::{InboundUpgrade, OutboundUpgrade}, @@ -187,6 +190,7 @@ pub use libp2p_swarm::{Stream, StreamProtocol}; /// /// > **Note**: This `Transport` is not suitable for production usage, as its implementation /// > reserves the right to support additional protocols or remove deprecated protocols. +#[deprecated(note = "Use `libp2p::SwarmBuilder` instead.")] #[cfg(all( not(target_arch = "wasm32"), feature = "tcp", @@ -232,6 +236,7 @@ pub async fn development_transport( /// /// > **Note**: This `Transport` is not suitable for production usage, as its implementation /// > reserves the right to support additional protocols or remove deprecated protocols. +#[deprecated(note = "Use `libp2p::SwarmBuilder` instead.")] #[cfg(all( not(target_arch = "wasm32"), feature = "tcp", diff --git a/libp2p/src/tutorials/ping.rs b/libp2p/src/tutorials/ping.rs index aedc149228e..dcb0715399a 100644 --- a/libp2p/src/tutorials/ping.rs +++ b/libp2p/src/tutorials/ping.rs @@ -55,10 +55,10 @@ //! edition = "2021" //! //! [dependencies] -//! libp2p = { version = "0.50", features = ["tcp", "dns", "async-std", "noise", "yamux", "websocket", "ping", "macros"] } -//! futures = "0.3.21" +//! libp2p = { version = "0.52", features = ["tcp", "dns", "async-std", "noise", "yamux", "websocket", "ping", "macros"] } +//! futures = "0.3" //! env_logger = "0.10.0" -//! async-std = { version = "1.12.0", features = ["attributes"] } +//! async-std = { version = "1.12", features = ["attributes"] } //! ``` //! //! ## Network identity @@ -71,38 +71,27 @@ //! derived from their public key. Now, replace the contents of main.rs by: //! //! ```rust -//! use libp2p::{identity, PeerId}; //! use std::error::Error; //! //! #[async_std::main] //! async fn main() -> Result<(), Box> { -//! let local_key = identity::Keypair::generate_ed25519(); -//! let local_peer_id = PeerId::from(local_key.public()); -//! println!("Local peer id: {local_peer_id:?}"); +//! env_logger::init(); +//! +//! let mut swarm = libp2p::SwarmBuilder::with_new_identity(); //! //! Ok(()) //! } //! ``` //! -//! Go ahead and build and run the above code with: `cargo run`. A unique -//! [`PeerId`](crate::PeerId) should be displayed. +//! Go ahead and build and run the above code with: `cargo run`. Nothing happening thus far. //! //! ## Transport //! -//! Next up we need to construct a transport. A transport in libp2p provides -//! connection-oriented communication channels (e.g. TCP) as well as upgrades -//! on top of those like authentication and encryption protocols. Technically, -//! a libp2p transport is anything that implements the [`Transport`] trait. -//! -//! Instead of constructing a transport ourselves for this tutorial, we use the -//! convenience function [`development_transport`](crate::development_transport) -//! that creates a TCP transport with [`noise`](crate::noise) for authenticated -//! encryption. -//! -//! Furthermore, [`development_transport`] builds a multiplexed transport, -//! whereby multiple logical substreams can coexist on the same underlying (TCP) -//! connection. For further details on substream multiplexing, take a look at -//! [`crate::core::muxing`] and [`yamux`](crate::yamux). +//! Next up we need to construct a transport. Each transport in libp2p provides encrypted streams. +//! E.g. combining TCP to establish connections, TLS to encrypt these connections and Yamux to run +//! one or more streams on a connection. Another libp2p transport is QUIC, providing encrypted +//! streams out-of-the-box. We will stick to TCP for now. Each of these implement the [`Transport`] +//! trait. //! //! ```rust //! use libp2p::{identity, PeerId}; @@ -110,11 +99,15 @@ //! //! #[async_std::main] //! async fn main() -> Result<(), Box> { -//! let local_key = identity::Keypair::generate_ed25519(); -//! let local_peer_id = PeerId::from(local_key.public()); -//! println!("Local peer id: {local_peer_id:?}"); +//! env_logger::init(); //! -//! let transport = libp2p::development_transport(local_key).await?; +//! let mut swarm = libp2p::SwarmBuilder::with_new_identity() +//! .with_async_std() +//! .with_tcp( +//! libp2p_tcp::Config::default(), +//! libp2p_tls::Config::new, +//! libp2p_yamux::Config::default, +//! )?; //! //! Ok(()) //! } @@ -125,20 +118,20 @@ //! Now it is time to look at another core trait of rust-libp2p: the //! [`NetworkBehaviour`]. While the previously introduced trait [`Transport`] //! defines _how_ to send bytes on the network, a [`NetworkBehaviour`] defines -//! _what_ bytes to send on the network. +//! _what_ bytes and to _whom_ to send on the network. //! //! To make this more concrete, let's take a look at a simple implementation of //! the [`NetworkBehaviour`] trait: the [`ping::Behaviour`](crate::ping::Behaviour). -//! As you might have guessed, similar to the good old `ping` network tool, +//! As you might have guessed, similar to the good old ICMP `ping` network tool, //! libp2p [`ping::Behaviour`](crate::ping::Behaviour) sends a ping to a peer and expects //! to receive a pong in turn. The [`ping::Behaviour`](crate::ping::Behaviour) does not care _how_ //! the ping and pong messages are sent on the network, whether they are sent via //! TCP, whether they are encrypted via [noise](crate::noise) or just in -//! [plaintext](crate::plaintext). It only cares about _what_ messages are sent -//! on the network. +//! [plaintext](crate::plaintext). It only cares about _what_ messages and to _whom_ to sent on the +//! network. //! //! The two traits [`Transport`] and [`NetworkBehaviour`] allow us to cleanly -//! separate _how_ to send bytes from _what_ bytes to send. +//! separate _how_ to send bytes from _what_ bytes and to _whom_ to send. //! //! With the above in mind, let's extend our example, creating a [`ping::Behaviour`](crate::ping::Behaviour) at the end: //! @@ -149,13 +142,16 @@ //! //! #[async_std::main] //! async fn main() -> Result<(), Box> { -//! let local_key = identity::Keypair::generate_ed25519(); -//! let local_peer_id = PeerId::from(local_key.public()); -//! println!("Local peer id: {local_peer_id:?}"); -//! -//! let transport = libp2p::development_transport(local_key).await?; +//! env_logger::init(); //! -//! let behaviour = ping::Behaviour::default(); +//! let mut swarm = libp2p::SwarmBuilder::with_new_identity() +//! .with_async_std() +//! .with_tcp( +//! libp2p_tcp::Config::default(), +//! libp2p_tls::Config::new, +//! libp2p_yamux::Config::default, +//! )? +//! .with_behaviour(|_| ping::Behaviour::default())?; //! //! Ok(()) //! } @@ -163,16 +159,10 @@ //! //! ## Swarm //! -//! Now that we have a [`Transport`] and a [`NetworkBehaviour`], we need -//! something that connects the two, allowing both to make progress. This job is -//! carried out by a [`Swarm`]. Put simply, a [`Swarm`] drives both a -//! [`Transport`] and a [`NetworkBehaviour`] forward, passing commands from the -//! [`NetworkBehaviour`] to the [`Transport`] as well as events from the -//! [`Transport`] to the [`NetworkBehaviour`]. As you can see, after [`Swarm`] initialization, we -//! removed the print of the local [`PeerId`](crate::PeerId) because every time a [`Swarm`] is -//! created, it prints the local [`PeerId`](crate::PeerId) in the logs at the INFO level. In order -//! to continue to see the local [`PeerId`](crate::PeerId) you must initialize the logger -//! (In our example, `env_logger` is used) +//! Now that we have a [`Transport`] and a [`NetworkBehaviour`], we can build the [`Swarm`] +//! which connects the two, allowing both to make progress. Put simply, a [`Swarm`] drives both a +//! [`Transport`] and a [`NetworkBehaviour`] forward, passing commands from the [`NetworkBehaviour`] +//! to the [`Transport`] as well as events from the [`Transport`] to the [`NetworkBehaviour`]. //! //! ```rust //! use libp2p::swarm::{NetworkBehaviour, SwarmBuilder}; @@ -182,14 +172,16 @@ //! #[async_std::main] //! async fn main() -> Result<(), Box> { //! env_logger::init(); -//! let local_key = identity::Keypair::generate_ed25519(); -//! let local_peer_id = PeerId::from(local_key.public()); -//! -//! let transport = libp2p::development_transport(local_key).await?; //! -//! let behaviour = ping::Behaviour::default(); -//! -//! let mut swarm = SwarmBuilder::with_async_std_executor(transport, behaviour, local_peer_id).build(); +//! let mut swarm = libp2p::SwarmBuilder::with_new_identity() +//! .with_async_std() +//! .with_tcp( +//! libp2p_tcp::Config::default(), +//! libp2p_tls::Config::new, +//! libp2p_yamux::Config::default, +//! )? +//! .with_behaviour(|_| ping::Behaviour::default())? +//! .build(); //! //! Ok(()) //! } @@ -213,17 +205,17 @@ //! //! #[async_std::main] //! async fn main() -> Result<(), Box> { -//! use std::time::Duration; -//! let local_key = identity::Keypair::generate_ed25519(); -//! let local_peer_id = PeerId::from(local_key.public()); -//! println!("Local peer id: {local_peer_id:?}"); -//! -//! let transport = libp2p::development_transport(local_key).await?; -//! -//! let behaviour = ping::Behaviour::default(); +//! env_logger::init(); //! -//! let mut swarm = SwarmBuilder::with_async_std_executor(transport, behaviour, local_peer_id) -//! .idle_connection_timeout(Duration::from_secs(30)) // Allows us to observe pings for 30 seconds. +//! let mut swarm = libp2p::SwarmBuilder::with_new_identity() +//! .with_async_std() +//! .with_tcp( +//! libp2p_tcp::Config::default(), +//! libp2p_tls::Config::new, +//! libp2p_yamux::Config::default, +//! )? +//! .with_behaviour(|_| ping::Behaviour::default())? +//! .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(30))) // Allows us to observe pings for 30 seconds. //! .build(); //! //! Ok(()) @@ -264,16 +256,15 @@ //! //! #[async_std::main] //! async fn main() -> Result<(), Box> { -//! env_logger::init(); -//! let local_key = identity::Keypair::generate_ed25519(); -//! let local_peer_id = PeerId::from(local_key.public()); -//! -//! let transport = libp2p::development_transport(local_key).await?; -//! -//! let behaviour = ping::Behaviour::default(); -//! -//! let mut swarm = SwarmBuilder::with_async_std_executor(transport, behaviour, local_peer_id) -//! .idle_connection_timeout(Duration::from_secs(30)) // Allows us to observe pings for 30 seconds. +//! let mut swarm = libp2p::SwarmBuilder::with_new_identity() +//! .with_async_std() +//! .with_tcp( +//! libp2p_tcp::Config::default(), +//! libp2p_tls::Config::new, +//! libp2p_yamux::Config::default, +//! )? +//! .with_behaviour(|_| ping::Behaviour::default())? +//! .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(30))) // Allows us to observe pings for 30 seconds. //! .build(); //! //! // Tell the swarm to listen on all interfaces and a random, OS-assigned @@ -307,16 +298,15 @@ //! //! #[async_std::main] //! async fn main() -> Result<(), Box> { -//! env_logger::init(); -//! let local_key = identity::Keypair::generate_ed25519(); -//! let local_peer_id = PeerId::from(local_key.public()); -//! -//! let transport = libp2p::development_transport(local_key).await?; -//! -//! let behaviour = ping::Behaviour::default(); -//! -//! let mut swarm = SwarmBuilder::with_async_std_executor(transport, behaviour, local_peer_id) -//! .idle_connection_timeout(Duration::from_secs(30)) // Allows us to observe pings for 30 seconds. +//! let mut swarm = libp2p::SwarmBuilder::with_new_identity() +//! .with_async_std() +//! .with_tcp( +//! libp2p_tcp::Config::default(), +//! libp2p_tls::Config::new, +//! libp2p_yamux::Config::default, +//! )? +//! .with_behaviour(|_| ping::Behaviour::default())? +//! .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(30))) // Allows us to observe pings for 30 seconds. //! .build(); //! //! // Tell the swarm to listen on all interfaces and a random, OS-assigned diff --git a/misc/server/src/main.rs b/misc/server/src/main.rs index e885301d590..882f538a2db 100644 --- a/misc/server/src/main.rs +++ b/misc/server/src/main.rs @@ -1,27 +1,18 @@ use base64::Engine; use clap::Parser; -use futures::future::Either; use futures::stream::StreamExt; use futures_timer::Delay; -use libp2p::core::muxing::StreamMuxerBox; -use libp2p::core::upgrade; -use libp2p::dns; -use libp2p::identify; use libp2p::identity; use libp2p::identity::PeerId; use libp2p::kad; use libp2p::metrics::{Metrics, Recorder}; -use libp2p::noise; -use libp2p::quic; -use libp2p::swarm::{SwarmBuilder, SwarmEvent}; +use libp2p::swarm::SwarmEvent; use libp2p::tcp; -use libp2p::yamux; -use libp2p::Transport; +use libp2p::{identify, noise, yamux}; use log::{debug, info, warn}; use prometheus_client::metrics::info::Info; use prometheus_client::registry::Registry; use std::error::Error; -use std::io; use std::path::PathBuf; use std::str::FromStr; use std::task::Poll; @@ -62,7 +53,7 @@ async fn main() -> Result<(), Box> { let config = Zeroizing::new(config::Config::from_file(opt.config.as_path())?); - let (local_peer_id, local_keypair) = { + let local_keypair = { let keypair = identity::Keypair::from_protobuf_encoding(&Zeroizing::new( base64::engine::general_purpose::STANDARD .decode(config.identity.priv_key.as_bytes())?, @@ -75,37 +66,25 @@ async fn main() -> Result<(), Box> { "Expect peer id derived from private key and peer id retrieved from config to match." ); - (peer_id, keypair) + keypair }; - let transport = { - let tcp_transport = - tcp::tokio::Transport::new(tcp::Config::new().port_reuse(true).nodelay(true)) - .upgrade(upgrade::Version::V1) - .authenticate(noise::Config::new(&local_keypair)?) - .multiplex(yamux::Config::default()) - .timeout(Duration::from_secs(20)); - - let quic_transport = quic::tokio::Transport::new(quic::Config::new(&local_keypair)); - - dns::tokio::Transport::system(libp2p::core::transport::OrTransport::new( - quic_transport, - tcp_transport, - ))? - .map(|either_output, _| match either_output { - Either::Left((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), - Either::Right((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), + let mut swarm = libp2p::SwarmBuilder::with_existing_identity(local_keypair) + .with_tokio() + .with_tcp( + tcp::Config::default().port_reuse(true).nodelay(true), + noise::Config::new, + yamux::Config::default, + )? + .with_quic_config(|mut cfg| { + cfg.support_draft_29 = true; + cfg }) - .map_err(|err| io::Error::new(io::ErrorKind::Other, err)) - .boxed() - }; - - let behaviour = behaviour::Behaviour::new( - local_keypair.public(), - opt.enable_kademlia, - opt.enable_autonat, - ); - let mut swarm = SwarmBuilder::with_tokio_executor(transport, behaviour, local_peer_id).build(); + .with_dns()? + .with_behaviour(|key| { + behaviour::Behaviour::new(key.public(), opt.enable_kademlia, opt.enable_autonat) + })? + .build(); if config.addresses.swarm.is_empty() { warn!("No listen addresses configured."); diff --git a/protocols/dcutr/tests/lib.rs b/protocols/dcutr/tests/lib.rs index 162b4a5ec78..1e12ad98776 100644 --- a/protocols/dcutr/tests/lib.rs +++ b/protocols/dcutr/tests/lib.rs @@ -26,7 +26,7 @@ use libp2p_identity as identity; use libp2p_identity::PeerId; use libp2p_plaintext as plaintext; use libp2p_relay as relay; -use libp2p_swarm::{NetworkBehaviour, Swarm, SwarmBuilder, SwarmEvent}; +use libp2p_swarm::{Config, NetworkBehaviour, Swarm, SwarmEvent}; use libp2p_swarm_test::SwarmExt as _; use std::time::Duration; @@ -123,15 +123,15 @@ fn build_client() -> Swarm { .multiplex(libp2p_yamux::Config::default()) .boxed(); - SwarmBuilder::without_executor( + Swarm::new( transport, Client { relay: behaviour, dcutr: dcutr::Behaviour::new(local_peer_id), }, local_peer_id, + Config::with_async_std_executor(), ) - .build() } #[derive(NetworkBehaviour)] diff --git a/protocols/kad/src/behaviour/test.rs b/protocols/kad/src/behaviour/test.rs index f85208ee817..79826d2653b 100644 --- a/protocols/kad/src/behaviour/test.rs +++ b/protocols/kad/src/behaviour/test.rs @@ -37,7 +37,7 @@ use libp2p_core::{ use libp2p_identity as identity; use libp2p_identity::PeerId; use libp2p_noise as noise; -use libp2p_swarm::{ConnectionId, Swarm, SwarmBuilder, SwarmEvent}; +use libp2p_swarm::{self as swarm, ConnectionId, Swarm, SwarmEvent}; use libp2p_yamux as yamux; use quickcheck::*; use rand::{random, rngs::StdRng, thread_rng, Rng, SeedableRng}; @@ -67,7 +67,12 @@ fn build_node_with_config(cfg: Config) -> (Multiaddr, TestSwarm) { let store = MemoryStore::new(local_id); let behaviour = Behaviour::with_config(local_id, store, cfg); - let mut swarm = SwarmBuilder::without_executor(transport, behaviour, local_id).build(); + let mut swarm = Swarm::new( + transport, + behaviour, + local_id, + swarm::Config::with_async_std_executor(), + ); let address: Multiaddr = Protocol::Memory(random::()).into(); swarm.listen_on(address.clone()).unwrap(); diff --git a/protocols/perf/src/bin/perf.rs b/protocols/perf/src/bin/perf.rs index 4205cc3843b..418c7fef97f 100644 --- a/protocols/perf/src/bin/perf.rs +++ b/protocols/perf/src/bin/perf.rs @@ -31,7 +31,7 @@ use libp2p_core::{ }; use libp2p_identity::PeerId; use libp2p_perf::{Run, RunDuration, RunParams}; -use libp2p_swarm::{NetworkBehaviour, Swarm, SwarmBuilder, SwarmEvent}; +use libp2p_swarm::{Config, NetworkBehaviour, Swarm, SwarmEvent}; use log::{error, info}; use serde::{Deserialize, Serialize}; @@ -414,11 +414,15 @@ async fn swarm() -> Result> { .boxed() }; - Ok( - SwarmBuilder::with_tokio_executor(transport, Default::default(), local_peer_id) - .substream_upgrade_protocol_override(upgrade::Version::V1Lazy) - .build(), - ) + let swarm = Swarm::new( + transport, + Default::default(), + local_peer_id, + Config::with_tokio_executor() + .with_substream_upgrade_protocol_override(upgrade::Version::V1Lazy), + ); + + Ok(swarm) } async fn connect( diff --git a/protocols/relay/tests/lib.rs b/protocols/relay/tests/lib.rs index b7784d17b3a..f5c834bcb37 100644 --- a/protocols/relay/tests/lib.rs +++ b/protocols/relay/tests/lib.rs @@ -33,7 +33,7 @@ use libp2p_identity::PeerId; use libp2p_ping as ping; use libp2p_plaintext as plaintext; use libp2p_relay as relay; -use libp2p_swarm::{NetworkBehaviour, Swarm, SwarmBuilder, SwarmEvent}; +use libp2p_swarm::{Config, NetworkBehaviour, Swarm, SwarmEvent}; use std::time::Duration; #[test] @@ -310,7 +310,7 @@ fn build_relay() -> Swarm { let transport = upgrade_transport(MemoryTransport::default().boxed(), &local_key); - SwarmBuilder::with_async_std_executor( + Swarm::new( transport, Relay { ping: ping::Behaviour::new(ping::Config::new()), @@ -323,8 +323,8 @@ fn build_relay() -> Swarm { ), }, local_peer_id, + Config::with_async_std_executor(), ) - .build() } fn build_client() -> Swarm { @@ -337,15 +337,15 @@ fn build_client() -> Swarm { &local_key, ); - SwarmBuilder::with_async_std_executor( + Swarm::new( transport, Client { ping: ping::Behaviour::new(ping::Config::new()), relay: behaviour, }, local_peer_id, + Config::with_async_std_executor(), ) - .build() } fn upgrade_transport( diff --git a/swarm-test/Cargo.toml b/swarm-test/Cargo.toml index 8c953b2b52f..eb62b1b06cf 100644 --- a/swarm-test/Cargo.toml +++ b/swarm-test/Cargo.toml @@ -16,7 +16,7 @@ async-trait = "0.1.73" libp2p-core = { workspace = true } libp2p-identity = { workspace = true } libp2p-plaintext = { workspace = true } -libp2p-swarm = { workspace = true } +libp2p-swarm = { workspace = true, features = ["async-std"] } libp2p-tcp = { workspace = true, features = ["async-io"] } libp2p-yamux = { workspace = true } futures = "0.3.28" diff --git a/swarm-test/src/lib.rs b/swarm-test/src/lib.rs index 5cc85728b3a..41a606b300c 100644 --- a/swarm-test/src/lib.rs +++ b/swarm-test/src/lib.rs @@ -28,7 +28,7 @@ use libp2p_identity::{Keypair, PeerId}; use libp2p_plaintext as plaintext; use libp2p_swarm::dial_opts::PeerCondition; use libp2p_swarm::{ - dial_opts::DialOpts, NetworkBehaviour, Swarm, SwarmBuilder, SwarmEvent, THandlerErr, + self as swarm, dial_opts::DialOpts, NetworkBehaviour, Swarm, SwarmEvent, THandlerErr, }; use libp2p_yamux as yamux; use std::fmt::Debug; @@ -216,9 +216,13 @@ where .timeout(Duration::from_secs(20)) .boxed(); - SwarmBuilder::without_executor(transport, behaviour_fn(identity), peer_id) - .idle_connection_timeout(Duration::from_secs(5)) // Some tests need connections to be kept alive beyond what the individual behaviour configures. - .build() + Swarm::new( + transport, + behaviour_fn(identity), + peer_id, + swarm::Config::with_async_std_executor() + .with_idle_connection_timeout(Duration::from_secs(5)), // Some tests need connections to be kept alive beyond what the individual behaviour configures., + ) } async fn connect(&mut self, other: &mut Swarm) diff --git a/swarm/CHANGELOG.md b/swarm/CHANGELOG.md index 230134862df..d0567f7d7a1 100644 --- a/swarm/CHANGELOG.md +++ b/swarm/CHANGELOG.md @@ -1,3 +1,12 @@ +## 0.43.6 - unreleased + +- Deprecate `libp2p::swarm::SwarmBuilder`. + Most users should use `libp2p::SwarmBuilder`. + In some special cases, users may need to use `Swarm::new` and `Config` instead of the new `libp2p::SwarmBuilder`. + See [PR 4120]. + +[PR 4120]: https://github.com/libp2p/rust-libp2p/pull/4120 + ## 0.43.5 - Fix overflow in `KeepAlive` computation that could occur if `SwarmBuilder::idle_connection_timeout` is configured with `u64::MAX`. diff --git a/swarm/Cargo.toml b/swarm/Cargo.toml index 2fc81b9356e..d151e7af614 100644 --- a/swarm/Cargo.toml +++ b/swarm/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-swarm" edition = "2021" rust-version = { workspace = true } description = "The libp2p swarm" -version = "0.43.5" +version = "0.43.6" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -55,7 +55,7 @@ quickcheck = { workspace = true } void = "1" once_cell = "1.18.0" trybuild = "1.0.85" -tokio = { version = "1.33.0", features = ["time", "rt", "macros"] } +tokio = { version = "1.33.0", features = ["time", "rt", "macros", "rt-multi-thread"] } [[test]] name = "swarm_derive" diff --git a/swarm/src/connection.rs b/swarm/src/connection.rs index 97251039abb..8fdc39edae0 100644 --- a/swarm/src/connection.rs +++ b/swarm/src/connection.rs @@ -748,6 +748,8 @@ mod tests { #[test] fn max_negotiating_inbound_streams() { + let _ = env_logger::try_init(); + fn prop(max_negotiating_inbound_streams: u8) { let max_negotiating_inbound_streams: usize = max_negotiating_inbound_streams.into(); @@ -756,7 +758,7 @@ mod tests { StreamMuxerBox::new(DummyStreamMuxer { counter: alive_substream_counter.clone(), }), - MockConnectionHandler::new(Duration::ZERO), + MockConnectionHandler::new(Duration::from_secs(10)), None, max_negotiating_inbound_streams, Duration::ZERO, diff --git a/swarm/src/lib.rs b/swarm/src/lib.rs index b81dcff802c..52498156f1f 100644 --- a/swarm/src/lib.rs +++ b/swarm/src/lib.rs @@ -359,6 +359,26 @@ impl Swarm where TBehaviour: NetworkBehaviour, { + /// Creates a new [`Swarm`] from the given [`Transport`], [`NetworkBehaviour`], [`PeerId`] and + /// [`Config`]. + pub fn new( + transport: transport::Boxed<(PeerId, StreamMuxerBox)>, + behaviour: TBehaviour, + local_peer_id: PeerId, + config: Config, + ) -> Self { + Swarm { + local_peer_id, + transport, + pool: Pool::new(local_peer_id, config.pool_config), + behaviour, + supported_protocols: Default::default(), + confirmed_external_addr: Default::default(), + listened_addrs: HashMap::new(), + pending_event: None, + } + } + /// Returns information about the connections underlying the [`Swarm`]. pub fn network_info(&self) -> NetworkInfo { let num_peers = self.pool.num_peers(); @@ -401,7 +421,9 @@ where /// # use libp2p_swarm::dummy; /// # use libp2p_identity::PeerId; /// # - /// let mut swarm = SwarmBuilder::without_executor( + /// # #[tokio::main] + /// # async fn main() { + /// let mut swarm = SwarmBuilder::with_tokio_executor( /// DummyTransport::new().boxed(), /// dummy::Behaviour, /// PeerId::random(), @@ -412,6 +434,7 @@ where /// /// // Dial an unknown peer. /// swarm.dial("/ip6/::1/tcp/12345".parse::().unwrap()); + /// # } /// ``` pub fn dial(&mut self, opts: impl Into) -> Result<(), DialError> { let dial_opts = opts.into(); @@ -1346,7 +1369,132 @@ impl<'a> PollParameters for SwarmPollParameters<'a> { } } +pub struct Config { + pool_config: PoolConfig, +} + +impl Config { + /// Creates a new [`Config`] from the given executor. The [`Swarm`] is obtained via + /// [`Swarm::new`]. + pub fn with_executor(executor: impl Executor + Send + 'static) -> Self { + Self { + pool_config: PoolConfig::new(Some(Box::new(executor))), + } + } + + /// Sets executor to the `wasm` executor. + /// Background tasks will be executed by the browser on the next micro-tick. + /// + /// Spawning a task is similar too: + /// ```typescript + /// function spawn(task: () => Promise) { + /// task() + /// } + /// ``` + #[cfg(feature = "wasm-bindgen")] + pub fn with_wasm_executor() -> Self { + Self::with_executor(crate::executor::WasmBindgenExecutor) + } + + /// Builds a new [`Config`] from the given `tokio` executor. + #[cfg(all( + feature = "tokio", + not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")) + ))] + pub fn with_tokio_executor() -> Self { + Self::with_executor(crate::executor::TokioExecutor) + } + + /// Builds a new [`Config`] from the given `async-std` executor. + #[cfg(all( + feature = "async-std", + not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")) + ))] + pub fn with_async_std_executor() -> Self { + Self::with_executor(crate::executor::AsyncStdExecutor) + } + + /// Configures the number of events from the [`NetworkBehaviour`] in + /// destination to the [`ConnectionHandler`] that can be buffered before + /// the [`Swarm`] has to wait. An individual buffer with this number of + /// events exists for each individual connection. + /// + /// The ideal value depends on the executor used, the CPU speed, and the + /// volume of events. If this value is too low, then the [`Swarm`] will + /// be sleeping more often than necessary. Increasing this value increases + /// the overall memory usage. + pub fn with_notify_handler_buffer_size(mut self, n: NonZeroUsize) -> Self { + self.pool_config = self.pool_config.with_notify_handler_buffer_size(n); + self + } + + /// Configures the size of the buffer for events sent by a [`ConnectionHandler`] to the + /// [`NetworkBehaviour`]. + /// + /// Each connection has its own buffer. + /// + /// The ideal value depends on the executor used, the CPU speed and the volume of events. + /// If this value is too low, then the [`ConnectionHandler`]s will be sleeping more often + /// than necessary. Increasing this value increases the overall memory + /// usage, and more importantly the latency between the moment when an + /// event is emitted and the moment when it is received by the + /// [`NetworkBehaviour`]. + pub fn with_per_connection_event_buffer_size(mut self, n: usize) -> Self { + self.pool_config = self.pool_config.with_per_connection_event_buffer_size(n); + self + } + + /// Number of addresses concurrently dialed for a single outbound connection attempt. + pub fn with_dial_concurrency_factor(mut self, factor: NonZeroU8) -> Self { + self.pool_config = self.pool_config.with_dial_concurrency_factor(factor); + self + } + + /// Configures an override for the substream upgrade protocol to use. + /// + /// The subtream upgrade protocol is the multistream-select protocol + /// used for protocol negotiation on substreams. Since a listener + /// supports all existing versions, the choice of upgrade protocol + /// only effects the "dialer", i.e. the peer opening a substream. + /// + /// > **Note**: If configured, specific upgrade protocols for + /// > individual [`SubstreamProtocol`]s emitted by the `NetworkBehaviour` + /// > are ignored. + pub fn with_substream_upgrade_protocol_override( + mut self, + v: libp2p_core::upgrade::Version, + ) -> Self { + self.pool_config = self.pool_config.with_substream_upgrade_protocol_override(v); + self + } + + /// The maximum number of inbound streams concurrently negotiating on a + /// connection. New inbound streams exceeding the limit are dropped and thus + /// reset. + /// + /// Note: This only enforces a limit on the number of concurrently + /// negotiating inbound streams. The total number of inbound streams on a + /// connection is the sum of negotiating and negotiated streams. A limit on + /// the total number of streams can be enforced at the + /// [`StreamMuxerBox`] level. + pub fn with_max_negotiating_inbound_streams(mut self, v: usize) -> Self { + self.pool_config = self.pool_config.with_max_negotiating_inbound_streams(v); + self + } + + /// How long to keep a connection alive once it is idling. + /// + /// Defaults to 0. + pub fn with_idle_connection_timeout(mut self, timeout: Duration) -> Self { + self.pool_config.idle_connection_timeout = timeout; + self + } +} + /// A [`SwarmBuilder`] provides an API for configuring and constructing a [`Swarm`]. +#[deprecated( + note = "Use the new `libp2p::SwarmBuilder` instead of `libp2p::swarm::SwarmBuilder` or create a `Swarm` directly via `Swarm::new`." +)] pub struct SwarmBuilder { local_peer_id: PeerId, transport: transport::Boxed<(PeerId, StreamMuxerBox)>, @@ -1354,6 +1502,7 @@ pub struct SwarmBuilder { pool_config: PoolConfig, } +#[allow(deprecated)] impl SwarmBuilder where TBehaviour: NetworkBehaviour, @@ -1820,9 +1969,7 @@ mod tests { use super::*; use crate::dummy; use crate::test::{CallTraceBehaviour, MockBehaviour}; - use futures::executor::block_on; - use futures::executor::ThreadPool; - use futures::{executor, future}; + use futures::future; use libp2p_core::multiaddr::multiaddr; use libp2p_core::transport::memory::MemoryTransportError; use libp2p_core::transport::TransportEvent; @@ -1841,7 +1988,8 @@ mod tests { } fn new_test_swarm( - ) -> SwarmBuilder>> { + config: Config, + ) -> Swarm>> { let id_keys = identity::Keypair::generate_ed25519(); let local_public_key = id_keys.public(); let transport = transport::MemoryTransport::default() @@ -1850,14 +1998,13 @@ mod tests { .multiplex(yamux::Config::default()) .boxed(); let behaviour = CallTraceBehaviour::new(MockBehaviour::new(dummy::ConnectionHandler)); - let builder = match ThreadPool::new().ok() { - Some(tp) => { - SwarmBuilder::with_executor(transport, behaviour, local_public_key.into(), tp) - } - None => SwarmBuilder::without_executor(transport, behaviour, local_public_key.into()), - }; - builder.idle_connection_timeout(Duration::from_secs(5)) + Swarm::new( + transport, + behaviour, + local_public_key.into(), + config.with_idle_connection_timeout(Duration::from_secs(5)), + ) } fn swarms_connected( @@ -1906,10 +2053,10 @@ mod tests { /// /// The test expects both behaviours to be notified via calls to [`NetworkBehaviour::on_swarm_event`] /// with pairs of [`FromSwarm::ConnectionEstablished`] / [`FromSwarm::ConnectionClosed`] - #[test] - fn test_swarm_disconnect() { - let mut swarm1 = new_test_swarm().build(); - let mut swarm2 = new_test_swarm().build(); + #[tokio::test] + async fn test_swarm_disconnect() { + let mut swarm1 = new_test_swarm(Config::with_tokio_executor()); + let mut swarm2 = new_test_swarm(Config::with_tokio_executor()); let addr1: Multiaddr = multiaddr::Protocol::Memory(rand::random::()).into(); let addr2: Multiaddr = multiaddr::Protocol::Memory(rand::random::()).into(); @@ -1927,7 +2074,7 @@ mod tests { } let mut state = State::Connecting; - executor::block_on(future::poll_fn(move |cx| loop { + future::poll_fn(move |cx| loop { let poll1 = Swarm::poll_next_event(Pin::new(&mut swarm1), cx); let poll2 = Swarm::poll_next_event(Pin::new(&mut swarm2), cx); match state { @@ -1959,7 +2106,8 @@ mod tests { if poll1.is_pending() && poll2.is_pending() { return Poll::Pending; } - })) + }) + .await } /// Establishes multiple connections between two peers, @@ -1968,10 +2116,10 @@ mod tests { /// /// The test expects both behaviours to be notified via calls to [`NetworkBehaviour::on_swarm_event`] /// with pairs of [`FromSwarm::ConnectionEstablished`] / [`FromSwarm::ConnectionClosed`] - #[test] - fn test_behaviour_disconnect_all() { - let mut swarm1 = new_test_swarm().build(); - let mut swarm2 = new_test_swarm().build(); + #[tokio::test] + async fn test_behaviour_disconnect_all() { + let mut swarm1 = new_test_swarm(Config::with_tokio_executor()); + let mut swarm2 = new_test_swarm(Config::with_tokio_executor()); let addr1: Multiaddr = multiaddr::Protocol::Memory(rand::random::()).into(); let addr2: Multiaddr = multiaddr::Protocol::Memory(rand::random::()).into(); @@ -1989,7 +2137,7 @@ mod tests { } let mut state = State::Connecting; - executor::block_on(future::poll_fn(move |cx| loop { + future::poll_fn(move |cx| loop { let poll1 = Swarm::poll_next_event(Pin::new(&mut swarm1), cx); let poll2 = Swarm::poll_next_event(Pin::new(&mut swarm2), cx); match state { @@ -2025,7 +2173,8 @@ mod tests { if poll1.is_pending() && poll2.is_pending() { return Poll::Pending; } - })) + }) + .await } /// Establishes multiple connections between two peers, @@ -2034,10 +2183,10 @@ mod tests { /// /// The test expects both behaviours to be notified via calls to [`NetworkBehaviour::on_swarm_event`] /// with pairs of [`FromSwarm::ConnectionEstablished`] / [`FromSwarm::ConnectionClosed`] - #[test] - fn test_behaviour_disconnect_one() { - let mut swarm1 = new_test_swarm().build(); - let mut swarm2 = new_test_swarm().build(); + #[tokio::test] + async fn test_behaviour_disconnect_one() { + let mut swarm1 = new_test_swarm(Config::with_tokio_executor()); + let mut swarm2 = new_test_swarm(Config::with_tokio_executor()); let addr1: Multiaddr = multiaddr::Protocol::Memory(rand::random::()).into(); let addr2: Multiaddr = multiaddr::Protocol::Memory(rand::random::()).into(); @@ -2055,7 +2204,7 @@ mod tests { let mut state = State::Connecting; let mut disconnected_conn_id = None; - executor::block_on(future::poll_fn(move |cx| loop { + future::poll_fn(move |cx| loop { let poll1 = Swarm::poll_next_event(Pin::new(&mut swarm1), cx); let poll2 = Swarm::poll_next_event(Pin::new(&mut swarm2), cx); match state { @@ -2099,7 +2248,8 @@ mod tests { if poll1.is_pending() && poll2.is_pending() { return Poll::Pending; } - })) + }) + .await } #[test] @@ -2114,10 +2264,11 @@ mod tests { } fn prop(concurrency_factor: DialConcurrencyFactor) { - block_on(async { - let mut swarm = new_test_swarm() - .dial_concurrency_factor(concurrency_factor.0) - .build(); + tokio::runtime::Runtime::new().unwrap().block_on(async { + let mut swarm = new_test_swarm( + Config::with_tokio_executor() + .with_dial_concurrency_factor(concurrency_factor.0), + ); // Listen on `concurrency_factor + 1` addresses. // @@ -2173,31 +2324,29 @@ mod tests { QuickCheck::new().tests(10).quickcheck(prop as fn(_) -> _); } - #[test] - fn invalid_peer_id() { + #[tokio::test] + async fn invalid_peer_id() { // Checks whether dialing an address containing the wrong peer id raises an error // for the expected peer id instead of the obtained peer id. - let mut swarm1 = new_test_swarm().build(); - let mut swarm2 = new_test_swarm().build(); + let mut swarm1 = new_test_swarm(Config::with_tokio_executor()); + let mut swarm2 = new_test_swarm(Config::with_tokio_executor()); swarm1.listen_on("/memory/0".parse().unwrap()).unwrap(); - let address = - futures::executor::block_on(future::poll_fn(|cx| match swarm1.poll_next_unpin(cx) { - Poll::Ready(Some(SwarmEvent::NewListenAddr { address, .. })) => { - Poll::Ready(address) - } - Poll::Pending => Poll::Pending, - _ => panic!("Was expecting the listen address to be reported"), - })); + let address = future::poll_fn(|cx| match swarm1.poll_next_unpin(cx) { + Poll::Ready(Some(SwarmEvent::NewListenAddr { address, .. })) => Poll::Ready(address), + Poll::Pending => Poll::Pending, + _ => panic!("Was expecting the listen address to be reported"), + }) + .await; let other_id = PeerId::random(); let other_addr = address.with(multiaddr::Protocol::P2p(other_id)); swarm2.dial(other_addr.clone()).unwrap(); - let (peer_id, error) = futures::executor::block_on(future::poll_fn(|cx| { + let (peer_id, error) = future::poll_fn(|cx| { if let Poll::Ready(Some(SwarmEvent::IncomingConnection { .. })) = swarm1.poll_next_unpin(cx) {} @@ -2209,7 +2358,8 @@ mod tests { Poll::Ready(x) => panic!("unexpected {x:?}"), Poll::Pending => Poll::Pending, } - })); + }) + .await; assert_eq!(peer_id.unwrap(), other_id); match error { DialError::WrongPeerId { obtained, endpoint } => { @@ -2226,8 +2376,8 @@ mod tests { } } - #[test] - fn dial_self() { + #[tokio::test] + async fn dial_self() { // Check whether dialing ourselves correctly fails. // // Dialing the same address we're listening should result in three events: @@ -2238,17 +2388,15 @@ mod tests { // // The last two can happen in any order. - let mut swarm = new_test_swarm().build(); + let mut swarm = new_test_swarm(Config::with_tokio_executor()); swarm.listen_on("/memory/0".parse().unwrap()).unwrap(); - let local_address = - futures::executor::block_on(future::poll_fn(|cx| match swarm.poll_next_unpin(cx) { - Poll::Ready(Some(SwarmEvent::NewListenAddr { address, .. })) => { - Poll::Ready(address) - } - Poll::Pending => Poll::Pending, - _ => panic!("Was expecting the listen address to be reported"), - })); + let local_address = future::poll_fn(|cx| match swarm.poll_next_unpin(cx) { + Poll::Ready(Some(SwarmEvent::NewListenAddr { address, .. })) => Poll::Ready(address), + Poll::Pending => Poll::Pending, + _ => panic!("Was expecting the listen address to be reported"), + }) + .await; swarm.listened_addrs.clear(); // This is a hack to actually execute the dial to ourselves which would otherwise be filtered. @@ -2256,7 +2404,7 @@ mod tests { let mut got_dial_err = false; let mut got_inc_err = false; - futures::executor::block_on(future::poll_fn(|cx| -> Poll> { + future::poll_fn(|cx| -> Poll> { loop { match swarm.poll_next_unpin(cx) { Poll::Ready(Some(SwarmEvent::OutgoingConnectionError { @@ -2290,26 +2438,27 @@ mod tests { Poll::Pending => break Poll::Pending, } } - })) + }) + .await .unwrap(); } - #[test] - fn dial_self_by_id() { + #[tokio::test] + async fn dial_self_by_id() { // Trying to dial self by passing the same `PeerId` shouldn't even be possible in the first // place. - let swarm = new_test_swarm().build(); + let swarm = new_test_swarm(Config::with_tokio_executor()); let peer_id = *swarm.local_peer_id(); assert!(!swarm.is_connected(&peer_id)); } - #[async_std::test] + #[tokio::test] async fn multiple_addresses_err() { // Tries dialing multiple addresses, and makes sure there's one dialing error per address. let target = PeerId::random(); - let mut swarm = new_test_swarm().build(); + let mut swarm = new_test_swarm(Config::with_tokio_executor()); let addresses = HashSet::from([ multiaddr![Ip4([0, 0, 0, 0]), Tcp(rand::random::())], @@ -2351,16 +2500,16 @@ mod tests { } } - #[test] - fn aborting_pending_connection_surfaces_error() { + #[tokio::test] + async fn aborting_pending_connection_surfaces_error() { let _ = env_logger::try_init(); - let mut dialer = new_test_swarm().build(); - let mut listener = new_test_swarm().build(); + let mut dialer = new_test_swarm(Config::with_tokio_executor()); + let mut listener = new_test_swarm(Config::with_tokio_executor()); let listener_peer_id = *listener.local_peer_id(); listener.listen_on(multiaddr![Memory(0u64)]).unwrap(); - let listener_address = match block_on(listener.next()).unwrap() { + let listener_address = match listener.next().await.unwrap() { SwarmEvent::NewListenAddr { address, .. } => address, e => panic!("Unexpected network event: {e:?}"), }; @@ -2377,7 +2526,7 @@ mod tests { .disconnect_peer_id(listener_peer_id) .expect_err("Expect peer to not yet be connected."); - match block_on(dialer.next()).unwrap() { + match dialer.next().await.unwrap() { SwarmEvent::OutgoingConnectionError { error: DialError::Aborted, .. diff --git a/transports/pnet/tests/smoke.rs b/transports/pnet/tests/smoke.rs index 5e02ed856c6..79ffaeab447 100644 --- a/transports/pnet/tests/smoke.rs +++ b/transports/pnet/tests/smoke.rs @@ -6,7 +6,7 @@ use libp2p_core::upgrade::Version; use libp2p_core::Transport; use libp2p_core::{multiaddr::Protocol, Multiaddr}; use libp2p_pnet::{PnetConfig, PreSharedKey}; -use libp2p_swarm::{dummy, NetworkBehaviour, Swarm, SwarmBuilder, SwarmEvent}; +use libp2p_swarm::{dummy, Config, NetworkBehaviour, Swarm, SwarmEvent}; const TIMEOUT: Duration = Duration::from_secs(5); @@ -113,9 +113,12 @@ where .authenticate(libp2p_noise::Config::new(&identity).unwrap()) .multiplex(libp2p_yamux::Config::default()) .boxed(); - SwarmBuilder::with_tokio_executor(transport, dummy::Behaviour, identity.public().to_peer_id()) - .idle_connection_timeout(Duration::from_secs(5)) - .build() + Swarm::new( + transport, + dummy::Behaviour, + identity.public().to_peer_id(), + Config::with_tokio_executor(), + ) } async fn listen_on(swarm: &mut Swarm, addr: Multiaddr) -> Multiaddr { diff --git a/transports/tcp/src/provider/async_io.rs b/transports/tcp/src/provider/async_io.rs index 9f43ed23236..fe0abe42d54 100644 --- a/transports/tcp/src/provider/async_io.rs +++ b/transports/tcp/src/provider/async_io.rs @@ -54,7 +54,7 @@ pub type Transport = crate::Transport; pub enum Tcp {} impl Provider for Tcp { - type Stream = Async; + type Stream = TcpStream; type Listener = Async; type IfWatcher = if_watch::smol::IfWatcher; @@ -116,3 +116,5 @@ impl Provider for Tcp { })) } } + +pub type TcpStream = Async; diff --git a/transports/tls/Cargo.toml b/transports/tls/Cargo.toml index f59e56beb8f..7c0ea41b2a0 100644 --- a/transports/tls/Cargo.toml +++ b/transports/tls/Cargo.toml @@ -31,7 +31,7 @@ hex = "0.4.3" hex-literal = "0.4.1" libp2p-core = { workspace = true } libp2p-identity = { workspace = true, features = ["ed25519", "rsa", "secp256k1", "ecdsa"] } -libp2p-swarm = { workspace = true } +libp2p-swarm = { workspace = true, features = ["tokio"] } libp2p-yamux = { workspace = true } tokio = { version = "1.33.0", features = ["full"] } diff --git a/transports/tls/tests/smoke.rs b/transports/tls/tests/smoke.rs index 0db39edf280..d488ae7846a 100644 --- a/transports/tls/tests/smoke.rs +++ b/transports/tls/tests/smoke.rs @@ -3,7 +3,7 @@ use libp2p_core::multiaddr::Protocol; use libp2p_core::transport::MemoryTransport; use libp2p_core::upgrade::Version; use libp2p_core::Transport; -use libp2p_swarm::{dummy, Swarm, SwarmBuilder, SwarmEvent}; +use libp2p_swarm::{dummy, Config, Swarm, SwarmEvent}; use std::time::Duration; #[tokio::test] @@ -65,7 +65,10 @@ fn make_swarm() -> Swarm { .multiplex(libp2p_yamux::Config::default()) .boxed(); - SwarmBuilder::without_executor(transport, dummy::Behaviour, identity.public().to_peer_id()) - .idle_connection_timeout(Duration::from_secs(5)) - .build() + Swarm::new( + transport, + dummy::Behaviour, + identity.public().to_peer_id(), + Config::with_tokio_executor().with_idle_connection_timeout(Duration::from_secs(60)), + ) }