From 34f4b6efae4629c06d6f33304579b0f03b2c761b Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Fri, 2 Aug 2024 17:50:23 +0200 Subject: [PATCH] chore(net): move `reth_eth_wire::DisconnectReason` to `reth-eth-wire-types` (#10006) Co-authored-by: Matthias Seitz --- .../eth-wire-types/src/disconnect_reason.rs | 134 +++++++++++++++++ crates/net/eth-wire-types/src/lib.rs | 3 + crates/net/eth-wire/Cargo.toml | 1 + crates/net/eth-wire/src/disconnect.rs | 137 +----------------- crates/net/eth-wire/src/errors/p2p.rs | 10 +- crates/net/eth-wire/src/lib.rs | 4 +- 6 files changed, 148 insertions(+), 141 deletions(-) create mode 100644 crates/net/eth-wire-types/src/disconnect_reason.rs diff --git a/crates/net/eth-wire-types/src/disconnect_reason.rs b/crates/net/eth-wire-types/src/disconnect_reason.rs new file mode 100644 index 0000000000..f7f52ea3f1 --- /dev/null +++ b/crates/net/eth-wire-types/src/disconnect_reason.rs @@ -0,0 +1,134 @@ +//! `RLPx` disconnect reason sent to/received from peer + +use alloy_rlp::{Decodable, Encodable, Header}; +use derive_more::Display; +use reth_codecs_derive::derive_arbitrary; +use reth_primitives::bytes::{Buf, BufMut}; +use thiserror::Error; + +/// RLPx disconnect reason. +#[derive_arbitrary(rlp)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Display)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum DisconnectReason { + /// Disconnect requested by the local node or remote peer. + #[default] + #[display(fmt = "disconnect requested")] + DisconnectRequested = 0x00, + /// TCP related error + #[display(fmt = "TCP sub-system error")] + TcpSubsystemError = 0x01, + /// Breach of protocol at the transport or p2p level + #[display(fmt = "breach of protocol, e.g. a malformed message, bad RLP, etc.")] + ProtocolBreach = 0x02, + /// Node has no matching protocols. + #[display(fmt = "useless peer")] + UselessPeer = 0x03, + /// Either the remote or local node has too many peers. + #[display(fmt = "too many peers")] + TooManyPeers = 0x04, + /// Already connected to the peer. + #[display(fmt = "already connected")] + AlreadyConnected = 0x05, + /// `p2p` protocol version is incompatible + #[display(fmt = "incompatible P2P protocol version")] + IncompatibleP2PProtocolVersion = 0x06, + /// Received a null node identity. + #[display(fmt = "null node identity received - this is automatically invalid")] + NullNodeIdentity = 0x07, + /// Reason when the client is shutting down. + #[display(fmt = "client quitting")] + ClientQuitting = 0x08, + /// When the received handshake's identify is different from what is expected. + #[display(fmt = "unexpected identity in handshake")] + UnexpectedHandshakeIdentity = 0x09, + /// The node is connected to itself + #[display(fmt = "identity is the same as this node (i.e. connected to itself)")] + ConnectedToSelf = 0x0a, + /// Peer or local node did not respond to a ping in time. + #[display(fmt = "ping timeout")] + PingTimeout = 0x0b, + /// Peer or local node violated a subprotocol-specific rule. + #[display(fmt = "some other reason specific to a subprotocol")] + SubprotocolSpecific = 0x10, +} + +impl TryFrom for DisconnectReason { + // This error type should not be used to crash the node, but rather to log the error and + // disconnect the peer. + type Error = UnknownDisconnectReason; + + fn try_from(value: u8) -> Result { + match value { + 0x00 => Ok(Self::DisconnectRequested), + 0x01 => Ok(Self::TcpSubsystemError), + 0x02 => Ok(Self::ProtocolBreach), + 0x03 => Ok(Self::UselessPeer), + 0x04 => Ok(Self::TooManyPeers), + 0x05 => Ok(Self::AlreadyConnected), + 0x06 => Ok(Self::IncompatibleP2PProtocolVersion), + 0x07 => Ok(Self::NullNodeIdentity), + 0x08 => Ok(Self::ClientQuitting), + 0x09 => Ok(Self::UnexpectedHandshakeIdentity), + 0x0a => Ok(Self::ConnectedToSelf), + 0x0b => Ok(Self::PingTimeout), + 0x10 => Ok(Self::SubprotocolSpecific), + _ => Err(UnknownDisconnectReason(value)), + } + } +} + +impl Encodable for DisconnectReason { + /// The [`Encodable`] implementation for [`DisconnectReason`] encodes the disconnect reason in + /// a single-element RLP list. + fn encode(&self, out: &mut dyn BufMut) { + vec![*self as u8].encode(out); + } + fn length(&self) -> usize { + vec![*self as u8].length() + } +} + +impl Decodable for DisconnectReason { + /// The [`Decodable`] implementation for [`DisconnectReason`] supports either a disconnect + /// reason encoded a single byte or a RLP list containing the disconnect reason. + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + if buf.is_empty() { + return Err(alloy_rlp::Error::InputTooShort) + } else if buf.len() > 2 { + return Err(alloy_rlp::Error::Overflow) + } + + if buf.len() > 1 { + // this should be a list, so decode the list header. this should advance the buffer so + // buf[0] is the first (and only) element of the list. + let header = Header::decode(buf)?; + + if !header.list { + return Err(alloy_rlp::Error::UnexpectedString) + } + + if header.payload_length != 1 { + return Err(alloy_rlp::Error::ListLengthMismatch { + expected: 1, + got: header.payload_length, + }) + } + } + + // geth rlp encodes [`DisconnectReason::DisconnectRequested`] as 0x00 and not as empty + // string 0x80 + if buf[0] == 0x00 { + buf.advance(1); + Ok(Self::DisconnectRequested) + } else { + Self::try_from(u8::decode(buf)?) + .map_err(|_| alloy_rlp::Error::Custom("unknown disconnect reason")) + } + } +} + +/// This represents an unknown disconnect reason with the given code. +#[derive(Debug, Clone, Error)] +#[error("unknown disconnect reason: {0}")] +pub struct UnknownDisconnectReason(u8); diff --git a/crates/net/eth-wire-types/src/lib.rs b/crates/net/eth-wire-types/src/lib.rs index 1f6a204c73..dc4197f1ed 100644 --- a/crates/net/eth-wire-types/src/lib.rs +++ b/crates/net/eth-wire-types/src/lib.rs @@ -34,3 +34,6 @@ pub use state::*; pub mod receipts; pub use receipts::*; + +pub mod disconnect_reason; +pub use disconnect_reason::*; diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml index 355491783b..4e1403c22c 100644 --- a/crates/net/eth-wire/Cargo.toml +++ b/crates/net/eth-wire/Cargo.toml @@ -41,6 +41,7 @@ arbitrary = { workspace = true, features = ["derive"], optional = true } [dev-dependencies] reth-primitives = { workspace = true, features = ["arbitrary"] } +reth-eth-wire-types = { workspace = true, features = ["arbitrary"] } reth-tracing.workspace = true test-fuzz.workspace = true diff --git a/crates/net/eth-wire/src/disconnect.rs b/crates/net/eth-wire/src/disconnect.rs index 70d429e04f..56a220291d 100644 --- a/crates/net/eth-wire/src/disconnect.rs +++ b/crates/net/eth-wire/src/disconnect.rs @@ -1,142 +1,13 @@ //! Disconnect -use alloy_rlp::{Decodable, Encodable, Error as RlpError, Header}; -use futures::{Sink, SinkExt}; -use reth_codecs::derive_arbitrary; -use reth_ecies::stream::ECIESStream; -use reth_primitives::bytes::{Buf, BufMut}; use std::future::Future; -use thiserror::Error; + +use futures::{Sink, SinkExt}; +use reth_ecies::stream::ECIESStream; +use reth_eth_wire_types::DisconnectReason; use tokio::io::AsyncWrite; use tokio_util::codec::{Encoder, Framed}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// RLPx disconnect reason. -#[derive_arbitrary(rlp)] -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, derive_more::Display)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum DisconnectReason { - /// Disconnect requested by the local node or remote peer. - #[default] - #[display(fmt = "disconnect requested")] - DisconnectRequested = 0x00, - /// TCP related error - #[display(fmt = "TCP sub-system error")] - TcpSubsystemError = 0x01, - /// Breach of protocol at the transport or p2p level - #[display(fmt = "breach of protocol, e.g. a malformed message, bad RLP, etc.")] - ProtocolBreach = 0x02, - /// Node has no matching protocols. - #[display(fmt = "useless peer")] - UselessPeer = 0x03, - /// Either the remote or local node has too many peers. - #[display(fmt = "too many peers")] - TooManyPeers = 0x04, - /// Already connected to the peer. - #[display(fmt = "already connected")] - AlreadyConnected = 0x05, - /// `p2p` protocol version is incompatible - #[display(fmt = "incompatible P2P protocol version")] - IncompatibleP2PProtocolVersion = 0x06, - /// Received a null node identity. - #[display(fmt = "null node identity received - this is automatically invalid")] - NullNodeIdentity = 0x07, - /// Reason when the client is shutting down. - #[display(fmt = "client quitting")] - ClientQuitting = 0x08, - /// When the received handshake's identify is different from what is expected. - #[display(fmt = "unexpected identity in handshake")] - UnexpectedHandshakeIdentity = 0x09, - /// The node is connected to itself - #[display(fmt = "identity is the same as this node (i.e. connected to itself)")] - ConnectedToSelf = 0x0a, - /// Peer or local node did not respond to a ping in time. - #[display(fmt = "ping timeout")] - PingTimeout = 0x0b, - /// Peer or local node violated a subprotocol-specific rule. - #[display(fmt = "some other reason specific to a subprotocol")] - SubprotocolSpecific = 0x10, -} - -/// This represents an unknown disconnect reason with the given code. -#[derive(Debug, Clone, Error)] -#[error("unknown disconnect reason: {0}")] -pub struct UnknownDisconnectReason(u8); - -impl TryFrom for DisconnectReason { - // This error type should not be used to crash the node, but rather to log the error and - // disconnect the peer. - type Error = UnknownDisconnectReason; - - fn try_from(value: u8) -> Result { - match value { - 0x00 => Ok(Self::DisconnectRequested), - 0x01 => Ok(Self::TcpSubsystemError), - 0x02 => Ok(Self::ProtocolBreach), - 0x03 => Ok(Self::UselessPeer), - 0x04 => Ok(Self::TooManyPeers), - 0x05 => Ok(Self::AlreadyConnected), - 0x06 => Ok(Self::IncompatibleP2PProtocolVersion), - 0x07 => Ok(Self::NullNodeIdentity), - 0x08 => Ok(Self::ClientQuitting), - 0x09 => Ok(Self::UnexpectedHandshakeIdentity), - 0x0a => Ok(Self::ConnectedToSelf), - 0x0b => Ok(Self::PingTimeout), - 0x10 => Ok(Self::SubprotocolSpecific), - _ => Err(UnknownDisconnectReason(value)), - } - } -} - -impl Encodable for DisconnectReason { - /// The [`Encodable`] implementation for [`DisconnectReason`] encodes the disconnect reason in - /// a single-element RLP list. - fn encode(&self, out: &mut dyn BufMut) { - vec![*self as u8].encode(out); - } - fn length(&self) -> usize { - vec![*self as u8].length() - } -} - -impl Decodable for DisconnectReason { - /// The [`Decodable`] implementation for [`DisconnectReason`] supports either a disconnect - /// reason encoded a single byte or a RLP list containing the disconnect reason. - fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - if buf.is_empty() { - return Err(RlpError::InputTooShort) - } else if buf.len() > 2 { - return Err(RlpError::Overflow) - } - - if buf.len() > 1 { - // this should be a list, so decode the list header. this should advance the buffer so - // buf[0] is the first (and only) element of the list. - let header = Header::decode(buf)?; - - if !header.list { - return Err(RlpError::UnexpectedString) - } - - if header.payload_length != 1 { - return Err(RlpError::ListLengthMismatch { expected: 1, got: header.payload_length }) - } - } - - // geth rlp encodes [`DisconnectReason::DisconnectRequested`] as 0x00 and not as empty - // string 0x80 - if buf[0] == 0x00 { - buf.advance(1); - Ok(Self::DisconnectRequested) - } else { - Self::try_from(u8::decode(buf)?) - .map_err(|_| RlpError::Custom("unknown disconnect reason")) - } - } -} - /// This trait is meant to allow higher level protocols like `eth` to disconnect from a peer, using /// lower-level disconnect functions (such as those that exist in the `p2p` protocol) if the /// underlying stream supports it. diff --git a/crates/net/eth-wire/src/errors/p2p.rs b/crates/net/eth-wire/src/errors/p2p.rs index a64385fe2b..2cfef92698 100644 --- a/crates/net/eth-wire/src/errors/p2p.rs +++ b/crates/net/eth-wire/src/errors/p2p.rs @@ -1,12 +1,12 @@ //! Error handling for [`P2PStream`](crate::P2PStream). -use crate::{ - capability::SharedCapabilityError, disconnect::UnknownDisconnectReason, DisconnectReason, - ProtocolVersion, -}; -use reth_primitives::GotExpected; use std::io; +use reth_eth_wire_types::{DisconnectReason, UnknownDisconnectReason}; +use reth_primitives::GotExpected; + +use crate::{capability::SharedCapabilityError, ProtocolVersion}; + /// Errors when sending/receiving p2p messages. These should result in kicking the peer. #[derive(thiserror::Error, Debug)] pub enum P2PStreamError { diff --git a/crates/net/eth-wire/src/lib.rs b/crates/net/eth-wire/src/lib.rs index 7788f603bc..e672bc5b37 100644 --- a/crates/net/eth-wire/src/lib.rs +++ b/crates/net/eth-wire/src/lib.rs @@ -33,7 +33,7 @@ pub use tokio_util::codec::{ pub use crate::{ capability::Capability, - disconnect::{CanDisconnect, DisconnectReason}, + disconnect::CanDisconnect, ethstream::{EthStream, UnauthedEthStream, MAX_MESSAGE_SIZE}, hello::{HelloMessage, HelloMessageBuilder, HelloMessageWithProtocols}, p2pstream::{ @@ -45,5 +45,3 @@ pub use crate::{ // Re-export wire types #[doc(inline)] pub use reth_eth_wire_types::*; - -pub use disconnect::UnknownDisconnectReason;