diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000000..91323d2d2c --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,6 @@ +# Tokio gates `tokio::net` (and friends) on wasm behind `--cfg tokio_unstable`. +# `WasiConnector` in jsonrpsee-http-client uses `tokio::net::TcpStream`, so wasip2 +# builds need the flag set. Scope it to the wasm32-wasip2 target so native builds +# keep their cache and don't pick up unstable APIs. +[target.wasm32-wasip2] +rustflags = ["--cfg", "tokio_unstable"] diff --git a/Cargo.toml b/Cargo.toml index 5860b19d45..288d8c6584 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,5 @@ [workspace] members = [ - "benches", "client/http-client", "client/transport", "client/wasm-client", @@ -16,6 +15,10 @@ members = [ "tests/proc-macro-core", "types", ] +# benches/ pulls jsonrpc-http-server (hyper 0.14), which pins socket2 to 0.5.x and breaks +# wasm32-wasip2 builds. Keep it out of the workspace so downstream consumers' lockfiles +# aren't poisoned; run benchmarks from inside benches/ with its own lockfile. +exclude = ["benches"] resolver = "3" [workspace.package] @@ -53,9 +56,12 @@ heck = "0.5.0" http = "1" http-body = "1" http-body-util = "0.1.0" -hyper = "1.5" +cfg_aliases = "0.2" +log = "0.4" +hyper = "1.8" hyper-rustls = { version = "0.27", default-features = false } hyper-util = "0.1" +mio = { version = "1.2.0", default-features = false } parking_lot = "0.12" pin-project = "1.1.3" proc-macro-crate = "3" @@ -67,12 +73,13 @@ rustc-hash = "2" rustls = { version = "0.23", default-features = false } rustls-pki-types = "1" rustls-platform-verifier = "0.5" +rustls-rustcrypto = "0.0.2-alpha" serde = { version = "1", default-features = false, features = ["derive"] } serde_json = { version = "1.0.142", default-features = false, features = ["alloc", "raw_value"] } soketto = "0.8.1" syn = { version = "2", default-features = false } thiserror = "2" -tokio = "1.42" +tokio = { version = "1.51", default-features = false } tokio-rustls = { version = "0.26", default-features = false } tokio-stream = "0.1.7" tokio-util = "0.7" @@ -80,6 +87,7 @@ tower = "0.5" tower-http = "0.6" tracing = "0.1.34" url = "2.4" +webpki-roots = "0.26" wasm-bindgen-futures = "0.4.19" # Dev dependencies @@ -89,7 +97,7 @@ criterion = { version = "0.5", features = ["async_tokio", "html_reports"] } fast-socks5 = "0.10" futures = { version = "0.3.14", default-features = false, features = ["std"] } pprof = { version = "0.15", features = ["flamegraph", "criterion"] } -socket2 = "0.6.0" +socket2 = "0.6.3" tracing-subscriber = { version = "0.3", features = ["env-filter"] } trybuild = "1.0.97" diff --git a/benches/Cargo.toml b/benches/Cargo.toml index c2ae9c3890..f352b6f5ca 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -1,16 +1,19 @@ [package] name = "jsonrpsee-benchmarks" description = "Benchmarks for jsonrpsee" -version.workspace = true -authors.workspace = true -edition.workspace = true -license.workspace = true +version = "0.26.0" +authors = ["Parity Technologies ", "Pierre Krieger "] +edition = "2024" +license = "MIT" publish = false +# benches/ is a standalone crate (excluded from the parent workspace) because the +# `jsonrpc-{http,ws}-server 18.0.0` deps drag in hyper 0.14, which pins socket2 to 0.5.x +# and breaks wasm32-wasip2 in the rest of the workspace. [dependencies] -pprof = { workspace = true } -criterion = { workspace = true } -futures-util = { workspace = true } +pprof = { version = "0.15", features = ["flamegraph", "criterion"] } +criterion = { version = "0.5", features = ["async_tokio", "html_reports"] } +futures-util = { version = "0.3.14", default-features = false } jsonrpsee = { path = "../jsonrpsee", features = ["server"] } jsonrpsee_v0_20 = { package = "jsonrpsee", version = "=0.20.0", features = ["ws-client", "client-ws-transport-native-tls"] } # Disable TLS for benches @@ -18,9 +21,9 @@ jsonrpsee_v0_20_http_client = { package = "jsonrpsee-http-client", version = "=0 jsonrpc-ws-server = { version = "18.0.0", optional = true } jsonrpc-http-server = { version = "18.0.0", optional = true } jsonrpc-pubsub = { version = "18.0.0", optional = true } -serde_json = { workspace = true } -tokio = { workspace = true, features = ["rt-multi-thread"] } -console-subscriber = { workspace = true } +serde_json = { version = "1.0.142", default-features = false, features = ["alloc", "raw_value"] } +tokio = { version = "1.51", default-features = false, features = ["rt-multi-thread"] } +console-subscriber = "0.4" [[bench]] name = "bench" diff --git a/client/http-client/Cargo.toml b/client/http-client/Cargo.toml index f1ec306a0d..ece91eb62a 100644 --- a/client/http-client/Cargo.toml +++ b/client/http-client/Cargo.toml @@ -16,16 +16,22 @@ publish = true [lints] workspace = true +[build-dependencies] +cfg_aliases = { workspace = true } + [dependencies] base64 = { workspace = true } hyper = { workspace = true, features = ["client", "http1", "http2"] } -hyper-rustls = { workspace = true, features = ["http1", "http2", "tls12", "logging", "ring"], optional = true } +hyper-rustls = { workspace = true, features = ["http1", "http2", "tls12", "logging"], optional = true } hyper-util = { workspace = true, features = ["client", "client-legacy", "tokio", "http1", "http2"] } http-body = { workspace = true } +log = { workspace = true } jsonrpsee-types = { workspace = true } jsonrpsee-core = { workspace = true, features = ["client", "http-helpers"] } -rustls = { workspace = true, optional = true, features = ["logging", "std", "tls12", "ring"] } +rustls = { workspace = true, optional = true, features = ["logging", "std", "tls12"] } rustls-platform-verifier = { workspace = true, optional = true } +rustls-rustcrypto = { workspace = true, optional = true } +webpki-roots = { workspace = true, optional = true } serde = { workspace = true, features = ["alloc"] } serde_json = { workspace = true } thiserror = { workspace = true } @@ -36,11 +42,17 @@ url = { workspace = true } [dev-dependencies] tracing-subscriber = { workspace = true } jsonrpsee-test-utils = { path = "../../test-utils" } -tokio = { workspace = true, features = ["net", "rt-multi-thread", "macros"] } +tokio = { workspace = true, features = ["macros"] } + +[target.'cfg(not(all(target_os = "wasi", target_env = "p2")))'.dev-dependencies] +tokio = { workspace = true, features = ["net", "rt-multi-thread"] } [features] -default = ["tls"] -tls = ["hyper-rustls", "rustls", "rustls-platform-verifier"] +default = ["tls-rustcrypto"] +tls = ["hyper-rustls", "hyper-rustls/ring", "rustls", "rustls/ring", "rustls-platform-verifier"] +# Note: rustls-platform-verifier is used on native targets (ConfigVerifierExt) but compiled-unused +# on wasip2 where webpki_roots provides cert verification instead. +tls-rustcrypto = ["hyper-rustls", "rustls", "rustls-rustcrypto", "rustls-platform-verifier", "webpki-roots"] [package.metadata.docs.rs] all-features = true diff --git a/client/http-client/build.rs b/client/http-client/build.rs new file mode 100644 index 0000000000..84e1b6edae --- /dev/null +++ b/client/http-client/build.rs @@ -0,0 +1,7 @@ +//! Build script for jsonrpsee-http-client: sets up cfg aliases. + +fn main() { + cfg_aliases::cfg_aliases! { + wasip2: { all(target_os = "wasi", target_env = "p2") }, + } +} diff --git a/client/http-client/src/client.rs b/client/http-client/src/client.rs index 81466914e5..2f7460a620 100644 --- a/client/http-client/src/client.rs +++ b/client/http-client/src/client.rs @@ -49,7 +49,7 @@ use tokio::sync::Semaphore; use tower::layer::util::Identity; use tower::{Layer, Service}; -#[cfg(feature = "tls")] +#[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] use crate::{CertificateStore, CustomCertStore}; type Logger = tower::layer::util::Stack; @@ -82,7 +82,7 @@ pub struct HttpClientBuilder max_request_size: u32, max_response_size: u32, request_timeout: Duration, - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] certificate_store: CertificateStore, id_kind: IdKind, headers: HeaderMap, @@ -185,7 +185,7 @@ impl HttpClientBuilder Self { self.certificate_store = CertificateStore::Custom(cfg); self @@ -234,7 +234,7 @@ impl HttpClientBuilder(self, rpc_builder: RpcServiceBuilder) -> HttpClientBuilder { HttpClientBuilder { - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] certificate_store: self.certificate_store, id_kind: self.id_kind, headers: self.headers, @@ -257,7 +257,7 @@ impl HttpClientBuilder, ) -> HttpClientBuilder { HttpClientBuilder { - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] certificate_store: self.certificate_store, id_kind: self.id_kind, headers: self.headers, @@ -291,7 +291,7 @@ where max_request_size, max_response_size, request_timeout, - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] certificate_store, id_kind, headers, @@ -313,7 +313,7 @@ where keep_alive_duration, keep_alive_interval, keep_alive_retries, - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] certificate_store, } .build(target) @@ -338,7 +338,7 @@ impl Default for HttpClientBuilder { max_request_size: TEN_MB_SIZE_BYTES, max_response_size: TEN_MB_SIZE_BYTES, request_timeout: Duration::from_secs(60), - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] certificate_store: CertificateStore::Native, id_kind: IdKind::Number, headers: HeaderMap::new(), diff --git a/client/http-client/src/lib.rs b/client/http-client/src/lib.rs index 49e64ab141..3a82627a8c 100644 --- a/client/http-client/src/lib.rs +++ b/client/http-client/src/lib.rs @@ -31,10 +31,44 @@ //! It is tightly-coupled to [`tokio`](https://docs.rs/tokio) because [`hyper`](https://docs.rs/hyper) is used as transport client, //! which is not compatible with other async runtimes such as //! [`async-std`](https://docs.rs/async-std/), [`smol`](https://docs.rs/smol) and similar. +//! +//! ## Building for `wasm32-wasip2` +//! +//! On `wasm32-wasip2` this crate uses `tokio::net::TcpStream`, which tokio gates behind the +//! unstable `--cfg tokio_unstable` rustc flag. Downstream consumers must set this flag themselves +//! when targeting `wasm32-wasip2`. The recommended way is a `.cargo/config.toml` in the consumer +//! crate: +//! +//! ```toml +//! [target.wasm32-wasip2] +//! rustflags = ["--cfg", "tokio_unstable"] +//! ``` +//! +//! Without it, the build fails inside `tokio` with +//! `compile_error!("Only features sync,macros,io-util,rt,time are supported on wasm.")`. +//! +//! The default `tls` feature uses [`ring`](https://docs.rs/ring) for crypto, which has a C build +//! step that requires `clang` on the host. For wasip2 builds without a C toolchain, use the +//! pure-Rust `tls-rustcrypto` feature instead: +//! +//! ```bash +//! cargo build --target wasm32-wasip2 --no-default-features --features tls-rustcrypto +//! ``` #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] +// These crates are only used behind wasip2 cfg gates, suppress unused warnings on other targets. +#[cfg(not(wasip2))] +use log as _; +#[cfg(all(feature = "tls-rustcrypto", not(wasip2)))] +use webpki_roots as _; + +// rustls_platform_verifier is used on native targets (ConfigVerifierExt) but not on wasip2, +// where we use webpki_roots instead. Suppress the unused dependency warning for wasip2. +#[cfg(all(wasip2, any(feature = "tls", feature = "tls-rustcrypto")))] +use rustls_platform_verifier as _; + mod client; mod rpc_service; @@ -62,10 +96,10 @@ pub use jsonrpsee_core::middleware::{RpcServiceBuilder, RpcServiceT}; pub use transport::{HttpBackend, HttpTransportClient}; /// Custom TLS configuration. -#[cfg(feature = "tls")] +#[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] pub type CustomCertStore = rustls::ClientConfig; -#[cfg(feature = "tls")] +#[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] // rustls needs the concrete `ClientConfig` type so we can't Box it here. #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug)] diff --git a/client/http-client/src/tests.rs b/client/http-client/src/tests.rs index f6af48e623..813be5f876 100644 --- a/client/http-client/src/tests.rs +++ b/client/http-client/src/tests.rs @@ -251,10 +251,10 @@ async fn batch_request_out_of_order_response() { assert_eq!(response, vec!["hello".to_string(), "goodbye".to_string(), "here's your swag".to_string()]); } -async fn run_batch_request_with_response( - batch: BatchRequestBuilder<'_>, +async fn run_batch_request_with_response<'a, T: Send + DeserializeOwned + std::fmt::Debug + Clone + 'static>( + batch: BatchRequestBuilder<'a>, response: String, -) -> Result, ClientError> { +) -> Result, ClientError> { let server_addr = http_server_with_hardcoded_response(response).with_default_timeout().await.unwrap(); let uri = format!("http://{server_addr}"); let client = HttpClientBuilder::default().build(&uri).unwrap(); diff --git a/client/http-client/src/transport.rs b/client/http-client/src/transport.rs index 5379cc95c0..782d95c395 100644 --- a/client/http-client/src/transport.rs +++ b/client/http-client/src/transport.rs @@ -6,10 +6,14 @@ // that we need to be guaranteed that hyper doesn't re-use an existing connection if we ever reset // the JSON-RPC request id to a value that might have already been used. +#[cfg(all(feature = "tls", feature = "tls-rustcrypto"))] +compile_error!("Features `tls` and `tls-rustcrypto` are mutually exclusive"); + use base64::Engine; use hyper::body::Bytes; use hyper::http::{HeaderMap, HeaderValue}; use hyper_util::client::legacy::Client; +#[cfg(not(wasip2))] use hyper_util::client::legacy::connect::HttpConnector; use hyper_util::rt::TokioExecutor; use jsonrpsee_core::BoxError; @@ -27,26 +31,87 @@ use url::Url; use crate::{HttpBody, HttpRequest, HttpResponse}; -#[cfg(feature = "tls")] +#[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] use crate::{CertificateStore, CustomCertStore}; +/// TCP connector for wasip2 that bypasses hyper-util's `HttpConnector`. +/// +/// `HttpConnector` creates sockets via socket2 and calls `set_nonblocking()`, +/// which fails on wasip2. This connector uses `tokio::net::TcpStream::connect()` +/// instead, which goes through mio's wasip2-compatible path. +/// +/// DNS resolution uses `std::net::ToSocketAddrs` directly (not `spawn_blocking`) +/// since wasip2 is single-threaded but wasi-libc provides working `getaddrinfo`. +#[cfg(wasip2)] +#[derive(Copy, Clone, Debug)] +pub struct WasiConnector; + +#[cfg(wasip2)] +impl Service for WasiConnector { + type Response = hyper_util::rt::TokioIo; + type Error = Box; + type Future = Pin> + Send>>; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, uri: hyper::Uri) -> Self::Future { + Box::pin(async move { + let host = uri.host().ok_or("missing host")?; + let port = uri.port_u16().unwrap_or(match uri.scheme_str() { + Some("https") => 443, + _ => 80, + }); + + // Resolve DNS synchronously via std::net (wasi-libc getaddrinfo) + let addr = std::net::ToSocketAddrs::to_socket_addrs(&(host, port))? + .next() + .ok_or("DNS resolved no addresses")?; + + // Connect via tokio (goes through mio's wasip2-compatible path) + let stream = tokio::net::TcpStream::connect(addr).await?; + Ok(hyper_util::rt::TokioIo::new(stream)) + }) + } +} + +/// Log a warning when TCP socket options are set on wasip2, where they are unsupported. +#[cfg(wasip2)] +fn warn_unsupported_tcp_options( + tcp_no_delay: bool, + keep_alive_duration: Option, + keep_alive_interval: Option, + keep_alive_retries: Option, +) { + if !tcp_no_delay || keep_alive_duration.is_some() || keep_alive_interval.is_some() || keep_alive_retries.is_some() { + log::warn!("TCP socket options (tcp_no_delay, keep_alive_*) are not supported on wasip2 and will be ignored"); + } +} + +/// Connector type selected by target: [`WasiConnector`] on wasip2, [`HttpConnector`] elsewhere. +#[cfg(wasip2)] +type Connector = WasiConnector; +#[cfg(not(wasip2))] +type Connector = HttpConnector; + const CONTENT_TYPE_JSON: &str = "application/json"; /// Wrapper over HTTP transport and connector. #[derive(Debug)] pub enum HttpBackend { /// Hyper client with https connector. - #[cfg(feature = "tls")] - Https(Client, B>), + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] + Https(Client, B>), /// Hyper client with http connector. - Http(Client), + Http(Client), } impl Clone for HttpBackend { fn clone(&self) -> Self { match self { Self::Http(inner) => Self::Http(inner.clone()), - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] Self::Https(inner) => Self::Https(inner.clone()), } } @@ -65,7 +130,7 @@ where fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll> { match self { Self::Http(inner) => inner.poll_ready(ctx), - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] Self::Https(inner) => inner.poll_ready(ctx), } .map_err(|e| Error::Http(HttpError::Stream(e.into()))) @@ -74,7 +139,7 @@ where fn call(&mut self, req: HttpRequest) -> Self::Future { let resp = match self { Self::Http(inner) => inner.call(req), - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] Self::Https(inner) => inner.call(req), }; @@ -86,7 +151,7 @@ where #[derive(Debug)] pub struct HttpTransportClientBuilder { /// Certificate store. - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] pub(crate) certificate_store: CertificateStore, /// Configurable max request body size pub(crate) max_request_size: u32, @@ -116,7 +181,7 @@ impl HttpTransportClientBuilder { /// Create a new [`HttpTransportClientBuilder`]. pub fn new() -> Self { Self { - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] certificate_store: CertificateStore::Native, max_request_size: TEN_MB_SIZE_BYTES, max_response_size: TEN_MB_SIZE_BYTES, @@ -132,7 +197,7 @@ impl HttpTransportClientBuilder { impl HttpTransportClientBuilder { /// See docs [`crate::HttpClientBuilder::with_custom_cert_store`] for more information. - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] pub fn with_custom_cert_store(mut self, cfg: CustomCertStore) -> Self { self.certificate_store = CertificateStore::Custom(cfg); self @@ -187,7 +252,7 @@ impl HttpTransportClientBuilder { /// Configure a tower service. pub fn set_service(self, service: tower::ServiceBuilder) -> HttpTransportClientBuilder { HttpTransportClientBuilder { - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] certificate_store: self.certificate_store, headers: self.headers, max_request_size: self.max_request_size, @@ -210,7 +275,7 @@ impl HttpTransportClientBuilder { B::Error: Into, { let Self { - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] certificate_store, max_request_size, max_response_size, @@ -230,52 +295,24 @@ impl HttpTransportClientBuilder { let client = match url.scheme() { "http" => { - let mut connector = HttpConnector::new(); - connector.set_nodelay(tcp_no_delay); - connector.set_keepalive(keep_alive_duration); - connector.set_keepalive_interval(keep_alive_interval); - connector.set_keepalive_retries(keep_alive_retries); + let connector = build_connector(tcp_no_delay, keep_alive_duration, keep_alive_interval, keep_alive_retries); HttpBackend::Http(Client::builder(TokioExecutor::new()).build(connector)) } - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] "https" => { - // Make sure that the TLS provider is set. If not, set a default one. - // Otherwise, creating `tls` configuration may panic if there are multiple - // providers available due to `rustls` features (e.g. both `ring` and `aws-lc-rs`). - // Function returns an error if the provider is already installed, and we're fine with it. - let _ = rustls::crypto::ring::default_provider().install_default(); - - let mut http_conn = HttpConnector::new(); - http_conn.set_nodelay(tcp_no_delay); - http_conn.enforce_http(false); - http_conn.set_keepalive(keep_alive_duration); - http_conn.set_keepalive_interval(keep_alive_interval); - http_conn.set_keepalive_retries(keep_alive_retries); - - let https_conn = match certificate_store { - CertificateStore::Native => { - use rustls_platform_verifier::ConfigVerifierExt; - - hyper_rustls::HttpsConnectorBuilder::new() - .with_tls_config(rustls::ClientConfig::with_platform_verifier()) - .https_or_http() - .enable_all_versions() - .wrap_connector(http_conn) - } - - CertificateStore::Custom(tls_config) => hyper_rustls::HttpsConnectorBuilder::new() - .with_tls_config(tls_config) - .https_or_http() - .enable_all_versions() - .wrap_connector(http_conn), - }; - + let https_conn = build_tls_connector( + tcp_no_delay, + keep_alive_duration, + keep_alive_interval, + keep_alive_retries, + certificate_store, + )?; HttpBackend::Https(Client::builder(TokioExecutor::new()).build(https_conn)) } _ => { - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] let err = "URL scheme not supported, expects 'http' or 'https'"; - #[cfg(not(feature = "tls"))] + #[cfg(not(any(feature = "tls", feature = "tls-rustcrypto")))] let err = "URL scheme not supported, expects 'http'"; return Err(Error::Url(err.into())); } @@ -316,6 +353,98 @@ impl HttpTransportClientBuilder { } } +/// Build an HTTP connector configured with TCP options. +fn build_connector( + tcp_no_delay: bool, + keep_alive_duration: Option, + keep_alive_interval: Option, + keep_alive_retries: Option, +) -> Connector { + #[cfg(wasip2)] + { + warn_unsupported_tcp_options(tcp_no_delay, keep_alive_duration, keep_alive_interval, keep_alive_retries); + WasiConnector + } + #[cfg(not(wasip2))] + { + let mut connector = HttpConnector::new(); + connector.set_nodelay(tcp_no_delay); + connector.set_keepalive(keep_alive_duration); + connector.set_keepalive_interval(keep_alive_interval); + connector.set_keepalive_retries(keep_alive_retries); + connector + } +} + +/// Build an HTTPS connector with TLS configured. +#[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] +fn build_tls_connector( + tcp_no_delay: bool, + keep_alive_duration: Option, + keep_alive_interval: Option, + keep_alive_retries: Option, + certificate_store: CertificateStore, +) -> Result, Error> { + install_crypto_provider(); + + let tls_config = match certificate_store { + CertificateStore::Native => build_native_tls_config()?, + CertificateStore::Custom(tls_config) => tls_config, + }; + + let connector = build_connector(tcp_no_delay, keep_alive_duration, keep_alive_interval, keep_alive_retries); + + #[cfg(not(wasip2))] + let connector = { + let mut c = connector; + c.enforce_http(false); + c + }; + + Ok(hyper_rustls::HttpsConnectorBuilder::new() + .with_tls_config(tls_config) + .https_or_http() + .enable_all_versions() + .wrap_connector(connector)) +} + +/// Install the appropriate rustls crypto provider. +#[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] +fn install_crypto_provider() { + // Make sure that the TLS provider is set. If not, set a default one. + // Otherwise, creating `tls` configuration may panic if there are multiple + // providers available due to `rustls` features (e.g. both `ring` and `aws-lc-rs`). + // Function returns an error if the provider is already installed, and we're fine with it. + #[cfg(feature = "tls")] + { + let _ = rustls::crypto::ring::default_provider().install_default(); + } + #[cfg(all(feature = "tls-rustcrypto", not(feature = "tls")))] + { + let _ = rustls_rustcrypto::provider().install_default(); + } +} + +/// Build a [`rustls::ClientConfig`] for the `CertificateStore::Native` case. +#[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] +fn build_native_tls_config() -> Result { + // On wasip2 there is no OS certificate store; use bundled Mozilla root certs instead. + #[cfg(wasip2)] + { + log::debug!("wasip2: CertificateStore::Native uses bundled Mozilla root certs (webpki-roots) since there is no OS cert store"); + let mut root_store = rustls::RootCertStore::empty(); + root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + Ok(rustls::ClientConfig::builder() + .with_root_certificates(root_store) + .with_no_client_auth()) + } + #[cfg(not(wasip2))] + { + use rustls_platform_verifier::ConfigVerifierExt; + Ok(rustls::ClientConfig::with_platform_verifier()) + } +} + /// HTTP Transport Client. #[derive(Debug, Clone)] pub struct HttpTransportClient { @@ -412,14 +541,14 @@ mod tests { assert!(matches!(err, Error::Url(_))); } - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] #[test] fn https_works() { let client = HttpTransportClientBuilder::new().build("https://localhost").unwrap(); assert_eq!(&client.target, "https://localhost/"); } - #[cfg(not(feature = "tls"))] + #[cfg(not(any(feature = "tls", feature = "tls-rustcrypto")))] #[test] fn https_fails_without_tls_feature() { let err = HttpTransportClientBuilder::new().build("https://localhost").unwrap_err(); @@ -459,7 +588,7 @@ mod tests { assert_eq!(&client.target, "http://127.0.0.1/"); } - #[cfg(feature = "tls")] + #[cfg(any(feature = "tls", feature = "tls-rustcrypto"))] #[test] fn https_custom_port_works() { let client = HttpTransportClientBuilder::new().build("https://localhost:9999").unwrap(); diff --git a/client/ws-client/src/tests.rs b/client/ws-client/src/tests.rs index eb0fd25110..18db7da0fa 100644 --- a/client/ws-client/src/tests.rs +++ b/client/ws-client/src/tests.rs @@ -439,10 +439,10 @@ async fn is_connected_works() { assert!(!client.is_connected()) } -async fn run_batch_request_with_response( - batch: BatchRequestBuilder<'_>, +async fn run_batch_request_with_response<'a, T: Send + DeserializeOwned + std::fmt::Debug + Clone + 'static>( + batch: BatchRequestBuilder<'a>, response: String, -) -> Result, Error> { +) -> Result, Error> { let server = WebSocketTestServer::with_hardcoded_response("127.0.0.1:0".parse().unwrap(), response) .with_default_timeout() .await diff --git a/server/src/server.rs b/server/src/server.rs index 87d2316149..52617a78c9 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -1163,7 +1163,7 @@ struct ProcessConnection<'a, HttpMiddleware, RpcMiddleware> { } #[instrument(name = "connection", skip_all, fields(remote_addr = %params.remote_addr, conn_id = %params.conn_id), level = "INFO")] -fn process_connection<'a, RpcMiddleware, HttpMiddleware, Body>(params: ProcessConnection) +fn process_connection(params: ProcessConnection) where HttpMiddleware: Layer> + Send + 'static, >>::Service: diff --git a/test_wasi.sh b/test_wasi.sh new file mode 100755 index 0000000000..e134fb4411 --- /dev/null +++ b/test_wasi.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "=== Native build with default tls feature ===" +cargo check -p jsonrpsee-http-client + +echo "" +echo "=== Native build with tls-rustcrypto (no default features) ===" +cargo check -p jsonrpsee-http-client --no-default-features --features tls-rustcrypto + +echo "" +echo "=== Native tests with tls ===" +cargo test -p jsonrpsee-http-client --features tls -- transport::tests + +echo "" +echo "=== Native tests with tls-rustcrypto ===" +cargo test -p jsonrpsee-http-client --no-default-features --features tls-rustcrypto -- transport::tests + +echo "" +echo "=== wasip2 build with tls-rustcrypto ===" +RUSTFLAGS="--cfg tokio_unstable" cargo build --target wasm32-wasip2 -p jsonrpsee-http-client --no-default-features --features tls-rustcrypto + +echo "" +echo "=== clippy warnings ===" +cargo clippy --all-targets +# cargo clippy -p jsonrpsee-http-client --no-default-features --features tls-rustcrypto --all-targets + +echo "" +echo "All checks passed."