Skip to main content

reth_network/
swarm.rs

1use crate::{
2    listener::{ConnectionListener, ListenerEvent},
3    message::PeerMessage,
4    peers::{InboundConnectionError, PeersManager},
5    protocol::IntoRlpxSubProtocol,
6    session::{Direction, PendingSessionHandshakeError, SessionEvent, SessionId, SessionManager},
7    state::{NetworkState, SessionActivation, StateAction},
8};
9use futures::Stream;
10use reth_eth_wire::{
11    errors::EthStreamError, Capabilities, DisconnectReason, EthNetworkPrimitives, EthVersion,
12    NetworkPrimitives, UnifiedStatus,
13};
14use reth_network_api::{PeerRequest, PeerRequestSender};
15use reth_network_peers::PeerId;
16use std::{
17    io,
18    net::SocketAddr,
19    pin::Pin,
20    sync::Arc,
21    task::{Context, Poll},
22};
23use tracing::trace;
24
25#[cfg_attr(doc, aquamarine::aquamarine)]
26/// Contains the connectivity related state of the network.
27///
28/// A swarm emits [`SwarmEvent`]s when polled.
29///
30/// It manages the [`ConnectionListener`] and delegates new incoming connections to the
31/// [`SessionManager`]. Outgoing connections are either initiated on demand or triggered by the
32/// [`NetworkState`] and also delegated to the [`NetworkState`].
33///
34/// Following diagram displays the dataflow contained in the [`Swarm`]
35///
36/// The [`ConnectionListener`] yields incoming [`TcpStream`]s from peers that are spawned as session
37/// tasks. After a successful `RLPx` authentication, the task is ready to accept ETH requests or
38/// broadcast messages. A task listens for messages from the [`SessionManager`] which include
39/// broadcast messages like `Transactions` or internal commands, for example to disconnect the
40/// session.
41///
42/// The [`NetworkState`] keeps track of all connected and discovered peers and can initiate outgoing
43/// connections. For each active session, the [`NetworkState`] keeps a sender half of the ETH
44/// request channel for the created session and sends requests it receives from the
45/// [`StateFetcher`], which receives request objects from the client interfaces responsible for
46/// downloading headers and bodies.
47///
48/// `include_mmd!("docs/mermaid/swarm.mmd`")
49#[derive(Debug)]
50#[must_use = "Swarm does nothing unless polled"]
51pub(crate) struct Swarm<N: NetworkPrimitives = EthNetworkPrimitives> {
52    /// Listens for new incoming connections.
53    incoming: ConnectionListener,
54    /// All sessions.
55    sessions: SessionManager<N>,
56    /// Tracks the entire state of the network and handles events received from the sessions.
57    state: NetworkState<N>,
58}
59
60// === impl Swarm ===
61
62impl<N: NetworkPrimitives> Swarm<N> {
63    /// Configures a new swarm instance.
64    pub(crate) const fn new(
65        incoming: ConnectionListener,
66        sessions: SessionManager<N>,
67        state: NetworkState<N>,
68    ) -> Self {
69        Self { incoming, sessions, state }
70    }
71
72    /// Adds a protocol handler to the `RLPx` sub-protocol list.
73    pub(crate) fn add_rlpx_sub_protocol(&mut self, protocol: impl IntoRlpxSubProtocol) {
74        self.sessions_mut().add_rlpx_sub_protocol(protocol);
75    }
76
77    /// Access to the state.
78    pub(crate) const fn state(&self) -> &NetworkState<N> {
79        &self.state
80    }
81
82    /// Mutable access to the state.
83    pub(crate) const fn state_mut(&mut self) -> &mut NetworkState<N> {
84        &mut self.state
85    }
86
87    /// Access to the [`ConnectionListener`].
88    pub(crate) const fn listener(&self) -> &ConnectionListener {
89        &self.incoming
90    }
91
92    /// Access to the [`SessionManager`].
93    pub(crate) const fn sessions(&self) -> &SessionManager<N> {
94        &self.sessions
95    }
96
97    /// Mutable access to the [`SessionManager`].
98    pub(crate) const fn sessions_mut(&mut self) -> &mut SessionManager<N> {
99        &mut self.sessions
100    }
101
102    /// Access to the [`PeersManager`].
103    pub(crate) const fn peers(&self) -> &PeersManager {
104        self.state.peers()
105    }
106
107    /// Mutable access to the [`PeersManager`].
108    pub(crate) const fn peers_mut(&mut self) -> &mut PeersManager {
109        self.state.peers_mut()
110    }
111}
112
113impl<N: NetworkPrimitives> Swarm<N> {
114    /// Triggers an outgoing connection, returning whether a session was started.
115    pub(crate) fn dial_outbound(&mut self, remote_addr: SocketAddr, remote_id: PeerId) -> bool {
116        if let Err(err) = self.sessions.try_dial_outbound(remote_addr, remote_id) {
117            trace!(target: "net", %err, "Outgoing connection rejected, capacity already reached.");
118            // Leave retries to the refill timer so a saturated session manager cannot busy-loop.
119            self.peers_mut().on_outgoing_pending_session_gracefully_closed(&remote_id);
120            return false
121        }
122        true
123    }
124
125    /// Handles a polled [`SessionEvent`]
126    ///
127    /// This either updates the state or produces a new [`SwarmEvent`] that is bubbled up to the
128    /// manager.
129    fn on_session_event(&mut self, event: SessionEvent<N>) -> Option<SwarmEvent<N>> {
130        match event {
131            SessionEvent::SessionEstablished {
132                peer_id,
133                remote_addr,
134                client_version,
135                capabilities,
136                version,
137                status,
138                messages,
139                direction,
140                timeout,
141                range_info,
142                supports_snap,
143            } => {
144                self.state.on_session_activated(SessionActivation {
145                    peer: peer_id,
146                    capabilities: capabilities.clone(),
147                    status: status.clone(),
148                    request_tx: messages.clone(),
149                    timeout,
150                    range_info,
151                    supports_snap,
152                });
153                Some(SwarmEvent::SessionEstablished {
154                    peer_id,
155                    remote_addr,
156                    client_version,
157                    capabilities,
158                    version,
159                    messages,
160                    status,
161                    direction,
162                })
163            }
164            SessionEvent::AlreadyConnected { peer_id, remote_addr, direction } => {
165                trace!(target: "net", ?peer_id, ?remote_addr, ?direction, "already connected");
166                self.state.peers_mut().on_already_connected(direction);
167                None
168            }
169            SessionEvent::ValidMessage { peer_id, message } => {
170                Some(SwarmEvent::ValidMessage { peer_id, message })
171            }
172            SessionEvent::IncomingPendingSessionClosed { remote_addr, error } => {
173                Some(SwarmEvent::IncomingPendingSessionClosed { remote_addr, error })
174            }
175            SessionEvent::OutgoingPendingSessionClosed { remote_addr, peer_id, error } => {
176                Some(SwarmEvent::OutgoingPendingSessionClosed { remote_addr, peer_id, error })
177            }
178            SessionEvent::Disconnected { peer_id, remote_addr } => {
179                self.state.on_session_closed(peer_id);
180                Some(SwarmEvent::SessionClosed { peer_id, remote_addr, error: None })
181            }
182            SessionEvent::SessionClosedOnConnectionError { peer_id, remote_addr, error } => {
183                self.state.on_session_closed(peer_id);
184                Some(SwarmEvent::SessionClosed { peer_id, remote_addr, error: Some(error) })
185            }
186            SessionEvent::OutgoingConnectionError { remote_addr, peer_id, error } => {
187                Some(SwarmEvent::OutgoingConnectionError { peer_id, remote_addr, error })
188            }
189            SessionEvent::BadMessage { peer_id } => Some(SwarmEvent::BadMessage { peer_id }),
190            SessionEvent::ProtocolBreach { peer_id } => {
191                Some(SwarmEvent::ProtocolBreach { peer_id })
192            }
193        }
194    }
195
196    /// Callback for events produced by [`ConnectionListener`].
197    ///
198    /// Depending on the event, this will produce a new [`SwarmEvent`].
199    fn on_connection(&mut self, event: ListenerEvent) -> Option<SwarmEvent<N>> {
200        match event {
201            ListenerEvent::Error(err) => return Some(SwarmEvent::TcpListenerError(err)),
202            ListenerEvent::ListenerClosed { local_address: address } => {
203                return Some(SwarmEvent::TcpListenerClosed { remote_addr: address })
204            }
205            ListenerEvent::Incoming { stream, remote_addr } => {
206                // Reject incoming connection if node is shutting down.
207                if self.is_shutting_down() {
208                    return None
209                }
210                // ensure we can handle an incoming connection from this address
211                if let Err(err) = self.peers_mut().on_incoming_pending_session(remote_addr.ip()) {
212                    match err {
213                        InboundConnectionError::IpBanned => {
214                            trace!(target: "net", ?remote_addr, "The incoming ip address is in the ban list");
215                        }
216                        InboundConnectionError::ExceedsCapacity => {
217                            trace!(target: "net", ?remote_addr, "No capacity for incoming connection");
218                            self.sessions.try_disconnect_incoming_connection(
219                                stream,
220                                DisconnectReason::TooManyPeers,
221                            );
222                        }
223                    }
224                    return None
225                }
226
227                match self.sessions.on_incoming(stream, remote_addr) {
228                    Ok(session_id) => {
229                        trace!(target: "net", ?remote_addr, "Incoming connection");
230                        return Some(SwarmEvent::IncomingTcpConnection { session_id, remote_addr })
231                    }
232                    Err(err) => {
233                        trace!(target: "net", %err, "Incoming connection rejected, capacity already reached.");
234                        self.state_mut()
235                            .peers_mut()
236                            .on_incoming_pending_session_rejected_internally();
237                    }
238                }
239            }
240        }
241        None
242    }
243
244    /// Hook for actions pulled from the state
245    fn on_state_action(&mut self, event: StateAction<N>) -> Option<SwarmEvent<N>> {
246        match event {
247            StateAction::Connect { remote_addr, peer_id } => {
248                if self.dial_outbound(remote_addr, peer_id) {
249                    return Some(SwarmEvent::OutgoingTcpConnection { remote_addr, peer_id })
250                }
251            }
252            StateAction::Disconnect { peer_id, reason } => {
253                self.sessions.disconnect(peer_id, reason);
254            }
255            StateAction::NewBlock { peer_id, block: msg } => {
256                let msg = PeerMessage::NewBlock(msg);
257                self.sessions.send_message(&peer_id, msg);
258            }
259            StateAction::NewBlockHashes { peer_id, hashes } => {
260                let msg = PeerMessage::NewBlockHashes(hashes);
261                self.sessions.send_message(&peer_id, msg);
262            }
263            StateAction::PeerAdded(peer_id) => return Some(SwarmEvent::PeerAdded(peer_id)),
264            StateAction::PeerRemoved(peer_id) => return Some(SwarmEvent::PeerRemoved(peer_id)),
265            StateAction::DiscoveredNode { peer_id, addr, fork_id } => {
266                if self.is_shutting_down() {
267                    return None
268                }
269
270                // When `enforce_enr_fork_id` is enabled, peers discovered without a confirmed
271                // fork ID (via EIP-868 ENR) are deferred — they'll only be added once a
272                // `DiscoveredEnrForkId` event arrives with a validated fork ID.
273                //
274                // When disabled (default), peers without a fork ID are admitted immediately.
275                // Peers that *do* carry a fork ID are always validated against ours.
276                let enforce = self.peers().enforce_enr_fork_id();
277                let allow = match fork_id {
278                    Some(f) => self.sessions.is_valid_fork_id(f),
279                    None => !enforce,
280                };
281                if allow {
282                    self.peers_mut().add_peer(peer_id, addr, fork_id);
283                }
284            }
285            StateAction::DiscoveredEnrForkId { peer_id, addr, fork_id } => {
286                if self.sessions.is_valid_fork_id(fork_id) {
287                    self.peers_mut().add_peer(peer_id, addr, Some(fork_id));
288                } else {
289                    trace!(target: "net", ?peer_id, remote_fork_id=?fork_id, our_fork_id=?self.sessions.fork_id(), "fork id mismatch, removing peer");
290                    self.peers_mut().remove_peer(peer_id);
291                }
292            }
293        }
294        None
295    }
296
297    /// Set network connection state to `ShuttingDown`
298    pub(crate) const fn on_shutdown_requested(&mut self) {
299        self.peers_mut().on_shutdown();
300    }
301
302    /// Checks if the node's network connection state is '`ShuttingDown`'
303    #[inline]
304    pub(crate) const fn is_shutting_down(&self) -> bool {
305        self.peers().connection_state().is_shutting_down()
306    }
307
308    /// Set network connection state to `Hibernate` or `Active`
309    pub(crate) const fn on_network_state_change(&mut self, network_state: NetworkConnectionState) {
310        self.peers_mut().on_network_state_change(network_state);
311    }
312}
313
314impl<N: NetworkPrimitives> Stream for Swarm<N> {
315    type Item = SwarmEvent<N>;
316
317    /// This advances all components.
318    ///
319    /// Processes, delegates (internal) commands received from the
320    /// [`NetworkManager`](crate::NetworkManager), then polls the [`SessionManager`] which
321    /// yields messages produced by individual peer sessions that are then handled. Least
322    /// priority are incoming connections that are handled and delegated to
323    /// the [`SessionManager`] to turn them into a session.
324    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
325        let this = self.get_mut();
326
327        // This loop advances the network's state prioritizing local work [NetworkState] over work
328        // coming in from the network [SessionManager], [ConnectionListener]
329        // Existing connections are prioritized over new __incoming__ connections
330        loop {
331            while let Poll::Ready(action) = this.state.poll(cx) {
332                if let Some(event) = this.on_state_action(action) {
333                    return Poll::Ready(Some(event))
334                }
335            }
336
337            // poll all sessions
338            match this.sessions.poll(cx, |peer_id| this.state.peers().has_inbound_capacity(peer_id))
339            {
340                Poll::Pending => {}
341                Poll::Ready(event) => {
342                    if let Some(event) = this.on_session_event(event) {
343                        return Poll::Ready(Some(event))
344                    }
345                    continue
346                }
347            }
348
349            // poll listener for incoming connections
350            match Pin::new(&mut this.incoming).poll(cx) {
351                Poll::Pending => {}
352                Poll::Ready(event) => {
353                    if let Some(event) = this.on_connection(event) {
354                        return Poll::Ready(Some(event))
355                    }
356                    continue
357                }
358            }
359
360            return Poll::Pending
361        }
362    }
363}
364
365/// All events created or delegated by the [`Swarm`] that represents changes to the state of the
366/// network.
367pub(crate) enum SwarmEvent<N: NetworkPrimitives = EthNetworkPrimitives> {
368    /// Events related to the actual network protocol.
369    ValidMessage {
370        /// The peer that sent the message
371        peer_id: PeerId,
372        /// Message received from the peer
373        message: PeerMessage<N>,
374    },
375    /// Received a bad message from the peer.
376    BadMessage {
377        /// Identifier of the remote peer.
378        peer_id: PeerId,
379    },
380    /// Remote peer is considered in protocol violation
381    ProtocolBreach {
382        /// Identifier of the remote peer.
383        peer_id: PeerId,
384    },
385    /// The underlying tcp listener closed.
386    TcpListenerClosed {
387        /// Address of the closed listener.
388        remote_addr: SocketAddr,
389    },
390    /// The underlying tcp listener encountered an error that we bubble up.
391    TcpListenerError(io::Error),
392    /// Received an incoming tcp connection.
393    ///
394    /// This represents the first step in the session authentication process. The swarm will
395    /// produce subsequent events once the stream has been authenticated, or was rejected.
396    IncomingTcpConnection {
397        /// The internal session identifier under which this connection is currently tracked.
398        session_id: SessionId,
399        /// Address of the remote peer.
400        remote_addr: SocketAddr,
401    },
402    /// An outbound connection is initiated.
403    OutgoingTcpConnection {
404        /// Address of the remote peer.
405        peer_id: PeerId,
406        remote_addr: SocketAddr,
407    },
408    SessionEstablished {
409        peer_id: PeerId,
410        remote_addr: SocketAddr,
411        client_version: Arc<str>,
412        capabilities: Arc<Capabilities>,
413        /// negotiated eth version
414        version: EthVersion,
415        messages: PeerRequestSender<PeerRequest<N>>,
416        status: Arc<UnifiedStatus>,
417        direction: Direction,
418    },
419    SessionClosed {
420        peer_id: PeerId,
421        remote_addr: SocketAddr,
422        /// Whether the session was closed due to an error
423        error: Option<EthStreamError>,
424    },
425    /// Admin rpc: new peer added
426    PeerAdded(PeerId),
427    /// Admin rpc: peer removed
428    PeerRemoved(PeerId),
429    /// Closed an incoming pending session during authentication.
430    IncomingPendingSessionClosed {
431        remote_addr: SocketAddr,
432        error: Option<PendingSessionHandshakeError>,
433    },
434    /// Closed an outgoing pending session during authentication.
435    OutgoingPendingSessionClosed {
436        remote_addr: SocketAddr,
437        peer_id: PeerId,
438        error: Option<PendingSessionHandshakeError>,
439    },
440    /// Failed to establish a tcp stream to the given address/node
441    OutgoingConnectionError { remote_addr: SocketAddr, peer_id: PeerId, error: io::Error },
442}
443
444/// Represents the state of the connection of the node. If shutting down,
445/// new connections won't be established.
446/// When in hibernation mode, the node will not initiate new outbound connections. This is
447/// beneficial for sync stages that do not require a network connection.
448#[derive(Debug, Default)]
449pub enum NetworkConnectionState {
450    /// Node is active, new outbound connections will be established.
451    #[default]
452    Active,
453    /// Node is shutting down, no new outbound connections will be established.
454    ShuttingDown,
455    /// Hibernate Network connection, no new outbound connections will be established.
456    Hibernate,
457}
458
459impl NetworkConnectionState {
460    /// Returns true if the node is active.
461    pub(crate) const fn is_active(&self) -> bool {
462        matches!(self, Self::Active)
463    }
464
465    /// Returns true if the node is shutting down.
466    pub(crate) const fn is_shutting_down(&self) -> bool {
467        matches!(self, Self::ShuttingDown)
468    }
469}