From 421c3c472061160232f8b94f97f69d6ac3a1948e Mon Sep 17 00:00:00 2001 From: Marcin Mielniczuk Date: Fri, 14 Jan 2022 12:37:21 +0100 Subject: [PATCH 1/4] Report HTTP failure codes as RPC errors if returned for an RPC call --- src/transports/http.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/transports/http.rs b/src/transports/http.rs index 680c110f..6b1d64e5 100644 --- a/src/transports/http.rs +++ b/src/transports/http.rs @@ -2,13 +2,18 @@ use crate::{ error::{Error, Result}, - helpers, BatchTransport, RequestId, Transport, + helpers, + rpc::error::Error as RPCError, + BatchTransport, RequestId, Transport, }; #[cfg(not(feature = "wasm"))] use futures::future::BoxFuture; #[cfg(feature = "wasm")] use futures::future::LocalBoxFuture as BoxFuture; -use jsonrpc_core::types::{Call, Output, Request, Value}; +use jsonrpc_core::{ + types::{Call, Output, Request, Value}, + ErrorCode, +}; use reqwest::{Client, Url}; use serde::de::DeserializeOwned; use std::{ @@ -86,16 +91,15 @@ async fn execute_rpc(client: &Client, url: Url, request: &R .bytes() .await .map_err(|err| Error::Transport(format!("failed to read response bytes: {}", err)))?; - log::debug!( - "[id:{}] received response: {:?}", - id, - String::from_utf8_lossy(&response).as_ref() - ); + let decoded_response = String::from_utf8_lossy(&response); + log::debug!("[id:{}] received response: {:?}", id, decoded_response.as_ref()); if !status.is_success() { - return Err(Error::Transport(format!( - "response status code is not success: {}", - status - ))); + let code = ErrorCode::from(i64::from(status.as_u16())); + return Err(Error::Rpc(RPCError { + code, + message: decoded_response.to_string(), + data: None, + })); } helpers::arbitrary_precision_deserialize_workaround(&response) .map_err(|err| Error::Transport(format!("failed to deserialize response: {}", err))) From f20020ee2b19c9ffadb0ace2596323d287262511 Mon Sep 17 00:00:00 2001 From: Marcin Mielniczuk Date: Mon, 24 Jan 2022 13:17:41 +0100 Subject: [PATCH 2/4] Extend the TransportError type. --- src/error.rs | 22 ++++++++++++++------ src/transports/http.rs | 47 +++++++++++++++++++++--------------------- src/transports/ipc.rs | 9 +++++--- src/transports/mod.rs | 6 ++++-- src/transports/ws.rs | 37 ++++++++++++++++++++------------- 5 files changed, 72 insertions(+), 49 deletions(-) diff --git a/src/error.rs b/src/error.rs index 83138955..d858d688 100644 --- a/src/error.rs +++ b/src/error.rs @@ -7,6 +7,17 @@ use std::io::Error as IoError; /// Web3 `Result` type. pub type Result = std::result::Result; +/// Transport-depended error. +#[derive(Display, Debug, Clone, PartialEq)] +pub enum TransportError { + /// Transport-specific error code. + #[display(fmt = "code {}", _0)] + Code(u16), + /// Arbitrary, developer-readable description of the occurred error. + #[display(fmt = "{}", _0)] + Message(String), +} + /// Errors which can occur when attempting to generate resource uri. #[derive(Debug, Display, From)] pub enum Error { @@ -21,9 +32,9 @@ pub enum Error { #[from(ignore)] InvalidResponse(String), /// transport error - #[display(fmt = "Transport error: {}", _0)] + #[display(fmt = "Transport error: {}" _0)] #[from(ignore)] - Transport(String), + Transport(TransportError), /// rpc error #[display(fmt = "RPC error: {:?}", _0)] Rpc(RPCError), @@ -42,7 +53,7 @@ impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { use self::Error::*; match *self { - Unreachable | Decoder(_) | InvalidResponse(_) | Transport(_) | Internal => None, + Unreachable | Decoder(_) | InvalidResponse(_) | Transport { .. } | Internal => None, Rpc(ref e) => Some(e), Io(ref e) => Some(e), Recovery(ref e) => Some(e), @@ -78,9 +89,8 @@ impl PartialEq for Error { use self::Error::*; match (self, other) { (Unreachable, Unreachable) | (Internal, Internal) => true, - (Decoder(a), Decoder(b)) | (InvalidResponse(a), InvalidResponse(b)) | (Transport(a), Transport(b)) => { - a == b - } + (Decoder(a), Decoder(b)) | (InvalidResponse(a), InvalidResponse(b)) => a == b, + (Transport(a), Transport(b)) => a == b, (Rpc(a), Rpc(b)) => a == b, (Io(a), Io(b)) => a.kind() == b.kind(), (Recovery(a), Recovery(b)) => a == b, diff --git a/src/transports/http.rs b/src/transports/http.rs index 6b1d64e5..f6aa507e 100644 --- a/src/transports/http.rs +++ b/src/transports/http.rs @@ -1,19 +1,14 @@ //! HTTP Transport use crate::{ - error::{Error, Result}, - helpers, - rpc::error::Error as RPCError, - BatchTransport, RequestId, Transport, + error::{Error, Result, TransportError}, + helpers, BatchTransport, RequestId, Transport, }; #[cfg(not(feature = "wasm"))] use futures::future::BoxFuture; #[cfg(feature = "wasm")] use futures::future::LocalBoxFuture as BoxFuture; -use jsonrpc_core::{ - types::{Call, Output, Request, Value}, - ErrorCode, -}; +use jsonrpc_core::types::{Call, Output, Request, Value}; use reqwest::{Client, Url}; use serde::de::DeserializeOwned; use std::{ @@ -53,7 +48,7 @@ impl Http { } let client = builder .build() - .map_err(|err| Error::Transport(format!("failed to build client: {}", err)))?; + .map_err(|err| Error::Transport(TransportError::Message(format!("failed to build client: {}", err))))?; Ok(Self::with_client(client, url.parse()?)) } @@ -85,24 +80,28 @@ async fn execute_rpc(client: &Client, url: Url, request: &R .json(request) .send() .await - .map_err(|err| Error::Transport(format!("failed to send request: {}", err)))?; + .map_err(|err| Error::Transport(TransportError::Message(format!("failed to send request: {}", err))))?; let status = response.status(); - let response = response - .bytes() - .await - .map_err(|err| Error::Transport(format!("failed to read response bytes: {}", err)))?; - let decoded_response = String::from_utf8_lossy(&response); - log::debug!("[id:{}] received response: {:?}", id, decoded_response.as_ref()); + let response = response.bytes().await.map_err(|err| { + Error::Transport(TransportError::Message(format!( + "failed to read response bytes: {}", + err + ))) + })?; + log::debug!( + "[id:{}] received response: {:?}", + id, + String::from_utf8_lossy(&response) + ); if !status.is_success() { - let code = ErrorCode::from(i64::from(status.as_u16())); - return Err(Error::Rpc(RPCError { - code, - message: decoded_response.to_string(), - data: None, - })); + return Err(Error::Transport(TransportError::Code(status.as_u16()))); } - helpers::arbitrary_precision_deserialize_workaround(&response) - .map_err(|err| Error::Transport(format!("failed to deserialize response: {}", err))) + helpers::arbitrary_precision_deserialize_workaround(&response).map_err(|err| { + Error::Transport(TransportError::Message(format!( + "failed to deserialize response: {}", + err + ))) + }) } type RpcResult = Result; diff --git a/src/transports/ipc.rs b/src/transports/ipc.rs index ad297082..f9139a79 100644 --- a/src/transports/ipc.rs +++ b/src/transports/ipc.rs @@ -1,6 +1,9 @@ //! IPC transport -use crate::{api::SubscriptionId, helpers, BatchTransport, DuplexTransport, Error, RequestId, Result, Transport}; +use crate::{ + api::SubscriptionId, error::TransportError, helpers, BatchTransport, DuplexTransport, Error, RequestId, Result, + Transport, +}; use futures::{ future::{join_all, JoinAll}, stream::StreamExt, @@ -324,13 +327,13 @@ fn respond_output( impl From> for Error { fn from(err: mpsc::error::SendError) -> Self { - Error::Transport(format!("Send Error: {:?}", err)) + Error::Transport(TransportError::Message(format!("Send Error: {:?}", err))) } } impl From for Error { fn from(err: oneshot::error::RecvError) -> Self { - Error::Transport(format!("Recv Error: {:?}", err)) + Error::Transport(TransportError::Message(format!("Recv Error: {:?}", err))) } } diff --git a/src/transports/mod.rs b/src/transports/mod.rs index df762993..01b91892 100644 --- a/src/transports/mod.rs +++ b/src/transports/mod.rs @@ -1,6 +1,8 @@ //! Supported Ethereum JSON-RPC transports. pub mod batch; +use crate::error::TransportError; + pub use self::batch::Batch; pub mod either; pub use self::either::Either; @@ -26,14 +28,14 @@ pub mod test; #[cfg(feature = "url")] impl From for crate::Error { fn from(err: url::ParseError) -> Self { - crate::Error::Transport(format!("failed to parse url: {}", err)) + crate::Error::Transport(TransportError::Message(format!("failed to parse url: {}", err))) } } #[cfg(feature = "async-native-tls")] impl From for crate::Error { fn from(err: async_native_tls::Error) -> Self { - crate::Error::Transport(format!("{:?}", err)) + crate::Error::Transport(TransportError::Message(format!("{:?}", err))) } } diff --git a/src/transports/ws.rs b/src/transports/ws.rs index 7c2b7381..d2bd3ece 100644 --- a/src/transports/ws.rs +++ b/src/transports/ws.rs @@ -1,7 +1,11 @@ //! WebSocket Transport use self::compat::{TcpStream, TlsStream}; -use crate::{api::SubscriptionId, error, helpers, rpc, BatchTransport, DuplexTransport, Error, RequestId, Transport}; +use crate::{ + api::SubscriptionId, + error::{self, TransportError}, + helpers, rpc, BatchTransport, DuplexTransport, Error, RequestId, Transport, +}; use futures::{ channel::{mpsc, oneshot}, task::{Context, Poll}, @@ -22,13 +26,13 @@ use url::Url; impl From for Error { fn from(err: soketto::handshake::Error) -> Self { - Error::Transport(format!("Handshake Error: {:?}", err)) + Error::Transport(TransportError::Message(format!("Handshake Error: {:?}", err))) } } impl From for Error { fn from(err: connection::Error) -> Self { - Error::Transport(format!("Connection Error: {:?}", err)) + Error::Transport(TransportError::Message(format!("Connection Error: {:?}", err))) } } @@ -100,12 +104,21 @@ impl WsServerTask { let scheme = match url.scheme() { s if s == "ws" || s == "wss" => s, - s => return Err(error::Error::Transport(format!("Wrong scheme: {}", s))), + s => { + return Err(error::Error::Transport(TransportError::Message(format!( + "Wrong scheme: {}", + s + )))) + } }; let host = match url.host_str() { Some(s) => s, - None => return Err(error::Error::Transport("Wrong host name".to_string())), + None => { + return Err(error::Error::Transport(TransportError::Message( + "Wrong host name".to_string(), + ))) + } }; let port = url.port().unwrap_or(if scheme == "ws" { 80 } else { 443 }); @@ -143,16 +156,10 @@ impl WsServerTask { let (sender, receiver) = match handshake.await? { ServerResponse::Accepted { .. } => client.into_builder().finish(), ServerResponse::Redirect { status_code, location } => { - return Err(error::Error::Transport(format!( - "(code: {}) Unable to follow redirects: {}", - status_code, location - ))) + return Err(error::Error::Transport(TransportError::Code(status_code))) } ServerResponse::Rejected { status_code } => { - return Err(error::Error::Transport(format!( - "(code: {}) Connection rejected.", - status_code - ))) + return Err(error::Error::Transport(TransportError::Code(status_code))) } }; @@ -342,7 +349,9 @@ impl WebSocket { } fn dropped_err(_: T) -> error::Error { - Error::Transport("Cannot send request. Internal task finished.".into()) + Error::Transport(TransportError::Message( + "Cannot send request. Internal task finished.".into(), + )) } fn batch_to_single(response: BatchResult) -> SingleResult { From a34460f3ad05fff375047de9f1852910f5de65de Mon Sep 17 00:00:00 2001 From: Marcin Mielniczuk Date: Fri, 4 Feb 2022 14:26:55 +0100 Subject: [PATCH 3/4] Update src/transports/ws.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tomasz Drwięga --- src/transports/ws.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transports/ws.rs b/src/transports/ws.rs index d2bd3ece..61d0773e 100644 --- a/src/transports/ws.rs +++ b/src/transports/ws.rs @@ -155,7 +155,7 @@ impl WsServerTask { let handshake = client.handshake(); let (sender, receiver) = match handshake.await? { ServerResponse::Accepted { .. } => client.into_builder().finish(), - ServerResponse::Redirect { status_code, location } => { + ServerResponse::Redirect { status_code, ... } => { return Err(error::Error::Transport(TransportError::Code(status_code))) } ServerResponse::Rejected { status_code } => { From 89861431e4f0e8a32c24df835c0f89e86acb0be4 Mon Sep 17 00:00:00 2001 From: Marcin Mielniczuk Date: Fri, 4 Feb 2022 16:03:13 +0100 Subject: [PATCH 4/4] Fix warnings --- src/transports/mod.rs | 3 ++- src/transports/ws.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/transports/mod.rs b/src/transports/mod.rs index 01b91892..17a2d5ff 100644 --- a/src/transports/mod.rs +++ b/src/transports/mod.rs @@ -1,7 +1,6 @@ //! Supported Ethereum JSON-RPC transports. pub mod batch; -use crate::error::TransportError; pub use self::batch::Batch; pub mod either; @@ -28,6 +27,7 @@ pub mod test; #[cfg(feature = "url")] impl From for crate::Error { fn from(err: url::ParseError) -> Self { + use crate::error::TransportError; crate::Error::Transport(TransportError::Message(format!("failed to parse url: {}", err))) } } @@ -35,6 +35,7 @@ impl From for crate::Error { #[cfg(feature = "async-native-tls")] impl From for crate::Error { fn from(err: async_native_tls::Error) -> Self { + use crate::error::TransportError; crate::Error::Transport(TransportError::Message(format!("{:?}", err))) } } diff --git a/src/transports/ws.rs b/src/transports/ws.rs index 61d0773e..5b9bbd51 100644 --- a/src/transports/ws.rs +++ b/src/transports/ws.rs @@ -155,7 +155,7 @@ impl WsServerTask { let handshake = client.handshake(); let (sender, receiver) = match handshake.await? { ServerResponse::Accepted { .. } => client.into_builder().finish(), - ServerResponse::Redirect { status_code, ... } => { + ServerResponse::Redirect { status_code, .. } => { return Err(error::Error::Transport(TransportError::Code(status_code))) } ServerResponse::Rejected { status_code } => {