From db2659a952d52241904d012d52cc20b4f75b1f6a Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 26 Sep 2022 11:49:15 +0100 Subject: [PATCH 1/3] refactor CLI commands for easier expansion --- cli/Cargo.toml | 2 +- cli/src/commands/codegen.rs | 77 ++++++++ cli/src/commands/compatibility.rs | 132 +++++++++++++ cli/src/commands/metadata.rs | 96 +++++++++ cli/src/commands/mod.rs | 3 + cli/src/main.rs | 312 ++---------------------------- 6 files changed, 321 insertions(+), 301 deletions(-) create mode 100644 cli/src/commands/codegen.rs create mode 100644 cli/src/commands/compatibility.rs create mode 100644 cli/src/commands/metadata.rs create mode 100644 cli/src/commands/mod.rs diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 332cadc8a7..8ce09cffb7 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -20,7 +20,7 @@ subxt-codegen = { version = "0.24.0", path = "../codegen" } # perform node compatibility subxt-metadata = { version = "0.24.0", path = "../metadata" } # parse command line args -structopt = "0.3.25" +clap = { version = "3.2.22", features = ["derive"] } # colourful error reports color-eyre = "0.6.1" # serialize the metadata diff --git a/cli/src/commands/codegen.rs b/cli/src/commands/codegen.rs new file mode 100644 index 0000000000..fe0a2d3e3d --- /dev/null +++ b/cli/src/commands/codegen.rs @@ -0,0 +1,77 @@ +use color_eyre::eyre; +use frame_metadata::RuntimeMetadataPrefixed; +use jsonrpsee::client_transport::ws::Uri; +use scale::{ + Decode, + Input, +}; +use std::{ + fs, + io::Read, + path::PathBuf, +}; +use subxt_codegen::DerivesRegistry; +use clap::Parser as ClapParser; + +/// Generate runtime API client code from metadata. +/// +/// # Example (with code formatting) +/// +/// `subxt codegen | rustfmt --edition=2018 --emit=stdout` +#[derive(Debug, ClapParser)] +pub struct Opts { + /// The url of the substrate node to query for metadata for codegen. + #[clap(name = "url", long, parse(try_from_str))] + url: Option, + /// The path to the encoded metadata file. + #[clap(short, long, parse(from_os_str))] + file: Option, + /// Additional derives + #[clap(long = "derive")] + derives: Vec, +} + +pub async fn run(opts: Opts) -> color_eyre::Result<()> { + if let Some(file) = opts.file.as_ref() { + if opts.url.is_some() { + eyre::bail!("specify one of `--url` or `--file` but not both") + }; + + let mut file = fs::File::open(file)?; + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes)?; + codegen(&mut &bytes[..], opts.derives)?; + return Ok(()) + } + + let url = opts.url.unwrap_or_else(|| { + "http://localhost:9933" + .parse::() + .expect("default url is valid") + }); + let (_, bytes) = super::metadata::fetch_metadata(&url).await?; + codegen(&mut &bytes[..], opts.derives)?; + Ok(()) +} + +fn codegen( + encoded: &mut I, + raw_derives: Vec, +) -> color_eyre::Result<()> { + let metadata = ::decode(encoded)?; + let generator = subxt_codegen::RuntimeGenerator::new(metadata); + let item_mod = syn::parse_quote!( + pub mod api {} + ); + + let p = raw_derives + .iter() + .map(|raw| syn::parse_str(raw)) + .collect::, _>>()?; + let mut derives = DerivesRegistry::default(); + derives.extend_for_all(p.into_iter()); + + let runtime_api = generator.generate_runtime(item_mod, derives); + println!("{}", runtime_api); + Ok(()) +} diff --git a/cli/src/commands/compatibility.rs b/cli/src/commands/compatibility.rs new file mode 100644 index 0000000000..1b357d0929 --- /dev/null +++ b/cli/src/commands/compatibility.rs @@ -0,0 +1,132 @@ +use color_eyre::eyre::{ + self, + WrapErr, +}; +use frame_metadata::{ + RuntimeMetadata, + RuntimeMetadataPrefixed, + RuntimeMetadataV14, + META_RESERVED, +}; +use jsonrpsee::client_transport::ws::Uri; +use scale::Decode; +use serde::{ + Deserialize, + Serialize, +}; +use std::collections::HashMap; +use subxt_metadata::{ + get_metadata_hash, + get_pallet_hash, +}; +use clap::Parser as ClapParser; + +/// Verify metadata compatibility between substrate nodes. +#[derive(Debug, ClapParser)] +pub struct Opts { + /// Urls of the substrate nodes to verify for metadata compatibility. + #[clap(name = "nodes", long, use_delimiter = true, parse(try_from_str))] + nodes: Vec, + /// Check the compatibility of metadata for a particular pallet. + /// + /// ### Note + /// The validation will omit the full metadata check and focus instead on the pallet. + #[clap(long, parse(try_from_str))] + pallet: Option, +} + +pub async fn run(opts: Opts) -> color_eyre::Result<()> { + match opts.pallet { + Some(pallet) => { + handle_pallet_metadata(opts.nodes.as_slice(), pallet.as_str()).await + } + None => handle_full_metadata(opts.nodes.as_slice()).await, + } +} + +async fn handle_pallet_metadata(nodes: &[Uri], name: &str) -> color_eyre::Result<()> { + #[derive(Serialize, Deserialize, Default)] + #[serde(rename_all = "camelCase")] + struct CompatibilityPallet { + pallet_present: HashMap>, + pallet_not_found: Vec, + } + + let mut compatibility: CompatibilityPallet = Default::default(); + for node in nodes.iter() { + 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, hex_hash); + + compatibility + .pallet_present + .entry(hex_hash) + .or_insert_with(Vec::new) + .push(node.to_string()); + } + None => { + compatibility.pallet_not_found.push(node.to_string()); + } + } + } + + println!( + "\nCompatible nodes by pallet\n{}", + serde_json::to_string_pretty(&compatibility) + .context("Failed to parse compatibility map")? + ); + + Ok(()) +} + +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).await?; + let hash = get_metadata_hash(&metadata); + let hex_hash = hex::encode(hash); + println!("Node {:?} has metadata hash {:?}", node, hex_hash,); + + compatibility_map + .entry(hex_hash) + .or_insert_with(Vec::new) + .push(node.to_string()); + } + + println!( + "\nCompatible nodes\n{}", + serde_json::to_string_pretty(&compatibility_map) + .context("Failed to parse compatibility map")? + ); + + Ok(()) +} + +async fn fetch_runtime_metadata(url: &Uri) -> color_eyre::Result { + let (_, bytes) = super::metadata::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, + metadata.0, + META_RESERVED + )) + } + + match metadata.1 { + RuntimeMetadata::V14(v14) => Ok(v14), + _ => { + Err(eyre::eyre!( + "Node {:?} with unsupported metadata version: {:?}", + url, + metadata.1 + )) + } + } +} \ No newline at end of file diff --git a/cli/src/commands/metadata.rs b/cli/src/commands/metadata.rs new file mode 100644 index 0000000000..6aafc2aaa7 --- /dev/null +++ b/cli/src/commands/metadata.rs @@ -0,0 +1,96 @@ +use color_eyre::eyre; +use frame_metadata::RuntimeMetadataPrefixed; +use jsonrpsee::{ + async_client::ClientBuilder, + client_transport::ws::{ + Uri, + WsTransportClientBuilder, + }, + core::{ + client::ClientT, + Error, + }, + http_client::HttpClientBuilder, + rpc_params, +}; +use std::io::{ self, Write }; +use scale::Decode; +use clap::Parser as ClapParser; + +/// Download metadata from a substrate node, for use with `subxt` codegen. +#[derive(Debug, ClapParser)] +pub struct Opts { + /// The url of the substrate node to query for metadata. + #[clap( + name = "url", + long, + parse(try_from_str), + default_value = "http://localhost:9933" + )] + url: Uri, + /// The format of the metadata to display: `json`, `hex` or `bytes`. + #[clap(long, short, default_value = "bytes")] + format: String, +} + +pub async fn run(opts: Opts) -> color_eyre::Result<()> { + let (hex_data, bytes) = fetch_metadata(&opts.url).await?; + + match opts.format.as_str() { + "json" => { + let metadata = + ::decode(&mut &bytes[..])?; + let json = serde_json::to_string_pretty(&metadata)?; + println!("{}", json); + Ok(()) + } + "hex" => { + println!("{}", hex_data); + Ok(()) + } + "bytes" => Ok(io::stdout().write_all(&bytes)?), + _ => { + Err(eyre::eyre!( + "Unsupported format `{}`, expected `json`, `hex` or `bytes`", + opts.format + )) + } + } +} + +pub 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)) +} + +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?) +} \ No newline at end of file diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs new file mode 100644 index 0000000000..708f03bda6 --- /dev/null +++ b/cli/src/commands/mod.rs @@ -0,0 +1,3 @@ +pub mod codegen; +pub mod compatibility; +pub mod metadata; \ No newline at end of file diff --git a/cli/src/main.rs b/cli/src/main.rs index 6ee42f1ff0..bdfcf7422d 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -4,313 +4,25 @@ #![deny(unused_crate_dependencies)] -use color_eyre::eyre::{ - self, - WrapErr, -}; -use frame_metadata::{ - RuntimeMetadata, - RuntimeMetadataPrefixed, - 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, -}; -use serde::{ - Deserialize, - Serialize, -}; -use std::{ - collections::HashMap, - fs, - io::{ - self, - Read, - Write, - }, - path::PathBuf, -}; -use structopt::StructOpt; -use subxt_codegen::DerivesRegistry; -use subxt_metadata::{ - get_metadata_hash, - get_pallet_hash, -}; +mod commands; +use clap::Parser as ClapParser; -/// Utilities for working with substrate metadata for subxt. -#[derive(Debug, StructOpt)] -struct Opts { - #[structopt(subcommand)] - command: Command, -} - -#[derive(Debug, StructOpt)] +/// Subxt utilities for interacting with Substrate based nodes. +#[derive(Debug, ClapParser)] enum Command { - /// Download metadata from a substrate node, for use with `subxt` codegen. - #[structopt(name = "metadata")] - Metadata { - /// The url of the substrate node to query for metadata. - #[structopt( - name = "url", - long, - parse(try_from_str), - default_value = "http://localhost:9933" - )] - url: Uri, - /// The format of the metadata to display: `json`, `hex` or `bytes`. - #[structopt(long, short, default_value = "bytes")] - format: String, - }, - /// Generate runtime API client code from metadata. - /// - /// # Example (with code formatting) - /// - /// `subxt codegen | rustfmt --edition=2018 --emit=stdout` - Codegen { - /// The url of the substrate node to query for metadata for codegen. - #[structopt(name = "url", long, parse(try_from_str))] - url: Option, - /// The path to the encoded metadata file. - #[structopt(short, long, parse(from_os_str))] - file: Option, - /// Additional derives - #[structopt(long = "derive")] - derives: Vec, - }, - /// Verify metadata compatibility between substrate nodes. - Compatibility { - /// Urls of the substrate nodes to verify for metadata compatibility. - #[structopt(name = "nodes", long, use_delimiter = true, parse(try_from_str))] - nodes: Vec, - /// Check the compatibility of metadata for a particular pallet. - /// - /// ### Note - /// The validation will omit the full metadata check and focus instead on the pallet. - #[structopt(long, parse(try_from_str))] - pallet: Option, - }, + Metadata(commands::metadata::Opts), + Codegen(commands::codegen::Opts), + Compatibility(commands::compatibility::Opts), } #[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).await?; + let args = Command::parse(); - match format.as_str() { - "json" => { - let metadata = - ::decode(&mut &bytes[..])?; - let json = serde_json::to_string_pretty(&metadata)?; - println!("{}", json); - Ok(()) - } - "hex" => { - println!("{}", hex_data); - Ok(()) - } - "bytes" => Ok(io::stdout().write_all(&bytes)?), - _ => { - Err(eyre::eyre!( - "Unsupported format `{}`, expected `json`, `hex` or `bytes`", - format - )) - } - } - } - Command::Codegen { url, file, derives } => { - if let Some(file) = file.as_ref() { - if url.is_some() { - eyre::bail!("specify one of `--url` or `--file` but not both") - }; - - let mut file = fs::File::open(file)?; - let mut bytes = Vec::new(); - file.read_to_end(&mut bytes)?; - codegen(&mut &bytes[..], derives)?; - return Ok(()) - } - - let url = url.unwrap_or_else(|| { - "http://localhost:9933" - .parse::() - .expect("default url is valid") - }); - 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()).await - } - None => handle_full_metadata(nodes.as_slice()).await, - } - } + match args { + Command::Metadata(opts) => commands::metadata::run(opts).await, + Command::Codegen(opts) => commands::codegen::run(opts).await, + Command::Compatibility(opts) => commands::compatibility::run(opts).await, } } - -async fn handle_pallet_metadata(nodes: &[Uri], name: &str) -> color_eyre::Result<()> { - #[derive(Serialize, Deserialize, Default)] - #[serde(rename_all = "camelCase")] - struct CompatibilityPallet { - pallet_present: HashMap>, - pallet_not_found: Vec, - } - - let mut compatibility: CompatibilityPallet = Default::default(); - for node in nodes.iter() { - 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, hex_hash); - - compatibility - .pallet_present - .entry(hex_hash) - .or_insert_with(Vec::new) - .push(node.to_string()); - } - None => { - compatibility.pallet_not_found.push(node.to_string()); - } - } - } - - println!( - "\nCompatible nodes by pallet\n{}", - serde_json::to_string_pretty(&compatibility) - .context("Failed to parse compatibility map")? - ); - - Ok(()) -} - -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).await?; - let hash = get_metadata_hash(&metadata); - let hex_hash = hex::encode(hash); - println!("Node {:?} has metadata hash {:?}", node, hex_hash,); - - compatibility_map - .entry(hex_hash) - .or_insert_with(Vec::new) - .push(node.to_string()); - } - - println!( - "\nCompatible nodes\n{}", - serde_json::to_string_pretty(&compatibility_map) - .context("Failed to parse compatibility map")? - ); - - Ok(()) -} - -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, - metadata.0, - META_RESERVED - )) - } - - match metadata.1 { - RuntimeMetadata::V14(v14) => Ok(v14), - _ => { - Err(eyre::eyre!( - "Node {:?} with unsupported metadata version: {:?}", - url, - metadata.1 - )) - } - } -} - -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)) -} - -fn codegen( - encoded: &mut I, - raw_derives: Vec, -) -> color_eyre::Result<()> { - let metadata = ::decode(encoded)?; - let generator = subxt_codegen::RuntimeGenerator::new(metadata); - let item_mod = syn::parse_quote!( - pub mod api {} - ); - - let p = raw_derives - .iter() - .map(|raw| syn::parse_str(raw)) - .collect::, _>>()?; - let mut derives = DerivesRegistry::default(); - derives.extend_for_all(p.into_iter()); - - let runtime_api = generator.generate_runtime(item_mod, derives); - println!("{}", runtime_api); - Ok(()) -} From 9dcffdaa57a42228769e3cbc34f33bb430e4485c Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 26 Sep 2022 11:50:07 +0100 Subject: [PATCH 2/3] add license headers --- cli/src/commands/codegen.rs | 4 ++++ cli/src/commands/compatibility.rs | 4 ++++ cli/src/commands/metadata.rs | 4 ++++ cli/src/commands/mod.rs | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/cli/src/commands/codegen.rs b/cli/src/commands/codegen.rs index fe0a2d3e3d..f35ab31567 100644 --- a/cli/src/commands/codegen.rs +++ b/cli/src/commands/codegen.rs @@ -1,3 +1,7 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + use color_eyre::eyre; use frame_metadata::RuntimeMetadataPrefixed; use jsonrpsee::client_transport::ws::Uri; diff --git a/cli/src/commands/compatibility.rs b/cli/src/commands/compatibility.rs index 1b357d0929..070fbd53b3 100644 --- a/cli/src/commands/compatibility.rs +++ b/cli/src/commands/compatibility.rs @@ -1,3 +1,7 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + use color_eyre::eyre::{ self, WrapErr, diff --git a/cli/src/commands/metadata.rs b/cli/src/commands/metadata.rs index 6aafc2aaa7..7499c0b35b 100644 --- a/cli/src/commands/metadata.rs +++ b/cli/src/commands/metadata.rs @@ -1,3 +1,7 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + use color_eyre::eyre; use frame_metadata::RuntimeMetadataPrefixed; use jsonrpsee::{ diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index 708f03bda6..536c61eda2 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -1,3 +1,7 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + pub mod codegen; pub mod compatibility; pub mod metadata; \ No newline at end of file From 766c00a58a246afcfeb54365c9f41845f643d695 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 26 Sep 2022 14:52:40 +0100 Subject: [PATCH 3/3] cargo fmt --- cli/src/commands/codegen.rs | 2 +- cli/src/commands/compatibility.rs | 4 ++-- cli/src/commands/metadata.rs | 12 +++++++----- cli/src/commands/mod.rs | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/cli/src/commands/codegen.rs b/cli/src/commands/codegen.rs index f35ab31567..7b46226b45 100644 --- a/cli/src/commands/codegen.rs +++ b/cli/src/commands/codegen.rs @@ -2,6 +2,7 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. +use clap::Parser as ClapParser; use color_eyre::eyre; use frame_metadata::RuntimeMetadataPrefixed; use jsonrpsee::client_transport::ws::Uri; @@ -15,7 +16,6 @@ use std::{ path::PathBuf, }; use subxt_codegen::DerivesRegistry; -use clap::Parser as ClapParser; /// Generate runtime API client code from metadata. /// diff --git a/cli/src/commands/compatibility.rs b/cli/src/commands/compatibility.rs index 070fbd53b3..8a600b8b9f 100644 --- a/cli/src/commands/compatibility.rs +++ b/cli/src/commands/compatibility.rs @@ -2,6 +2,7 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. +use clap::Parser as ClapParser; use color_eyre::eyre::{ self, WrapErr, @@ -23,7 +24,6 @@ use subxt_metadata::{ get_metadata_hash, get_pallet_hash, }; -use clap::Parser as ClapParser; /// Verify metadata compatibility between substrate nodes. #[derive(Debug, ClapParser)] @@ -133,4 +133,4 @@ async fn fetch_runtime_metadata(url: &Uri) -> color_eyre::Result color_eyre::Result<()> { match opts.format.as_str() { "json" => { - let metadata = - ::decode(&mut &bytes[..])?; + let metadata = ::decode(&mut &bytes[..])?; let json = serde_json::to_string_pretty(&metadata)?; println!("{}", json); Ok(()) @@ -97,4 +99,4 @@ 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?) -} \ No newline at end of file +} diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index 536c61eda2..3b5093741b 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -4,4 +4,4 @@ pub mod codegen; pub mod compatibility; -pub mod metadata; \ No newline at end of file +pub mod metadata;