refactor(swarm)!: remove handler from NetworkBehaviourAction::Dial (#3328)

We create the `ConnectionId` for the new connection as part of `DialOpts`. This allows `NetworkBehaviour`s to accurately track state regarding their own dial attempts.

This patch is the main enabler of https://github.com/libp2p/rust-libp2p/pull/3254. Removing the `handler` field will allow us to deprecate the `NetworkBehaviour::new_handler` function in favor of four new ones that give more control over the connection lifecycle.
This commit is contained in:
Thomas Eizinger
2023-02-14 14:09:29 +13:00
committed by GitHub
parent 9247cfa878
commit caed1fe2c7
31 changed files with 584 additions and 967 deletions

View File

@ -29,12 +29,13 @@ use libp2p_swarm::behaviour::{ConnectionClosed, ConnectionEstablished, DialFailu
use libp2p_swarm::dial_opts::{self, DialOpts};
use libp2p_swarm::{
ConnectionHandlerUpgrErr, ExternalAddresses, NetworkBehaviour, NetworkBehaviourAction,
NotifyHandler, PollParameters,
NotifyHandler, PollParameters, THandlerInEvent,
};
use libp2p_swarm::{ConnectionId, THandlerOutEvent};
use std::collections::{HashMap, HashSet, VecDeque};
use std::task::{Context, Poll};
use thiserror::Error;
use void::Void;
const MAX_NUMBER_OF_UPGRADE_ATTEMPTS: u8 = 3;
@ -68,7 +69,7 @@ pub enum Error {
pub struct Behaviour {
/// Queue of actions to return when polled.
queued_events: VecDeque<NetworkBehaviourAction<Event, handler::Prototype>>,
queued_events: VecDeque<NetworkBehaviourAction<Event, Either<handler::relayed::Command, Void>>>,
/// All direct (non-relayed) connections.
direct_connections: HashMap<PeerId, HashSet<ConnectionId>>,
@ -76,6 +77,12 @@ pub struct Behaviour {
external_addresses: ExternalAddresses,
local_peer_id: PeerId,
direct_to_relayed_connections: HashMap<ConnectionId, ConnectionId>,
/// Indexed by the [`ConnectionId`] of the relayed connection and
/// the [`PeerId`] we are trying to establish a direct connection to.
outgoing_direct_connection_attempts: HashMap<(ConnectionId, PeerId), u8>,
}
impl Behaviour {
@ -85,6 +92,8 @@ impl Behaviour {
direct_connections: Default::default(),
external_addresses: Default::default(),
local_peer_id,
direct_to_relayed_connections: Default::default(),
outgoing_direct_connection_attempts: Default::default(),
}
}
@ -148,40 +157,57 @@ impl Behaviour {
fn on_dial_failure(
&mut self,
DialFailure {
peer_id, handler, ..
}: DialFailure<<Self as NetworkBehaviour>::ConnectionHandler>,
peer_id,
connection_id: failed_direct_connection,
..
}: DialFailure,
) {
if let handler::Prototype::DirectConnection {
relayed_connection_id,
role: handler::Role::Initiator { attempt },
} = handler
let peer_id = if let Some(peer_id) = peer_id {
peer_id
} else {
return;
};
let relayed_connection_id = if let Some(relayed_connection_id) = self
.direct_to_relayed_connections
.get(&failed_direct_connection)
{
let peer_id = peer_id.expect("Peer of `Prototype::DirectConnection` is always known.");
if attempt < MAX_NUMBER_OF_UPGRADE_ATTEMPTS {
self.queued_events
.push_back(NetworkBehaviourAction::NotifyHandler {
handler: NotifyHandler::One(relayed_connection_id),
peer_id,
event: Either::Left(handler::relayed::Command::Connect {
attempt: attempt + 1,
obs_addrs: self.observed_addreses(),
}),
})
} else {
self.queued_events.extend([
NetworkBehaviourAction::NotifyHandler {
peer_id,
handler: NotifyHandler::One(relayed_connection_id),
event: Either::Left(
handler::relayed::Command::UpgradeFinishedDontKeepAlive,
),
},
NetworkBehaviourAction::GenerateEvent(Event::DirectConnectionUpgradeFailed {
remote_peer_id: peer_id,
error: Error::Dial,
*relayed_connection_id
} else {
return;
};
let attempt = if let Some(attempt) = self
.outgoing_direct_connection_attempts
.get(&(relayed_connection_id, peer_id))
{
*attempt
} else {
return;
};
if attempt < MAX_NUMBER_OF_UPGRADE_ATTEMPTS {
self.queued_events
.push_back(NetworkBehaviourAction::NotifyHandler {
handler: NotifyHandler::One(relayed_connection_id),
peer_id,
event: Either::Left(handler::relayed::Command::Connect {
attempt: attempt + 1,
obs_addrs: self.observed_addreses(),
}),
]);
}
})
} else {
self.queued_events.extend([
NetworkBehaviourAction::NotifyHandler {
peer_id,
handler: NotifyHandler::One(relayed_connection_id),
event: Either::Left(handler::relayed::Command::UpgradeFinishedDontKeepAlive),
},
NetworkBehaviourAction::GenerateEvent(Event::DirectConnectionUpgradeFailed {
remote_peer_id: peer_id,
error: Error::Dial,
}),
]);
}
}
@ -215,15 +241,26 @@ impl NetworkBehaviour for Behaviour {
type OutEvent = Event;
fn new_handler(&mut self) -> Self::ConnectionHandler {
handler::Prototype::UnknownConnection
handler::Prototype
}
fn on_connection_handler_event(
&mut self,
event_source: PeerId,
connection: ConnectionId,
connection_id: ConnectionId,
handler_event: THandlerOutEvent<Self>,
) {
let relayed_connection_id = match handler_event.as_ref() {
Either::Left(_) => connection_id,
Either::Right(_) => match self.direct_to_relayed_connections.get(&connection_id) {
None => {
// If the connection ID is unknown to us, it means we didn't create it so ignore any event coming from it.
return;
}
Some(relayed_connection_id) => *relayed_connection_id,
},
};
match handler_event {
Either::Left(handler::relayed::Event::InboundConnectRequest {
inbound_connect,
@ -231,7 +268,7 @@ impl NetworkBehaviour for Behaviour {
}) => {
self.queued_events.extend([
NetworkBehaviourAction::NotifyHandler {
handler: NotifyHandler::One(connection),
handler: NotifyHandler::One(relayed_connection_id),
peer_id: event_source,
event: Either::Left(handler::relayed::Command::AcceptInboundConnect {
inbound_connect,
@ -256,16 +293,17 @@ impl NetworkBehaviour for Behaviour {
));
}
Either::Left(handler::relayed::Event::InboundConnectNegotiated(remote_addrs)) => {
self.queued_events.push_back(NetworkBehaviourAction::Dial {
opts: DialOpts::peer_id(event_source)
.addresses(remote_addrs)
.condition(dial_opts::PeerCondition::Always)
.build(),
handler: handler::Prototype::DirectConnection {
relayed_connection_id: connection,
role: handler::Role::Listener,
},
});
let opts = DialOpts::peer_id(event_source)
.addresses(remote_addrs)
.condition(dial_opts::PeerCondition::Always)
.build();
let maybe_direct_connection_id = opts.connection_id();
self.direct_to_relayed_connections
.insert(maybe_direct_connection_id, relayed_connection_id);
self.queued_events
.push_back(NetworkBehaviourAction::Dial { opts });
}
Either::Left(handler::relayed::Event::OutboundNegotiationFailed { error }) => {
self.queued_events
@ -276,27 +314,25 @@ impl NetworkBehaviour for Behaviour {
},
));
}
Either::Left(handler::relayed::Event::OutboundConnectNegotiated {
remote_addrs,
attempt,
}) => {
self.queued_events.push_back(NetworkBehaviourAction::Dial {
opts: DialOpts::peer_id(event_source)
.condition(dial_opts::PeerCondition::Always)
.addresses(remote_addrs)
.override_role()
.build(),
handler: handler::Prototype::DirectConnection {
relayed_connection_id: connection,
role: handler::Role::Initiator { attempt },
},
});
Either::Left(handler::relayed::Event::OutboundConnectNegotiated { remote_addrs }) => {
let opts = DialOpts::peer_id(event_source)
.condition(dial_opts::PeerCondition::Always)
.addresses(remote_addrs)
.override_role()
.build();
let maybe_direct_connection_id = opts.connection_id();
self.direct_to_relayed_connections
.insert(maybe_direct_connection_id, relayed_connection_id);
*self
.outgoing_direct_connection_attempts
.entry((relayed_connection_id, event_source))
.or_default() += 1;
self.queued_events
.push_back(NetworkBehaviourAction::Dial { opts });
}
Either::Right(Either::Left(
handler::direct::Event::DirectConnectionUpgradeSucceeded {
relayed_connection_id,
},
)) => {
Either::Right(handler::direct::Event::DirectConnectionEstablished) => {
self.queued_events.extend([
NetworkBehaviourAction::NotifyHandler {
peer_id: event_source,
@ -312,7 +348,6 @@ impl NetworkBehaviour for Behaviour {
),
]);
}
Either::Right(Either::Right(event)) => void::unreachable(event),
};
}
@ -320,7 +355,7 @@ impl NetworkBehaviour for Behaviour {
&mut self,
_cx: &mut Context<'_>,
_: &mut impl PollParameters,
) -> Poll<NetworkBehaviourAction<Self::OutEvent, Self::ConnectionHandler>> {
) -> Poll<NetworkBehaviourAction<Self::OutEvent, THandlerInEvent<Self>>> {
if let Some(event) = self.queued_events.pop_front() {
return Poll::Ready(event);
}

View File

@ -20,61 +20,27 @@
use crate::protocol;
use either::Either;
use libp2p_core::upgrade::DeniedUpgrade;
use libp2p_core::{ConnectedPoint, PeerId};
use libp2p_swarm::dummy;
use libp2p_swarm::handler::SendWrapper;
use libp2p_swarm::{ConnectionHandler, ConnectionId, IntoConnectionHandler};
use libp2p_swarm::{ConnectionHandler, IntoConnectionHandler};
pub mod direct;
pub mod relayed;
pub enum Prototype {
DirectConnection {
role: Role,
relayed_connection_id: ConnectionId,
},
UnknownConnection,
}
pub enum Role {
Initiator { attempt: u8 },
Listener,
}
pub struct Prototype;
impl IntoConnectionHandler for Prototype {
type Handler = Either<relayed::Handler, Either<direct::Handler, dummy::ConnectionHandler>>;
type Handler = Either<relayed::Handler, direct::Handler>;
fn into_handler(self, _remote_peer_id: &PeerId, endpoint: &ConnectedPoint) -> Self::Handler {
match self {
Self::UnknownConnection => {
if endpoint.is_relayed() {
Either::Left(relayed::Handler::new(endpoint.clone()))
} else {
Either::Right(Either::Right(dummy::ConnectionHandler))
}
}
Self::DirectConnection {
relayed_connection_id,
..
} => {
assert!(
!endpoint.is_relayed(),
"`Prototype::DirectConnection` is never created for relayed connection."
);
Either::Right(Either::Left(direct::Handler::new(relayed_connection_id)))
}
if endpoint.is_relayed() {
Either::Left(relayed::Handler::new(endpoint.clone()))
} else {
Either::Right(direct::Handler::default()) // This is a direct connection. What we don't know is whether it is the one we created or another one that happened accidentally.
}
}
fn inbound_protocol(&self) -> <Self::Handler as ConnectionHandler>::InboundProtocol {
match self {
Prototype::UnknownConnection => {
Either::Left(SendWrapper(Either::Left(protocol::inbound::Upgrade {})))
}
Prototype::DirectConnection { .. } => {
Either::Left(SendWrapper(Either::Right(DeniedUpgrade)))
}
}
Either::Left(SendWrapper(Either::Left(protocol::inbound::Upgrade {})))
}
}

View File

@ -23,7 +23,7 @@
use libp2p_core::upgrade::DeniedUpgrade;
use libp2p_swarm::handler::ConnectionEvent;
use libp2p_swarm::{
ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, ConnectionId, KeepAlive,
ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, KeepAlive,
SubstreamProtocol,
};
use std::task::{Context, Poll};
@ -31,23 +31,14 @@ use void::Void;
#[derive(Debug)]
pub enum Event {
DirectConnectionUpgradeSucceeded { relayed_connection_id: ConnectionId },
DirectConnectionEstablished,
}
#[derive(Default)]
pub struct Handler {
relayed_connection_id: ConnectionId,
reported: bool,
}
impl Handler {
pub(crate) fn new(relayed_connection_id: ConnectionId) -> Self {
Self {
reported: false,
relayed_connection_id,
}
}
}
impl ConnectionHandler for Handler {
type InEvent = void::Void;
type OutEvent = Event;
@ -81,9 +72,7 @@ impl ConnectionHandler for Handler {
if !self.reported {
self.reported = true;
return Poll::Ready(ConnectionHandlerEvent::Custom(
Event::DirectConnectionUpgradeSucceeded {
relayed_connection_id: self.relayed_connection_id,
},
Event::DirectConnectionEstablished,
));
}
Poll::Pending

View File

@ -92,7 +92,6 @@ pub enum Event {
},
OutboundConnectNegotiated {
remote_addrs: Vec<Multiaddr>,
attempt: u8,
},
}
@ -118,13 +117,9 @@ impl fmt::Debug for Event {
.debug_struct("Event::OutboundNegotiationFailed")
.field("error", error)
.finish(),
Event::OutboundConnectNegotiated {
remote_addrs,
attempt,
} => f
Event::OutboundConnectNegotiated { remote_addrs } => f
.debug_struct("Event::OutboundConnectNegotiated")
.field("remote_addrs", remote_addrs)
.field("attempt", attempt)
.finish(),
}
}
@ -195,7 +190,7 @@ impl Handler {
&mut self,
FullyNegotiatedOutbound {
protocol: protocol::outbound::Connect { obs_addrs },
info: attempt,
..
}: FullyNegotiatedOutbound<
<Self as ConnectionHandler>::OutboundProtocol,
<Self as ConnectionHandler>::OutboundOpenInfo,
@ -208,7 +203,6 @@ impl Handler {
self.queued_events.push_back(ConnectionHandlerEvent::Custom(
Event::OutboundConnectNegotiated {
remote_addrs: obs_addrs,
attempt,
},
));
}