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 support for ws in subxt-cli #579

Merged
merged 4 commits into from
Jun 24, 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
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