Skip to main content

reth_network/session/
conn.rs

1//! Connection types for a session
2
3use futures::{Sink, SinkExt, Stream, StreamExt};
4use reth_ecies::stream::ECIESStream;
5use reth_eth_wire::{
6    errors::{EthStreamError, P2PStreamError},
7    message::EthBroadcastMessage,
8    multiplex::{ProtocolProxy, RlpxSatelliteStream},
9    snap::SnapProtocolMessage,
10    EthMessage, EthNetworkPrimitives, EthSnapMessage, EthSnapStream, EthStream, EthVersion,
11    NetworkPrimitives, P2PStream,
12};
13use reth_eth_wire_types::RawCapabilityMessage;
14use std::{
15    pin::Pin,
16    task::{Context, Poll},
17};
18use tokio::net::TcpStream;
19
20/// The type of the underlying peer network connection.
21pub type EthPeerConnection<N> = EthStream<P2PStream<ECIESStream<TcpStream>>, N>;
22
23/// Various connection types that at least support the ETH protocol.
24pub type EthSatelliteConnection<N = EthNetworkPrimitives> =
25    RlpxSatelliteStream<ECIESStream<TcpStream>, EthStream<ProtocolProxy, N>>;
26
27/// A dedicated `eth` + `snap/2` connection.
28pub type EthSnapConnection<N = EthNetworkPrimitives> = EthSnapStream<ECIESStream<TcpStream>, N>;
29
30/// Connection types that support the ETH protocol.
31///
32/// This can be either:
33/// - A connection that only supports the ETH protocol
34/// - A connection that supports the ETH protocol and `snap/2` ([`EthSnapStream`])
35/// - A connection that supports the ETH protocol and at least one other `RLPx` protocol
36// This type is boxed because the underlying stream is ~6KB,
37// mostly coming from `P2PStream`'s `snap::Encoder` (2072), and `ECIESStream` (3600).
38#[derive(Debug)]
39pub enum EthRlpxConnection<N: NetworkPrimitives = EthNetworkPrimitives> {
40    /// A connection that only supports the ETH protocol.
41    EthOnly(Box<EthPeerConnection<N>>),
42    /// A dedicated connection that supports the ETH protocol and `snap/2` (EIP-8189).
43    EthSnap(Box<EthSnapConnection<N>>),
44    /// A connection that supports the ETH protocol and __at least one other__ `RLPx` protocol.
45    Satellite(Box<EthSatelliteConnection<N>>),
46}
47
48impl<N: NetworkPrimitives> EthRlpxConnection<N> {
49    /// Returns the negotiated ETH version.
50    #[inline]
51    pub(crate) const fn version(&self) -> EthVersion {
52        match self {
53            Self::EthOnly(conn) => conn.version(),
54            Self::EthSnap(conn) => conn.version(),
55            Self::Satellite(conn) => conn.primary().version(),
56        }
57    }
58
59    /// Returns `true` if `snap/2` was negotiated on this connection.
60    #[inline]
61    pub(crate) const fn supports_snap(&self) -> bool {
62        matches!(self, Self::EthSnap(_))
63    }
64
65    /// Consumes this type and returns the wrapped [`P2PStream`].
66    #[inline]
67    pub(crate) fn into_inner(self) -> P2PStream<ECIESStream<TcpStream>> {
68        match self {
69            Self::EthOnly(conn) => conn.into_inner(),
70            Self::EthSnap(conn) => conn.into_inner(),
71            Self::Satellite(conn) => conn.into_inner(),
72        }
73    }
74
75    /// Returns mutable access to the underlying stream.
76    #[inline]
77    pub(crate) fn inner_mut(&mut self) -> &mut P2PStream<ECIESStream<TcpStream>> {
78        match self {
79            Self::EthOnly(conn) => conn.inner_mut(),
80            Self::EthSnap(conn) => conn.inner_mut(),
81            Self::Satellite(conn) => conn.inner_mut(),
82        }
83    }
84
85    /// Returns access to the underlying stream.
86    #[inline]
87    pub(crate) const fn inner(&self) -> &P2PStream<ECIESStream<TcpStream>> {
88        match self {
89            Self::EthOnly(conn) => conn.inner(),
90            Self::EthSnap(conn) => conn.inner(),
91            Self::Satellite(conn) => conn.inner(),
92        }
93    }
94
95    /// Same as [`Sink::start_send`] but accepts a [`EthBroadcastMessage`] instead.
96    #[inline]
97    pub fn start_send_broadcast(
98        &mut self,
99        item: EthBroadcastMessage<N>,
100    ) -> Result<(), EthStreamError> {
101        match self {
102            Self::EthOnly(conn) => conn.start_send_broadcast(item),
103            Self::EthSnap(conn) => conn.start_send_broadcast(item),
104            Self::Satellite(conn) => conn.primary_mut().start_send_broadcast(item),
105        }
106    }
107
108    /// Sends a raw capability message over the connection
109    pub fn start_send_raw(&mut self, msg: RawCapabilityMessage) -> Result<(), EthStreamError> {
110        match self {
111            Self::EthOnly(conn) => conn.start_send_raw(msg),
112            Self::EthSnap(conn) => conn.start_send_raw(msg),
113            Self::Satellite(conn) => conn.primary_mut().start_send_raw(msg),
114        }
115    }
116
117    /// Queues a `snap/2` message to be sent on the wire.
118    ///
119    /// Returns an error on connections that did not negotiate `snap/2`, so a caller never believes
120    /// a request was sent when it was discarded.
121    pub fn start_send_snap(&mut self, msg: SnapProtocolMessage) -> Result<(), EthStreamError> {
122        match self {
123            Self::EthSnap(conn) => conn.start_send_unpin(EthSnapMessage::Snap(msg)),
124            Self::EthOnly(_) | Self::Satellite(_) => {
125                Err(P2PStreamError::CapabilityNotShared.into())
126            }
127        }
128    }
129
130    /// Sets whether to reject block announcement messages (`NewBlock`, `NewBlockHashes`) before
131    /// RLP decoding to avoid memory amplification from deserializing blocks that will be discarded.
132    pub fn set_reject_block_announcements(&mut self, reject: bool) {
133        match self {
134            Self::EthOnly(conn) => conn.set_reject_block_announcements(reject),
135            Self::EthSnap(conn) => conn.set_reject_block_announcements(reject),
136            Self::Satellite(conn) => conn.primary_mut().set_reject_block_announcements(reject),
137        }
138    }
139}
140
141impl<N: NetworkPrimitives> From<EthPeerConnection<N>> for EthRlpxConnection<N> {
142    #[inline]
143    fn from(conn: EthPeerConnection<N>) -> Self {
144        Self::EthOnly(Box::new(conn))
145    }
146}
147
148impl<N: NetworkPrimitives> From<EthSnapConnection<N>> for EthRlpxConnection<N> {
149    #[inline]
150    fn from(conn: EthSnapConnection<N>) -> Self {
151        Self::EthSnap(Box::new(conn))
152    }
153}
154
155impl<N: NetworkPrimitives> From<EthSatelliteConnection<N>> for EthRlpxConnection<N> {
156    #[inline]
157    fn from(conn: EthSatelliteConnection<N>) -> Self {
158        Self::Satellite(Box::new(conn))
159    }
160}
161
162/// Delegates a call to the active variant's boxed stream (every variant is `Unpin`).
163///
164/// The second form runs `$adapt` on the eth-only variants to lift their result into the shared
165/// item type; the snap variant already yields it.
166macro_rules! delegate_call {
167    ($self:ident.$method:ident($($args:ident),+)) => {
168        match $self.get_mut() {
169            Self::EthOnly(l) => l.$method($($args),+),
170            Self::EthSnap(s) => s.$method($($args),+),
171            Self::Satellite(r) => r.$method($($args),+),
172        }
173    };
174    ($self:ident.$method:ident($($args:ident),+) => $adapt:expr) => {
175        match $self.get_mut() {
176            Self::EthOnly(l) => $adapt(l.$method($($args),+)),
177            Self::Satellite(r) => $adapt(r.$method($($args),+)),
178            Self::EthSnap(s) => s.$method($($args),+),
179        }
180    };
181}
182
183impl<N: NetworkPrimitives> Stream for EthRlpxConnection<N> {
184    type Item = Result<EthSnapMessage<N>, EthStreamError>;
185
186    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
187        delegate_call!(self.poll_next_unpin(cx) => lift_eth)
188    }
189}
190
191impl<N: NetworkPrimitives> Sink<EthMessage<N>> for EthRlpxConnection<N> {
192    type Error = EthStreamError;
193
194    fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
195        delegate_call!(self.poll_ready_unpin(cx))
196    }
197
198    fn start_send(self: Pin<&mut Self>, item: EthMessage<N>) -> Result<(), Self::Error> {
199        match self.get_mut() {
200            Self::EthOnly(l) => l.start_send_unpin(item),
201            Self::Satellite(r) => r.start_send_unpin(item),
202            Self::EthSnap(s) => s.start_send_unpin(EthSnapMessage::Eth(item)),
203        }
204    }
205
206    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
207        delegate_call!(self.poll_flush_unpin(cx))
208    }
209
210    fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
211        delegate_call!(self.poll_close_unpin(cx))
212    }
213}
214
215/// Lifts a polled `eth` item into the shared [`EthSnapMessage`] item type.
216#[inline]
217fn lift_eth<N: NetworkPrimitives>(
218    poll: Poll<Option<Result<EthMessage<N>, EthStreamError>>>,
219) -> Poll<Option<Result<EthSnapMessage<N>, EthStreamError>>> {
220    poll.map(|opt| opt.map(|res| res.map(EthSnapMessage::Eth)))
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    const fn assert_eth_stream<N, St>()
228    where
229        N: NetworkPrimitives,
230        St: Stream<Item = Result<EthMessage<N>, EthStreamError>> + Sink<EthMessage<N>>,
231    {
232    }
233
234    const fn assert_eth_snap_stream<N, St>()
235    where
236        N: NetworkPrimitives,
237        St: Stream<Item = Result<EthSnapMessage<N>, EthStreamError>> + Sink<EthMessage<N>>,
238    {
239    }
240
241    #[test]
242    const fn test_eth_stream_variants() {
243        assert_eth_stream::<EthNetworkPrimitives, EthSatelliteConnection<EthNetworkPrimitives>>();
244        assert_eth_snap_stream::<EthNetworkPrimitives, EthRlpxConnection<EthNetworkPrimitives>>();
245    }
246}