diff --git a/cli/Cargo.toml b/cli/Cargo.toml index eb7a3a77be..4db9a7269b 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -21,8 +21,6 @@ subxt-codegen = { version = "0.22.0", path = "../codegen" } subxt-metadata = { version = "0.22.0", path = "../metadata" } # parse command line args structopt = "0.3.25" -# make the request to a substrate node to get the metadata -ureq = { version = "2.2.0", features = ["json"] } # colourful error reports color-eyre = "0.6.1" # serialize the metadata @@ -35,7 +33,9 @@ hex = "0.4.3" frame-metadata = { version = "15.0.0", features = ["v14", "std"] } # decode bytes into the metadata types scale = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -# handle urls to communicate with substrate nodes -url = { version = "2.2.2", features = ["serde"] } # generate the item mod for codegen syn = "1.0.80" +# communicate with the substrate nodes +jsonrpsee = { version = "0.14.0", features = ["async-client", "client-ws-transport", "http-client"] } +# async runtime +tokio = { version = "1.8", features = ["rt-multi-thread", "macros", "time"] } diff --git a/cli/src/main.rs b/cli/src/main.rs index 78cf2fc53b..99b1f1bac7 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -26,6 +26,19 @@ use frame_metadata::{ RuntimeMetadataV14, META_RESERVED, }; +use jsonrpsee::{ + async_client::ClientBuilder, + client_transport::ws::{ + Uri, + WsTransportClientBuilder, + }, + core::{ + client::ClientT, + Error, + }, + http_client::HttpClientBuilder, + rpc_params, +}; use scale::{ Decode, Input, @@ -70,7 +83,7 @@ enum Command { parse(try_from_str), default_value = "http://localhost:9933" )] - url: url::Url, + url: Uri, /// The format of the metadata to display: `json`, `hex` or `bytes`. #[structopt(long, short, default_value = "bytes")] format: String, @@ -83,7 +96,7 @@ enum Command { Codegen { /// The url of the substrate node to query for metadata for codegen. #[structopt(name = "url", long, parse(try_from_str))] - url: Option, + url: Option, /// The path to the encoded metadata file. #[structopt(short, long, parse(from_os_str))] file: Option, @@ -95,7 +108,7 @@ enum Command { Compatibility { /// Urls of the substrate nodes to verify for metadata compatibility. #[structopt(name = "nodes", long, use_delimiter = true, parse(try_from_str))] - nodes: Vec, + nodes: Vec, /// Check the compatibility of metadata for a particular pallet. /// /// ### Note @@ -105,13 +118,14 @@ enum Command { }, } -fn main() -> color_eyre::Result<()> { +#[tokio::main] +async fn main() -> color_eyre::Result<()> { color_eyre::install()?; let args = Opts::from_args(); match args.command { Command::Metadata { url, format } => { - let (hex_data, bytes) = fetch_metadata(&url)?; + let (hex_data, bytes) = fetch_metadata(&url).await?; match format.as_str() { "json" => { @@ -148,22 +162,26 @@ fn main() -> color_eyre::Result<()> { } let url = url.unwrap_or_else(|| { - url::Url::parse("http://localhost:9933").expect("default url is valid") + "http://localhost:9933" + .parse::() + .expect("default url is valid") }); - let (_, bytes) = fetch_metadata(&url)?; + let (_, bytes) = fetch_metadata(&url).await?; codegen(&mut &bytes[..], derives)?; Ok(()) } Command::Compatibility { nodes, pallet } => { match pallet { - Some(pallet) => handle_pallet_metadata(nodes.as_slice(), pallet.as_str()), - None => handle_full_metadata(nodes.as_slice()), + Some(pallet) => { + handle_pallet_metadata(nodes.as_slice(), pallet.as_str()).await + } + None => handle_full_metadata(nodes.as_slice()).await, } } } } -fn handle_pallet_metadata(nodes: &[url::Url], name: &str) -> color_eyre::Result<()> { +async fn handle_pallet_metadata(nodes: &[Uri], name: &str) -> color_eyre::Result<()> { #[derive(Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] struct CompatibilityPallet { @@ -173,28 +191,22 @@ fn handle_pallet_metadata(nodes: &[url::Url], name: &str) -> color_eyre::Result< let mut compatibility: CompatibilityPallet = Default::default(); for node in nodes.iter() { - let metadata = fetch_runtime_metadata(node)?; + let metadata = fetch_runtime_metadata(node).await?; match metadata.pallets.iter().find(|pallet| pallet.name == name) { Some(pallet_metadata) => { let hash = get_pallet_hash(&metadata.types, pallet_metadata); let hex_hash = hex::encode(hash); - println!( - "Node {:?} has pallet metadata hash {:?}", - node.as_str(), - hex_hash - ); + println!("Node {:?} has pallet metadata hash {:?}", node, hex_hash); compatibility .pallet_present .entry(hex_hash) .or_insert_with(Vec::new) - .push(node.as_str().to_string()); + .push(node.to_string()); } None => { - compatibility - .pallet_not_found - .push(node.as_str().to_string()); + compatibility.pallet_not_found.push(node.to_string()); } } } @@ -208,18 +220,18 @@ fn handle_pallet_metadata(nodes: &[url::Url], name: &str) -> color_eyre::Result< Ok(()) } -fn handle_full_metadata(nodes: &[url::Url]) -> color_eyre::Result<()> { +async fn handle_full_metadata(nodes: &[Uri]) -> color_eyre::Result<()> { let mut compatibility_map: HashMap> = HashMap::new(); for node in nodes.iter() { - let metadata = fetch_runtime_metadata(node)?; + let metadata = fetch_runtime_metadata(node).await?; let hash = get_metadata_hash(&metadata); let hex_hash = hex::encode(hash); - println!("Node {:?} has metadata hash {:?}", node.as_str(), hex_hash,); + println!("Node {:?} has metadata hash {:?}", node, hex_hash,); compatibility_map .entry(hex_hash) .or_insert_with(Vec::new) - .push(node.as_str().to_string()); + .push(node.to_string()); } println!( @@ -231,14 +243,14 @@ fn handle_full_metadata(nodes: &[url::Url]) -> color_eyre::Result<()> { Ok(()) } -fn fetch_runtime_metadata(url: &url::Url) -> color_eyre::Result { - let (_, bytes) = fetch_metadata(url)?; +async fn fetch_runtime_metadata(url: &Uri) -> color_eyre::Result { + let (_, bytes) = fetch_metadata(url).await?; let metadata = ::decode(&mut &bytes[..])?; if metadata.0 != META_RESERVED { return Err(eyre::eyre!( "Node {:?} has invalid metadata prefix: {:?} expected prefix: {:?}", - url.as_str(), + url, metadata.0, META_RESERVED )) @@ -249,28 +261,45 @@ fn fetch_runtime_metadata(url: &url::Url) -> color_eyre::Result { Err(eyre::eyre!( "Node {:?} with unsupported metadata version: {:?}", - url.as_str(), + url, metadata.1 )) } } } -fn fetch_metadata(url: &url::Url) -> color_eyre::Result<(String, Vec)> { - let resp = ureq::post(url.as_str()) - .set("Content-Type", "application/json") - .send_json(ureq::json!({ - "jsonrpc": "2.0", - "method": "state_getMetadata", - "id": 1 - })) - .context("error fetching metadata from the substrate node")?; - let json: serde_json::Value = resp.into_json()?; - - let hex_data = json["result"] - .as_str() - .map(ToString::to_string) - .ok_or_else(|| eyre::eyre!("metadata result field should be a string"))?; +async fn fetch_metadata_ws(url: &Uri) -> color_eyre::Result { + let (sender, receiver) = WsTransportClientBuilder::default() + .build(url.to_string().parse::().unwrap()) + .await + .map_err(|e| Error::Transport(e.into()))?; + + let client = ClientBuilder::default() + .max_notifs_per_subscription(4096) + .build_with_tokio(sender, receiver); + + Ok(client.request("state_getMetadata", rpc_params![]).await?) +} + +async fn fetch_metadata_http(url: &Uri) -> color_eyre::Result { + let client = HttpClientBuilder::default().build(url.to_string())?; + + Ok(client.request::("state_getMetadata", None).await?) +} + +async fn fetch_metadata(url: &Uri) -> color_eyre::Result<(String, Vec)> { + let hex_data = match url.scheme_str() { + Some("http") => fetch_metadata_http(url).await, + Some("ws") | Some("wss") => fetch_metadata_ws(url).await, + invalid_scheme => { + let scheme = invalid_scheme.unwrap_or("no scheme"); + Err(eyre::eyre!(format!( + "`{}` not supported, expects 'http', 'ws', or 'wss'", + scheme + ))) + } + }?; + let bytes = hex::decode(hex_data.trim_start_matches("0x"))?; Ok((hex_data, bytes))