diff --git a/Cargo.lock b/Cargo.lock index e9a4f9755..935381996 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -338,7 +338,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2" dependencies = [ - "http", + "http 0.2.11", "log", "url", ] @@ -658,8 +658,8 @@ dependencies = [ "futures-core", "futures-util", "hex", - "http", - "hyper", + "http 0.2.11", + "hyper 0.14.27", "hyperlocal", "log", "pin-project-lite 0.2.13", @@ -688,9 +688,8 @@ dependencies = [ [[package]] name = "boring" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12a46ed7bcfc8c7243686b870fb0a500ddca45596806088920429dd50aa4089f" +version = "5.0.0" +source = "git+https://github.com/cloudflare/boring?rev=423c260d87b69a926594ded0dd693b5cf1220452#423c260d87b69a926594ded0dd693b5cf1220452" dependencies = [ "bitflags 2.4.1", "boring-sys", @@ -701,9 +700,8 @@ dependencies = [ [[package]] name = "boring-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a25b0f5d733611b9cc685db5df9dabfc8cdf7e1475518f7f966ce5eb8a0365" +version = "5.0.0" +source = "git+https://github.com/cloudflare/boring?rev=423c260d87b69a926594ded0dd693b5cf1220452#423c260d87b69a926594ded0dd693b5cf1220452" dependencies = [ "bindgen 0.68.1", "cmake", @@ -2063,7 +2061,7 @@ dependencies = [ "futures-timer", "futures-util", "hashers", - "http", + "http 0.2.11", "instant", "jsonwebtoken", "once_cell", @@ -2836,7 +2834,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.11", "indexmap 2.1.0", "slab", "tokio", @@ -2987,6 +2985,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.5" @@ -2994,7 +3003,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", - "http", + "http 0.2.11", + "pin-project-lite 0.2.13", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.0.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" +dependencies = [ + "bytes", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", "pin-project-lite 0.2.13", ] @@ -3033,8 +3065,8 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", - "http-body", + "http 0.2.11", + "http-body 0.4.5", "httparse", "httpdate", "itoa", @@ -3047,20 +3079,39 @@ dependencies = [ ] [[package]] -name = "hyper-boring" -version = "4.1.0" +name = "hyper" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e7bdbf33719f0fc0ea8131870aeaf57e885e381058c01c5422e5b3c467c56f" +checksum = "403f9214f3e703236b221f1a9cd88ec8b4adfa5296de01ab96216361f4692f56" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "httparse", + "itoa", + "pin-project-lite 0.2.13", + "tokio", + "want", +] + +[[package]] +name = "hyper-boring" +version = "5.0.0" +source = "git+https://github.com/cloudflare/boring?rev=423c260d87b69a926594ded0dd693b5cf1220452#423c260d87b69a926594ded0dd693b5cf1220452" dependencies = [ "antidote", "boring", - "http", - "hyper", + "http 1.0.0", + "hyper 1.0.1", + "hyper-util", "linked_hash_set", "once_cell", "tokio", "tokio-boring", "tower-layer", + "tower-service", ] [[package]] @@ -3070,8 +3121,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", - "hyper", + "http 0.2.11", + "hyper 0.14.27", "log", "rustls", "rustls-native-certs", @@ -3079,6 +3130,26 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "hyper-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca339002caeb0d159cc6e023dff48e199f081e42fa039895c7c6f38b37f2e9d" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "hyper 1.0.1", + "pin-project-lite 0.2.13", + "socket2 0.5.5", + "tokio", + "tower", + "tower-service", + "tracing", +] + [[package]] name = "hyperlocal" version = "0.8.0" @@ -3087,7 +3158,7 @@ checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c" dependencies = [ "futures-util", "hex", - "hyper", + "hyper 0.14.27", "pin-project", "tokio", ] @@ -3175,8 +3246,8 @@ dependencies = [ "attohttpc", "bytes", "futures", - "http", - "hyper", + "http 0.2.11", + "hyper 0.14.27", "log", "rand", "tokio", @@ -3372,7 +3443,7 @@ dependencies = [ "futures-channel", "futures-util", "globset", - "hyper", + "hyper 0.14.27", "jsonrpsee-types", "parking_lot 0.12.1", "rand", @@ -3406,8 +3477,8 @@ checksum = "cf4d945a6008c9b03db3354fb3c83ee02d2faa9f2e755ec1dfb69c3551b8f4ba" dependencies = [ "futures-channel", "futures-util", - "http", - "hyper", + "http 0.2.11", + "hyper 0.14.27", "jsonrpsee-core", "jsonrpsee-types", "serde", @@ -5961,9 +6032,9 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", - "http-body", - "hyper", + "http 0.2.11", + "http-body 0.4.5", + "hyper 0.14.27", "ipnet", "js-sys", "log", @@ -6839,7 +6910,7 @@ dependencies = [ "fnv", "futures", "futures-timer", - "hyper", + "hyper 0.14.27", "hyper-rustls", "libp2p", "log", @@ -6925,7 +6996,7 @@ name = "sc-rpc-server" version = "4.0.0-dev" source = "git+https://github.com/serai-dex/substrate#49b7d20ef96b6ad42ea0266ea27f128e0ef3214d" dependencies = [ - "http", + "http 0.2.11", "jsonrpsee", "log", "serde_json", @@ -8048,9 +8119,12 @@ name = "simple-request" version = "0.1.0" dependencies = [ "base64ct", - "hyper", + "http-body-util", + "hyper 1.0.1", "hyper-boring", + "hyper-util", "tokio", + "tower-service", "zeroize", ] @@ -8145,7 +8219,7 @@ dependencies = [ "base64 0.13.1", "bytes", "futures", - "http", + "http 0.2.11", "httparse", "log", "rand", @@ -8947,7 +9021,7 @@ name = "substrate-prometheus-endpoint" version = "0.10.0-dev" source = "git+https://github.com/serai-dex/substrate#49b7d20ef96b6ad42ea0266ea27f128e0ef3214d" dependencies = [ - "hyper", + "hyper 0.14.27", "log", "prometheus", "thiserror", @@ -9240,9 +9314,8 @@ dependencies = [ [[package]] name = "tokio-boring" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db23acf051bc43dd3862ef7b13a8e0f7f274316a17ca77a265dda0b2bea9d1f7" +version = "5.0.0" +source = "git+https://github.com/cloudflare/boring?rev=423c260d87b69a926594ded0dd693b5cf1220452#423c260d87b69a926594ded0dd693b5cf1220452" dependencies = [ "boring", "boring-sys", @@ -9358,6 +9431,11 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite 0.2.13", + "tokio", "tower-layer", "tower-service", "tracing", @@ -9373,8 +9451,8 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http", - "http-body", + "http 0.2.11", + "http-body 0.4.5", "http-range-header", "pin-project-lite 0.2.13", "tower-layer", diff --git a/Cargo.toml b/Cargo.toml index 14add4b98..906368b4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,3 +93,5 @@ lazy_static = { git = "https://github.com/rust-lang-nursery/lazy-static.rs", rev # subxt *can* pull these off crates.io yet there's no benefit to this sp-core-hashing = { git = "https://github.com/serai-dex/substrate" } sp-std = { git = "https://github.com/serai-dex/substrate" } + +hyper-boring = { git = "https://github.com/cloudflare/boring", rev = "423c260d87b69a926594ded0dd693b5cf1220452" } diff --git a/coins/bitcoin/src/rpc.rs b/coins/bitcoin/src/rpc.rs index bde7252ff..3f32d0971 100644 --- a/coins/bitcoin/src/rpc.rs +++ b/coins/bitcoin/src/rpc.rs @@ -6,7 +6,7 @@ use thiserror::Error; use serde::{Deserialize, de::DeserializeOwned}; use serde_json::json; -use simple_request::{hyper, Request, Client}; +use simple_request::{hyper, Full, Request, Client}; use bitcoin::{ hashes::{Hash, hex::FromHex}, @@ -111,11 +111,11 @@ impl Rpc { let mut request = Request::from( hyper::Request::post(&self.url) .header("Content-Type", "application/json") - .body( + .body(Full::new( serde_json::to_vec(&json!({ "jsonrpc": "2.0", "method": method, "params": params })) .unwrap() .into(), - ) + )) .unwrap(), ); request.with_basic_auth(); diff --git a/coins/monero/src/rpc/http.rs b/coins/monero/src/rpc/http.rs index fab2a27eb..324141c70 100644 --- a/coins/monero/src/rpc/http.rs +++ b/coins/monero/src/rpc/http.rs @@ -7,7 +7,7 @@ use tokio::sync::Mutex; use digest_auth::{WwwAuthenticateHeader, AuthContext}; use simple_request::{ hyper::{header::HeaderValue, Request}, - Response, Client, + Full, Response, Client, }; use crate::rpc::{RpcError, RpcConnection, Rpc}; @@ -42,12 +42,10 @@ impl HttpRpc { ) -> Result, RpcError> { Ok(if let Some(header) = response.headers().get("www-authenticate") { Some(( - digest_auth::parse( - header - .to_str() - .map_err(|_| RpcError::InvalidNode("www-authenticate header wasn't a string"))?, - ) - .map_err(|_| RpcError::InvalidNode("invalid digest-auth response"))?, + digest_auth::parse(header.to_str().map_err(|_| { + RpcError::InvalidNode("www-authenticate header wasn't a string".to_string()) + })?) + .map_err(|_| RpcError::InvalidNode("invalid digest-auth response".to_string()))?, 0, )) } else { @@ -92,7 +90,7 @@ impl HttpRpc { &client .request( Request::post(url.clone()) - .body(vec![].into()) + .body(Full::new(vec![].into())) .map_err(|e| RpcError::ConnectionError(format!("couldn't make request: {e:?}")))?, ) .await @@ -117,7 +115,7 @@ impl HttpRpc { async fn inner_post(&self, route: &str, body: Vec) -> Result, RpcError> { let request_fn = |uri| { Request::post(uri) - .body(body.clone().into()) + .body(Full::new(body.clone().into())) .map_err(|e| RpcError::ConnectionError(format!("couldn't make request: {e:?}"))) }; @@ -162,7 +160,9 @@ impl HttpRpc { HeaderValue::from_str( &challenge .respond(&context) - .map_err(|_| RpcError::InvalidNode("couldn't respond to digest-auth challenge"))? + .map_err(|_| { + RpcError::InvalidNode("couldn't respond to digest-auth challenge".to_string()) + })? .to_header_string(), ) .unwrap(), diff --git a/coins/monero/src/rpc/mod.rs b/coins/monero/src/rpc/mod.rs index 0f0122d2d..0480d5757 100644 --- a/coins/monero/src/rpc/mod.rs +++ b/coins/monero/src/rpc/mod.rs @@ -60,7 +60,7 @@ pub enum RpcError { #[cfg_attr(feature = "std", error("connection error ({0})"))] ConnectionError(String), #[cfg_attr(feature = "std", error("invalid node ({0})"))] - InvalidNode(&'static str), + InvalidNode(String), #[cfg_attr(feature = "std", error("unsupported protocol version ({0})"))] UnsupportedProtocol(usize), #[cfg_attr(feature = "std", error("transactions not found"))] @@ -78,11 +78,11 @@ pub enum RpcError { } fn rpc_hex(value: &str) -> Result, RpcError> { - hex::decode(value).map_err(|_| RpcError::InvalidNode("expected hex wasn't hex")) + hex::decode(value).map_err(|_| RpcError::InvalidNode("expected hex wasn't hex".to_string())) } fn hash_hex(hash: &str) -> Result<[u8; 32], RpcError> { - rpc_hex(hash)?.try_into().map_err(|_| RpcError::InvalidNode("hash wasn't 32-bytes")) + rpc_hex(hash)?.try_into().map_err(|_| RpcError::InvalidNode("hash wasn't 32-bytes".to_string())) } fn rpc_point(point: &str) -> Result { @@ -135,17 +135,13 @@ impl Rpc { .0 .post( route, - if let Some(params) = params { - serde_json::to_string(¶ms).unwrap().into_bytes() - } else { - vec![] - }, + if let Some(params) = params { serde_json::to_vec(¶ms).unwrap() } else { vec![] }, ) .await?; let res_str = std_shims::str::from_utf8(&res) - .map_err(|_| RpcError::InvalidNode("response wasn't utf-8"))?; + .map_err(|_| RpcError::InvalidNode("response wasn't utf-8".to_string()))?; serde_json::from_str(res_str) - .map_err(|_| RpcError::InvalidNode("response wasn't json: {res_str}")) + .map_err(|_| RpcError::InvalidNode(format!("response wasn't json: {res_str}"))) } /// Perform a JSON-RPC call with the specified method with the provided parameters @@ -254,7 +250,9 @@ impl Rpc { // This does run a few keccak256 hashes, which is pointless if the node is trusted // In exchange, this provides resilience against invalid/malicious nodes if tx.hash() != hashes[i] { - Err(RpcError::InvalidNode("replied with transaction wasn't the requested transaction"))?; + Err(RpcError::InvalidNode( + "replied with transaction wasn't the requested transaction".to_string(), + ))?; } Ok(tx) @@ -295,9 +293,9 @@ impl Rpc { self.json_rpc_call("get_block", Some(json!({ "hash": hex::encode(hash) }))).await?; let block = Block::read::<&[u8]>(&mut rpc_hex(&res.blob)?.as_ref()) - .map_err(|_| RpcError::InvalidNode("invalid block"))?; + .map_err(|_| RpcError::InvalidNode("invalid block".to_string()))?; if block.hash() != hash { - Err(RpcError::InvalidNode("different block than requested (hash)"))?; + Err(RpcError::InvalidNode("different block than requested (hash)".to_string()))?; } Ok(block) } @@ -312,7 +310,7 @@ impl Rpc { self.json_rpc_call("get_block", Some(json!({ "height": number }))).await?; let block = Block::read::<&[u8]>(&mut rpc_hex(&res.blob)?.as_ref()) - .map_err(|_| RpcError::InvalidNode("invalid block"))?; + .map_err(|_| RpcError::InvalidNode("invalid block".to_string()))?; // Make sure this is actually the block for this number match block.miner_tx.prefix.inputs.first() { @@ -320,10 +318,12 @@ impl Rpc { if usize::try_from(*actual).unwrap() == number { Ok(block) } else { - Err(RpcError::InvalidNode("different block than requested (number)")) + Err(RpcError::InvalidNode("different block than requested (number)".to_string())) } } - _ => Err(RpcError::InvalidNode("block's miner_tx didn't have an input of kind Input::Gen")), + _ => Err(RpcError::InvalidNode( + "block's miner_tx didn't have an input of kind Input::Gen".to_string(), + )), } } @@ -493,7 +493,7 @@ impl Rpc { read_object(&mut indexes) })() - .map_err(|_| RpcError::InvalidNode("invalid binary response")) + .map_err(|_| RpcError::InvalidNode("invalid binary response".to_string())) } /// Get the output distribution, from the specified height to the specified height (both @@ -582,7 +582,9 @@ impl Rpc { // invalid keys may honestly exist on the blockchain // Only a recent hard fork checked output keys were valid points let Some(key) = CompressedEdwardsY( - rpc_hex(&out.key)?.try_into().map_err(|_| RpcError::InvalidNode("non-32-byte point"))?, + rpc_hex(&out.key)? + .try_into() + .map_err(|_| RpcError::InvalidNode("non-32-byte point".to_string()))?, ) .decompress() else { return Ok(None); diff --git a/coins/monero/src/wallet/scan.rs b/coins/monero/src/wallet/scan.rs index 611793fe7..bfbb284ab 100644 --- a/coins/monero/src/wallet/scan.rs +++ b/coins/monero/src/wallet/scan.rs @@ -234,7 +234,7 @@ impl SpendableOutput { .await? .get(usize::from(self.output.absolute.o)) .ok_or(RpcError::InvalidNode( - "node returned output indexes didn't include an index for this output", + "node returned output indexes didn't include an index for this output".to_string(), ))?; Ok(()) } diff --git a/common/request/Cargo.toml b/common/request/Cargo.toml index f4baab988..528aff107 100644 --- a/common/request/Cargo.toml +++ b/common/request/Cargo.toml @@ -14,11 +14,14 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] -# Deprecated here means to enable deprecated warnings, not to restore deprecated APIs -hyper = { version = "0.14", default-features = false, features = ["http1", "tcp", "client", "runtime", "backports", "deprecated"] } +hyper = { version = "1", default-features = false, features = ["http1", "client"] } +hyper-util = { version = "0.1", default-features = false, features = ["http1", "client", "tokio"] } +http-body-util = { version = "0.1", default-features = false } + tokio = { version = "1", default-features = false } -hyper-boring = { version = "4", default-features = false, features = ["runtime"], optional = true } +tower-service = { version = "0.3", default-features = false, optional = true } +hyper-boring = { version = "5", default-features = false, optional = true } zeroize = { version = "1", optional = true } base64ct = { version = "1", features = ["alloc"], optional = true } diff --git a/common/request/src/lib.rs b/common/request/src/lib.rs index 8b51941e2..894882dc6 100644 --- a/common/request/src/lib.rs +++ b/common/request/src/lib.rs @@ -5,17 +5,19 @@ use std::sync::Arc; use tokio::sync::Mutex; -#[cfg(feature = "tls")] -use hyper_boring::HttpsConnector; -use hyper::{ - Uri, - header::HeaderValue, - body::Body, - service::Service, - client::{HttpConnector, conn::http1::SendRequest}, +use hyper::{Uri, header::HeaderValue, body::Bytes, client::conn::http1::SendRequest}; +use hyper_util::{ + rt::tokio::TokioExecutor, + client::legacy::{Client as HyperClient, connect::HttpConnector}, }; +pub use http_body_util::Full; pub use hyper; +#[cfg(feature = "tls")] +use tower_service::Service as TowerService; +#[cfg(feature = "tls")] +use hyper_boring::HttpsConnector; + mod request; pub use request::*; @@ -29,6 +31,7 @@ pub enum Error { InconsistentHost, ConnectionError(Box), Hyper(hyper::Error), + HyperUtil(hyper_util::client::legacy::Error), } #[cfg(not(feature = "tls"))] @@ -38,8 +41,12 @@ type Connector = HttpsConnector; #[derive(Clone)] enum Connection { - ConnectionPool(hyper::Client), - Connection { connector: Connector, host: Uri, connection: Arc>>> }, + ConnectionPool(HyperClient>), + Connection { + connector: Connector, + host: Uri, + connection: Arc>>>>, + }, } impl core::fmt::Debug for Connection { fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { @@ -63,7 +70,9 @@ impl Client { pub fn with_connection_pool() -> Result { Ok(Client { - connection: Connection::ConnectionPool(hyper::Client::builder().build(Self::connector()?)), + connection: Connection::ConnectionPool( + HyperClient::builder(TokioExecutor::new()).build(Self::connector()?), + ), }) } @@ -116,7 +125,9 @@ impl Client { } Ok(Response(match &self.connection { - Connection::ConnectionPool(client) => client.request(request).await.map_err(Error::Hyper)?, + Connection::ConnectionPool(client) => { + client.request(request).await.map_err(Error::HyperUtil)? + } Connection::Connection { connector, host, connection } => { let mut connection_lock = connection.lock().await; diff --git a/common/request/src/request.rs b/common/request/src/request.rs index 1117e9fd6..fb8931926 100644 --- a/common/request/src/request.rs +++ b/common/request/src/request.rs @@ -1,4 +1,6 @@ -use hyper::body::Body; +use hyper::body::Bytes; +use http_body_util::Full; + #[cfg(feature = "basic-auth")] use hyper::header::HeaderValue; @@ -6,7 +8,7 @@ use hyper::header::HeaderValue; use crate::Error; #[derive(Debug)] -pub struct Request(pub(crate) hyper::Request); +pub struct Request(pub(crate) hyper::Request>); impl Request { #[cfg(feature = "basic-auth")] fn username_password_from_uri(&self) -> Result<(String, String), Error> { @@ -59,8 +61,8 @@ impl Request { let _ = self.basic_auth_from_uri(); } } -impl From> for Request { - fn from(request: hyper::Request) -> Request { +impl From>> for Request { + fn from(request: hyper::Request>) -> Request { Request(request) } } diff --git a/common/request/src/response.rs b/common/request/src/response.rs index 4611324a4..3e0897698 100644 --- a/common/request/src/response.rs +++ b/common/request/src/response.rs @@ -1,13 +1,14 @@ use hyper::{ StatusCode, header::{HeaderValue, HeaderMap}, - body::{Buf, Body}, + body::{Buf, Incoming}, }; +use http_body_util::BodyExt; use crate::Error; #[derive(Debug)] -pub struct Response(pub(crate) hyper::Response); +pub struct Response(pub(crate) hyper::Response); impl Response { pub fn status(&self) -> StatusCode { self.0.status() @@ -16,6 +17,12 @@ impl Response { self.0.headers() } pub async fn body(self) -> Result { - hyper::body::aggregate(self.0.into_body()).await.map(Buf::reader).map_err(Error::Hyper) + self + .0 + .into_body() + .collect() + .await + .map_err(Error::Hyper) + .map(|collected| Buf::reader(collected.aggregate())) } } diff --git a/substrate/client/src/serai/mod.rs b/substrate/client/src/serai/mod.rs index b725c02a1..91f2969b0 100644 --- a/substrate/client/src/serai/mod.rs +++ b/substrate/client/src/serai/mod.rs @@ -150,7 +150,7 @@ impl Serai { } pub async fn new(url: String) -> Result { - let client = Client::with_connection_pool(); + let client = Client::with_connection_pool().map_err(|_| SeraiError::ConnectionError)?; let mut res = Serai { url, client, genesis: [0xfe; 32] }; res.genesis = res.block_hash(0).await?.ok_or_else(|| { SeraiError::InvalidNode("node didn't have the first block's hash".to_string())