// Copyright 2023 Protocol Labs. // // 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 async_trait::async_trait; use futures::future::Either; use futures::StreamExt; use libp2p_core::{ multiaddr::Protocol, transport::MemoryTransport, upgrade::Version, Multiaddr, Transport, }; 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, }; use libp2p_yamux as yamux; use std::fmt::Debug; use std::time::Duration; /// An extension trait for [`Swarm`] that makes it easier to set up a network of [`Swarm`]s for tests. #[async_trait] pub trait SwarmExt { type NB: NetworkBehaviour; /// Create a new [`Swarm`] with an ephemeral identity. /// /// The swarm will use a [`MemoryTransport`] together with a [`plaintext::Config`] authentication layer and /// [`yamux::Config`] as the multiplexer. However, these details should not be relied upon by the test /// and may change at any time. fn new_ephemeral(behaviour_fn: impl FnOnce(Keypair) -> Self::NB) -> Self where Self: Sized; /// Establishes a connection to the given [`Swarm`], polling both of them until the connection is established. async fn connect(&mut self, other: &mut Swarm) where T: NetworkBehaviour + Send, ::ToSwarm: Debug; /// Dial the provided address and wait until a connection has been established. /// /// In a normal test scenario, you should prefer [`SwarmExt::connect`] but that is not always possible. /// This function only abstracts away the "dial and wait for `ConnectionEstablished` event" part. /// /// Because we don't have access to the other [`Swarm`], we can't guarantee that it makes progress. async fn dial_and_wait(&mut self, addr: Multiaddr) -> PeerId; /// Wait for specified condition to return `Some`. async fn wait(&mut self, predicate: P) -> E where P: Fn( SwarmEvent<::ToSwarm, THandlerErr>, ) -> Option, P: Send; /// Listens for incoming connections, polling the [`Swarm`] until the transport is ready to accept connections. /// /// The first address is for the memory transport, the second one for the TCP transport. async fn listen(&mut self) -> (Multiaddr, Multiaddr); /// Returns the next [`SwarmEvent`] or times out after 10 seconds. /// /// If the 10s timeout does not fit your usecase, please fall back to `StreamExt::next`. async fn next_swarm_event( &mut self, ) -> SwarmEvent<::ToSwarm, THandlerErr>; /// Returns the next behaviour event or times out after 10 seconds. /// /// If the 10s timeout does not fit your usecase, please fall back to `StreamExt::next`. async fn next_behaviour_event(&mut self) -> ::ToSwarm; async fn loop_on_next(self); } /// Drives two [`Swarm`]s until a certain number of events are emitted. /// /// # Usage /// /// ## Number of events /// /// The number of events is configured via const generics based on the array size of the return type. /// This allows the compiler to infer how many events you are expecting based on how you use this function. /// For example, if you expect the first [`Swarm`] to emit 2 events, you should assign the first variable of the returned tuple value to an array of size 2. /// This works especially well if you directly pattern-match on the return value. /// /// ## Type of event /// /// This function utilizes the [`TryIntoOutput`] trait. /// Similar as to the number of expected events, the type of event is inferred based on your usage. /// If you match against a [`SwarmEvent`], the first [`SwarmEvent`] will be returned. /// If you match against your [`NetworkBehaviour::ToSwarm`] type, [`SwarmEvent`]s which are not [`SwarmEvent::Behaviour`] will be skipped until the [`Swarm`] returns a behaviour event. /// /// You can implement the [`TryIntoOutput`] for any other type to further customize this behaviour. /// /// # Difference to [`futures::future::join`] /// /// This function is similar to joining two futures with two crucial differences: /// 1. As described above, it allows you to obtain more than a single event. /// 2. More importantly, it will continue to poll the [`Swarm`]s **even if they already has emitted all expected events**. /// /// Especially (2) is crucial for our usage of this function. /// If a [`Swarm`] is not polled, nothing within it makes progress. /// This can "starve" the other swarm which for example may wait for another message to be sent on a connection. /// /// Using [`drive`] instead of [`futures::future::join`] ensures that a [`Swarm`] continues to be polled, even after it emitted its events. pub async fn drive< TBehaviour1, const NUM_EVENTS_SWARM_1: usize, Out1, TBehaviour2, const NUM_EVENTS_SWARM_2: usize, Out2, >( swarm1: &mut Swarm, swarm2: &mut Swarm, ) -> ([Out1; NUM_EVENTS_SWARM_1], [Out2; NUM_EVENTS_SWARM_2]) where TBehaviour2: NetworkBehaviour + Send, TBehaviour2::ToSwarm: Debug, TBehaviour1: NetworkBehaviour + Send, TBehaviour1::ToSwarm: Debug, SwarmEvent>: TryIntoOutput, SwarmEvent>: TryIntoOutput, Out1: Debug, Out2: Debug, { let mut res1 = Vec::::with_capacity(NUM_EVENTS_SWARM_1); let mut res2 = Vec::::with_capacity(NUM_EVENTS_SWARM_2); while res1.len() < NUM_EVENTS_SWARM_1 || res2.len() < NUM_EVENTS_SWARM_2 { match futures::future::select(swarm1.next_swarm_event(), swarm2.next_swarm_event()).await { Either::Left((o1, _)) => { if let Ok(o1) = o1.try_into_output() { res1.push(o1); } } Either::Right((o2, _)) => { if let Ok(o2) = o2.try_into_output() { res2.push(o2); } } } } ( res1.try_into().unwrap_or_else(|res1: Vec<_>| { panic!( "expected {NUM_EVENTS_SWARM_1} items from first swarm but got {}", res1.len() ) }), res2.try_into().unwrap_or_else(|res2: Vec<_>| { panic!( "expected {NUM_EVENTS_SWARM_2} items from second swarm but got {}", res2.len() ) }), ) } pub trait TryIntoOutput: Sized { fn try_into_output(self) -> Result; } impl TryIntoOutput for SwarmEvent { fn try_into_output(self) -> Result { self.try_into_behaviour_event() } } impl TryIntoOutput> for SwarmEvent { fn try_into_output(self) -> Result, Self> { Ok(self) } } #[async_trait] impl SwarmExt for Swarm where B: NetworkBehaviour + Send, ::ToSwarm: Debug, { type NB = B; fn new_ephemeral(behaviour_fn: impl FnOnce(Keypair) -> Self::NB) -> Self where Self: Sized, { let identity = Keypair::generate_ed25519(); let peer_id = PeerId::from(identity.public()); let transport = MemoryTransport::default() .or_transport(libp2p_tcp::async_io::Transport::default()) .upgrade(Version::V1) .authenticate(plaintext::Config::new(&identity)) .multiplex(yamux::Config::default()) .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() } async fn connect(&mut self, other: &mut Swarm) where T: NetworkBehaviour + Send, ::ToSwarm: Debug, { let external_addresses = other.external_addresses().cloned().collect(); let dial_opts = DialOpts::peer_id(*other.local_peer_id()) .addresses(external_addresses) .condition(PeerCondition::Always) .build(); self.dial(dial_opts).unwrap(); let mut dialer_done = false; let mut listener_done = false; loop { match futures::future::select(self.next_swarm_event(), other.next_swarm_event()).await { Either::Left((SwarmEvent::ConnectionEstablished { .. }, _)) => { dialer_done = true; } Either::Right((SwarmEvent::ConnectionEstablished { .. }, _)) => { listener_done = true; } Either::Left((other, _)) => { log::debug!("Ignoring event from dialer {:?}", other); } Either::Right((other, _)) => { log::debug!("Ignoring event from listener {:?}", other); } } if dialer_done && listener_done { return; } } } async fn dial_and_wait(&mut self, addr: Multiaddr) -> PeerId { self.dial(addr.clone()).unwrap(); self.wait(|e| match e { SwarmEvent::ConnectionEstablished { endpoint, peer_id, .. } => (endpoint.get_remote_address() == &addr).then_some(peer_id), other => { log::debug!("Ignoring event from dialer {:?}", other); None } }) .await } async fn wait(&mut self, predicate: P) -> E where P: Fn(SwarmEvent<::ToSwarm, THandlerErr>) -> Option, P: Send, { loop { let event = self.next_swarm_event().await; if let Some(e) = predicate(event) { break e; } } } async fn listen(&mut self) -> (Multiaddr, Multiaddr) { let memory_addr_listener_id = self.listen_on(Protocol::Memory(0).into()).unwrap(); // block until we are actually listening let memory_multiaddr = self .wait(|e| match e { SwarmEvent::NewListenAddr { address, listener_id, } => (listener_id == memory_addr_listener_id).then_some(address), other => { log::debug!( "Ignoring {:?} while waiting for listening to succeed", other ); None } }) .await; // Memory addresses are externally reachable because they all share the same memory-space. self.add_external_address(memory_multiaddr.clone()); let tcp_addr_listener_id = self .listen_on("/ip4/127.0.0.1/tcp/0".parse().unwrap()) .unwrap(); let tcp_multiaddr = self .wait(|e| match e { SwarmEvent::NewListenAddr { address, listener_id, } => (listener_id == tcp_addr_listener_id).then_some(address), other => { log::debug!( "Ignoring {:?} while waiting for listening to succeed", other ); None } }) .await; // We purposely don't add the TCP addr as an external one because we want to only use the memory transport for making connections in here. // The TCP transport is only supported for protocols that manage their own connections. (memory_multiaddr, tcp_multiaddr) } async fn next_swarm_event( &mut self, ) -> SwarmEvent<::ToSwarm, THandlerErr> { match futures::future::select( futures_timer::Delay::new(Duration::from_secs(10)), self.select_next_some(), ) .await { Either::Left(((), _)) => panic!("Swarm did not emit an event within 10s"), Either::Right((event, _)) => { log::trace!("Swarm produced: {:?}", event); event } } } async fn next_behaviour_event(&mut self) -> ::ToSwarm { loop { if let Ok(event) = self.next_swarm_event().await.try_into_behaviour_event() { return event; } } } async fn loop_on_next(mut self) { while let Some(event) = self.next().await { log::trace!("Swarm produced: {:?}", event); } } }