Skip to content

Commit

Permalink
Add support for ws in subxt-cli (#579)
Browse files Browse the repository at this point in the history
* cli: Support ws or wss connection via jsonrpsee

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Handle `http` connections via `ureq`

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Use Jsonrpsee HTTP client

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Remove `node.to_string()` calls

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
  • Loading branch information
lexnv committed Jun 24, 2022
1 parent 6d7a6c9 commit f97b896
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 47 deletions.
8 changes: 4 additions & 4 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"] }
115 changes: 72 additions & 43 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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::Url>,
url: Option<Uri>,
/// The path to the encoded metadata file.
#[structopt(short, long, parse(from_os_str))]
file: Option<PathBuf>,
Expand All @@ -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<url::Url>,
nodes: Vec<Uri>,
/// Check the compatibility of metadata for a particular pallet.
///
/// ### Note
Expand All @@ -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" => {
Expand Down Expand Up @@ -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::<Uri>()
.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 {
Expand All @@ -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());
}
}
}
Expand All @@ -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<String, Vec<String>> = 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!(
Expand All @@ -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<RuntimeMetadataV14> {
let (_, bytes) = fetch_metadata(url)?;
async fn fetch_runtime_metadata(url: &Uri) -> color_eyre::Result<RuntimeMetadataV14> {
let (_, bytes) = fetch_metadata(url).await?;

let metadata = <RuntimeMetadataPrefixed as Decode>::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
))
Expand All @@ -249,28 +261,45 @@ fn fetch_runtime_metadata(url: &url::Url) -> color_eyre::Result<RuntimeMetadataV
_ => {
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<u8>)> {
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<String> {
let (sender, receiver) = WsTransportClientBuilder::default()
.build(url.to_string().parse::<Uri>().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<String> {
let client = HttpClientBuilder::default().build(url.to_string())?;

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

async fn fetch_metadata(url: &Uri) -> color_eyre::Result<(String, Vec<u8>)> {
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))
Expand Down

0 comments on commit f97b896

Please sign in to comment.