2023-03-08 20:36:35 +11:00
// 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 ::{
2023-06-06 21:22:50 +02:00
multiaddr ::Protocol , transport ::MemoryTransport , upgrade ::Version , Multiaddr , Transport ,
2023-03-08 20:36:35 +11:00
} ;
2023-06-06 21:22:50 +02:00
use libp2p_identity ::{ Keypair , PeerId } ;
2023-09-28 05:42:45 +10:00
use libp2p_plaintext as plaintext ;
2023-03-21 16:04:53 +01:00
use libp2p_swarm ::dial_opts ::PeerCondition ;
2023-03-08 20:36:35 +11:00
use libp2p_swarm ::{
2023-05-24 09:52:16 +02:00
dial_opts ::DialOpts , NetworkBehaviour , Swarm , SwarmBuilder , SwarmEvent , THandlerErr ,
2023-03-08 20:36:35 +11:00
} ;
2023-05-01 04:25:52 +02:00
use libp2p_yamux as yamux ;
2023-03-08 20:36:35 +11:00
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.
///
2023-09-28 05:42:45 +10:00
/// 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
2023-03-08 20:36:35 +11:00
/// 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 < T > ( & mut self , other : & mut Swarm < T > )
where
T : NetworkBehaviour + Send ,
2023-05-14 12:58:08 +02:00
< T as NetworkBehaviour > ::ToSwarm : Debug ;
2023-03-08 20:36:35 +11:00
/// 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 < E , P > ( & mut self , predicate : P ) -> E
where
P : Fn (
2023-05-14 12:58:08 +02:00
SwarmEvent < < Self ::NB as NetworkBehaviour > ::ToSwarm , THandlerErr < Self ::NB > > ,
2023-03-08 20:36:35 +11:00
) -> Option < E > ,
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 ,
2023-05-14 12:58:08 +02:00
) -> SwarmEvent < < Self ::NB as NetworkBehaviour > ::ToSwarm , THandlerErr < Self ::NB > > ;
2023-03-08 20:36:35 +11:00
/// 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`.
2023-05-14 12:58:08 +02:00
async fn next_behaviour_event ( & mut self ) -> < Self ::NB as NetworkBehaviour > ::ToSwarm ;
2023-03-08 20:36:35 +11:00
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.
2023-05-14 12:58:08 +02:00
/// 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.
2023-03-08 20:36:35 +11:00
///
/// 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 < TBehaviour2 > ,
swarm2 : & mut Swarm < TBehaviour1 > ,
) -> ( [ Out1 ; NUM_EVENTS_SWARM_1 ] , [ Out2 ; NUM_EVENTS_SWARM_2 ] )
where
TBehaviour2 : NetworkBehaviour + Send ,
2023-05-14 12:58:08 +02:00
TBehaviour2 ::ToSwarm : Debug ,
2023-03-08 20:36:35 +11:00
TBehaviour1 : NetworkBehaviour + Send ,
2023-05-14 12:58:08 +02:00
TBehaviour1 ::ToSwarm : Debug ,
SwarmEvent < TBehaviour2 ::ToSwarm , THandlerErr < TBehaviour2 > > : TryIntoOutput < Out1 > ,
SwarmEvent < TBehaviour1 ::ToSwarm , THandlerErr < TBehaviour1 > > : TryIntoOutput < Out2 > ,
2023-03-08 20:36:35 +11:00
Out1 : Debug ,
Out2 : Debug ,
{
let mut res1 = Vec ::< Out1 > ::with_capacity ( NUM_EVENTS_SWARM_1 ) ;
let mut res2 = Vec ::< Out2 > ::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 < O > : Sized {
fn try_into_output ( self ) -> Result < O , Self > ;
}
impl < O , THandlerErr > TryIntoOutput < O > for SwarmEvent < O , THandlerErr > {
fn try_into_output ( self ) -> Result < O , Self > {
self . try_into_behaviour_event ( )
}
}
impl < TBehaviourOutEvent , THandlerErr > TryIntoOutput < SwarmEvent < TBehaviourOutEvent , THandlerErr > >
for SwarmEvent < TBehaviourOutEvent , THandlerErr >
{
fn try_into_output ( self ) -> Result < SwarmEvent < TBehaviourOutEvent , THandlerErr > , Self > {
Ok ( self )
}
}
#[ async_trait ]
impl < B > SwarmExt for Swarm < B >
where
B : NetworkBehaviour + Send ,
2023-05-14 12:58:08 +02:00
< B as NetworkBehaviour > ::ToSwarm : Debug ,
2023-03-08 20:36:35 +11:00
{
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 )
2023-09-28 05:42:45 +10:00
. authenticate ( plaintext ::Config ::new ( & identity ) )
2023-05-01 04:25:52 +02:00
. multiplex ( yamux ::Config ::default ( ) )
2023-03-08 20:36:35 +11:00
. timeout ( Duration ::from_secs ( 20 ) )
. boxed ( ) ;
2023-09-20 04:02:29 +05:30
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 ( )
2023-03-08 20:36:35 +11:00
}
async fn connect < T > ( & mut self , other : & mut Swarm < T > )
where
T : NetworkBehaviour + Send ,
2023-05-14 12:58:08 +02:00
< T as NetworkBehaviour > ::ToSwarm : Debug ,
2023-03-08 20:36:35 +11:00
{
2023-05-24 09:52:16 +02:00
let external_addresses = other . external_addresses ( ) . cloned ( ) . collect ( ) ;
2023-03-08 20:36:35 +11:00
let dial_opts = DialOpts ::peer_id ( * other . local_peer_id ( ) )
. addresses ( external_addresses )
2023-03-21 16:04:53 +01:00
. condition ( PeerCondition ::Always )
2023-03-08 20:36:35 +11:00
. 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 < E , P > ( & mut self , predicate : P ) -> E
where
2023-05-14 12:58:08 +02:00
P : Fn ( SwarmEvent < < B as NetworkBehaviour > ::ToSwarm , THandlerErr < B > > ) -> Option < E > ,
2023-03-08 20:36:35 +11:00
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.
2023-05-24 09:52:16 +02:00
self . add_external_address ( memory_multiaddr . clone ( ) ) ;
2023-03-08 20:36:35 +11:00
let tcp_addr_listener_id = self
2023-06-27 03:21:47 +02:00
. listen_on ( " /ip4/127.0.0.1/tcp/0 " . parse ( ) . unwrap ( ) )
2023-03-08 20:36:35 +11:00
. 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 ,
2023-05-14 12:58:08 +02:00
) -> SwarmEvent < < Self ::NB as NetworkBehaviour > ::ToSwarm , THandlerErr < Self ::NB > > {
2023-03-08 20:36:35 +11:00
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
}
}
}
2023-05-14 12:58:08 +02:00
async fn next_behaviour_event ( & mut self ) -> < Self ::NB as NetworkBehaviour > ::ToSwarm {
2023-03-08 20:36:35 +11:00
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 ) ;
}
}
}