Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add wasm support #700

Merged
merged 18 commits into from
Nov 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,14 @@ jobs:
- name: Rust Cache
uses: Swatinem/rust-cache@22c9328bcba27aa81a32b1bef27c7e3c78052531 # v2.0.1

- name: Build
uses: actions-rs/cargo@v1.0.3
- name: Install cargo-hack
uses: baptiste0928/cargo-install@v1
with:
command: check
args: --all-targets --all-features --workspace
crate: cargo-hack
version: 0.5

- name: Cargo check
run: cargo hack --exclude-all-features --each-feature check --all-targets --workspace

fmt:
name: Cargo fmt
Expand Down Expand Up @@ -165,3 +168,28 @@ jobs:
with:
command: clippy
args: --all-targets -- -D warnings

wasm_tests:
name: Test wasm
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3.1.0

- name: Install
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

- name: Download Substrate
run: |
curl $SUBSTRATE_URL --output substrate --location
chmod +x substrate
mkdir -p ~/.local/bin
mv substrate ~/.local/bin

- name: Run WASM tests
run: |
substrate --dev --tmp > /dev/null 2>&1 &
wasm-pack test --headless --firefox
wasm-pack test --headless --chrome
pkill substrate
working-directory: testing/wasm-tests
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@ members = [
"testing/ui-tests",
"macro",
"metadata",
"subxt",
"subxt"
]
exclude = ["testing/wasm-tests"]

resolver = "2"
2 changes: 1 addition & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ scale = { package = "parity-scale-codec", version = "3.0.0", default-features =
# generate the item mod for codegen
syn = "1.0.80"
# communicate with the substrate nodes
jsonrpsee = { version = "0.15.1", features = ["async-client", "client-ws-transport", "http-client"] }
jsonrpsee = { version = "0.16.0", features = ["async-client", "client-ws-transport", "http-client"] }
# async runtime
tokio = { version = "1.8", features = ["rt-multi-thread", "macros", "time"] }
2 changes: 1 addition & 1 deletion codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ quote = "1.0.8"
syn = "1.0.58"
scale-info = { version = "2.0.0", features = ["bit-vec"] }
subxt-metadata = { version = "0.24.0", path = "../metadata" }
jsonrpsee = { version = "0.15.1", features = ["async-client", "client-ws-transport", "http-client"] }
jsonrpsee = { version = "0.16.0", features = ["async-client", "client-ws-transport", "http-client"] }
hex = "0.4.3"
tokio = { version = "1.8", features = ["macros", "rt-multi-thread"] }

Expand Down
2 changes: 1 addition & 1 deletion codegen/src/utils/fetch_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ async fn fetch_metadata_http(url: &Uri) -> Result<String, FetchMetadataError> {
.request_timeout(Duration::from_secs(180))
.build(url.to_string())?;

Ok(client.request::<String>("state_getMetadata", None).await?)
Ok(client.request("state_getMetadata", rpc_params![]).await?)
}

#[derive(Debug)]
Expand Down
14 changes: 9 additions & 5 deletions subxt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ description = "Submit extrinsics (transactions) to a substrate node via RPC"
keywords = ["parity", "substrate", "blockchain"]

[features]
default = ["jsonrpsee"]
default = ["jsonrpsee-ws"]

# Activate this to expose functionality only used for integration testing.
# The exposed functionality is subject to breaking changes at any point,
Expand All @@ -22,19 +22,20 @@ integration-tests = []

# Jsonrpsee if the default RPC provider used in Subxt. However, it can be
# swapped out for an alternative implementation, and so is optional.
jsonrpsee = ["dep:jsonrpsee"]
jsonrpsee-ws = ["jsonrpsee/async-client", "jsonrpsee/client-ws-transport"]
jsonrpsee-web = ["jsonrpsee/async-wasm-client", "jsonrpsee/client-web-transport"]

[dependencies]
bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] }
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full", "bit-vec"] }
scale-info = { version = "2.0.0", features = ["bit-vec"] }
scale-value = "0.6.0"
scale-decode = "0.4.0"
futures = "0.3.13"
futures = { version = "0.3.13", default-features = false }
hex = "0.4.3"
jsonrpsee = { version = "0.15.1", features = ["async-client", "client-ws-transport", "jsonrpsee-types"], optional = true }
jsonrpsee = { version = "0.16", optional = true, features = ["jsonrpsee-types"] }
ascjones marked this conversation as resolved.
Show resolved Hide resolved
serde = { version = "1.0.124", features = ["derive"] }
serde_json = "1.0.64"
serde_json = { version = "1.0.64", features = ["raw_value"] }
thiserror = "1.0.24"
tracing = "0.1.34"
parking_lot = "0.12.0"
Expand All @@ -48,5 +49,8 @@ sp-runtime = "6.0.0"
frame-metadata = "15.0.0"
derivative = "2.2.0"

[target.wasm32-unknown-unknown.dependencies]
getrandom = { version = "0.2", features = ["js"] }

[dev-dependencies]
tokio = { version = "1.8", features = ["macros", "time", "rt-multi-thread"] }
34 changes: 30 additions & 4 deletions subxt/src/client/online_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ impl<T: Config> std::fmt::Debug for OnlineClient<T> {
}

// The default constructors assume Jsonrpsee.
#[cfg(feature = "jsonrpsee")]
#[cfg(any(
feature = "jsonrpsee-ws",
all(feature = "jsonrpsee-web", target_arch = "wasm32")
))]
impl<T: Config> OnlineClient<T> {
/// Construct a new [`OnlineClient`] using default settings which
/// point to a locally running node on `ws://127.0.0.1:9944`.
Expand All @@ -72,7 +75,7 @@ impl<T: Config> OnlineClient<T> {

/// Construct a new [`OnlineClient`], providing a URL to connect to.
pub async fn from_url(url: impl AsRef<str>) -> Result<OnlineClient<T>, Error> {
let client = jsonrpsee_helpers::ws_client(url.as_ref())
let client = jsonrpsee_helpers::client(url.as_ref())
.await
.map_err(|e| crate::error::RpcError::ClientError(Box::new(e)))?;
OnlineClient::from_rpc_client(Arc::new(client)).await
Expand Down Expand Up @@ -346,7 +349,7 @@ impl Update {
}

// helpers for a jsonrpsee specific OnlineClient.
#[cfg(feature = "jsonrpsee")]
#[cfg(feature = "jsonrpsee-ws")]
mod jsonrpsee_helpers {
pub use jsonrpsee::{
client_transport::ws::{
Expand All @@ -366,7 +369,7 @@ mod jsonrpsee_helpers {
};

/// Build WS RPC client from URL
pub async fn ws_client(url: &str) -> Result<Client, Error> {
pub async fn client(url: &str) -> Result<Client, Error> {
let (sender, receiver) = ws_transport(url).await?;
Ok(ClientBuilder::default()
.max_notifs_per_subscription(4096)
Expand All @@ -383,3 +386,26 @@ mod jsonrpsee_helpers {
.map_err(|e| Error::Transport(e.into()))
}
}

// helpers for a jsonrpsee specific OnlineClient.
#[cfg(all(feature = "jsonrpsee-web", target_arch = "wasm32"))]
mod jsonrpsee_helpers {
pub use jsonrpsee::{
client_transport::web,
core::{
client::{
Client,
ClientBuilder,
},
Error,
},
};

/// Build web RPC client from URL
pub async fn client(url: &str) -> Result<Client, Error> {
let (sender, receiver) = web::connect(url).await.unwrap();
Ok(ClientBuilder::default()
.max_notifs_per_subscription(4096)
.build_with_wasm(sender, receiver))
}
}
7 changes: 7 additions & 0 deletions subxt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ use tokio as _;

pub use subxt_macro::subxt;

// Used to enable the js feature for wasm.
#[cfg(target_arch = "wasm32")]
pub use getrandom as _;

#[cfg(all(feature = "jsonrpsee-ws", feature = "jsonrpsee-web"))]
std::compile_error!("Both the features `jsonrpsee-ws` and `jsonrpsee-web` are enabled which are mutually exclusive");

pub mod blocks;
pub mod client;
pub mod config;
Expand Down
46 changes: 16 additions & 30 deletions subxt/src/rpc/jsonrpsee_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,24 @@ use futures::stream::{
StreamExt,
TryStreamExt,
};
use jsonrpsee::{
core::client::{
use jsonrpsee::core::{
client::{
Client,
ClientT,
SubscriptionClientT,
},
types::ParamsSer,
};
use serde_json::value::{
RawValue,
Value,
traits::ToRpcParams,
Error as JsonRpseeError,
};
use serde_json::value::RawValue;

struct Params(Option<Box<RawValue>>);

impl ToRpcParams for Params {
fn to_rpc_params(self) -> Result<Option<Box<RawValue>>, JsonRpseeError> {
Ok(self.0)
}
}

impl RpcClientT for Client {
fn request_raw<'a>(
Expand All @@ -32,8 +38,7 @@ impl RpcClientT for Client {
params: Option<Box<RawValue>>,
) -> RpcFuture<'a, Box<RawValue>> {
Box::pin(async move {
let params = prep_params_for_jsonrpsee(params);
let res = ClientT::request(self, method, Some(params))
let res = ClientT::request(self, method, Params(params))
.await
.map_err(|e| RpcError::ClientError(Box::new(e)))?;
Ok(res)
Expand All @@ -47,11 +52,10 @@ impl RpcClientT for Client {
unsub: &'a str,
) -> RpcFuture<'a, RpcSubscription> {
Box::pin(async move {
let params = prep_params_for_jsonrpsee(params);
let sub = SubscriptionClientT::subscribe::<Box<RawValue>>(
let sub = SubscriptionClientT::subscribe::<Box<RawValue>, _>(
self,
sub,
Some(params),
Params(params),
unsub,
)
.await
Expand All @@ -62,21 +66,3 @@ impl RpcClientT for Client {
})
}
}

// This is ugly; we have to encode to Value's to be compat with the jsonrpc interface.
// Remove and simplify this once something like https://github.com/paritytech/jsonrpsee/issues/862 is in:
fn prep_params_for_jsonrpsee(params: Option<Box<RawValue>>) -> ParamsSer<'static> {
let params = match params {
Some(params) => params,
// No params? avoid any work and bail early.
None => return ParamsSer::Array(Vec::new()),
};
let val = serde_json::to_value(&params).expect("RawValue guarantees valid JSON");
let arr = match val {
Value::Array(arr) => arr,
_ => {
panic!("RPC Params are expected to be an array but got {params}");
}
};
ParamsSer::Array(arr)
}
niklasad1 marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion testing/test-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ subxt = { path = "../../subxt" }
sp-core = "6.0.0"
tokio = { version = "1.8", features = ["macros", "rt-multi-thread"] }
which = "4.2.2"
jsonrpsee = { version = "0.15.1", features = ["async-client", "client-ws-transport"] }
jsonrpsee = { version = "0.16.0", features = ["async-client", "client-ws-transport"] }
7 changes: 5 additions & 2 deletions testing/test-runtime/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ async fn run() {
// Thus, the connection might get rejected a few times.
use client::ClientT;
let res = match client::build(&format!("ws://localhost:{}", port)).await {
Ok(c) => c.request("state_getMetadata", None).await,
Ok(c) => c.request("state_getMetadata", client::rpc_params![]).await,
Err(e) => Err(e),
};

Expand Down Expand Up @@ -174,7 +174,10 @@ mod client {
},
};

pub use jsonrpsee::core::client::ClientT;
pub use jsonrpsee::core::{
client::ClientT,
rpc_params,
};

/// Build WS RPC client from URL
pub async fn build(url: &str) -> Result<Client, Error> {
Expand Down
11 changes: 11 additions & 0 deletions testing/wasm-tests/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "wasm-test"
version = "0.1.0"
edition = "2021"

[dev-dependencies]
wasm-bindgen-test = "0.3.24"
tracing-wasm = "0.2.1"
console_error_panic_hook = "0.1.7"
serde_json = "1.0.57"
subxt = { path = "../../subxt", default-features = false, features = ["jsonrpsee-web"] }
26 changes: 26 additions & 0 deletions testing/wasm-tests/tests/wasm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#![cfg(target_arch = "wasm32")]

use subxt::config::PolkadotConfig;
use wasm_bindgen_test::*;

wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);

/// Run the tests by `$ wasm-pack test --firefox --headless`

fn init_tracing() {
console_error_panic_hook::set_once();
tracing_wasm::set_as_global_default();
}

#[wasm_bindgen_test]
async fn wasm_ws_transport_works() {
init_tracing();

let client =
subxt::client::OnlineClient::<PolkadotConfig>::from_url("ws://127.0.0.1:9944")
.await
.unwrap();

let chain = client.rpc().system_chain().await.unwrap();
assert_eq!(&chain, "Development");
}