1use crate::{
2 capability::SharedCapabilities,
3 disconnect::CanDisconnect,
4 errors::{P2PHandshakeError, P2PStreamError},
5 pinger::{Pinger, PingerEvent},
6 DisconnectReason, HelloMessage, HelloMessageWithProtocols,
7};
8use alloy_primitives::{
9 bytes::{Buf, BufMut, Bytes, BytesMut},
10 hex,
11};
12use alloy_rlp::{Decodable, Encodable, Error as RlpError, EMPTY_LIST_CODE};
13use futures::{Sink, SinkExt, StreamExt};
14use pin_project::pin_project;
15use reth_codecs::add_arbitrary_tests;
16use reth_metrics::metrics::counter;
17use reth_primitives_traits::GotExpected;
18use std::{
19 collections::VecDeque,
20 future::Future,
21 io,
22 pin::Pin,
23 task::{ready, Context, Poll},
24 time::Duration,
25};
26use tokio_stream::Stream;
27use tracing::{debug, trace};
28
29#[cfg(feature = "serde")]
30use serde::{Deserialize, Serialize};
31
32const MAX_PAYLOAD_SIZE: usize = 16 * 1024 * 1024;
35
36pub const MAX_RESERVED_MESSAGE_ID: u8 = 0x0f;
39
40const MAX_P2P_MESSAGE_ID: u8 = P2PMessageID::Pong as u8;
42
43const SNAPPY_EMPTY_LIST_PAYLOAD: &[u8] = &[0x01, 0x00, EMPTY_LIST_CODE];
45
46const SNAPPY_PING_MESSAGE: &[u8] = &[0x02, 0x01, 0x00, EMPTY_LIST_CODE];
48
49const SNAPPY_PONG_MESSAGE: &[u8] = &[0x03, 0x01, 0x00, EMPTY_LIST_CODE];
51
52pub const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(10);
55
56const PING_TIMEOUT: Duration = Duration::from_secs(15);
59
60const PING_INTERVAL: Duration = Duration::from_secs(60);
63
64const MAX_P2P_CAPACITY: usize = 2;
71
72const MAX_COMPRESS_SCRATCH_SIZE: usize = 256 * 1024;
79
80#[pin_project]
83#[derive(Debug)]
84pub struct UnauthedP2PStream<S> {
85 #[pin]
86 inner: S,
87}
88
89impl<S> UnauthedP2PStream<S> {
90 pub const fn new(inner: S) -> Self {
92 Self { inner }
93 }
94
95 pub const fn inner(&self) -> &S {
97 &self.inner
98 }
99}
100
101impl<S> UnauthedP2PStream<S>
102where
103 S: Stream<Item = io::Result<BytesMut>> + Sink<Bytes, Error = io::Error> + Unpin,
104{
105 pub async fn handshake(
108 mut self,
109 hello: HelloMessageWithProtocols,
110 ) -> Result<(P2PStream<S>, HelloMessage), P2PStreamError> {
111 trace!(?hello, "sending p2p hello to peer");
112
113 self.inner.send(alloy_rlp::encode(P2PMessage::Hello(hello.message())).into()).await?;
115
116 let first_message_bytes = tokio::time::timeout(HANDSHAKE_TIMEOUT, self.inner.next())
117 .await
118 .or(Err(P2PStreamError::HandshakeError(P2PHandshakeError::Timeout)))?
119 .ok_or(P2PStreamError::HandshakeError(P2PHandshakeError::NoResponse))??;
120
121 if first_message_bytes.len() > MAX_PAYLOAD_SIZE {
125 return Err(P2PStreamError::MessageTooBig {
126 message_size: first_message_bytes.len(),
127 max_size: MAX_PAYLOAD_SIZE,
128 })
129 }
130
131 let their_hello = match P2PMessage::decode(&mut &first_message_bytes[..]) {
138 Ok(P2PMessage::Hello(hello)) => Ok(hello),
139 Ok(P2PMessage::Disconnect(reason)) => {
140 if matches!(reason, DisconnectReason::TooManyPeers) {
141 trace!(%reason, "Disconnected by peer during handshake");
143 } else {
144 debug!(%reason, "Disconnected by peer during handshake");
145 };
146 counter!("p2pstream.disconnected_errors").increment(1);
147 Err(P2PStreamError::HandshakeError(P2PHandshakeError::Disconnected(reason)))
148 }
149 Err(err) => {
150 debug!(%err, msg=%hex::encode(&first_message_bytes), "Failed to decode first message from peer");
151 Err(P2PStreamError::HandshakeError(err.into()))
152 }
153 Ok(msg) => {
154 debug!(?msg, "expected hello message but received another message");
155 Err(P2PStreamError::HandshakeError(P2PHandshakeError::NonHelloMessageInHandshake))
156 }
157 }?;
158
159 trace!(
160 hello=?their_hello,
161 "validating incoming p2p hello from peer"
162 );
163
164 if (hello.protocol_version as u8) != their_hello.protocol_version as u8 {
165 self.send_disconnect(DisconnectReason::IncompatibleP2PProtocolVersion).await?;
167 return Err(P2PStreamError::MismatchedProtocolVersion(GotExpected {
168 got: their_hello.protocol_version,
169 expected: hello.protocol_version,
170 }))
171 }
172
173 let capability_res =
175 SharedCapabilities::try_new(hello.protocols, their_hello.capabilities.clone());
176
177 let shared_capability = match capability_res {
178 Err(err) => {
179 self.send_disconnect(DisconnectReason::UselessPeer).await?;
181 Err(err)
182 }
183 Ok(cap) => Ok(cap),
184 }?;
185
186 let stream = P2PStream::new(self.inner, shared_capability);
187
188 Ok((stream, their_hello))
189 }
190}
191
192impl<S> UnauthedP2PStream<S>
193where
194 S: Sink<Bytes, Error = io::Error> + Unpin,
195{
196 pub async fn send_disconnect(
198 &mut self,
199 reason: DisconnectReason,
200 ) -> Result<(), P2PStreamError> {
201 trace!(
202 %reason,
203 "Sending disconnect message during the handshake",
204 );
205 self.inner
206 .send(Bytes::from(alloy_rlp::encode(P2PMessage::Disconnect(reason))))
207 .await
208 .map_err(P2PStreamError::Io)
209 }
210}
211
212impl<S> CanDisconnect<Bytes> for P2PStream<S>
213where
214 S: Sink<Bytes, Error = io::Error> + Unpin + Send + Sync,
215{
216 fn disconnect(
217 &mut self,
218 reason: DisconnectReason,
219 ) -> Pin<Box<dyn Future<Output = Result<(), P2PStreamError>> + Send + '_>> {
220 Box::pin(async move { self.disconnect(reason).await })
221 }
222}
223
224#[pin_project]
257#[derive(Debug)]
258pub struct P2PStream<S> {
259 #[pin]
260 inner: S,
261
262 encoder: snap::raw::Encoder,
264
265 compress_scratch: Vec<u8>,
271
272 decoder: snap::raw::Decoder,
274
275 pinger: Pinger,
277
278 shared_capabilities: SharedCapabilities,
280
281 outgoing_messages: VecDeque<Bytes>,
283
284 outgoing_message_buffer_capacity: usize,
287
288 disconnecting: bool,
291
292 needs_flush: bool,
294
295 needs_control_flush: bool,
298}
299
300impl<S> P2PStream<S> {
301 pub fn new(inner: S, shared_capabilities: SharedCapabilities) -> Self {
305 Self {
306 inner,
307 encoder: snap::raw::Encoder::new(),
308 compress_scratch: Vec::new(),
309 decoder: snap::raw::Decoder::new(),
310 pinger: Pinger::new(PING_INTERVAL, PING_TIMEOUT),
311 shared_capabilities,
312 outgoing_messages: VecDeque::new(),
313 outgoing_message_buffer_capacity: MAX_P2P_CAPACITY,
314 disconnecting: false,
315 needs_flush: false,
316 needs_control_flush: false,
317 }
318 }
319
320 pub const fn inner(&self) -> &S {
322 &self.inner
323 }
324
325 pub const fn set_outgoing_message_buffer_capacity(&mut self, capacity: usize) {
331 assert!(capacity != 0);
332 self.outgoing_message_buffer_capacity = capacity;
333 }
334
335 pub const fn shared_capabilities(&self) -> &SharedCapabilities {
340 &self.shared_capabilities
341 }
342
343 fn has_outgoing_capacity(&self) -> bool {
345 self.outgoing_messages.len() < self.outgoing_message_buffer_capacity
346 }
347
348 fn send_pong(&mut self) {
350 self.outgoing_messages.push_back(Bytes::from_static(SNAPPY_PONG_MESSAGE));
351 self.needs_control_flush = true;
352 }
353
354 pub fn send_ping(&mut self) {
356 self.outgoing_messages.push_back(Bytes::from_static(SNAPPY_PING_MESSAGE));
357 self.needs_control_flush = true;
358 }
359}
360
361pub trait DisconnectP2P {
364 fn start_disconnect(&mut self, reason: DisconnectReason) -> Result<(), P2PStreamError>;
366
367 fn is_disconnecting(&self) -> bool;
369}
370
371impl<S> DisconnectP2P for P2PStream<S> {
372 fn start_disconnect(&mut self, reason: DisconnectReason) -> Result<(), P2PStreamError> {
381 self.outgoing_messages.clear();
383 let disconnect = P2PMessage::Disconnect(reason);
384 let mut buf = Vec::with_capacity(disconnect.length());
385 disconnect.encode(&mut buf);
386
387 let compressed =
390 compress_frame(&mut self.encoder, &mut self.compress_scratch, buf[0], &buf[1..])
391 .map_err(|err| {
392 debug!(
393 %err,
394 msg=%hex::encode(&buf[1..]),
395 "error compressing disconnect"
396 );
397 err
398 })?;
399
400 self.outgoing_messages.push_back(compressed);
401 self.needs_control_flush = true;
402 self.disconnecting = true;
403 Ok(())
404 }
405
406 fn is_disconnecting(&self) -> bool {
407 self.disconnecting
408 }
409}
410
411impl<S> P2PStream<S>
412where
413 S: Sink<Bytes, Error = io::Error> + Unpin + Send,
414{
415 pub async fn disconnect(&mut self, reason: DisconnectReason) -> Result<(), P2PStreamError> {
420 self.start_disconnect(reason)?;
421 self.close().await
422 }
423}
424
425impl<S> P2PStream<S>
426where
427 S: Sink<Bytes, Error = io::Error> + Unpin,
428{
429 fn poll_drain_outgoing(
431 mut self: Pin<&mut Self>,
432 cx: &mut Context<'_>,
433 ) -> Poll<Result<(), P2PStreamError>> {
434 let mut this = self.as_mut().project();
435 while !this.outgoing_messages.is_empty() {
436 ready!(this.inner.as_mut().poll_ready(cx))?;
437 let message = this.outgoing_messages.pop_front().expect("checked non-empty");
438 this.inner.as_mut().start_send(message)?;
439 *this.needs_flush = true;
440 }
441
442 Poll::Ready(Ok(()))
443 }
444}
445
446impl<S> Stream for P2PStream<S>
449where
450 S: Stream<Item = io::Result<BytesMut>> + Sink<Bytes, Error = io::Error> + Unpin,
451{
452 type Item = Result<BytesMut, P2PStreamError>;
453
454 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
455 let this = self.get_mut();
456
457 if this.disconnecting {
458 return Poll::Ready(None)
460 }
461
462 while let Poll::Ready(res) = this.inner.poll_next_unpin(cx) {
465 let bytes = match res {
466 Some(Ok(bytes)) => bytes,
467 Some(Err(err)) => return Poll::Ready(Some(Err(err.into()))),
468 None => return Poll::Ready(None),
469 };
470
471 if bytes.is_empty() {
472 return Poll::Ready(Some(Err(P2PStreamError::EmptyProtocolMessage)))
474 }
475
476 let id = bytes[0];
481 if id == P2PMessageID::Disconnect as u8 {
482 if let Ok(reason) = DisconnectReason::decode(&mut &bytes[1..]) {
494 return Poll::Ready(Some(Err(P2PStreamError::Disconnected(reason))))
495 }
496 }
497
498 let decompressed_len = snap::raw::decompress_len(&bytes[1..])?;
501 if decompressed_len > MAX_PAYLOAD_SIZE {
502 return Poll::Ready(Some(Err(P2PStreamError::MessageTooBig {
503 message_size: decompressed_len,
504 max_size: MAX_PAYLOAD_SIZE,
505 })))
506 }
507
508 let mut decompress_buf = BytesMut::zeroed(decompressed_len + 1);
511
512 this.decoder.decompress(&bytes[1..], &mut decompress_buf[1..]).map_err(|err| {
515 debug!(
516 %err,
517 msg=%hex::encode(&bytes[1..]),
518 "error decompressing p2p message"
519 );
520 err
521 })?;
522
523 match id {
524 _ if id == P2PMessageID::Ping as u8 => {
525 trace!("Received Ping, Sending Pong");
526 this.send_pong();
527 cx.waker().wake_by_ref();
530 }
531 _ if id == P2PMessageID::Hello as u8 => {
532 return Poll::Ready(Some(Err(P2PStreamError::HandshakeError(
535 P2PHandshakeError::HelloNotInHandshake,
536 ))))
537 }
538 _ if id == P2PMessageID::Pong as u8 => {
539 this.pinger.on_pong()?
541 }
542 _ if id == P2PMessageID::Disconnect as u8 => {
543 let reason = DisconnectReason::decode(&mut &decompress_buf[1..]).inspect_err(|err| {
549 debug!(
550 %err, msg=%hex::encode(&decompress_buf[1..]), "Failed to decode disconnect message from peer"
551 );
552 })?;
553 return Poll::Ready(Some(Err(P2PStreamError::Disconnected(reason))))
554 }
555 _ if id > MAX_P2P_MESSAGE_ID && id <= MAX_RESERVED_MESSAGE_ID => {
556 return Poll::Ready(Some(Err(P2PStreamError::UnknownReservedMessageId(id))))
558 }
559 _ => {
560 decompress_buf[0] = bytes[0] - MAX_RESERVED_MESSAGE_ID - 1;
584
585 return Poll::Ready(Some(Ok(decompress_buf)))
586 }
587 }
588 }
589
590 Poll::Pending
591 }
592}
593
594impl<S> Sink<Bytes> for P2PStream<S>
595where
596 S: Sink<Bytes, Error = io::Error> + Unpin,
597{
598 type Error = P2PStreamError;
599
600 fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
601 let this = self.as_mut().get_mut();
602
603 match this.pinger.poll_ping(cx) {
606 Poll::Pending => {}
607 Poll::Ready(Ok(PingerEvent::Ping)) => {
608 this.send_ping();
609 }
610 Poll::Ready(Ok(PingerEvent::Timeout) | Err(_)) => {
611 this.start_disconnect(DisconnectReason::PingTimeout)?;
612 }
613 }
614
615 if self.needs_control_flush {
620 ready!(self.as_mut().poll_flush(cx))?;
621 } else if !self.has_outgoing_capacity() {
622 ready!(self.as_mut().poll_drain_outgoing(cx))?;
623 }
624
625 debug_assert!(self.has_outgoing_capacity());
627 Poll::Ready(Ok(()))
628 }
629
630 fn start_send(self: Pin<&mut Self>, item: Bytes) -> Result<(), Self::Error> {
631 if item.len() > MAX_PAYLOAD_SIZE {
632 return Err(P2PStreamError::MessageTooBig {
633 message_size: item.len(),
634 max_size: MAX_PAYLOAD_SIZE,
635 })
636 }
637
638 if item.is_empty() {
639 return Err(P2PStreamError::EmptyProtocolMessage)
641 }
642
643 if !self.has_outgoing_capacity() {
645 return Err(P2PStreamError::SendBufferFull)
646 }
647
648 let this = self.project();
649
650 let compressed = compress_frame(
653 this.encoder,
654 this.compress_scratch,
655 item[0] + MAX_RESERVED_MESSAGE_ID + 1,
656 &item[1..],
657 )
658 .map_err(|err| {
659 debug!(
660 %err,
661 msg=%hex::encode(&item[1..]),
662 "error compressing p2p message"
663 );
664 err
665 })?;
666 this.outgoing_messages.push_back(compressed);
667
668 Ok(())
669 }
670
671 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
673 ready!(self.as_mut().poll_drain_outgoing(cx))?;
674
675 let mut this = self.project();
676
677 if *this.needs_flush {
678 ready!(this.inner.as_mut().poll_flush(cx))?;
679 *this.needs_flush = false;
680 }
681 *this.needs_control_flush = false;
682
683 Poll::Ready(Ok(()))
684 }
685
686 fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
687 ready!(self.as_mut().poll_flush(cx))?;
688 ready!(self.project().inner.poll_close(cx))?;
689
690 Poll::Ready(Ok(()))
691 }
692}
693
694#[derive(Debug, Clone, PartialEq, Eq)]
696#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
697#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
698#[add_arbitrary_tests(rlp)]
699pub enum P2PMessage {
700 Hello(HelloMessage),
702
703 Disconnect(DisconnectReason),
706
707 Ping,
709
710 Pong,
712}
713
714impl P2PMessage {
715 pub const fn message_id(&self) -> P2PMessageID {
717 match self {
718 Self::Hello(_) => P2PMessageID::Hello,
719 Self::Disconnect(_) => P2PMessageID::Disconnect,
720 Self::Ping => P2PMessageID::Ping,
721 Self::Pong => P2PMessageID::Pong,
722 }
723 }
724}
725
726impl Encodable for P2PMessage {
727 fn encode(&self, out: &mut dyn BufMut) {
732 (self.message_id() as u8).encode(out);
733 match self {
734 Self::Hello(msg) => msg.encode(out),
735 Self::Disconnect(msg) => msg.encode(out),
736 Self::Ping => {
737 out.put_slice(SNAPPY_EMPTY_LIST_PAYLOAD);
739 }
740 Self::Pong => {
741 out.put_slice(SNAPPY_EMPTY_LIST_PAYLOAD);
743 }
744 }
745 }
746
747 fn length(&self) -> usize {
748 let payload_len = match self {
749 Self::Hello(msg) => msg.length(),
750 Self::Disconnect(msg) => msg.length(),
751 Self::Ping | Self::Pong => SNAPPY_EMPTY_LIST_PAYLOAD.len(),
753 };
754 payload_len + 1 }
756}
757
758impl Decodable for P2PMessage {
759 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
766 fn advance_snappy_ping_pong_payload(buf: &mut &[u8]) -> alloy_rlp::Result<()> {
768 if buf.len() < 3 {
769 return Err(RlpError::InputTooShort)
770 }
771 if buf[..3] != [0x01, 0x00, EMPTY_LIST_CODE] {
772 return Err(RlpError::Custom("expected snappy payload"))
773 }
774 buf.advance(3);
775 Ok(())
776 }
777
778 let message_id = u8::decode(&mut &buf[..])?;
779 let id = P2PMessageID::try_from(message_id)
780 .or(Err(RlpError::Custom("unknown p2p message id")))?;
781 buf.advance(1);
782 match id {
783 P2PMessageID::Hello => Ok(Self::Hello(HelloMessage::decode(buf)?)),
784 P2PMessageID::Disconnect => Ok(Self::Disconnect(DisconnectReason::decode(buf)?)),
785 P2PMessageID::Ping => {
786 advance_snappy_ping_pong_payload(buf)?;
787 Ok(Self::Ping)
788 }
789 P2PMessageID::Pong => {
790 advance_snappy_ping_pong_payload(buf)?;
791 Ok(Self::Pong)
792 }
793 }
794 }
795}
796
797#[derive(Debug, Copy, Clone, Eq, PartialEq)]
799pub enum P2PMessageID {
800 Hello = 0x00,
802
803 Disconnect = 0x01,
805
806 Ping = 0x02,
808
809 Pong = 0x03,
811}
812
813impl From<P2PMessage> for P2PMessageID {
814 fn from(msg: P2PMessage) -> Self {
815 match msg {
816 P2PMessage::Hello(_) => Self::Hello,
817 P2PMessage::Disconnect(_) => Self::Disconnect,
818 P2PMessage::Ping => Self::Ping,
819 P2PMessage::Pong => Self::Pong,
820 }
821 }
822}
823
824impl TryFrom<u8> for P2PMessageID {
825 type Error = P2PStreamError;
826
827 fn try_from(id: u8) -> Result<Self, Self::Error> {
828 match id {
829 0x00 => Ok(Self::Hello),
830 0x01 => Ok(Self::Disconnect),
831 0x02 => Ok(Self::Ping),
832 0x03 => Ok(Self::Pong),
833 _ => Err(P2PStreamError::UnknownReservedMessageId(id)),
834 }
835 }
836}
837
838fn compress_frame(
845 encoder: &mut snap::raw::Encoder,
846 scratch: &mut Vec<u8>,
847 wire_id: u8,
848 payload: &[u8],
849) -> Result<Bytes, snap::Error> {
850 let needed = 1 + snap::raw::max_compress_len(payload.len());
851
852 if needed > MAX_COMPRESS_SCRATCH_SIZE {
853 let mut compressed = vec![0u8; needed];
854 let compressed_size = encoder.compress(payload, &mut compressed[1..])?;
855 compressed[0] = wire_id;
856 compressed.truncate(compressed_size + 1);
857 return Ok(compressed.into())
858 }
859
860 if scratch.len() < needed {
861 scratch.resize(needed, 0);
862 }
863 let compressed_size = encoder.compress(payload, &mut scratch[1..])?;
864 scratch[0] = wire_id;
865 Ok(Bytes::copy_from_slice(&scratch[..compressed_size + 1]))
866}
867
868#[cfg(test)]
869mod tests {
870 use super::*;
871 use crate::{
872 capability::SharedCapability, test_utils::eth_hello, Capability, EthVersion,
873 ProtocolVersion,
874 };
875 use futures::task::noop_waker_ref;
876 use tokio::net::{TcpListener, TcpStream};
877 use tokio_util::codec::Decoder;
878
879 #[derive(Default)]
881 struct FlushCountingTransport {
882 sent: Vec<Bytes>,
883 flushes: usize,
884 }
885
886 impl Stream for FlushCountingTransport {
887 type Item = io::Result<BytesMut>;
888
889 fn poll_next(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Option<Self::Item>> {
890 Poll::Pending
891 }
892 }
893
894 impl Sink<Bytes> for FlushCountingTransport {
895 type Error = io::Error;
896
897 fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
898 Poll::Ready(Ok(()))
899 }
900
901 fn start_send(mut self: Pin<&mut Self>, item: Bytes) -> Result<(), Self::Error> {
902 self.sent.push(item);
903 Ok(())
904 }
905
906 fn poll_flush(
907 mut self: Pin<&mut Self>,
908 _: &mut Context<'_>,
909 ) -> Poll<Result<(), Self::Error>> {
910 self.flushes += 1;
911 Poll::Ready(Ok(()))
912 }
913
914 fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
915 Poll::Ready(Ok(()))
916 }
917 }
918
919 fn eth_shared_capabilities() -> SharedCapabilities {
920 SharedCapabilities::try_new(
921 vec![EthVersion::Eth68.into()],
922 vec![Capability::eth(EthVersion::Eth68)],
923 )
924 .unwrap()
925 }
926
927 #[tokio::test]
928 async fn poll_ready_drains_full_subprotocol_queue_without_flushing_inner() {
929 let mut stream =
930 P2PStream::new(FlushCountingTransport::default(), eth_shared_capabilities());
931 stream.set_outgoing_message_buffer_capacity(1);
932 Pin::new(&mut stream).start_send(Bytes::from_static(&[0x00, EMPTY_LIST_CODE])).unwrap();
933
934 let waker = noop_waker_ref();
935 let mut cx = Context::from_waker(waker);
936 assert!(Pin::new(&mut stream).poll_ready(&mut cx).is_ready());
937
938 assert_eq!(stream.inner().sent.len(), 1);
940 assert_eq!(stream.inner().flushes, 0);
941
942 assert!(Pin::new(&mut stream).poll_flush(&mut cx).is_ready());
944 assert_eq!(stream.inner().flushes, 1);
945
946 assert!(Pin::new(&mut stream).poll_flush(&mut cx).is_ready());
948 assert_eq!(stream.inner().flushes, 1);
949 }
950
951 #[tokio::test]
952 async fn poll_ready_flushes_queued_control_messages() {
953 let mut stream =
954 P2PStream::new(FlushCountingTransport::default(), eth_shared_capabilities());
955 stream.send_ping();
956
957 let waker = noop_waker_ref();
958 let mut cx = Context::from_waker(waker);
959 assert!(Pin::new(&mut stream).poll_ready(&mut cx).is_ready());
960
961 assert_eq!(stream.inner().sent.len(), 1);
963 assert_eq!(stream.inner().flushes, 1);
964 }
965
966 #[tokio::test]
967 async fn test_can_disconnect() {
968 reth_tracing::init_test_tracing();
969 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
970 let local_addr = listener.local_addr().unwrap();
971
972 let expected_disconnect = DisconnectReason::UselessPeer;
973
974 let handle = tokio::spawn(async move {
975 let (incoming, _) = listener.accept().await.unwrap();
977 let stream = crate::PassthroughCodec::default().framed(incoming);
978
979 let (server_hello, _) = eth_hello();
980
981 let (mut p2p_stream, _) =
982 UnauthedP2PStream::new(stream).handshake(server_hello).await.unwrap();
983
984 p2p_stream.disconnect(expected_disconnect).await.unwrap();
985 });
986
987 let outgoing = TcpStream::connect(local_addr).await.unwrap();
988 let sink = crate::PassthroughCodec::default().framed(outgoing);
989
990 let (client_hello, _) = eth_hello();
991
992 let (mut p2p_stream, _) =
993 UnauthedP2PStream::new(sink).handshake(client_hello).await.unwrap();
994
995 let err = p2p_stream.next().await.unwrap().unwrap_err();
996 match err {
997 P2PStreamError::Disconnected(reason) => assert_eq!(reason, expected_disconnect),
998 e => panic!("unexpected err: {e}"),
999 }
1000
1001 handle.await.unwrap();
1002 }
1003
1004 #[tokio::test]
1005 async fn test_can_disconnect_weird_disconnect_encoding() {
1006 reth_tracing::init_test_tracing();
1007 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
1008 let local_addr = listener.local_addr().unwrap();
1009
1010 let expected_disconnect = DisconnectReason::SubprotocolSpecific;
1011
1012 let handle = tokio::spawn(async move {
1013 let (incoming, _) = listener.accept().await.unwrap();
1015 let stream = crate::PassthroughCodec::default().framed(incoming);
1016
1017 let (server_hello, _) = eth_hello();
1018
1019 let (mut p2p_stream, _) =
1020 UnauthedP2PStream::new(stream).handshake(server_hello).await.unwrap();
1021
1022 p2p_stream.outgoing_messages.clear();
1024
1025 p2p_stream.outgoing_messages.push_back(Bytes::from(alloy_rlp::encode(
1026 P2PMessage::Disconnect(DisconnectReason::SubprotocolSpecific),
1027 )));
1028 p2p_stream.disconnecting = true;
1029 p2p_stream.close().await.unwrap();
1030 });
1031
1032 let outgoing = TcpStream::connect(local_addr).await.unwrap();
1033 let sink = crate::PassthroughCodec::default().framed(outgoing);
1034
1035 let (client_hello, _) = eth_hello();
1036
1037 let (mut p2p_stream, _) =
1038 UnauthedP2PStream::new(sink).handshake(client_hello).await.unwrap();
1039
1040 let err = p2p_stream.next().await.unwrap().unwrap_err();
1041 match err {
1042 P2PStreamError::Disconnected(reason) => assert_eq!(reason, expected_disconnect),
1043 e => panic!("unexpected err: {e}"),
1044 }
1045
1046 handle.await.unwrap();
1047 }
1048
1049 #[tokio::test]
1050 async fn test_handshake_passthrough() {
1051 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
1054 let local_addr = listener.local_addr().unwrap();
1055
1056 let handle = tokio::spawn(async move {
1057 let (incoming, _) = listener.accept().await.unwrap();
1059 let stream = crate::PassthroughCodec::default().framed(incoming);
1060
1061 let (server_hello, _) = eth_hello();
1062
1063 let unauthed_stream = UnauthedP2PStream::new(stream);
1064 let (p2p_stream, _) = unauthed_stream.handshake(server_hello).await.unwrap();
1065
1066 assert_eq!(
1068 *p2p_stream.shared_capabilities.iter_caps().next().unwrap(),
1069 SharedCapability::Eth {
1070 version: EthVersion::Eth67,
1071 offset: MAX_RESERVED_MESSAGE_ID + 1
1072 }
1073 );
1074 });
1075
1076 let outgoing = TcpStream::connect(local_addr).await.unwrap();
1077 let sink = crate::PassthroughCodec::default().framed(outgoing);
1078
1079 let (client_hello, _) = eth_hello();
1080
1081 let unauthed_stream = UnauthedP2PStream::new(sink);
1082 let (p2p_stream, _) = unauthed_stream.handshake(client_hello).await.unwrap();
1083
1084 assert_eq!(
1086 *p2p_stream.shared_capabilities.iter_caps().next().unwrap(),
1087 SharedCapability::Eth {
1088 version: EthVersion::Eth67,
1089 offset: MAX_RESERVED_MESSAGE_ID + 1
1090 }
1091 );
1092
1093 handle.await.unwrap();
1095 }
1096
1097 #[tokio::test]
1098 async fn test_handshake_disconnect() {
1099 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
1102 let local_addr = listener.local_addr().unwrap();
1103
1104 let handle = tokio::spawn(async move {
1105 let (incoming, _) = listener.accept().await.unwrap();
1107 let stream = crate::PassthroughCodec::default().framed(incoming);
1108
1109 let (server_hello, _) = eth_hello();
1110
1111 let unauthed_stream = UnauthedP2PStream::new(stream);
1112 match unauthed_stream.handshake(server_hello.clone()).await {
1113 Ok((_, hello)) => {
1114 panic!("expected handshake to fail, instead got a successful Hello: {hello:?}")
1115 }
1116 Err(P2PStreamError::MismatchedProtocolVersion(GotExpected { got, expected })) => {
1117 assert_ne!(expected, got);
1118 assert_eq!(expected, server_hello.protocol_version);
1119 }
1120 Err(other_err) => {
1121 panic!("expected mismatched protocol version error, got {other_err:?}")
1122 }
1123 }
1124 });
1125
1126 let outgoing = TcpStream::connect(local_addr).await.unwrap();
1127 let sink = crate::PassthroughCodec::default().framed(outgoing);
1128
1129 let (mut client_hello, _) = eth_hello();
1130
1131 client_hello.protocol_version = ProtocolVersion::V4;
1133
1134 let unauthed_stream = UnauthedP2PStream::new(sink);
1135 match unauthed_stream.handshake(client_hello.clone()).await {
1136 Ok((_, hello)) => {
1137 panic!("expected handshake to fail, instead got a successful Hello: {hello:?}")
1138 }
1139 Err(P2PStreamError::MismatchedProtocolVersion(GotExpected { got, expected })) => {
1140 assert_ne!(expected, got);
1141 assert_eq!(expected, client_hello.protocol_version);
1142 }
1143 Err(other_err) => {
1144 panic!("expected mismatched protocol version error, got {other_err:?}")
1145 }
1146 }
1147
1148 handle.await.unwrap();
1150 }
1151
1152 #[test]
1153 fn snappy_ping_pong_consts_match_rlp_encoding() {
1154 assert_eq!(alloy_rlp::encode(P2PMessage::Ping).as_slice(), SNAPPY_PING_MESSAGE);
1155 assert_eq!(alloy_rlp::encode(P2PMessage::Pong).as_slice(), SNAPPY_PONG_MESSAGE);
1156 }
1157
1158 #[test]
1159 fn snappy_decode_encode_ping() {
1160 let snappy_ping = b"\x02\x01\0\xc0";
1161 let ping = P2PMessage::decode(&mut &snappy_ping[..]).unwrap();
1162 assert!(matches!(ping, P2PMessage::Ping));
1163 assert_eq!(alloy_rlp::encode(ping), &snappy_ping[..]);
1164 }
1165
1166 #[test]
1167 fn snappy_decode_encode_pong() {
1168 let snappy_pong = b"\x03\x01\0\xc0";
1169 let pong = P2PMessage::decode(&mut &snappy_pong[..]).unwrap();
1170 assert!(matches!(pong, P2PMessage::Pong));
1171 assert_eq!(alloy_rlp::encode(pong), &snappy_pong[..]);
1172 }
1173}