Skip to main content

reth_network_api/
events.rs

1//! API related to listening for network events.
2
3use reth_eth_wire_types::{
4    message::RequestPair, snap::SnapProtocolMessage, BlockAccessLists, BlockBodies, BlockHeaders,
5    Capabilities, Cells, DisconnectReason, EthMessage, EthNetworkPrimitives, EthVersion,
6    GetBlockAccessLists, GetBlockBodies, GetBlockHeaders, GetCells, GetNodeData,
7    GetPooledTransactions, GetReceipts, GetReceipts70, NetworkPrimitives, NodeData,
8    PooledTransactions, Receipts, Receipts69, Receipts70, UnifiedStatus,
9};
10use reth_ethereum_forks::ForkId;
11use reth_network_p2p::{
12    error::{RequestError, RequestResult},
13    snap::client::SnapResponse,
14};
15use reth_network_peers::{NodeRecord, PeerId};
16use reth_network_types::{PeerAddr, PeerKind};
17use reth_tokio_util::EventStream;
18use std::{
19    fmt,
20    net::SocketAddr,
21    pin::Pin,
22    sync::Arc,
23    task::{Context, Poll},
24};
25use tokio::sync::{mpsc, oneshot};
26use tokio_stream::{wrappers::UnboundedReceiverStream, Stream, StreamExt};
27
28/// A boxed stream of network peer events that provides a type-erased interface.
29pub struct PeerEventStream(Pin<Box<dyn Stream<Item = PeerEvent> + Send + Sync>>);
30
31impl fmt::Debug for PeerEventStream {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        f.debug_struct("PeerEventStream").finish_non_exhaustive()
34    }
35}
36
37impl PeerEventStream {
38    /// Create a new stream [`PeerEventStream`] by converting the provided stream's items into peer
39    /// events [`PeerEvent`]
40    pub fn new<S, T>(stream: S) -> Self
41    where
42        S: Stream<Item = T> + Send + Sync + 'static,
43        T: Into<PeerEvent> + 'static,
44    {
45        let mapped_stream = stream.map(Into::into);
46        Self(Box::pin(mapped_stream))
47    }
48}
49
50impl Stream for PeerEventStream {
51    type Item = PeerEvent;
52
53    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
54        self.0.as_mut().poll_next(cx)
55    }
56}
57
58/// Represents information about an established peer session.
59#[derive(Debug, Clone)]
60pub struct SessionInfo {
61    /// The identifier of the peer to which a session was established.
62    pub peer_id: PeerId,
63    /// The remote addr of the peer to which a session was established.
64    pub remote_addr: SocketAddr,
65    /// The client version of the peer to which a session was established.
66    pub client_version: Arc<str>,
67    /// Capabilities the peer announced.
68    pub capabilities: Arc<Capabilities>,
69    /// The status of the peer to which a session was established.
70    pub status: Arc<UnifiedStatus>,
71    /// Negotiated eth version of the session.
72    pub version: EthVersion,
73    /// The kind of peer this session represents
74    pub peer_kind: PeerKind,
75}
76
77/// (Non-exhaustive) List of the different events emitted by the network that are of interest for
78/// subscribers.
79///
80/// This includes any event types that may be relevant to tasks, for metrics, keep track of peers
81/// etc.
82#[derive(Debug, Clone)]
83pub enum PeerEvent {
84    /// Closed the peer session.
85    SessionClosed {
86        /// The identifier of the peer to which a session was closed.
87        peer_id: PeerId,
88        /// Why the disconnect was triggered
89        reason: Option<DisconnectReason>,
90    },
91    /// Established a new session with the given peer.
92    SessionEstablished(SessionInfo),
93    /// Event emitted when a new peer is added
94    PeerAdded(PeerId),
95    /// Event emitted when a new peer is removed
96    PeerRemoved(PeerId),
97}
98
99/// (Non-exhaustive) Network events representing peer lifecycle events and session requests.
100#[derive(Debug)]
101pub enum NetworkEvent<R = PeerRequest> {
102    /// Basic peer lifecycle event.
103    Peer(PeerEvent),
104    /// Session established with requests.
105    ActivePeerSession {
106        /// Session information
107        info: SessionInfo,
108        /// A request channel to the session task.
109        messages: PeerRequestSender<R>,
110    },
111}
112
113impl<R> Clone for NetworkEvent<R> {
114    fn clone(&self) -> Self {
115        match self {
116            Self::Peer(event) => Self::Peer(event.clone()),
117            Self::ActivePeerSession { info, messages } => {
118                Self::ActivePeerSession { info: info.clone(), messages: messages.clone() }
119            }
120        }
121    }
122}
123
124impl<R> From<NetworkEvent<R>> for PeerEvent {
125    fn from(event: NetworkEvent<R>) -> Self {
126        match event {
127            NetworkEvent::Peer(peer_event) => peer_event,
128            NetworkEvent::ActivePeerSession { info, .. } => Self::SessionEstablished(info),
129        }
130    }
131}
132
133/// Provides peer event subscription for the network.
134#[auto_impl::auto_impl(&, Arc)]
135pub trait NetworkPeersEvents: Send + Sync {
136    /// Creates a new peer event listener stream.
137    fn peer_events(&self) -> PeerEventStream;
138}
139
140/// Provides event subscription for the network.
141#[auto_impl::auto_impl(&, Arc)]
142pub trait NetworkEventListenerProvider: NetworkPeersEvents {
143    /// The primitive types to use in the `PeerRequest` used in the stream.
144    type Primitives: NetworkPrimitives;
145
146    /// Creates a new [`NetworkEvent`] listener channel.
147    fn event_listener(&self) -> EventStream<NetworkEvent<PeerRequest<Self::Primitives>>>;
148    /// Returns a new [`DiscoveryEvent`] stream.
149    ///
150    /// This stream yields [`DiscoveryEvent`]s for each peer that is discovered.
151    fn discovery_listener(&self) -> UnboundedReceiverStream<DiscoveryEvent>;
152}
153
154/// Events produced by the `Discovery` manager.
155#[derive(Debug, Clone, PartialEq, Eq)]
156pub enum DiscoveryEvent {
157    /// Discovered a node
158    NewNode(DiscoveredEvent),
159    /// Retrieved a [`ForkId`] from the peer via ENR request.
160    ///
161    /// Contains the full [`NodeRecord`] (peer ID + address) and the reported [`ForkId`].
162    /// Used to verify fork compatibility before admitting the peer.
163    ///
164    /// See also <https://eips.ethereum.org/EIPS/eip-868>
165    EnrForkId(NodeRecord, ForkId),
166}
167
168/// Represents events related to peer discovery in the network.
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub enum DiscoveredEvent {
171    /// Indicates that a new peer has been discovered and queued for potential connection.
172    ///
173    /// This event is generated when the system becomes aware of a new peer
174    /// but hasn't yet established a connection.
175    ///
176    /// # Fields
177    ///
178    /// * `peer_id` - The unique identifier of the discovered peer.
179    /// * `addr` - The network address of the discovered peer.
180    /// * `fork_id` - An optional identifier for the fork that this peer is associated with. `None`
181    ///   if the peer is not associated with a specific fork.
182    EventQueued {
183        /// The unique identifier of the discovered peer.
184        peer_id: PeerId,
185        /// The network address of the discovered peer.
186        addr: PeerAddr,
187        /// An optional identifier for the fork that this peer is associated with.
188        /// `None` if the peer is not associated with a specific fork.
189        fork_id: Option<ForkId>,
190    },
191}
192
193/// Protocol related request messages that expect a response
194#[derive(Debug)]
195pub enum PeerRequest<N: NetworkPrimitives = EthNetworkPrimitives> {
196    /// Requests block headers from the peer.
197    ///
198    /// The response should be sent through the channel.
199    GetBlockHeaders {
200        /// The request for block headers.
201        request: GetBlockHeaders,
202        /// The channel to send the response for block headers.
203        response: oneshot::Sender<RequestResult<BlockHeaders<N::BlockHeader>>>,
204    },
205    /// Requests block bodies from the peer.
206    ///
207    /// The response should be sent through the channel.
208    GetBlockBodies {
209        /// The request for block bodies.
210        request: GetBlockBodies,
211        /// The channel to send the response for block bodies.
212        response: oneshot::Sender<RequestResult<BlockBodies<N::BlockBody>>>,
213    },
214    /// Requests pooled transactions from the peer.
215    ///
216    /// The response should be sent through the channel.
217    GetPooledTransactions {
218        /// The request for pooled transactions.
219        request: GetPooledTransactions,
220        /// The channel to send the response for pooled transactions.
221        response: oneshot::Sender<RequestResult<PooledTransactions<N::PooledTransaction>>>,
222    },
223    /// Requests `NodeData` from the peer.
224    ///
225    /// The response should be sent through the channel.
226    GetNodeData {
227        /// The request for `NodeData`.
228        request: GetNodeData,
229        /// The channel to send the response for `NodeData`.
230        response: oneshot::Sender<RequestResult<NodeData>>,
231    },
232    /// Requests receipts from the peer.
233    ///
234    /// The response should be sent through the channel.
235    GetReceipts {
236        /// The request for receipts.
237        request: GetReceipts,
238        /// The channel to send the response for receipts.
239        response: oneshot::Sender<RequestResult<Receipts<N::Receipt>>>,
240    },
241    /// Requests receipts from the peer without bloom filter.
242    ///
243    /// The response should be sent through the channel.
244    GetReceipts69 {
245        /// The request for receipts.
246        request: GetReceipts,
247        /// The channel to send the response for receipts.
248        response: oneshot::Sender<RequestResult<Receipts69<N::Receipt>>>,
249    },
250    /// Requests receipts from the peer using eth/70 (supports `firstBlockReceiptIndex`).
251    ///
252    /// The response should be sent through the channel.
253    GetReceipts70 {
254        /// The request for receipts.
255        request: GetReceipts70,
256        /// The channel to send the response for receipts.
257        response: oneshot::Sender<RequestResult<Receipts70<N::Receipt>>>,
258    },
259    /// Requests block access lists from the peer.
260    ///
261    /// The response should be sent through the channel.
262    GetBlockAccessLists {
263        /// The request for block access lists.
264        request: GetBlockAccessLists,
265        /// The channel to send the response for block access lists.
266        response: oneshot::Sender<RequestResult<BlockAccessLists>>,
267    },
268    /// Requests cells from the peer.
269    ///
270    /// The response should be sent through the channel.
271    GetCells {
272        /// The request for cells.
273        request: GetCells,
274        /// The channel to send the response for cells.
275        response: oneshot::Sender<RequestResult<Cells>>,
276    },
277    /// Requests a `snap/2` (EIP-8189) message from the peer.
278    ///
279    /// The response should be sent through the channel.
280    GetSnap {
281        /// The `snap/2` request to send.
282        request: SnapProtocolMessage,
283        /// The channel to send the response for the request.
284        response: oneshot::Sender<RequestResult<SnapResponse>>,
285    },
286}
287
288/// The wire message a [`PeerRequest`] resolves to before it's queued for sending: either an `eth`
289/// message or a `snap/2` message.
290#[derive(Debug)]
291pub enum RequestMessage<N: NetworkPrimitives = EthNetworkPrimitives> {
292    /// An `eth` protocol message.
293    Eth(EthMessage<N>),
294    /// A `snap/2` (EIP-8189) protocol message.
295    Snap(SnapProtocolMessage),
296}
297
298// === impl PeerRequest ===
299
300impl<N: NetworkPrimitives> PeerRequest<N> {
301    /// Invoked if we received a response which does not match the request
302    pub fn send_bad_response(self) {
303        self.send_err_response(RequestError::BadResponse)
304    }
305
306    /// Send an error back to the receiver.
307    pub fn send_err_response(self, err: RequestError) {
308        let _ = match self {
309            Self::GetBlockHeaders { response, .. } => response.send(Err(err)).ok(),
310            Self::GetBlockBodies { response, .. } => response.send(Err(err)).ok(),
311            Self::GetPooledTransactions { response, .. } => response.send(Err(err)).ok(),
312            Self::GetNodeData { response, .. } => response.send(Err(err)).ok(),
313            Self::GetReceipts { response, .. } => response.send(Err(err)).ok(),
314            Self::GetReceipts69 { response, .. } => response.send(Err(err)).ok(),
315            Self::GetReceipts70 { response, .. } => response.send(Err(err)).ok(),
316            Self::GetBlockAccessLists { response, .. } => response.send(Err(err)).ok(),
317            Self::GetCells { response, .. } => response.send(Err(err)).ok(),
318            Self::GetSnap { response, .. } => response.send(Err(err)).ok(),
319        };
320    }
321
322    /// Returns true if this request is supported for the negotiated eth protocol version.
323    #[inline]
324    pub fn is_supported_by_eth_version(&self, version: EthVersion) -> bool {
325        match self {
326            Self::GetBlockAccessLists { .. } => version >= EthVersion::Eth71,
327            Self::GetCells { .. } => version >= EthVersion::Eth72,
328            _ => true,
329        }
330    }
331
332    /// Returns the [`RequestMessage`] for this type.
333    pub fn create_request_message(&self, request_id: u64) -> RequestMessage<N> {
334        match self {
335            Self::GetBlockHeaders { request, .. } => {
336                RequestMessage::Eth(EthMessage::GetBlockHeaders(RequestPair {
337                    request_id,
338                    message: *request,
339                }))
340            }
341            Self::GetBlockBodies { request, .. } => {
342                RequestMessage::Eth(EthMessage::GetBlockBodies(RequestPair {
343                    request_id,
344                    message: request.clone(),
345                }))
346            }
347            Self::GetPooledTransactions { request, .. } => {
348                RequestMessage::Eth(EthMessage::GetPooledTransactions(RequestPair {
349                    request_id,
350                    message: request.clone(),
351                }))
352            }
353            Self::GetNodeData { request, .. } => {
354                RequestMessage::Eth(EthMessage::GetNodeData(RequestPair {
355                    request_id,
356                    message: request.clone(),
357                }))
358            }
359            Self::GetReceipts { request, .. } | Self::GetReceipts69 { request, .. } => {
360                RequestMessage::Eth(EthMessage::GetReceipts(RequestPair {
361                    request_id,
362                    message: request.clone(),
363                }))
364            }
365            Self::GetReceipts70 { request, .. } => {
366                RequestMessage::Eth(EthMessage::GetReceipts70(RequestPair {
367                    request_id,
368                    message: request.clone(),
369                }))
370            }
371            Self::GetBlockAccessLists { request, .. } => {
372                RequestMessage::Eth(EthMessage::GetBlockAccessLists(RequestPair {
373                    request_id,
374                    message: request.clone(),
375                }))
376            }
377            Self::GetCells { request, .. } => {
378                RequestMessage::Eth(EthMessage::GetCells(RequestPair {
379                    request_id,
380                    message: request.clone(),
381                }))
382            }
383            Self::GetSnap { request, .. } => {
384                let mut message = request.clone();
385                message.set_request_id(request_id);
386                RequestMessage::Snap(message)
387            }
388        }
389    }
390
391    /// Consumes the type and returns the inner [`GetPooledTransactions`] variant.
392    pub fn into_get_pooled_transactions(self) -> Option<GetPooledTransactions> {
393        match self {
394            Self::GetPooledTransactions { request, .. } => Some(request),
395            _ => None,
396        }
397    }
398}
399
400/// A Cloneable connection for sending _requests_ directly to the session of a peer.
401pub struct PeerRequestSender<R = PeerRequest> {
402    /// id of the remote node.
403    pub peer_id: PeerId,
404    /// The Sender half connected to a session.
405    pub to_session_tx: mpsc::Sender<R>,
406}
407
408impl<R> Clone for PeerRequestSender<R> {
409    fn clone(&self) -> Self {
410        Self { peer_id: self.peer_id, to_session_tx: self.to_session_tx.clone() }
411    }
412}
413
414// === impl PeerRequestSender ===
415
416impl<R> PeerRequestSender<R> {
417    /// Constructs a new sender instance that's wired to a session
418    pub const fn new(peer_id: PeerId, to_session_tx: mpsc::Sender<R>) -> Self {
419        Self { peer_id, to_session_tx }
420    }
421
422    /// Attempts to immediately send a message on this Sender
423    pub fn try_send(&self, req: R) -> Result<(), mpsc::error::TrySendError<R>> {
424        self.to_session_tx.try_send(req)
425    }
426
427    /// Returns the peer id of the remote peer.
428    pub const fn peer_id(&self) -> &PeerId {
429        &self.peer_id
430    }
431}
432
433impl<R> fmt::Debug for PeerRequestSender<R> {
434    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
435        f.debug_struct("PeerRequestSender").field("peer_id", &self.peer_id).finish_non_exhaustive()
436    }
437}
438
439#[cfg(test)]
440mod tests {
441    use super::*;
442
443    #[test]
444    fn test_get_block_access_lists_version_support() {
445        let (tx, _rx) = oneshot::channel();
446        let req: PeerRequest<EthNetworkPrimitives> =
447            PeerRequest::GetBlockAccessLists { request: GetBlockAccessLists(vec![]), response: tx };
448
449        assert!(!req.is_supported_by_eth_version(EthVersion::Eth70));
450        assert!(req.is_supported_by_eth_version(EthVersion::Eth71));
451    }
452
453    #[test]
454    fn test_get_cells_version_support() {
455        let (tx, _rx) = oneshot::channel();
456        let req: PeerRequest<EthNetworkPrimitives> =
457            PeerRequest::GetCells { request: GetCells::default(), response: tx };
458
459        assert!(!req.is_supported_by_eth_version(EthVersion::Eth71));
460        assert!(req.is_supported_by_eth_version(EthVersion::Eth72));
461    }
462}