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    /// Only supported on sessions older than `eth/69`.
235    ///
236    /// The response should be sent through the channel.
237    GetReceipts {
238        /// The request for receipts.
239        request: GetReceipts,
240        /// The channel to send the response for receipts.
241        response: oneshot::Sender<RequestResult<Receipts<N::Receipt>>>,
242    },
243    /// Requests receipts from the peer without bloom filter.
244    ///
245    /// Only supported on `eth/69` sessions.
246    ///
247    /// The response should be sent through the channel.
248    GetReceipts69 {
249        /// The request for receipts.
250        request: GetReceipts,
251        /// The channel to send the response for receipts.
252        response: oneshot::Sender<RequestResult<Receipts69<N::Receipt>>>,
253    },
254    /// Requests receipts from the peer using eth/70 (supports `firstBlockReceiptIndex`).
255    ///
256    /// Only supported on `eth/70` and newer sessions.
257    ///
258    /// The response should be sent through the channel.
259    GetReceipts70 {
260        /// The request for receipts.
261        request: GetReceipts70,
262        /// The channel to send the response for receipts.
263        response: oneshot::Sender<RequestResult<Receipts70<N::Receipt>>>,
264    },
265    /// Requests block access lists from the peer.
266    ///
267    /// The response should be sent through the channel.
268    GetBlockAccessLists {
269        /// The request for block access lists.
270        request: GetBlockAccessLists,
271        /// The channel to send the response for block access lists.
272        response: oneshot::Sender<RequestResult<BlockAccessLists>>,
273    },
274    /// Requests cells from the peer.
275    ///
276    /// The response should be sent through the channel.
277    GetCells {
278        /// The request for cells.
279        request: GetCells,
280        /// The channel to send the response for cells.
281        response: oneshot::Sender<RequestResult<Cells>>,
282    },
283    /// Requests a `snap/2` (EIP-8189) message from the peer.
284    ///
285    /// The response should be sent through the channel.
286    GetSnap {
287        /// The `snap/2` request to send.
288        request: SnapProtocolMessage,
289        /// The channel to send the response for the request.
290        response: oneshot::Sender<RequestResult<SnapResponse>>,
291    },
292}
293
294/// The wire message a [`PeerRequest`] resolves to before it's queued for sending: either an `eth`
295/// message or a `snap/2` message.
296#[derive(Debug)]
297pub enum RequestMessage<N: NetworkPrimitives = EthNetworkPrimitives> {
298    /// An `eth` protocol message.
299    Eth(EthMessage<N>),
300    /// A `snap/2` (EIP-8189) protocol message.
301    Snap(SnapProtocolMessage),
302}
303
304// === impl PeerRequest ===
305
306impl<N: NetworkPrimitives> PeerRequest<N> {
307    /// Invoked if we received a response which does not match the request
308    pub fn send_bad_response(self) {
309        self.send_err_response(RequestError::BadResponse)
310    }
311
312    /// Send an error back to the receiver.
313    pub fn send_err_response(self, err: RequestError) {
314        let _ = match self {
315            Self::GetBlockHeaders { response, .. } => response.send(Err(err)).ok(),
316            Self::GetBlockBodies { response, .. } => response.send(Err(err)).ok(),
317            Self::GetPooledTransactions { response, .. } => response.send(Err(err)).ok(),
318            Self::GetNodeData { response, .. } => response.send(Err(err)).ok(),
319            Self::GetReceipts { response, .. } => response.send(Err(err)).ok(),
320            Self::GetReceipts69 { response, .. } => response.send(Err(err)).ok(),
321            Self::GetReceipts70 { response, .. } => response.send(Err(err)).ok(),
322            Self::GetBlockAccessLists { response, .. } => response.send(Err(err)).ok(),
323            Self::GetCells { response, .. } => response.send(Err(err)).ok(),
324            Self::GetSnap { response, .. } => response.send(Err(err)).ok(),
325        };
326    }
327
328    /// Returns true if this request is supported for the negotiated eth protocol version.
329    #[inline]
330    pub fn is_supported_by_eth_version(&self, version: EthVersion) -> bool {
331        match self {
332            // The reply is decoded by the negotiated version, so each receipts request can only be
333            // answered on the versions that produce its response type.
334            Self::GetReceipts { .. } => version < EthVersion::Eth69,
335            Self::GetReceipts69 { .. } => version == EthVersion::Eth69,
336            Self::GetReceipts70 { .. } => version >= EthVersion::Eth70,
337            Self::GetBlockAccessLists { .. } => version >= EthVersion::Eth71,
338            Self::GetCells { .. } => version >= EthVersion::Eth72,
339            _ => true,
340        }
341    }
342
343    /// Returns the [`RequestMessage`] for this type.
344    pub fn create_request_message(&self, request_id: u64) -> RequestMessage<N> {
345        match self {
346            Self::GetBlockHeaders { request, .. } => {
347                RequestMessage::Eth(EthMessage::GetBlockHeaders(RequestPair {
348                    request_id,
349                    message: *request,
350                }))
351            }
352            Self::GetBlockBodies { request, .. } => {
353                RequestMessage::Eth(EthMessage::GetBlockBodies(RequestPair {
354                    request_id,
355                    message: request.clone(),
356                }))
357            }
358            Self::GetPooledTransactions { request, .. } => {
359                RequestMessage::Eth(EthMessage::GetPooledTransactions(RequestPair {
360                    request_id,
361                    message: request.clone(),
362                }))
363            }
364            Self::GetNodeData { request, .. } => {
365                RequestMessage::Eth(EthMessage::GetNodeData(RequestPair {
366                    request_id,
367                    message: request.clone(),
368                }))
369            }
370            Self::GetReceipts { request, .. } | Self::GetReceipts69 { request, .. } => {
371                RequestMessage::Eth(EthMessage::GetReceipts(RequestPair {
372                    request_id,
373                    message: request.clone(),
374                }))
375            }
376            Self::GetReceipts70 { request, .. } => {
377                RequestMessage::Eth(EthMessage::GetReceipts70(RequestPair {
378                    request_id,
379                    message: request.clone(),
380                }))
381            }
382            Self::GetBlockAccessLists { request, .. } => {
383                RequestMessage::Eth(EthMessage::GetBlockAccessLists(RequestPair {
384                    request_id,
385                    message: request.clone(),
386                }))
387            }
388            Self::GetCells { request, .. } => {
389                RequestMessage::Eth(EthMessage::GetCells(RequestPair {
390                    request_id,
391                    message: request.clone(),
392                }))
393            }
394            Self::GetSnap { request, .. } => {
395                let mut message = request.clone();
396                message.set_request_id(request_id);
397                RequestMessage::Snap(message)
398            }
399        }
400    }
401
402    /// Consumes the type and returns the inner [`GetPooledTransactions`] variant.
403    pub fn into_get_pooled_transactions(self) -> Option<GetPooledTransactions> {
404        match self {
405            Self::GetPooledTransactions { request, .. } => Some(request),
406            _ => None,
407        }
408    }
409}
410
411/// A Cloneable connection for sending _requests_ directly to the session of a peer.
412pub struct PeerRequestSender<R = PeerRequest> {
413    /// id of the remote node.
414    pub peer_id: PeerId,
415    /// The Sender half connected to a session.
416    pub to_session_tx: mpsc::Sender<R>,
417}
418
419impl<R> Clone for PeerRequestSender<R> {
420    fn clone(&self) -> Self {
421        Self { peer_id: self.peer_id, to_session_tx: self.to_session_tx.clone() }
422    }
423}
424
425// === impl PeerRequestSender ===
426
427impl<R> PeerRequestSender<R> {
428    /// Constructs a new sender instance that's wired to a session
429    pub const fn new(peer_id: PeerId, to_session_tx: mpsc::Sender<R>) -> Self {
430        Self { peer_id, to_session_tx }
431    }
432
433    /// Attempts to immediately send a message on this Sender
434    pub fn try_send(&self, req: R) -> Result<(), mpsc::error::TrySendError<R>> {
435        self.to_session_tx.try_send(req)
436    }
437
438    /// Returns the peer id of the remote peer.
439    pub const fn peer_id(&self) -> &PeerId {
440        &self.peer_id
441    }
442}
443
444impl<R> fmt::Debug for PeerRequestSender<R> {
445    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446        f.debug_struct("PeerRequestSender").field("peer_id", &self.peer_id).finish_non_exhaustive()
447    }
448}
449
450#[cfg(test)]
451mod tests {
452    use super::*;
453
454    #[test]
455    fn test_get_block_access_lists_version_support() {
456        let (tx, _rx) = oneshot::channel();
457        let req: PeerRequest<EthNetworkPrimitives> =
458            PeerRequest::GetBlockAccessLists { request: GetBlockAccessLists(vec![]), response: tx };
459
460        assert!(!req.is_supported_by_eth_version(EthVersion::Eth70));
461        assert!(req.is_supported_by_eth_version(EthVersion::Eth71));
462    }
463
464    #[test]
465    fn test_get_cells_version_support() {
466        let (tx, _rx) = oneshot::channel();
467        let req: PeerRequest<EthNetworkPrimitives> =
468            PeerRequest::GetCells { request: GetCells::default(), response: tx };
469
470        assert!(!req.is_supported_by_eth_version(EthVersion::Eth71));
471        assert!(req.is_supported_by_eth_version(EthVersion::Eth72));
472    }
473
474    #[test]
475    fn test_get_receipts_version_support() {
476        let (tx, _rx) = oneshot::channel();
477        let req = PeerRequest::<EthNetworkPrimitives>::GetReceipts {
478            request: GetReceipts(vec![]),
479            response: tx,
480        };
481
482        assert!(req.is_supported_by_eth_version(EthVersion::Eth68));
483        assert!(!req.is_supported_by_eth_version(EthVersion::Eth69));
484
485        let (tx, _rx) = oneshot::channel();
486        let req = PeerRequest::<EthNetworkPrimitives>::GetReceipts69 {
487            request: GetReceipts(vec![]),
488            response: tx,
489        };
490
491        assert!(!req.is_supported_by_eth_version(EthVersion::Eth68));
492        assert!(req.is_supported_by_eth_version(EthVersion::Eth69));
493        assert!(!req.is_supported_by_eth_version(EthVersion::Eth70));
494
495        let (tx, _rx) = oneshot::channel();
496        let req = PeerRequest::<EthNetworkPrimitives>::GetReceipts70 {
497            request: GetReceipts70 { first_block_receipt_index: 0, block_hashes: vec![] },
498            response: tx,
499        };
500
501        assert!(!req.is_supported_by_eth_version(EthVersion::Eth69));
502        assert!(req.is_supported_by_eth_version(EthVersion::Eth70));
503        assert!(req.is_supported_by_eth_version(EthVersion::Eth71));
504    }
505}