Skip to main content

reth_network/
swarm.rs

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