diff --git a/.changelog/unreleased/features/1485-wasm-http-client.md b/.changelog/unreleased/features/1485-wasm-http-client.md new file mode 100644 index 000000000..68bd46603 --- /dev/null +++ b/.changelog/unreleased/features/1485-wasm-http-client.md @@ -0,0 +1 @@ +- [tendermint-rpc] allow usage of `http-client` trait in `wasm32-unknown-unknown` target. ([\#1485](https://github.com/informalsystems/tendermint-rs/pull/1485)) diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 4581a27c1..8561ebf18 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -45,7 +45,10 @@ cli = [ http-client = [ "futures", "reqwest", + "tokio/sync", "tokio/macros", + "tokio/time", + "wasmtimer/tokio", "tracing" ] websocket-client = [ @@ -70,6 +73,7 @@ tendermint-proto = { version = "0.40.1", path = "../proto", default-features = f async-trait = { version = "0.1", default-features = false } bytes = { version = "1.0", default-features = false } +cfg-if = "1.0.0" getrandom = { version = "0.2", default-features = false, features = ["js"] } peg = { version = "0.8", default-features = false } pin-project = { version = "1.0.1", default-features = false } @@ -92,10 +96,14 @@ async-tungstenite = { version = "0.24", default-features = false, features = ["t futures = { version = "0.3", optional = true, default-features = false } reqwest = { version = "0.11.20", optional = true, default-features = false, features = ["rustls-tls-native-roots"] } structopt = { version = "0.3", optional = true, default-features = false } -tokio = { version = "1.0", optional = true, default-features = false, features = ["rt-multi-thread"] } +tokio = { version = "1.0", optional = true, default-features = false } tracing = { version = "0.1", optional = true, default-features = false } tracing-subscriber = { version = "0.3", optional = true, default-features = false, features = ["fmt"] } +[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer] +version = "0.4.1" +optional = true + [dev-dependencies] tendermint = { version = "0.40.1", default-features = false, path = "../tendermint", features = ["secp256k1"] } http = { version = "1", default-features = false, features = ["std"] } diff --git a/rpc/src/client.rs b/rpc/src/client.rs index dabb4f8dc..d563b4bb0 100644 --- a/rpc/src/client.rs +++ b/rpc/src/client.rs @@ -293,6 +293,7 @@ pub trait Client { /// `/genesis_chunked`: get genesis file in multiple chunks. #[cfg(any(feature = "http-client", feature = "websocket-client"))] + #[cfg_attr(target_arch = "wasm32", allow(elided_named_lifetimes))] async fn genesis_chunked_stream( &self, ) -> core::pin::Pin, Error>> + '_>> { @@ -366,7 +367,12 @@ pub trait Client { } attempts_remaining -= 1; - tokio::time::sleep(poll_interval).await; + + cfg_if::cfg_if! { if #[cfg(target_arch = "wasm32")] { + wasmtimer::tokio::sleep(poll_interval).await; + } else { + tokio::time::sleep(poll_interval).await; + }} } Ok(()) diff --git a/rpc/src/client/transport/http.rs b/rpc/src/client/transport/http.rs index 2885d63b9..cd2172414 100644 --- a/rpc/src/client/transport/http.rs +++ b/rpc/src/client/transport/http.rs @@ -4,7 +4,8 @@ use core::str::FromStr; use core::time::Duration; use async_trait::async_trait; -use reqwest::{header, Proxy}; +use cfg_if::cfg_if; +use reqwest::header; use tendermint::{block::Height, evidence::Evidence, Hash}; use tendermint_config::net; @@ -20,6 +21,7 @@ use crate::{Error, Order, Scheme, SimpleRequest, Url}; use super::auth; +#[cfg(not(target_arch = "wasm32"))] const USER_AGENT: &str = concat!("tendermint.rs/", env!("CARGO_PKG_VERSION")); /// A JSON-RPC/HTTP Tendermint RPC client (implements [`crate::Client`]). @@ -115,23 +117,30 @@ impl Builder { let inner = if let Some(inner) = self.client { inner } else { - let builder = reqwest::ClientBuilder::new() - .user_agent(self.user_agent.unwrap_or_else(|| USER_AGENT.to_string())) - .timeout(self.timeout); - - match self.proxy_url { - None => builder.build().map_err(Error::http)?, - Some(proxy_url) => { - let proxy = if self.url.0.is_secure() { - Proxy::https(reqwest::Url::from(proxy_url.0)) - .map_err(Error::invalid_proxy)? - } else { - Proxy::http(reqwest::Url::from(proxy_url.0)) - .map_err(Error::invalid_proxy)? - }; - builder.proxy(proxy).build().map_err(Error::http)? - }, - } + cfg_if! {if #[cfg(target_arch = "wasm32")] { + // .user_agent method will become available in reqwest 0.12 + reqwest::ClientBuilder::new() + // .user_agent(self.user_agent.unwrap_or_else(|| USER_AGENT.to_string())) + .build().map_err(Error::http)? + } else { + let builder = reqwest::ClientBuilder::new() + .user_agent(self.user_agent.unwrap_or_else(|| USER_AGENT.to_string())) + .timeout(self.timeout); + + match self.proxy_url { + None => builder.build().map_err(Error::http)?, + Some(proxy_url) => { + let proxy = if self.url.0.is_secure() { + reqwest::Proxy::https(reqwest::Url::from(proxy_url.0)) + .map_err(Error::invalid_proxy)? + } else { + reqwest::Proxy::http(reqwest::Url::from(proxy_url.0)) + .map_err(Error::invalid_proxy)? + }; + builder.proxy(proxy).build().map_err(Error::http)? + }, + } + }} }; Ok(HttpClient { @@ -248,7 +257,8 @@ impl HttpClient { } } -#[async_trait] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl Client for HttpClient { async fn perform(&self, request: R) -> Result where diff --git a/rpc/src/client/transport/router.rs b/rpc/src/client/transport/router.rs index 0d4a3f853..bc9d37663 100644 --- a/rpc/src/client/transport/router.rs +++ b/rpc/src/client/transport/router.rs @@ -91,6 +91,7 @@ impl SubscriptionRouter { /// Immediately add a new subscription to the router without waiting for /// confirmation. + #[cfg_attr(not(feature = "websocket-client"), allow(dead_code))] pub fn add(&mut self, id: impl ToString, query: impl ToString, tx: SubscriptionTx) { let query = query.to_string(); let subs_for_query = match self.subscriptions.get_mut(&query) { @@ -105,6 +106,7 @@ impl SubscriptionRouter { } /// Removes all the subscriptions relating to the given query. + #[cfg_attr(not(feature = "websocket-client"), allow(dead_code))] pub fn remove_by_query(&mut self, query: impl ToString) -> usize { self.subscriptions .remove(&query.to_string()) diff --git a/rpc/src/error.rs b/rpc/src/error.rs index 7674fb1a8..e5f558688 100644 --- a/rpc/src/error.rs +++ b/rpc/src/error.rs @@ -18,10 +18,10 @@ type HttpStatusCode = reqwest::StatusCode; #[cfg(not(feature = "reqwest"))] type HttpStatusCode = core::num::NonZeroU16; -#[cfg(feature = "tokio")] +#[cfg(all(feature = "tokio", not(target_arch = "wasm32")))] type JoinError = flex_error::DisplayOnly; -#[cfg(not(feature = "tokio"))] +#[cfg(any(not(feature = "tokio"), target_arch = "wasm32"))] type JoinError = flex_error::NoSource; #[cfg(feature = "async-tungstenite")]