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 a new outgoing connection to the given node
115    pub(crate) fn dial_outbound(&mut self, remote_addr: SocketAddr, remote_id: PeerId) {
116        self.sessions.dial_outbound(remote_addr, remote_id)
117    }
118
119    /// Handles a polled [`SessionEvent`]
120    ///
121    /// This either updates the state or produces a new [`SwarmEvent`] that is bubbled up to the
122    /// manager.
123    fn on_session_event(&mut self, event: SessionEvent<N>) -> Option<SwarmEvent<N>> {
124        match event {
125            SessionEvent::SessionEstablished {
126                peer_id,
127                remote_addr,
128                client_version,
129                capabilities,
130                version,
131                status,
132                messages,
133                direction,
134                timeout,
135                range_info,
136                supports_snap,
137            } => {
138                self.state.on_session_activated(SessionActivation {
139                    peer: peer_id,
140                    capabilities: capabilities.clone(),
141                    status: status.clone(),
142                    request_tx: messages.clone(),
143                    timeout,
144                    range_info,
145                    supports_snap,
146                });
147                Some(SwarmEvent::SessionEstablished {
148                    peer_id,
149                    remote_addr,
150                    client_version,
151                    capabilities,
152                    version,
153                    messages,
154                    status,
155                    direction,
156                })
157            }
158            SessionEvent::AlreadyConnected { peer_id, remote_addr, direction } => {
159                trace!(target: "net", ?peer_id, ?remote_addr, ?direction, "already connected");
160                self.state.peers_mut().on_already_connected(direction);
161                None
162            }
163            SessionEvent::ValidMessage { peer_id, message } => {
164                Some(SwarmEvent::ValidMessage { peer_id, message })
165            }
166            SessionEvent::IncomingPendingSessionClosed { remote_addr, error } => {
167                Some(SwarmEvent::IncomingPendingSessionClosed { remote_addr, error })
168            }
169            SessionEvent::OutgoingPendingSessionClosed { remote_addr, peer_id, error } => {
170                Some(SwarmEvent::OutgoingPendingSessionClosed { remote_addr, peer_id, error })
171            }
172            SessionEvent::Disconnected { peer_id, remote_addr } => {
173                self.state.on_session_closed(peer_id);
174                Some(SwarmEvent::SessionClosed { peer_id, remote_addr, error: None })
175            }
176            SessionEvent::SessionClosedOnConnectionError { peer_id, remote_addr, error } => {
177                self.state.on_session_closed(peer_id);
178                Some(SwarmEvent::SessionClosed { peer_id, remote_addr, error: Some(error) })
179            }
180            SessionEvent::OutgoingConnectionError { remote_addr, peer_id, error } => {
181                Some(SwarmEvent::OutgoingConnectionError { peer_id, remote_addr, error })
182            }
183            SessionEvent::BadMessage { peer_id } => Some(SwarmEvent::BadMessage { peer_id }),
184            SessionEvent::ProtocolBreach { peer_id } => {
185                Some(SwarmEvent::ProtocolBreach { peer_id })
186            }
187        }
188    }
189
190    /// Callback for events produced by [`ConnectionListener`].
191    ///
192    /// Depending on the event, this will produce a new [`SwarmEvent`].
193    fn on_connection(&mut self, event: ListenerEvent) -> Option<SwarmEvent<N>> {
194        match event {
195            ListenerEvent::Error(err) => return Some(SwarmEvent::TcpListenerError(err)),
196            ListenerEvent::ListenerClosed { local_address: address } => {
197                return Some(SwarmEvent::TcpListenerClosed { remote_addr: address })
198            }
199            ListenerEvent::Incoming { stream, remote_addr } => {
200                // Reject incoming connection if node is shutting down.
201                if self.is_shutting_down() {
202                    return None
203                }
204                // ensure we can handle an incoming connection from this address
205                if let Err(err) = self.peers_mut().on_incoming_pending_session(remote_addr.ip()) {
206                    match err {
207                        InboundConnectionError::IpBanned => {
208                            trace!(target: "net", ?remote_addr, "The incoming ip address is in the ban list");
209                        }
210                        InboundConnectionError::ExceedsCapacity => {
211                            trace!(target: "net", ?remote_addr, "No capacity for incoming connection");
212                            self.sessions.try_disconnect_incoming_connection(
213                                stream,
214                                DisconnectReason::TooManyPeers,
215                            );
216                        }
217                    }
218                    return None
219                }
220
221                match self.sessions.on_incoming(stream, remote_addr) {
222                    Ok(session_id) => {
223                        trace!(target: "net", ?remote_addr, "Incoming connection");
224                        return Some(SwarmEvent::IncomingTcpConnection { session_id, remote_addr })
225                    }
226                    Err(err) => {
227                        trace!(target: "net", %err, "Incoming connection rejected, capacity already reached.");
228                        self.state_mut()
229                            .peers_mut()
230                            .on_incoming_pending_session_rejected_internally();
231                    }
232                }
233            }
234        }
235        None
236    }
237
238    /// Hook for actions pulled from the state
239    fn on_state_action(&mut self, event: StateAction<N>) -> Option<SwarmEvent<N>> {
240        match event {
241            StateAction::Connect { remote_addr, peer_id } => {
242                self.dial_outbound(remote_addr, peer_id);
243                return Some(SwarmEvent::OutgoingTcpConnection { remote_addr, peer_id })
244            }
245            StateAction::Disconnect { peer_id, reason } => {
246                self.sessions.disconnect(peer_id, reason);
247            }
248            StateAction::NewBlock { peer_id, block: msg } => {
249                let msg = PeerMessage::NewBlock(msg);
250                self.sessions.send_message(&peer_id, msg);
251            }
252            StateAction::NewBlockHashes { peer_id, hashes } => {
253                let msg = PeerMessage::NewBlockHashes(hashes);
254                self.sessions.send_message(&peer_id, msg);
255            }
256            StateAction::PeerAdded(peer_id) => return Some(SwarmEvent::PeerAdded(peer_id)),
257            StateAction::PeerRemoved(peer_id) => return Some(SwarmEvent::PeerRemoved(peer_id)),
258            StateAction::DiscoveredNode { peer_id, addr, fork_id } => {
259                if self.is_shutting_down() {
260                    return None
261                }
262
263                // When `enforce_enr_fork_id` is enabled, peers discovered without a confirmed
264                // fork ID (via EIP-868 ENR) are deferred — they'll only be added once a
265                // `DiscoveredEnrForkId` event arrives with a validated fork ID.
266                //
267                // When disabled (default), peers without a fork ID are admitted immediately.
268                // Peers that *do* carry a fork ID are always validated against ours.
269                let enforce = self.peers().enforce_enr_fork_id();
270                let allow = match fork_id {
271                    Some(f) => self.sessions.is_valid_fork_id(f),
272                    None => !enforce,
273                };
274                if allow {
275                    self.peers_mut().add_peer(peer_id, addr, fork_id);
276                }
277            }
278            StateAction::DiscoveredEnrForkId { peer_id, addr, fork_id } => {
279                if self.sessions.is_valid_fork_id(fork_id) {
280                    self.peers_mut().add_peer(peer_id, addr, Some(fork_id));
281                } else {
282                    trace!(target: "net", ?peer_id, remote_fork_id=?fork_id, our_fork_id=?self.sessions.fork_id(), "fork id mismatch, removing peer");
283                    self.peers_mut().remove_peer(peer_id);
284                }
285            }
286        }
287        None
288    }
289
290    /// Set network connection state to `ShuttingDown`
291    pub(crate) const fn on_shutdown_requested(&mut self) {
292        self.peers_mut().on_shutdown();
293    }
294
295    /// Checks if the node's network connection state is '`ShuttingDown`'
296    #[inline]
297    pub(crate) const fn is_shutting_down(&self) -> bool {
298        self.peers().connection_state().is_shutting_down()
299    }
300
301    /// Set network connection state to `Hibernate` or `Active`
302    pub(crate) const fn on_network_state_change(&mut self, network_state: NetworkConnectionState) {
303        self.peers_mut().on_network_state_change(network_state);
304    }
305}
306
307impl<N: NetworkPrimitives> Stream for Swarm<N> {
308    type Item = SwarmEvent<N>;
309
310    /// This advances all components.
311    ///
312    /// Processes, delegates (internal) commands received from the
313    /// [`NetworkManager`](crate::NetworkManager), then polls the [`SessionManager`] which
314    /// yields messages produced by individual peer sessions that are then handled. Least
315    /// priority are incoming connections that are handled and delegated to
316    /// the [`SessionManager`] to turn them into a session.
317    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
318        let this = self.get_mut();
319
320        // This loop advances the network's state prioritizing local work [NetworkState] over work
321        // coming in from the network [SessionManager], [ConnectionListener]
322        // Existing connections are prioritized over new __incoming__ connections
323        loop {
324            while let Poll::Ready(action) = this.state.poll(cx) {
325                if let Some(event) = this.on_state_action(action) {
326                    return Poll::Ready(Some(event))
327                }
328            }
329
330            // poll all sessions
331            match this.sessions.poll(cx) {
332                Poll::Pending => {}
333                Poll::Ready(event) => {
334                    if let Some(event) = this.on_session_event(event) {
335                        return Poll::Ready(Some(event))
336                    }
337                    continue
338                }
339            }
340
341            // poll listener for incoming connections
342            match Pin::new(&mut this.incoming).poll(cx) {
343                Poll::Pending => {}
344                Poll::Ready(event) => {
345                    if let Some(event) = this.on_connection(event) {
346                        return Poll::Ready(Some(event))
347                    }
348                    continue
349                }
350            }
351
352            return Poll::Pending
353        }
354    }
355}
356
357/// All events created or delegated by the [`Swarm`] that represents changes to the state of the
358/// network.
359pub(crate) enum SwarmEvent<N: NetworkPrimitives = EthNetworkPrimitives> {
360    /// Events related to the actual network protocol.
361    ValidMessage {
362        /// The peer that sent the message
363        peer_id: PeerId,
364        /// Message received from the peer
365        message: PeerMessage<N>,
366    },
367    /// Received a bad message from the peer.
368    BadMessage {
369        /// Identifier of the remote peer.
370        peer_id: PeerId,
371    },
372    /// Remote peer is considered in protocol violation
373    ProtocolBreach {
374        /// Identifier of the remote peer.
375        peer_id: PeerId,
376    },
377    /// The underlying tcp listener closed.
378    TcpListenerClosed {
379        /// Address of the closed listener.
380        remote_addr: SocketAddr,
381    },
382    /// The underlying tcp listener encountered an error that we bubble up.
383    TcpListenerError(io::Error),
384    /// Received an incoming tcp connection.
385    ///
386    /// This represents the first step in the session authentication process. The swarm will
387    /// produce subsequent events once the stream has been authenticated, or was rejected.
388    IncomingTcpConnection {
389        /// The internal session identifier under which this connection is currently tracked.
390        session_id: SessionId,
391        /// Address of the remote peer.
392        remote_addr: SocketAddr,
393    },
394    /// An outbound connection is initiated.
395    OutgoingTcpConnection {
396        /// Address of the remote peer.
397        peer_id: PeerId,
398        remote_addr: SocketAddr,
399    },
400    SessionEstablished {
401        peer_id: PeerId,
402        remote_addr: SocketAddr,
403        client_version: Arc<str>,
404        capabilities: Arc<Capabilities>,
405        /// negotiated eth version
406        version: EthVersion,
407        messages: PeerRequestSender<PeerRequest<N>>,
408        status: Arc<UnifiedStatus>,
409        direction: Direction,
410    },
411    SessionClosed {
412        peer_id: PeerId,
413        remote_addr: SocketAddr,
414        /// Whether the session was closed due to an error
415        error: Option<EthStreamError>,
416    },
417    /// Admin rpc: new peer added
418    PeerAdded(PeerId),
419    /// Admin rpc: peer removed
420    PeerRemoved(PeerId),
421    /// Closed an incoming pending session during authentication.
422    IncomingPendingSessionClosed {
423        remote_addr: SocketAddr,
424        error: Option<PendingSessionHandshakeError>,
425    },
426    /// Closed an outgoing pending session during authentication.
427    OutgoingPendingSessionClosed {
428        remote_addr: SocketAddr,
429        peer_id: PeerId,
430        error: Option<PendingSessionHandshakeError>,
431    },
432    /// Failed to establish a tcp stream to the given address/node
433    OutgoingConnectionError { remote_addr: SocketAddr, peer_id: PeerId, error: io::Error },
434}
435
436/// Represents the state of the connection of the node. If shutting down,
437/// new connections won't be established.
438/// When in hibernation mode, the node will not initiate new outbound connections. This is
439/// beneficial for sync stages that do not require a network connection.
440#[derive(Debug, Default)]
441pub enum NetworkConnectionState {
442    /// Node is active, new outbound connections will be established.
443    #[default]
444    Active,
445    /// Node is shutting down, no new outbound connections will be established.
446    ShuttingDown,
447    /// Hibernate Network connection, no new outbound connections will be established.
448    Hibernate,
449}
450
451impl NetworkConnectionState {
452    /// Returns true if the node is active.
453    pub(crate) const fn is_active(&self) -> bool {
454        matches!(self, Self::Active)
455    }
456
457    /// Returns true if the node is shutting down.
458    pub(crate) const fn is_shutting_down(&self) -> bool {
459        matches!(self, Self::ShuttingDown)
460    }
461}