1use crate::{
8 errors::{EthHandshakeError, EthStreamError},
9 handshake::EthereumEthHandshake,
10 message::{EthBroadcastMessage, MAX_MESSAGE_SIZE, TX_MEMORY_BUDGET_MULTIPLIER},
11 p2pstream::HANDSHAKE_TIMEOUT,
12 CanDisconnect, DisconnectReason, EthMessage, EthNetworkPrimitives, EthVersion, ProtocolMessage,
13 UnifiedStatus,
14};
15use alloy_primitives::bytes::{Bytes, BytesMut};
16use futures::{ready, Sink, SinkExt};
17use pin_project::pin_project;
18use reth_eth_wire_types::{EthMessageID, NetworkPrimitives, RawCapabilityMessage};
19use reth_ethereum_forks::ForkFilter;
20use std::{
21 future::Future,
22 pin::Pin,
23 task::{Context, Poll},
24 time::Duration,
25};
26use tokio::time::timeout;
27use tokio_stream::Stream;
28use tracing::{debug, trace};
29
30#[pin_project]
33#[derive(Debug)]
34pub struct UnauthedEthStream<S> {
35 #[pin]
36 inner: S,
37}
38
39impl<S> UnauthedEthStream<S> {
40 pub const fn new(inner: S) -> Self {
42 Self { inner }
43 }
44
45 pub fn into_inner(self) -> S {
47 self.inner
48 }
49}
50
51impl<S, E> UnauthedEthStream<S>
52where
53 S: Stream<Item = Result<BytesMut, E>> + CanDisconnect<Bytes> + Send + Unpin,
54 EthStreamError: From<E> + From<<S as Sink<Bytes>>::Error>,
55{
56 pub async fn handshake<N: NetworkPrimitives>(
63 self,
64 status: UnifiedStatus,
65 fork_filter: ForkFilter,
66 ) -> Result<(EthStream<S, N>, UnifiedStatus), EthStreamError> {
67 self.handshake_with_timeout(status, fork_filter, HANDSHAKE_TIMEOUT).await
68 }
69
70 pub async fn handshake_with_timeout<N: NetworkPrimitives>(
72 self,
73 status: UnifiedStatus,
74 fork_filter: ForkFilter,
75 timeout_limit: Duration,
76 ) -> Result<(EthStream<S, N>, UnifiedStatus), EthStreamError> {
77 timeout(timeout_limit, Self::handshake_without_timeout(self, status, fork_filter))
78 .await
79 .map_err(|_| EthStreamError::StreamTimeout)?
80 }
81
82 pub async fn handshake_without_timeout<N: NetworkPrimitives>(
84 mut self,
85 status: UnifiedStatus,
86 fork_filter: ForkFilter,
87 ) -> Result<(EthStream<S, N>, UnifiedStatus), EthStreamError> {
88 trace!(
89 status = %status.into_message(),
90 "sending eth status to peer"
91 );
92 let their_status =
93 EthereumEthHandshake(&mut self.inner).eth_handshake(status, fork_filter).await?;
94
95 let stream = EthStream::new(status.version, self.inner);
98
99 Ok((stream, their_status))
100 }
101}
102
103#[derive(Debug)]
105pub struct EthStreamInner<N> {
106 version: EthVersion,
108 max_message_size: usize,
110 reject_block_announcements: bool,
113 _pd: std::marker::PhantomData<N>,
114}
115
116impl<N> EthStreamInner<N>
117where
118 N: NetworkPrimitives,
119{
120 pub const fn new(version: EthVersion) -> Self {
122 Self::with_max_message_size(version, MAX_MESSAGE_SIZE)
123 }
124
125 pub const fn with_max_message_size(version: EthVersion, max_message_size: usize) -> Self {
127 Self {
128 version,
129 max_message_size,
130 reject_block_announcements: false,
131 _pd: std::marker::PhantomData,
132 }
133 }
134
135 #[inline]
137 pub const fn version(&self) -> EthVersion {
138 self.version
139 }
140
141 pub const fn set_reject_block_announcements(&mut self, reject: bool) {
144 self.reject_block_announcements = reject;
145 }
146
147 pub fn decode_message(&self, bytes: BytesMut) -> Result<EthMessage<N>, EthStreamError> {
149 if bytes.len() > self.max_message_size {
150 return Err(EthStreamError::MessageTooBig(bytes.len()));
151 }
152
153 if self.reject_block_announcements &&
154 let Some(&id) = bytes.first() &&
155 (id == EthMessageID::NewBlock.to_u8() || id == EthMessageID::NewBlockHashes.to_u8())
156 {
157 return Err(EthStreamError::UnsupportedMessage { message_id: id });
158 }
159
160 let msg = match ProtocolMessage::decode_message_with_tx_memory_budget(
161 self.version,
162 &mut bytes.as_ref(),
163 self.max_message_size * TX_MEMORY_BUDGET_MULTIPLIER,
164 ) {
165 Ok(m) => m,
166 Err(err) => {
167 let msg = if bytes.len() > 50 {
168 format!("{:02x?}...{:x?}", &bytes[..10], &bytes[bytes.len() - 10..])
169 } else {
170 format!("{bytes:02x?}")
171 };
172 debug!(
173 version=?self.version,
174 %msg,
175 "failed to decode protocol message"
176 );
177 return Err(EthStreamError::InvalidMessage(err));
178 }
179 };
180
181 if matches!(msg.message, EthMessage::Status(_)) {
182 return Err(EthStreamError::EthHandshakeError(EthHandshakeError::StatusNotInHandshake));
183 }
184
185 Ok(msg.message)
186 }
187
188 pub fn encode_message(&self, item: EthMessage<N>) -> Result<Bytes, EthStreamError> {
192 if matches!(item, EthMessage::Status(_)) {
193 return Err(EthStreamError::EthHandshakeError(EthHandshakeError::StatusNotInHandshake));
194 }
195
196 Ok(Bytes::from(alloy_rlp::encode(ProtocolMessage::from(item))))
197 }
198}
199
200#[pin_project]
203#[derive(Debug)]
204pub struct EthStream<S, N = EthNetworkPrimitives> {
205 eth: EthStreamInner<N>,
207 #[pin]
208 inner: S,
209}
210
211impl<S, N: NetworkPrimitives> EthStream<S, N> {
212 #[inline]
215 pub const fn new(version: EthVersion, inner: S) -> Self {
216 Self::with_max_message_size(version, inner, MAX_MESSAGE_SIZE)
217 }
218
219 #[inline]
221 pub const fn with_max_message_size(
222 version: EthVersion,
223 inner: S,
224 max_message_size: usize,
225 ) -> Self {
226 Self { eth: EthStreamInner::with_max_message_size(version, max_message_size), inner }
227 }
228
229 #[inline]
231 pub const fn version(&self) -> EthVersion {
232 self.eth.version()
233 }
234
235 pub const fn set_reject_block_announcements(&mut self, reject: bool) {
238 self.eth.set_reject_block_announcements(reject);
239 }
240
241 #[inline]
243 pub const fn inner(&self) -> &S {
244 &self.inner
245 }
246
247 #[inline]
249 pub const fn inner_mut(&mut self) -> &mut S {
250 &mut self.inner
251 }
252
253 #[inline]
255 pub fn into_inner(self) -> S {
256 self.inner
257 }
258}
259
260impl<S, E, N> EthStream<S, N>
261where
262 S: Sink<Bytes, Error = E> + Unpin,
263 EthStreamError: From<E>,
264 N: NetworkPrimitives,
265{
266 pub fn start_send_broadcast(
268 &mut self,
269 item: EthBroadcastMessage<N>,
270 ) -> Result<(), EthStreamError> {
271 self.inner.start_send_unpin(item.encoded())?;
272 Ok(())
273 }
274
275 pub fn start_send_raw(&mut self, msg: RawCapabilityMessage) -> Result<(), EthStreamError> {
277 self.inner.start_send_unpin(msg.encoded())?;
278 Ok(())
279 }
280}
281
282impl<S, E, N> Stream for EthStream<S, N>
283where
284 S: Stream<Item = Result<BytesMut, E>> + Unpin,
285 EthStreamError: From<E>,
286 N: NetworkPrimitives,
287{
288 type Item = Result<EthMessage<N>, EthStreamError>;
289
290 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
291 let this = self.project();
292 let res = ready!(this.inner.poll_next(cx));
293
294 match res {
295 Some(Ok(bytes)) => Poll::Ready(Some(this.eth.decode_message(bytes))),
296 Some(Err(err)) => Poll::Ready(Some(Err(err.into()))),
297 None => Poll::Ready(None),
298 }
299 }
300}
301
302impl<S, N> Sink<EthMessage<N>> for EthStream<S, N>
303where
304 S: CanDisconnect<Bytes> + Unpin,
305 EthStreamError: From<<S as Sink<Bytes>>::Error>,
306 N: NetworkPrimitives,
307{
308 type Error = EthStreamError;
309
310 fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
311 self.project().inner.poll_ready(cx).map_err(Into::into)
312 }
313
314 fn start_send(self: Pin<&mut Self>, item: EthMessage<N>) -> Result<(), Self::Error> {
315 if matches!(item, EthMessage::Status(_)) {
316 let mut this = self.project();
319 let _disconnect_future = this.inner.disconnect(DisconnectReason::ProtocolBreach);
323 return Err(EthStreamError::EthHandshakeError(EthHandshakeError::StatusNotInHandshake))
324 }
325
326 self.project()
327 .inner
328 .start_send(Bytes::from(alloy_rlp::encode(ProtocolMessage::from(item))))?;
329
330 Ok(())
331 }
332
333 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
334 self.project().inner.poll_flush(cx).map_err(Into::into)
335 }
336
337 fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
338 self.project().inner.poll_close(cx).map_err(Into::into)
339 }
340}
341
342impl<S, N> CanDisconnect<EthMessage<N>> for EthStream<S, N>
343where
344 S: CanDisconnect<Bytes> + Send,
345 EthStreamError: From<<S as Sink<Bytes>>::Error>,
346 N: NetworkPrimitives,
347{
348 fn disconnect(
349 &mut self,
350 reason: DisconnectReason,
351 ) -> Pin<Box<dyn Future<Output = Result<(), EthStreamError>> + Send + '_>> {
352 Box::pin(async move { self.inner.disconnect(reason).await.map_err(Into::into) })
353 }
354}
355
356#[cfg(test)]
357mod tests {
358 use super::UnauthedEthStream;
359 use crate::{
360 broadcast::BlockHashNumber,
361 errors::{EthHandshakeError, EthStreamError},
362 ethstream::RawCapabilityMessage,
363 hello::DEFAULT_TCP_PORT,
364 p2pstream::UnauthedP2PStream,
365 EthMessage, EthStream, EthVersion, HelloMessageWithProtocols, PassthroughCodec,
366 ProtocolVersion, Status, StatusMessage,
367 };
368 use alloy_chains::NamedChain;
369 use alloy_primitives::{bytes::Bytes, B256, U256};
370 use alloy_rlp::Decodable;
371 use futures::{SinkExt, StreamExt};
372 use reth_ecies::stream::ECIESStream;
373 use reth_eth_wire_types::{EthNetworkPrimitives, UnifiedStatus};
374 use reth_ethereum_forks::{ForkFilter, Head};
375 use reth_network_peers::pk2id;
376 use secp256k1::{SecretKey, SECP256K1};
377 use std::time::Duration;
378 use tokio::net::{TcpListener, TcpStream};
379 use tokio_util::codec::Decoder;
380
381 #[tokio::test]
382 async fn can_handshake() {
383 let genesis = B256::random();
384 let fork_filter = ForkFilter::new(Head::default(), genesis, 0, Vec::new());
385
386 let status = Status {
387 version: EthVersion::Eth67,
388 chain: NamedChain::Mainnet.into(),
389 total_difficulty: U256::ZERO,
390 blockhash: B256::random(),
391 genesis,
392 forkid: fork_filter.current(),
394 };
395 let unified_status = UnifiedStatus::from_message(StatusMessage::Legacy(status));
396
397 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
398 let local_addr = listener.local_addr().unwrap();
399
400 let status_clone = unified_status;
401 let fork_filter_clone = fork_filter.clone();
402 let handle = tokio::spawn(async move {
403 let (incoming, _) = listener.accept().await.unwrap();
405 let stream = PassthroughCodec::default().framed(incoming);
406 let (_, their_status) = UnauthedEthStream::new(stream)
407 .handshake::<EthNetworkPrimitives>(status_clone, fork_filter_clone)
408 .await
409 .unwrap();
410
411 assert_eq!(their_status, status_clone);
413 });
414
415 let outgoing = TcpStream::connect(local_addr).await.unwrap();
416 let sink = PassthroughCodec::default().framed(outgoing);
417
418 let (_, their_status) = UnauthedEthStream::new(sink)
420 .handshake::<EthNetworkPrimitives>(unified_status, fork_filter)
421 .await
422 .unwrap();
423
424 assert_eq!(their_status, unified_status);
426
427 handle.await.unwrap();
429 }
430
431 #[tokio::test]
432 async fn pass_handshake_on_low_td_bitlen() {
433 let genesis = B256::random();
434 let fork_filter = ForkFilter::new(Head::default(), genesis, 0, Vec::new());
435
436 let status = Status {
437 version: EthVersion::Eth67,
438 chain: NamedChain::Mainnet.into(),
439 total_difficulty: U256::from(2).pow(U256::from(100)) - U256::from(1),
440 blockhash: B256::random(),
441 genesis,
442 forkid: fork_filter.current(),
444 };
445 let unified_status = UnifiedStatus::from_message(StatusMessage::Legacy(status));
446
447 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
448 let local_addr = listener.local_addr().unwrap();
449
450 let status_clone = unified_status;
451 let fork_filter_clone = fork_filter.clone();
452 let handle = tokio::spawn(async move {
453 let (incoming, _) = listener.accept().await.unwrap();
455 let stream = PassthroughCodec::default().framed(incoming);
456 let (_, their_status) = UnauthedEthStream::new(stream)
457 .handshake::<EthNetworkPrimitives>(status_clone, fork_filter_clone)
458 .await
459 .unwrap();
460
461 assert_eq!(their_status, status_clone);
463 });
464
465 let outgoing = TcpStream::connect(local_addr).await.unwrap();
466 let sink = PassthroughCodec::default().framed(outgoing);
467
468 let (_, their_status) = UnauthedEthStream::new(sink)
470 .handshake::<EthNetworkPrimitives>(unified_status, fork_filter)
471 .await
472 .unwrap();
473
474 assert_eq!(their_status, unified_status);
476
477 handle.await.unwrap();
479 }
480
481 #[tokio::test]
482 async fn fail_handshake_on_high_td_bitlen() {
483 let genesis = B256::random();
484 let fork_filter = ForkFilter::new(Head::default(), genesis, 0, Vec::new());
485
486 let status = Status {
487 version: EthVersion::Eth67,
488 chain: NamedChain::Mainnet.into(),
489 total_difficulty: U256::from(2).pow(U256::from(164)),
490 blockhash: B256::random(),
491 genesis,
492 forkid: fork_filter.current(),
494 };
495 let unified_status = UnifiedStatus::from_message(StatusMessage::Legacy(status));
496
497 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
498 let local_addr = listener.local_addr().unwrap();
499
500 let status_clone = unified_status;
501 let fork_filter_clone = fork_filter.clone();
502 let handle = tokio::spawn(async move {
503 let (incoming, _) = listener.accept().await.unwrap();
505 let stream = PassthroughCodec::default().framed(incoming);
506 let handshake_res = UnauthedEthStream::new(stream)
507 .handshake::<EthNetworkPrimitives>(status_clone, fork_filter_clone)
508 .await;
509
510 assert!(matches!(
512 handshake_res,
513 Err(EthStreamError::EthHandshakeError(
514 EthHandshakeError::TotalDifficultyBitLenTooLarge { got: 165, maximum: 160 }
515 ))
516 ));
517 });
518
519 let outgoing = TcpStream::connect(local_addr).await.unwrap();
520 let sink = PassthroughCodec::default().framed(outgoing);
521
522 let handshake_res = UnauthedEthStream::new(sink)
524 .handshake::<EthNetworkPrimitives>(unified_status, fork_filter)
525 .await;
526
527 assert!(matches!(
529 handshake_res,
530 Err(EthStreamError::EthHandshakeError(
531 EthHandshakeError::TotalDifficultyBitLenTooLarge { got: 165, maximum: 160 }
532 ))
533 ));
534
535 handle.await.unwrap();
537 }
538
539 #[tokio::test]
540 async fn can_write_and_read_cleartext() {
541 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
542 let local_addr = listener.local_addr().unwrap();
543 let test_msg = EthMessage::<EthNetworkPrimitives>::NewBlockHashes(
544 vec![
545 BlockHashNumber { hash: B256::random(), number: 5 },
546 BlockHashNumber { hash: B256::random(), number: 6 },
547 ]
548 .into(),
549 );
550
551 let test_msg_clone = test_msg.clone();
552 let handle = tokio::spawn(async move {
553 let (incoming, _) = listener.accept().await.unwrap();
555 let stream = PassthroughCodec::default().framed(incoming);
556 let mut stream = EthStream::new(EthVersion::Eth67, stream);
557
558 let message = stream.next().await.unwrap().unwrap();
560 assert_eq!(message, test_msg_clone);
561 });
562
563 let outgoing = TcpStream::connect(local_addr).await.unwrap();
564 let sink = PassthroughCodec::default().framed(outgoing);
565 let mut client_stream = EthStream::new(EthVersion::Eth67, sink);
566
567 client_stream.send(test_msg).await.unwrap();
568
569 handle.await.unwrap();
571 }
572
573 #[tokio::test]
574 async fn can_write_and_read_ecies() {
575 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
576 let local_addr = listener.local_addr().unwrap();
577 let server_key = SecretKey::new(&mut rand_08::thread_rng());
578 let test_msg = EthMessage::<EthNetworkPrimitives>::NewBlockHashes(
579 vec![
580 BlockHashNumber { hash: B256::random(), number: 5 },
581 BlockHashNumber { hash: B256::random(), number: 6 },
582 ]
583 .into(),
584 );
585
586 let test_msg_clone = test_msg.clone();
587 let handle = tokio::spawn(async move {
588 let (incoming, _) = listener.accept().await.unwrap();
590 let stream = ECIESStream::incoming(incoming, server_key).await.unwrap();
591 let mut stream = EthStream::new(EthVersion::Eth67, stream);
592
593 let message = stream.next().await.unwrap().unwrap();
595 assert_eq!(message, test_msg_clone);
596 });
597
598 let server_id = pk2id(&server_key.public_key(SECP256K1));
600
601 let client_key = SecretKey::new(&mut rand_08::thread_rng());
602
603 let outgoing = TcpStream::connect(local_addr).await.unwrap();
604 let outgoing = ECIESStream::connect(outgoing, client_key, server_id).await.unwrap();
605 let mut client_stream = EthStream::new(EthVersion::Eth67, outgoing);
606
607 client_stream.send(test_msg).await.unwrap();
608
609 handle.await.unwrap();
611 }
612
613 #[tokio::test(flavor = "multi_thread")]
614 async fn ethstream_over_p2p() {
615 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
618 let local_addr = listener.local_addr().unwrap();
619 let server_key = SecretKey::new(&mut rand_08::thread_rng());
620 let test_msg = EthMessage::<EthNetworkPrimitives>::NewBlockHashes(
621 vec![
622 BlockHashNumber { hash: B256::random(), number: 5 },
623 BlockHashNumber { hash: B256::random(), number: 6 },
624 ]
625 .into(),
626 );
627
628 let genesis = B256::random();
629 let fork_filter = ForkFilter::new(Head::default(), genesis, 0, Vec::new());
630
631 let status = Status {
632 version: EthVersion::Eth67,
633 chain: NamedChain::Mainnet.into(),
634 total_difficulty: U256::ZERO,
635 blockhash: B256::random(),
636 genesis,
637 forkid: fork_filter.current(),
639 };
640 let unified_status = UnifiedStatus::from_message(StatusMessage::Legacy(status));
641
642 let status_copy = unified_status;
643 let fork_filter_clone = fork_filter.clone();
644 let test_msg_clone = test_msg.clone();
645 let handle = tokio::spawn(async move {
646 let (incoming, _) = listener.accept().await.unwrap();
648 let stream = ECIESStream::incoming(incoming, server_key).await.unwrap();
649
650 let server_hello = HelloMessageWithProtocols {
651 protocol_version: ProtocolVersion::V5,
652 client_version: "bitcoind/1.0.0".to_string(),
653 protocols: vec![EthVersion::Eth67.into()],
654 port: DEFAULT_TCP_PORT,
655 id: pk2id(&server_key.public_key(SECP256K1)),
656 };
657
658 let unauthed_stream = UnauthedP2PStream::new(stream);
659 let (p2p_stream, _) = unauthed_stream.handshake(server_hello).await.unwrap();
660 let (mut eth_stream, _) = UnauthedEthStream::new(p2p_stream)
661 .handshake(status_copy, fork_filter_clone)
662 .await
663 .unwrap();
664
665 let message = eth_stream.next().await.unwrap().unwrap();
667 assert_eq!(message, test_msg_clone);
668 });
669
670 let server_id = pk2id(&server_key.public_key(SECP256K1));
672
673 let client_key = SecretKey::new(&mut rand_08::thread_rng());
674
675 let outgoing = TcpStream::connect(local_addr).await.unwrap();
676 let sink = ECIESStream::connect(outgoing, client_key, server_id).await.unwrap();
677
678 let client_hello = HelloMessageWithProtocols {
679 protocol_version: ProtocolVersion::V5,
680 client_version: "bitcoind/1.0.0".to_string(),
681 protocols: vec![EthVersion::Eth67.into()],
682 port: DEFAULT_TCP_PORT,
683 id: pk2id(&client_key.public_key(SECP256K1)),
684 };
685
686 let unauthed_stream = UnauthedP2PStream::new(sink);
687 let (p2p_stream, _) = unauthed_stream.handshake(client_hello).await.unwrap();
688
689 let (mut client_stream, _) = UnauthedEthStream::new(p2p_stream)
690 .handshake(unified_status, fork_filter)
691 .await
692 .unwrap();
693
694 client_stream.send(test_msg).await.unwrap();
695
696 handle.await.unwrap();
698 }
699
700 #[tokio::test]
701 async fn handshake_should_timeout() {
702 let genesis = B256::random();
703 let fork_filter = ForkFilter::new(Head::default(), genesis, 0, Vec::new());
704
705 let status = Status {
706 version: EthVersion::Eth67,
707 chain: NamedChain::Mainnet.into(),
708 total_difficulty: U256::ZERO,
709 blockhash: B256::random(),
710 genesis,
711 forkid: fork_filter.current(),
713 };
714 let unified_status = UnifiedStatus::from_message(StatusMessage::Legacy(status));
715
716 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
717 let local_addr = listener.local_addr().unwrap();
718
719 let status_clone = unified_status;
720 let fork_filter_clone = fork_filter.clone();
721 let _handle = tokio::spawn(async move {
722 tokio::time::sleep(Duration::from_secs(11)).await;
724 let (incoming, _) = listener.accept().await.unwrap();
726 let stream = PassthroughCodec::default().framed(incoming);
727 let (_, their_status) = UnauthedEthStream::new(stream)
728 .handshake::<EthNetworkPrimitives>(status_clone, fork_filter_clone)
729 .await
730 .unwrap();
731
732 assert_eq!(their_status, status_clone);
734 });
735
736 let outgoing = TcpStream::connect(local_addr).await.unwrap();
737 let sink = PassthroughCodec::default().framed(outgoing);
738
739 let handshake_result = UnauthedEthStream::new(sink)
741 .handshake_with_timeout::<EthNetworkPrimitives>(
742 unified_status,
743 fork_filter,
744 Duration::from_secs(1),
745 )
746 .await;
747
748 assert!(
750 matches!(handshake_result, Err(e) if e.to_string() == EthStreamError::StreamTimeout.to_string())
751 );
752 }
753
754 #[tokio::test]
755 async fn can_write_and_read_raw_capability() {
756 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
757 let local_addr = listener.local_addr().unwrap();
758
759 let test_msg = RawCapabilityMessage { id: 0x1234, payload: Bytes::from(vec![1, 2, 3, 4]) };
760
761 let test_msg_clone = test_msg.clone();
762 let handle = tokio::spawn(async move {
763 let (incoming, _) = listener.accept().await.unwrap();
764 let stream = PassthroughCodec::default().framed(incoming);
765 let mut stream = EthStream::<_, EthNetworkPrimitives>::new(EthVersion::Eth67, stream);
766
767 let bytes = stream.inner_mut().next().await.unwrap().unwrap();
768
769 let mut id_bytes = &bytes[..];
771 let decoded_id = <usize as Decodable>::decode(&mut id_bytes).unwrap();
772 assert_eq!(decoded_id, test_msg_clone.id);
773
774 let remaining = id_bytes;
776 assert_eq!(remaining, &test_msg_clone.payload[..]);
777 });
778
779 let outgoing = TcpStream::connect(local_addr).await.unwrap();
780 let sink = PassthroughCodec::default().framed(outgoing);
781 let mut client_stream = EthStream::<_, EthNetworkPrimitives>::new(EthVersion::Eth67, sink);
782
783 client_stream.start_send_raw(test_msg).unwrap();
784 client_stream.inner_mut().flush().await.unwrap();
785
786 handle.await.unwrap();
787 }
788
789 #[tokio::test]
790 async fn status_message_after_handshake_triggers_disconnect() {
791 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
792 let local_addr = listener.local_addr().unwrap();
793
794 let handle = tokio::spawn(async move {
795 let (incoming, _) = listener.accept().await.unwrap();
796 let stream = PassthroughCodec::default().framed(incoming);
797 let mut stream = EthStream::<_, EthNetworkPrimitives>::new(EthVersion::Eth67, stream);
798
799 let status = Status {
801 version: EthVersion::Eth67,
802 chain: NamedChain::Mainnet.into(),
803 total_difficulty: U256::ZERO,
804 blockhash: B256::random(),
805 genesis: B256::random(),
806 forkid: ForkFilter::new(Head::default(), B256::random(), 0, Vec::new()).current(),
807 };
808 let status_message =
809 EthMessage::<EthNetworkPrimitives>::Status(StatusMessage::Legacy(status));
810
811 let result = stream.send(status_message).await;
813 assert!(result.is_err());
814 assert!(matches!(
815 result.unwrap_err(),
816 EthStreamError::EthHandshakeError(EthHandshakeError::StatusNotInHandshake)
817 ));
818 });
819
820 let outgoing = TcpStream::connect(local_addr).await.unwrap();
821 let sink = PassthroughCodec::default().framed(outgoing);
822 let mut client_stream = EthStream::<_, EthNetworkPrimitives>::new(EthVersion::Eth67, sink);
823
824 let test_msg = EthMessage::<EthNetworkPrimitives>::NewBlockHashes(
826 vec![BlockHashNumber { hash: B256::random(), number: 5 }].into(),
827 );
828 client_stream.send(test_msg).await.unwrap();
829
830 handle.await.unwrap();
831 }
832}