reth_network_p2p/
error.rs1use std::ops::RangeInclusive;
2
3use super::headers::client::HeadersRequest;
4use alloy_consensus::BlockHeader;
5use alloy_eips::BlockHashOrNumber;
6use alloy_primitives::{BlockNumber, B256};
7use derive_more::{Display, Error};
8use reth_consensus::ConsensusError;
9use reth_network_peers::WithPeerId;
10use reth_network_types::ReputationChangeKind;
11use reth_primitives::{GotExpected, GotExpectedBoxed};
12use reth_storage_errors::{db::DatabaseError, provider::ProviderError};
13use tokio::sync::{mpsc, oneshot};
14
15pub type RequestResult<T> = Result<T, RequestError>;
17
18pub type PeerRequestResult<T> = RequestResult<WithPeerId<T>>;
20
21pub trait EthResponseValidator {
23 fn is_likely_bad_headers_response(&self, request: &HeadersRequest) -> bool;
25
26 fn reputation_change_err(&self) -> Option<ReputationChangeKind>;
28}
29
30impl<H: BlockHeader> EthResponseValidator for RequestResult<Vec<H>> {
31 fn is_likely_bad_headers_response(&self, request: &HeadersRequest) -> bool {
32 match self {
33 Ok(headers) => {
34 let request_length = headers.len() as u64;
35
36 if request_length <= 1 && request.limit != request_length {
37 return true
38 }
39
40 match request.start {
41 BlockHashOrNumber::Number(block_number) => {
42 headers.first().is_some_and(|header| block_number != header.number())
43 }
44 BlockHashOrNumber::Hash(_) => {
45 false
47 }
48 }
49 }
50 Err(_) => true,
51 }
52 }
53
54 fn reputation_change_err(&self) -> Option<ReputationChangeKind> {
63 if let Err(err) = self {
64 match err {
65 RequestError::ChannelClosed |
66 RequestError::ConnectionDropped |
67 RequestError::UnsupportedCapability |
68 RequestError::BadResponse => None,
69 RequestError::Timeout => Some(ReputationChangeKind::Timeout),
70 }
71 } else {
72 None
73 }
74 }
75}
76
77#[derive(Clone, Debug, Eq, PartialEq, Display, Error)]
81pub enum RequestError {
82 #[display("closed channel to the peer")]
85 ChannelClosed,
86 #[display("connection to a peer dropped while handling the request")]
89 ConnectionDropped,
90 #[display("capability message is not supported by remote peer")]
93 UnsupportedCapability,
94 #[display("request timed out while awaiting response")]
97 Timeout,
98 #[display("received bad response")]
101 BadResponse,
102}
103
104impl RequestError {
107 pub const fn is_retryable(&self) -> bool {
109 matches!(self, Self::Timeout | Self::ConnectionDropped)
110 }
111
112 pub const fn is_channel_closed(&self) -> bool {
114 matches!(self, Self::ChannelClosed)
115 }
116}
117
118impl<T> From<mpsc::error::SendError<T>> for RequestError {
119 fn from(_: mpsc::error::SendError<T>) -> Self {
120 Self::ChannelClosed
121 }
122}
123
124impl From<oneshot::error::RecvError> for RequestError {
125 fn from(_: oneshot::error::RecvError) -> Self {
126 Self::ChannelClosed
127 }
128}
129
130pub type DownloadResult<T> = Result<T, DownloadError>;
132
133#[derive(Debug, Clone, PartialEq, Eq, Display, Error)]
135pub enum DownloadError {
136 #[display("failed to validate header {hash}, block number {number}: {error}")]
139 HeaderValidation {
140 hash: B256,
142 number: u64,
144 #[error(source)]
146 error: Box<ConsensusError>,
147 },
148 #[display("received invalid tip: {_0}")]
150 InvalidTip(GotExpectedBoxed<B256>),
151 #[display("received invalid tip number: {_0}")]
153 InvalidTipNumber(GotExpected<u64>),
154 #[display("headers response starts at unexpected block: {_0}")]
156 HeadersResponseStartBlockMismatch(GotExpected<u64>),
157 #[display("received less headers than expected: {_0}")]
159 HeadersResponseTooShort(GotExpected<u64>),
160
161 #[display("failed to validate body for header {hash}, block number {number}: {error}")]
164 BodyValidation {
165 hash: B256,
167 number: u64,
169 error: Box<ConsensusError>,
171 },
172 #[display("received more bodies than requested: {_0}")]
174 TooManyBodies(GotExpected<usize>),
175 #[display("header missing from the database: {block_number}")]
177 MissingHeader {
178 block_number: BlockNumber,
180 },
181 #[display("requested body range is invalid: {range:?}")]
183 InvalidBodyRange {
184 range: RangeInclusive<BlockNumber>,
186 },
187 #[display("timed out while waiting for response")]
190 Timeout,
191 #[display("received empty response")]
193 EmptyResponse,
194 RequestError(RequestError),
196 Provider(ProviderError),
198}
199
200impl From<DatabaseError> for DownloadError {
201 fn from(error: DatabaseError) -> Self {
202 Self::Provider(ProviderError::Database(error))
203 }
204}
205
206impl From<RequestError> for DownloadError {
207 fn from(error: RequestError) -> Self {
208 Self::RequestError(error)
209 }
210}
211
212impl From<ProviderError> for DownloadError {
213 fn from(error: ProviderError) -> Self {
214 Self::Provider(error)
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use alloy_consensus::Header;
221
222 use super::*;
223
224 #[test]
225 fn test_is_likely_bad_headers_response() {
226 let request =
227 HeadersRequest { start: 0u64.into(), limit: 0, direction: Default::default() };
228 let headers: Vec<Header> = vec![];
229 assert!(!Ok(headers).is_likely_bad_headers_response(&request));
230
231 let request =
232 HeadersRequest { start: 0u64.into(), limit: 1, direction: Default::default() };
233 let headers: Vec<Header> = vec![];
234 assert!(Ok(headers).is_likely_bad_headers_response(&request));
235 }
236}