Skip to main content

reth_eth_wire/errors/
eth.rs

1//! Error handling for (`EthStream`)[`crate::EthStream`]
2
3use crate::{
4    errors::P2PStreamError, message::MessageError, version::ParseVersionError, DisconnectReason,
5};
6use alloy_chains::Chain;
7use alloy_primitives::B256;
8use reth_eth_wire_types::{snap::SnapProtocolError, EthMessageID, EthVersion};
9use reth_ethereum_forks::ValidationError;
10use reth_primitives_traits::{GotExpected, GotExpectedBoxed};
11use std::io;
12
13/// Errors when sending/receiving messages
14#[derive(thiserror::Error, Debug)]
15pub enum EthStreamError {
16    #[error(transparent)]
17    /// Error of the underlying P2P connection.
18    P2PStreamError(#[from] P2PStreamError),
19    #[error(transparent)]
20    /// Failed to parse peer's version.
21    ParseVersionError(#[from] ParseVersionError),
22    #[error(transparent)]
23    /// Failed Ethereum handshake.
24    EthHandshakeError(#[from] EthHandshakeError),
25    /// Thrown when decoding a message failed.
26    #[error(transparent)]
27    InvalidMessage(#[from] MessageError),
28    /// Thrown when decoding an inbound `snap` protocol message failed.
29    #[error(transparent)]
30    InvalidSnapMessage(#[from] SnapProtocolError),
31    #[error("message size ({0}) exceeds max length (10MB)")]
32    /// Received a message whose size exceeds the standard limit.
33    MessageTooBig(usize),
34    #[error(
35        "TransactionHashes invalid len of fields: hashes_len={hashes_len} types_len={types_len} sizes_len={sizes_len}"
36    )]
37    /// Received malformed transaction hashes message with discrepancies in field lengths.
38    TransactionHashesInvalidLenOfFields {
39        /// The number of transaction hashes.
40        hashes_len: usize,
41        /// The number of transaction types.
42        types_len: usize,
43        /// The number of transaction sizes.
44        sizes_len: usize,
45    },
46    /// Error when data is not received from peer for a prolonged period.
47    #[error("never received data from remote peer")]
48    StreamTimeout,
49    /// Error triggered when an unknown or unsupported Ethereum message ID is received.
50    #[error("Received unknown ETH message ID: 0x{message_id:X}")]
51    UnsupportedMessage {
52        /// The identifier of the unknown Ethereum message.
53        message_id: u8,
54    },
55    /// Received a response that answers no request sent on this stream.
56    ///
57    /// The response is rejected before its items are decoded.
58    #[error("received unsolicited {message_id:?} response with request id {request_id}")]
59    UnsolicitedResponse {
60        /// The message id of the response.
61        message_id: EthMessageID,
62        /// The request id the response carried.
63        request_id: u64,
64    },
65    /// Received a response with more items than its request asked for.
66    ///
67    /// The response is rejected before its items are decoded.
68    #[error(
69        "{message_id:?} response with request id {request_id} exceeds the {max_items} requested items"
70    )]
71    ResponseExceedsRequest {
72        /// The message id of the response.
73        message_id: EthMessageID,
74        /// The request id the response carried.
75        request_id: u64,
76        /// The number of items the request asked for.
77        max_items: usize,
78    },
79}
80
81// === impl EthStreamError ===
82
83impl EthStreamError {
84    /// Returns the [`DisconnectReason`] if the error is a disconnect message
85    pub const fn as_disconnected(&self) -> Option<DisconnectReason> {
86        if let Self::P2PStreamError(err) = self {
87            err.as_disconnected()
88        } else {
89            None
90        }
91    }
92
93    /// Returns whether this error indicates a protocol breach on the receive side.
94    ///
95    /// These are errors caused by the remote peer sending invalid or malformed data
96    /// that warrant disconnecting with [`DisconnectReason::ProtocolBreach`].
97    pub const fn is_protocol_breach(&self) -> bool {
98        matches!(
99            self,
100            Self::InvalidMessage(_) |
101                Self::InvalidSnapMessage(_) |
102                Self::MessageTooBig(_) |
103                Self::TransactionHashesInvalidLenOfFields { .. } |
104                Self::UnsupportedMessage { .. } |
105                Self::UnsolicitedResponse { .. } |
106                Self::ResponseExceedsRequest { .. } |
107                Self::P2PStreamError(
108                    P2PStreamError::Rlp(_) |
109                        P2PStreamError::Snap(_) |
110                        P2PStreamError::MessageTooBig { .. } |
111                        P2PStreamError::UnknownReservedMessageId(_) |
112                        P2PStreamError::UnknownSubprotocolMessageId(_) |
113                        P2PStreamError::EmptyProtocolMessage |
114                        P2PStreamError::InvalidPingPongPayload(_) |
115                        P2PStreamError::TooManyPings |
116                        P2PStreamError::UnknownDisconnectReason(_)
117                )
118        )
119    }
120
121    /// Returns the [`io::Error`] if it was caused by IO
122    pub const fn as_io(&self) -> Option<&io::Error> {
123        if let Self::P2PStreamError(P2PStreamError::Io(io)) = self {
124            return Some(io)
125        }
126        None
127    }
128}
129
130impl From<io::Error> for EthStreamError {
131    fn from(err: io::Error) -> Self {
132        P2PStreamError::from(err).into()
133    }
134}
135
136/// Error  that can occur during the `eth` sub-protocol handshake.
137#[derive(thiserror::Error, Debug)]
138pub enum EthHandshakeError {
139    /// Status message received or sent outside of the handshake process.
140    #[error("status message can only be recv/sent in handshake")]
141    StatusNotInHandshake,
142    /// Receiving a non-status message during the handshake phase.
143    #[error("received non-status message when trying to handshake")]
144    NonStatusMessageInHandshake,
145    #[error("no response received when sending out handshake")]
146    /// No response received during the handshake process.
147    NoResponse,
148    #[error(transparent)]
149    /// Invalid fork data.
150    InvalidFork(#[from] ValidationError),
151    #[error("mismatched genesis in status message: {0}")]
152    /// Mismatch in the genesis block during status exchange.
153    MismatchedGenesis(GotExpectedBoxed<B256>),
154    #[error("mismatched protocol version in status message: {0}")]
155    /// Mismatched protocol versions in status messages.
156    MismatchedProtocolVersion(GotExpected<EthVersion>),
157    #[error("mismatched chain in status message: {0}")]
158    /// Mismatch in chain details in status messages.
159    MismatchedChain(GotExpected<Chain>),
160    #[error("total difficulty bitlen is too large: got {got}, maximum {maximum}")]
161    /// Excessively large total difficulty bit lengths.
162    TotalDifficultyBitLenTooLarge {
163        /// The actual bit length of the total difficulty.
164        got: usize,
165        /// The maximum allowed bit length for the total difficulty.
166        maximum: usize,
167    },
168    #[error("earliest block > latest block: got {got}, latest {latest}")]
169    /// Earliest block > latest block.
170    EarliestBlockGreaterThanLatestBlock {
171        /// The earliest block.
172        got: u64,
173        /// The latest block.
174        latest: u64,
175    },
176    #[error("blockhash is zero")]
177    /// Blockhash is zero.
178    BlockhashZero,
179}