*: Dial with handler and return handler on error and closed (#2191)

Require `NetworkBehaviourAction::{DialPeer,DialAddress}` to contain a
`ProtocolsHandler`. This allows a behaviour to attach custom state to its
handler. The behaviour would no longer need to track this state separately
during connection establishment, thus reducing state required in a behaviour.
E.g. in the case of `libp2p-kad` the behaviour can include a `GetRecord` request
in its handler, or e.g. in the case of `libp2p-request-response` the behaviour
can include the first request in the handler.

Return `ProtocolsHandler` on connection error and close. This allows a behaviour
to extract its custom state previously included in the handler on connection
failure and connection closing. E.g. in the case of `libp2p-kad` the behaviour
could extract the attached `GetRecord` from the handler of the failed connection
and then start another connection attempt with a new handler with the same
`GetRecord` or bubble up an error to the user.

Co-authored-by: Thomas Eizinger <thomas@eizinger.io>
This commit is contained in:
Max Inden
2021-08-31 17:00:51 +02:00
committed by GitHub
parent b55ee69645
commit c161acfb50
38 changed files with 1111 additions and 477 deletions

View File

@ -19,13 +19,17 @@
// DEALINGS IN THE SOFTWARE.
use crate::protocols_handler::{IntoProtocolsHandler, ProtocolsHandler};
use crate::{AddressRecord, AddressScore};
use crate::{AddressRecord, AddressScore, DialError};
use libp2p_core::{
connection::{ConnectionId, ListenerId},
ConnectedPoint, Multiaddr, PeerId,
};
use std::{error, task::Context, task::Poll};
/// Custom event that can be received by the [`ProtocolsHandler`].
type THandlerInEvent<THandler> =
<<THandler as IntoProtocolsHandler>::Handler as ProtocolsHandler>::InEvent;
/// A behaviour for the network. Allows customizing the swarm.
///
/// This trait has been designed to be composable. Multiple implementations can be combined into
@ -65,16 +69,20 @@ pub trait NetworkBehaviour: Send + 'static {
/// Creates a new `ProtocolsHandler` for a connection with a peer.
///
/// Every time an incoming connection is opened, and every time we start dialing a node, this
/// method is called.
/// Every time an incoming connection is opened, and every time another [`NetworkBehaviour`]
/// emitted a dial request, this method is called.
///
/// The returned object is a handler for that specific connection, and will be moved to a
/// background task dedicated to that connection.
///
/// The network behaviour (ie. the implementation of this trait) and the handlers it has
/// spawned (ie. the objects returned by `new_handler`) can communicate by passing messages.
/// Messages sent from the handler to the behaviour are injected with `inject_event`, and
/// the behaviour can send a message to the handler by making `poll` return `SendEvent`.
/// The network behaviour (ie. the implementation of this trait) and the handlers it has spawned
/// (ie. the objects returned by `new_handler`) can communicate by passing messages. Messages
/// sent from the handler to the behaviour are injected with [`NetworkBehaviour::inject_event`],
/// and the behaviour can send a message to the handler by making [`NetworkBehaviour::poll`]
/// return [`NetworkBehaviourAction::NotifyHandler`].
///
/// Note that the handler is returned to the [`NetworkBehaviour`] on connection failure and
/// connection closing.
fn new_handler(&mut self) -> Self::ProtocolsHandler;
/// Addresses that this behaviour is aware of for this specific peer, and that may allow
@ -112,7 +120,14 @@ pub trait NetworkBehaviour: Send + 'static {
/// A call to this method is always paired with an earlier call to
/// `inject_connection_established` with the same peer ID, connection ID and
/// endpoint.
fn inject_connection_closed(&mut self, _: &PeerId, _: &ConnectionId, _: &ConnectedPoint) {}
fn inject_connection_closed(
&mut self,
_: &PeerId,
_: &ConnectionId,
_: &ConnectedPoint,
_: <Self::ProtocolsHandler as IntoProtocolsHandler>::Handler,
) {
}
/// Informs the behaviour that the [`ConnectedPoint`] of an existing connection has changed.
fn inject_address_change(
@ -153,7 +168,26 @@ pub trait NetworkBehaviour: Send + 'static {
///
/// The `peer_id` is guaranteed to be in a disconnected state. In other words,
/// `inject_connected` has not been called, or `inject_disconnected` has been called since then.
fn inject_dial_failure(&mut self, _peer_id: &PeerId) {}
fn inject_dial_failure(
&mut self,
_peer_id: &PeerId,
_handler: Self::ProtocolsHandler,
_error: DialError,
) {
}
/// Indicates to the behaviour that an error happened on an incoming connection during its
/// initial handshake.
///
/// This can include, for example, an error during the handshake of the encryption layer, or the
/// connection unexpectedly closed.
fn inject_listen_failure(
&mut self,
_local_addr: &Multiaddr,
_send_back_addr: &Multiaddr,
_handler: Self::ProtocolsHandler,
) {
}
/// Indicates to the behaviour that a new listener was created.
fn inject_new_listener(&mut self, _id: ListenerId) {}
@ -182,8 +216,11 @@ pub trait NetworkBehaviour: Send + 'static {
///
/// This API mimics the API of the `Stream` trait. The method may register the current task in
/// order to wake it up at a later point in time.
fn poll(&mut self, cx: &mut Context<'_>, params: &mut impl PollParameters)
-> Poll<NetworkBehaviourAction<<<Self::ProtocolsHandler as IntoProtocolsHandler>::Handler as ProtocolsHandler>::InEvent, Self::OutEvent>>;
fn poll(
&mut self,
cx: &mut Context<'_>,
params: &mut impl PollParameters,
) -> Poll<NetworkBehaviourAction<Self::OutEvent, Self::ProtocolsHandler>>;
}
/// Parameters passed to `poll()`, that the `NetworkBehaviour` has access to.
@ -228,15 +265,35 @@ pub trait NetworkBehaviourEventProcess<TEvent> {
/// in whose context it is executing.
///
/// [`Swarm`]: super::Swarm
//
// Note: `TInEvent` is needed to be able to implement
// [`NetworkBehaviourAction::map_in`], mapping the handler `InEvent` leaving the
// handler itself untouched.
#[derive(Debug)]
pub enum NetworkBehaviourAction<TInEvent, TOutEvent> {
pub enum NetworkBehaviourAction<
TOutEvent,
THandler: IntoProtocolsHandler,
TInEvent = THandlerInEvent<THandler>,
> {
/// Instructs the `Swarm` to return an event when it is being polled.
GenerateEvent(TOutEvent),
/// Instructs the swarm to dial the given multiaddress optionally including a [`PeerId`].
///
/// On success, [`NetworkBehaviour::inject_connection_established`] is invoked.
/// On failure, [`NetworkBehaviour::inject_dial_failure`] is invoked.
///
/// Note that the provided handler is returned to the [`NetworkBehaviour`] on connection failure
/// and connection closing. Thus it can be used to carry state, which otherwise would have to be
/// tracked in the [`NetworkBehaviour`] itself. E.g. a message destined to an unconnected peer
/// can be included in the handler, and thus directly send on connection success or extracted by
/// the [`NetworkBehaviour`] on connection failure. See [`NetworkBehaviourAction::DialPeer`] for
/// example.
DialAddress {
/// The address to dial.
address: Multiaddr,
/// The handler to be used to handle the connection to the peer.
handler: THandler,
},
/// Instructs the swarm to dial a known `PeerId`.
@ -247,13 +304,194 @@ pub enum NetworkBehaviourAction<TInEvent, TOutEvent> {
/// If we were already trying to dial this node, the addresses that are not yet in the queue of
/// addresses to try are added back to this queue.
///
/// On success, [`NetworkBehaviour::inject_connected`] is invoked.
/// On success, [`NetworkBehaviour::inject_connection_established`] is invoked.
/// On failure, [`NetworkBehaviour::inject_dial_failure`] is invoked.
///
/// Note that the provided handler is returned to the [`NetworkBehaviour`] on connection failure
/// and connection closing. Thus it can be used to carry state, which otherwise would have to be
/// tracked in the [`NetworkBehaviour`] itself. E.g. a message destined to an unconnected peer
/// can be included in the handler, and thus directly send on connection success or extracted by
/// the [`NetworkBehaviour`] on connection failure.
///
/// # Example
///
/// ```rust
/// # use futures::executor::block_on;
/// # use futures::stream::StreamExt;
/// # use libp2p::core::connection::ConnectionId;
/// # use libp2p::core::identity;
/// # use libp2p::core::transport::{MemoryTransport, Transport};
/// # use libp2p::core::upgrade::{self, DeniedUpgrade, InboundUpgrade, OutboundUpgrade};
/// # use libp2p::core::PeerId;
/// # use libp2p::plaintext::PlainText2Config;
/// # use libp2p::swarm::{
/// # DialError, DialPeerCondition, IntoProtocolsHandler, KeepAlive, NegotiatedSubstream,
/// # NetworkBehaviour, NetworkBehaviourAction, PollParameters, ProtocolsHandler,
/// # ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr, SubstreamProtocol, Swarm, SwarmEvent,
/// # };
/// # use libp2p::yamux;
/// # use std::collections::VecDeque;
/// # use std::task::{Context, Poll};
/// # use void::Void;
/// #
/// # let local_key = identity::Keypair::generate_ed25519();
/// # let local_public_key = local_key.public();
/// # let local_peer_id = PeerId::from(local_public_key.clone());
/// #
/// # let transport = MemoryTransport::default()
/// # .upgrade(upgrade::Version::V1)
/// # .authenticate(PlainText2Config { local_public_key })
/// # .multiplex(yamux::YamuxConfig::default())
/// # .boxed();
/// #
/// # let mut swarm = Swarm::new(transport, MyBehaviour::default(), local_peer_id);
/// #
/// // Super precious message that we should better not lose.
/// let message = PreciousMessage("My precious message".to_string());
///
/// // Unfortunately this peer is offline, thus sending our message to it will fail.
/// let offline_peer = PeerId::random();
///
/// // Let's send it anyways. We should get it back in case connecting to the peer fails.
/// swarm.behaviour_mut().send(offline_peer, message);
///
/// block_on(async {
/// // As expected, sending failed. But great news, we got our message back.
/// matches!(
/// swarm.next().await.expect("Infinite stream"),
/// SwarmEvent::Behaviour(PreciousMessage(_))
/// );
/// });
///
/// # #[derive(Default)]
/// # struct MyBehaviour {
/// # outbox_to_swarm: VecDeque<NetworkBehaviourAction<PreciousMessage, MyHandler>>,
/// # }
/// #
/// # impl MyBehaviour {
/// # fn send(&mut self, peer_id: PeerId, msg: PreciousMessage) {
/// # self.outbox_to_swarm
/// # .push_back(NetworkBehaviourAction::DialPeer {
/// # peer_id,
/// # condition: DialPeerCondition::Always,
/// # handler: MyHandler { message: Some(msg) },
/// # });
/// # }
/// # }
/// #
/// impl NetworkBehaviour for MyBehaviour {
/// # type ProtocolsHandler = MyHandler;
/// # type OutEvent = PreciousMessage;
/// #
/// # fn new_handler(&mut self) -> Self::ProtocolsHandler {
/// # MyHandler { message: None }
/// # }
/// #
/// #
/// # fn inject_event(
/// # &mut self,
/// # _: PeerId,
/// # _: ConnectionId,
/// # _: <<Self::ProtocolsHandler as IntoProtocolsHandler>::Handler as ProtocolsHandler>::OutEvent,
/// # ) {
/// # unreachable!();
/// # }
/// #
/// fn inject_dial_failure(
/// &mut self,
/// _: &PeerId,
/// handler: Self::ProtocolsHandler,
/// _: DialError,
/// ) {
/// // As expected, sending the message failed. But lucky us, we got the handler back, thus
/// // the precious message is not lost and we can return it back to the user.
/// let msg = handler.message.unwrap();
/// self.outbox_to_swarm
/// .push_back(NetworkBehaviourAction::GenerateEvent(msg))
/// }
/// #
/// # fn poll(
/// # &mut self,
/// # _: &mut Context<'_>,
/// # _: &mut impl PollParameters,
/// # ) -> Poll<NetworkBehaviourAction<Self::OutEvent, Self::ProtocolsHandler>> {
/// # if let Some(action) = self.outbox_to_swarm.pop_front() {
/// # return Poll::Ready(action);
/// # }
/// # Poll::Pending
/// # }
/// }
///
/// # struct MyHandler {
/// # message: Option<PreciousMessage>,
/// # }
/// #
/// # impl ProtocolsHandler for MyHandler {
/// # type InEvent = Void;
/// # type OutEvent = Void;
/// # type Error = Void;
/// # type InboundProtocol = DeniedUpgrade;
/// # type OutboundProtocol = DeniedUpgrade;
/// # type InboundOpenInfo = ();
/// # type OutboundOpenInfo = Void;
/// #
/// # fn listen_protocol(
/// # &self,
/// # ) -> SubstreamProtocol<Self::InboundProtocol, Self::InboundOpenInfo> {
/// # SubstreamProtocol::new(DeniedUpgrade, ())
/// # }
/// #
/// # fn inject_fully_negotiated_inbound(
/// # &mut self,
/// # _: <Self::InboundProtocol as InboundUpgrade<NegotiatedSubstream>>::Output,
/// # _: Self::InboundOpenInfo,
/// # ) {
/// # }
/// #
/// # fn inject_fully_negotiated_outbound(
/// # &mut self,
/// # _: <Self::OutboundProtocol as OutboundUpgrade<NegotiatedSubstream>>::Output,
/// # _: Self::OutboundOpenInfo,
/// # ) {
/// # }
/// #
/// # fn inject_event(&mut self, _event: Self::InEvent) {}
/// #
/// # fn inject_dial_upgrade_error(
/// # &mut self,
/// # _: Self::OutboundOpenInfo,
/// # _: ProtocolsHandlerUpgrErr<Void>,
/// # ) {
/// # }
/// #
/// # fn connection_keep_alive(&self) -> KeepAlive {
/// # KeepAlive::Yes
/// # }
/// #
/// # fn poll(
/// # &mut self,
/// # _: &mut Context<'_>,
/// # ) -> Poll<
/// # ProtocolsHandlerEvent<
/// # Self::OutboundProtocol,
/// # Self::OutboundOpenInfo,
/// # Self::OutEvent,
/// # Self::Error,
/// # >,
/// # > {
/// # todo!("If `Self::message.is_some()` send the message to the remote.")
/// # }
/// # }
/// # #[derive(Debug, PartialEq, Eq)]
/// # struct PreciousMessage(String);
/// ```
DialPeer {
/// The peer to try reach.
peer_id: PeerId,
/// The condition for initiating a new dialing attempt.
condition: DialPeerCondition,
/// The handler to be used to handle the connection to the peer.
handler: THandler,
},
/// Instructs the `Swarm` to send an event to the handler dedicated to a
@ -314,17 +552,28 @@ pub enum NetworkBehaviourAction<TInEvent, TOutEvent> {
},
}
impl<TInEvent, TOutEvent> NetworkBehaviourAction<TInEvent, TOutEvent> {
impl<TOutEvent, THandler: IntoProtocolsHandler, TInEventOld>
NetworkBehaviourAction<TOutEvent, THandler, TInEventOld>
{
/// Map the handler event.
pub fn map_in<E>(self, f: impl FnOnce(TInEvent) -> E) -> NetworkBehaviourAction<E, TOutEvent> {
pub fn map_in<TInEventNew>(
self,
f: impl FnOnce(TInEventOld) -> TInEventNew,
) -> NetworkBehaviourAction<TOutEvent, THandler, TInEventNew> {
match self {
NetworkBehaviourAction::GenerateEvent(e) => NetworkBehaviourAction::GenerateEvent(e),
NetworkBehaviourAction::DialAddress { address } => {
NetworkBehaviourAction::DialAddress { address }
}
NetworkBehaviourAction::DialPeer { peer_id, condition } => {
NetworkBehaviourAction::DialPeer { peer_id, condition }
NetworkBehaviourAction::DialAddress { address, handler } => {
NetworkBehaviourAction::DialAddress { address, handler }
}
NetworkBehaviourAction::DialPeer {
peer_id,
condition,
handler,
} => NetworkBehaviourAction::DialPeer {
peer_id,
condition,
handler,
},
NetworkBehaviourAction::NotifyHandler {
peer_id,
handler,
@ -346,17 +595,25 @@ impl<TInEvent, TOutEvent> NetworkBehaviourAction<TInEvent, TOutEvent> {
},
}
}
}
impl<TOutEvent, THandler: IntoProtocolsHandler> NetworkBehaviourAction<TOutEvent, THandler> {
/// Map the event the swarm will return.
pub fn map_out<E>(self, f: impl FnOnce(TOutEvent) -> E) -> NetworkBehaviourAction<TInEvent, E> {
pub fn map_out<E>(self, f: impl FnOnce(TOutEvent) -> E) -> NetworkBehaviourAction<E, THandler> {
match self {
NetworkBehaviourAction::GenerateEvent(e) => NetworkBehaviourAction::GenerateEvent(f(e)),
NetworkBehaviourAction::DialAddress { address } => {
NetworkBehaviourAction::DialAddress { address }
}
NetworkBehaviourAction::DialPeer { peer_id, condition } => {
NetworkBehaviourAction::DialPeer { peer_id, condition }
NetworkBehaviourAction::DialAddress { address, handler } => {
NetworkBehaviourAction::DialAddress { address, handler }
}
NetworkBehaviourAction::DialPeer {
peer_id,
condition,
handler,
} => NetworkBehaviourAction::DialPeer {
peer_id,
condition,
handler,
},
NetworkBehaviourAction::NotifyHandler {
peer_id,
handler,
@ -380,6 +637,60 @@ impl<TInEvent, TOutEvent> NetworkBehaviourAction<TInEvent, TOutEvent> {
}
}
impl<TInEvent, TOutEvent, THandlerOld> NetworkBehaviourAction<TOutEvent, THandlerOld>
where
THandlerOld: IntoProtocolsHandler,
<THandlerOld as IntoProtocolsHandler>::Handler: ProtocolsHandler<InEvent = TInEvent>,
{
/// Map the handler.
pub fn map_handler<THandlerNew>(
self,
f: impl FnOnce(THandlerOld) -> THandlerNew,
) -> NetworkBehaviourAction<TOutEvent, THandlerNew>
where
THandlerNew: IntoProtocolsHandler,
<THandlerNew as IntoProtocolsHandler>::Handler: ProtocolsHandler<InEvent = TInEvent>,
{
match self {
NetworkBehaviourAction::GenerateEvent(e) => NetworkBehaviourAction::GenerateEvent(e),
NetworkBehaviourAction::DialAddress { address, handler } => {
NetworkBehaviourAction::DialAddress {
address,
handler: f(handler),
}
}
NetworkBehaviourAction::DialPeer {
peer_id,
condition,
handler,
} => NetworkBehaviourAction::DialPeer {
peer_id,
condition,
handler: f(handler),
},
NetworkBehaviourAction::NotifyHandler {
peer_id,
handler,
event,
} => NetworkBehaviourAction::NotifyHandler {
peer_id,
handler,
event: event,
},
NetworkBehaviourAction::ReportObservedAddr { address, score } => {
NetworkBehaviourAction::ReportObservedAddr { address, score }
}
NetworkBehaviourAction::CloseConnection {
peer_id,
connection,
} => NetworkBehaviourAction::CloseConnection {
peer_id,
connection,
},
}
}
}
/// The options w.r.t. which connection handler to notify of an event.
#[derive(Debug, Clone)]
pub enum NotifyHandler {
@ -392,7 +703,6 @@ pub enum NotifyHandler {
/// The available conditions under which a new dialing attempt to
/// a peer is initiated when requested by [`NetworkBehaviourAction::DialPeer`].
#[derive(Debug, Copy, Clone)]
#[non_exhaustive]
pub enum DialPeerCondition {
/// A new dialing attempt is initiated _only if_ the peer is currently
/// considered disconnected, i.e. there is no established connection