1use crate::{
9 capability::SharedCapabilities,
10 errors::{EthStreamError, P2PStreamError},
11 handshake::EthRlpxHandshake,
12 message::EthBroadcastMessage,
13 Capability, EthMessage, EthNetworkPrimitives, EthStreamInner, EthVersion, NetworkPrimitives,
14 P2PStream, UnifiedStatus, HANDSHAKE_TIMEOUT,
15};
16use alloy_primitives::bytes::{Bytes, BytesMut};
17use futures::{Sink, SinkExt, Stream, StreamExt};
18use reth_eth_wire_types::{
19 snap::{SnapProtocolMessage, SnapVersion},
20 RawCapabilityMessage,
21};
22use reth_ethereum_forks::ForkFilter;
23use std::{
24 io,
25 pin::Pin,
26 sync::Arc,
27 task::{ready, Context, Poll},
28};
29
30#[derive(Debug)]
37pub struct EthSnapStream<St, N: NetworkPrimitives = EthNetworkPrimitives> {
38 conn: P2PStream<St>,
40 eth: EthStreamInner<N>,
42 snap_offset: u8,
46}
47
48impl<St, N> EthSnapStream<St, N>
49where
50 St: Stream<Item = io::Result<BytesMut>> + Sink<Bytes, Error = io::Error> + Unpin + Send + Sync,
51 N: NetworkPrimitives,
52{
53 pub async fn handshake(
60 mut conn: P2PStream<St>,
61 status: UnifiedStatus,
62 fork_filter: ForkFilter,
63 handshake: Arc<dyn EthRlpxHandshake>,
64 eth_max_message_size: usize,
65 ) -> Result<(Self, UnifiedStatus), EthStreamError> {
66 let eth_version = conn.shared_capabilities().eth_version()?;
67 let snap_offset = eth_snap_layout(conn.shared_capabilities())?;
68
69 let their_status =
70 handshake.handshake(&mut conn, status, fork_filter, HANDSHAKE_TIMEOUT).await?;
71
72 let eth = EthStreamInner::with_max_message_size(eth_version, eth_max_message_size);
73 Ok((Self { conn, eth, snap_offset }, their_status))
74 }
75}
76
77impl<St, N: NetworkPrimitives> EthSnapStream<St, N> {
78 #[inline]
80 pub const fn version(&self) -> EthVersion {
81 self.eth.version()
82 }
83
84 #[inline]
86 pub const fn set_reject_block_announcements(&mut self, reject: bool) {
87 self.eth.set_reject_block_announcements(reject);
88 }
89
90 #[inline]
92 pub const fn inner(&self) -> &P2PStream<St> {
93 &self.conn
94 }
95
96 #[inline]
98 pub const fn inner_mut(&mut self) -> &mut P2PStream<St> {
99 &mut self.conn
100 }
101
102 #[inline]
104 pub fn into_inner(self) -> P2PStream<St> {
105 self.conn
106 }
107}
108
109impl<St, N> EthSnapStream<St, N>
110where
111 St: Stream<Item = io::Result<BytesMut>> + Sink<Bytes, Error = io::Error> + Unpin,
112 N: NetworkPrimitives,
113{
114 pub fn start_send_broadcast(
116 &mut self,
117 item: EthBroadcastMessage<N>,
118 ) -> Result<(), EthStreamError> {
119 self.conn.start_send_unpin(item.encoded()).map_err(Into::into)
120 }
121
122 pub fn start_send_raw(&mut self, msg: RawCapabilityMessage) -> Result<(), EthStreamError> {
124 self.conn.start_send_unpin(msg.encoded()).map_err(Into::into)
125 }
126}
127
128#[derive(Debug)]
130pub enum EthSnapMessage<N: NetworkPrimitives = EthNetworkPrimitives> {
131 Eth(EthMessage<N>),
133 Snap(SnapProtocolMessage),
135}
136
137impl<St, N> Stream for EthSnapStream<St, N>
138where
139 St: Stream<Item = io::Result<BytesMut>> + Sink<Bytes, Error = io::Error> + Unpin,
140 N: NetworkPrimitives,
141{
142 type Item = Result<EthSnapMessage<N>, EthStreamError>;
143
144 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
145 let this = self.get_mut();
146 let mut bytes = match ready!(this.conn.poll_next_unpin(cx)) {
147 Some(Ok(bytes)) => bytes,
148 Some(Err(err)) => return Poll::Ready(Some(Err(err.into()))),
149 None => return Poll::Ready(None),
150 };
151
152 let Some(&id) = bytes.first() else {
153 return Poll::Ready(Some(Err(P2PStreamError::EmptyProtocolMessage.into())))
154 };
155
156 let Some(snap_id) = id.checked_sub(this.snap_offset) else {
160 return Poll::Ready(Some(this.eth.decode_message(bytes).map(EthSnapMessage::Eth)))
161 };
162 bytes[0] = snap_id;
163 Poll::Ready(Some(
164 SnapProtocolMessage::decode_versioned(SnapVersion::V2, &bytes)
165 .map(EthSnapMessage::Snap)
166 .map_err(Into::into),
167 ))
168 }
169}
170
171impl<St, N> Sink<EthSnapMessage<N>> for EthSnapStream<St, N>
172where
173 St: Stream<Item = io::Result<BytesMut>> + Sink<Bytes, Error = io::Error> + Unpin,
174 N: NetworkPrimitives,
175{
176 type Error = EthStreamError;
177
178 fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
179 self.get_mut().conn.poll_ready_unpin(cx).map_err(Into::into)
180 }
181
182 fn start_send(self: Pin<&mut Self>, item: EthSnapMessage<N>) -> Result<(), Self::Error> {
183 let this = self.get_mut();
184 let bytes = match item {
185 EthSnapMessage::Eth(msg) => this.eth.encode_message(msg)?,
186 EthSnapMessage::Snap(msg) => {
187 let mut buf =
190 msg.encode().0.try_into_mut().unwrap_or_else(|b| BytesMut::from(b.as_ref()));
191 mask_snap(&mut buf, this.snap_offset)?;
192 buf.freeze()
193 }
194 };
195 this.conn.start_send_unpin(bytes).map_err(Into::into)
196 }
197
198 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
199 self.get_mut().conn.poll_flush_unpin(cx).map_err(Into::into)
200 }
201
202 fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
203 self.get_mut().conn.poll_close_unpin(cx).map_err(Into::into)
204 }
205}
206
207fn eth_snap_layout(caps: &SharedCapabilities) -> Result<u8, EthStreamError> {
215 if !caps.is_exact_eth_snap_v2() {
216 return Err(P2PStreamError::CapabilityNotShared.into())
217 }
218 let snap = caps
219 .ensure_matching_capability(&Capability::snap_2())
220 .map_err(|_| EthStreamError::from(P2PStreamError::CapabilityNotShared))?;
221 let snap_offset = snap.relative_message_id_offset();
222
223 let eth = caps.eth()?;
224 if eth.relative_message_id_offset() != 0 || eth.num_messages() != snap_offset {
225 return Err(P2PStreamError::CapabilityNotShared.into())
226 }
227 Ok(snap_offset)
228}
229
230#[inline]
232fn mask_snap(bytes: &mut BytesMut, snap_offset: u8) -> Result<(), io::Error> {
233 let id = bytes.first().ok_or_else(|| io::Error::from(io::ErrorKind::InvalidInput))?;
234 bytes[0] =
235 id.checked_add(snap_offset).ok_or_else(|| io::Error::from(io::ErrorKind::InvalidInput))?;
236 Ok(())
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use crate::{
243 handshake::EthHandshake,
244 message::MAX_MESSAGE_SIZE,
245 protocol::Protocol,
246 test_utils::{connect_passthrough, eth_handshake, eth_hello},
247 UnauthedP2PStream,
248 };
249 use reth_eth_wire_types::{
250 snap::{BlockAccessListsMessage, GetBlockAccessListsMessage},
251 EthVersion,
252 };
253 use tokio::net::TcpListener;
254 use tokio_util::codec::Decoder;
255
256 fn shared_caps(local: Vec<Protocol>, peer: Vec<Capability>) -> SharedCapabilities {
258 SharedCapabilities::try_new(local, peer).unwrap()
259 }
260
261 #[test]
262 fn eth_snap_layout_accepts_eth_then_snap() {
263 let caps = shared_caps(
264 vec![EthVersion::Eth68.into(), Protocol::snap_2()],
265 vec![EthVersion::Eth68.into(), Capability::snap_2()],
266 );
267 let offset = eth_snap_layout(&caps).unwrap();
268 assert_eq!(offset, caps.eth().unwrap().num_messages());
269 }
270
271 #[test]
272 fn eth_snap_layout_rejects_missing_snap() {
273 let caps = shared_caps(vec![EthVersion::Eth68.into()], vec![EthVersion::Eth68.into()]);
274 assert!(eth_snap_layout(&caps).is_err());
275 }
276
277 #[test]
278 fn eth_snap_layout_rejects_capability_before_eth() {
279 let cap = Capability::new_static("aaa", 1);
281 let caps = shared_caps(
282 vec![Protocol::new(cap.clone(), 5), EthVersion::Eth68.into(), Protocol::snap_2()],
283 vec![cap, EthVersion::Eth68.into(), Capability::snap_2()],
284 );
285 assert!(eth_snap_layout(&caps).is_err());
286 }
287
288 #[test]
289 fn eth_snap_layout_rejects_capability_between_eth_and_snap() {
290 let cap = Capability::new_static("les", 1);
293 let caps = shared_caps(
294 vec![EthVersion::Eth68.into(), Protocol::new(cap.clone(), 5), Protocol::snap_2()],
295 vec![EthVersion::Eth68.into(), cap, Capability::snap_2()],
296 );
297 assert!(eth_snap_layout(&caps).is_err());
298 }
299
300 #[test]
301 fn eth_snap_layout_rejects_capability_after_snap() {
302 let cap = Capability::new_static("zzz", 1);
306 let caps = shared_caps(
307 vec![EthVersion::Eth68.into(), Protocol::snap_2(), Protocol::new(cap.clone(), 5)],
308 vec![EthVersion::Eth68.into(), Capability::snap_2(), cap],
309 );
310 assert!(eth_snap_layout(&caps).is_err());
311 }
312
313 #[test]
314 fn mask_snap_rebases_into_combined_space() {
315 let mut bytes = BytesMut::from(&[8u8, 0xcc][..]);
316 mask_snap(&mut bytes, 17).unwrap();
317 assert_eq!(bytes[0], 17 + 8);
318 }
319
320 #[tokio::test(flavor = "multi_thread")]
323 async fn snap_request_response_round_trips_over_the_wire() {
324 reth_tracing::init_test_tracing();
325 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
326 let local_addr = listener.local_addr().unwrap();
327 let (status, fork_filter) = eth_handshake();
328 let server_status = status;
329 let server_fork_filter = fork_filter.clone();
330
331 let server = tokio::spawn(async move {
333 let (incoming, _) = listener.accept().await.unwrap();
334 let stream = crate::PassthroughCodec::default().framed(incoming);
335 let server_hello = eth_snap_hello();
336 let (conn, _) = UnauthedP2PStream::new(stream).handshake(server_hello).await.unwrap();
337
338 let (mut stream, _) = EthSnapStream::<_, EthNetworkPrimitives>::handshake(
339 conn,
340 server_status,
341 server_fork_filter,
342 Arc::new(EthHandshake::default()),
343 MAX_MESSAGE_SIZE,
344 )
345 .await
346 .unwrap();
347
348 while let Some(Ok(msg)) = stream.next().await {
349 if let EthSnapMessage::Snap(SnapProtocolMessage::GetBlockAccessLists(req)) = msg {
350 let response = SnapProtocolMessage::BlockAccessLists(BlockAccessListsMessage {
351 request_id: req.request_id,
352 block_access_lists: reth_eth_wire_types::BlockAccessLists(vec![None]),
353 });
354 stream.send(EthSnapMessage::Snap(response)).await.unwrap();
355 }
356 }
357 });
358
359 let conn = connect_passthrough(local_addr, eth_snap_hello()).await;
361 let (mut stream, _) = EthSnapStream::<_, EthNetworkPrimitives>::handshake(
362 conn,
363 status,
364 fork_filter,
365 Arc::new(EthHandshake::default()),
366 MAX_MESSAGE_SIZE,
367 )
368 .await
369 .unwrap();
370
371 stream
372 .send(EthSnapMessage::Snap(SnapProtocolMessage::GetBlockAccessLists(
373 GetBlockAccessListsMessage {
374 request_id: 7,
375 block_hashes: Vec::new(),
376 response_bytes: u64::MAX,
377 },
378 )))
379 .await
380 .unwrap();
381
382 let response = loop {
383 if let EthSnapMessage::Snap(SnapProtocolMessage::BlockAccessLists(resp)) =
384 stream.next().await.unwrap().unwrap()
385 {
386 break resp;
387 }
388 };
389 assert_eq!(response.request_id, 7);
390
391 server.abort();
392 }
393
394 fn eth_snap_hello() -> crate::HelloMessageWithProtocols {
396 let mut hello = eth_hello().0;
397 hello.try_add_protocol(Protocol::snap_2()).unwrap();
398 hello
399 }
400}