Files
pezkuwi-subxt/cli/src/commands/diff.rs
T
Tadeo Hepperle 12e343449e Make the codegen more accessible for running in WASM (#1154)
* minor api changes

* parsing instead of format_ident!

* fix self issue

* expose types on typegen

* web wasm support via feature flag

* fmt and clippy

* small adjustments

* remove exposing of types() in type generator

* adjust compile error

* remove little any flag

* Need access to the type registry from the typegen again

* Bump proc-macro2 from 1.0.66 to 1.0.67 (#1164)

Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.66 to 1.0.67.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.66...1.0.67)

---
updated-dependencies:
- dependency-name: proc-macro2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump serde_json from 1.0.106 to 1.0.107 (#1166)

Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.106 to 1.0.107.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.106...v1.0.107)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump trybuild from 1.0.84 to 1.0.85 (#1167)

Bumps [trybuild](https://github.com/dtolnay/trybuild) from 1.0.84 to 1.0.85.
- [Release notes](https://github.com/dtolnay/trybuild/releases)
- [Commits](https://github.com/dtolnay/trybuild/compare/1.0.84...1.0.85)

---
updated-dependencies:
- dependency-name: trybuild
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump Swatinem/rust-cache from 2.6.2 to 2.7.0 (#1168)

Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.6.2 to 2.7.0.
- [Release notes](https://github.com/swatinem/rust-cache/releases)
- [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md)
- [Commits](https://github.com/swatinem/rust-cache/compare/e207df5d269b42b69c8bc5101da26f7d31feddb4...a95ba195448af2da9b00fb742d14ffaaf3c21f43)

---
updated-dependencies:
- dependency-name: Swatinem/rust-cache
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump jsonrpsee from 0.20.0 to 0.20.1 (#1165)

Bumps [jsonrpsee](https://github.com/paritytech/jsonrpsee) from 0.20.0 to 0.20.1.
- [Release notes](https://github.com/paritytech/jsonrpsee/releases)
- [Changelog](https://github.com/paritytech/jsonrpsee/blob/v0.20.1/CHANGELOG.md)
- [Commits](https://github.com/paritytech/jsonrpsee/compare/v0.20.0...v0.20.1)

---
updated-dependencies:
- dependency-name: jsonrpsee
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump clap from 4.4.2 to 4.4.3 (#1163)

Bumps [clap](https://github.com/clap-rs/clap) from 4.4.2 to 4.4.3.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.2...v4.4.3)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix double quote import introduced by merge

* clippy

* put ensure_unique_types on metadata, revert toml changes

* fmt

* solve frame_metadata import

* tidy fetch-metadata and non_exhaustive on errors

* remove the getrandom thing

* fix toml autoformatting

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: James Wilson <james@jsdw.me>
2023-09-20 13:16:05 +02:00

458 lines
15 KiB
Rust

use clap::Args;
use codec::Decode;
use frame_metadata::RuntimeMetadataPrefixed;
use std::collections::HashMap;
use std::hash::Hash;
use crate::utils::FileOrUrl;
use color_eyre::owo_colors::OwoColorize;
use scale_info::form::PortableForm;
use scale_info::Variant;
use subxt_metadata::{
ConstantMetadata, Metadata, PalletMetadata, RuntimeApiMetadata, StorageEntryMetadata,
};
/// Explore the differences between two nodes
///
/// # Example
/// ```
/// subxt diff ./artifacts/polkadot_metadata_small.scale ./artifacts/polkadot_metadata_tiny.scale
/// subxt diff ./artifacts/polkadot_metadata_small.scale wss://rpc.polkadot.io:443
/// ```
#[derive(Debug, Args)]
#[command(author, version, about, long_about = None)]
pub struct Opts {
/// metadata file or node URL
metadata_or_url_1: FileOrUrl,
/// metadata file or node URL
metadata_or_url_2: FileOrUrl,
}
pub async fn run(opts: Opts, output: &mut impl std::io::Write) -> color_eyre::Result<()> {
let (entry_1_metadata, entry_2_metadata) = get_metadata(&opts).await?;
let metadata_diff = MetadataDiff::construct(&entry_1_metadata, &entry_2_metadata);
if metadata_diff.is_empty() {
writeln!(output, "No difference in metadata found.")?;
return Ok(());
}
if !metadata_diff.pallets.is_empty() {
writeln!(output, "Pallets:")?;
for diff in metadata_diff.pallets {
match diff {
Diff::Added(new) => {
writeln!(output, "{}", format!(" + {}", new.name()).green())?
}
Diff::Removed(old) => {
writeln!(output, "{}", format!(" - {}", old.name()).red())?
}
Diff::Changed { from, to } => {
writeln!(output, "{}", format!(" ~ {}", from.name()).yellow())?;
let pallet_diff = PalletDiff::construct(&from, &to);
if !pallet_diff.calls.is_empty() {
writeln!(output, " Calls:")?;
for diff in pallet_diff.calls {
match diff {
Diff::Added(new) => writeln!(
output,
"{}",
format!(" + {}", &new.name).green()
)?,
Diff::Removed(old) => writeln!(
output,
"{}",
format!(" - {}", &old.name).red()
)?,
Diff::Changed { from, to: _ } => {
writeln!(
output,
"{}",
format!(" ~ {}", &from.name).yellow()
)?;
}
}
}
}
if !pallet_diff.constants.is_empty() {
writeln!(output, " Constants:")?;
for diff in pallet_diff.constants {
match diff {
Diff::Added(new) => writeln!(
output,
"{}",
format!(" + {}", new.name()).green()
)?,
Diff::Removed(old) => writeln!(
output,
"{}",
format!(" - {}", old.name()).red()
)?,
Diff::Changed { from, to: _ } => writeln!(
output,
"{}",
format!(" ~ {}", from.name()).yellow()
)?,
}
}
}
if !pallet_diff.storage_entries.is_empty() {
writeln!(output, " Storage Entries:")?;
for diff in pallet_diff.storage_entries {
match diff {
Diff::Added(new) => writeln!(
output,
"{}",
format!(" + {}", new.name()).green()
)?,
Diff::Removed(old) => writeln!(
output,
"{}",
format!(" - {}", old.name()).red()
)?,
Diff::Changed { from, to } => {
let storage_diff = StorageEntryDiff::construct(
from,
to,
&entry_1_metadata,
&entry_2_metadata,
);
writeln!(
output,
"{}",
format!(
" ~ {} (Changed: {})",
from.name(),
storage_diff.to_strings().join(", ")
)
.yellow()
)?;
}
}
}
}
}
}
}
}
if !metadata_diff.runtime_apis.is_empty() {
writeln!(output, "Runtime APIs:")?;
for diff in metadata_diff.runtime_apis {
match diff {
Diff::Added(new) => {
writeln!(output, "{}", format!(" + {}", new.name()).green())?
}
Diff::Removed(old) => {
writeln!(output, "{}", format!(" - {}", old.name()).red())?
}
Diff::Changed { from, to: _ } => {
writeln!(output, "{}", format!(" ~ {}", from.name()).yellow())?
}
}
}
}
Ok(())
}
struct MetadataDiff<'a> {
pallets: Vec<Diff<PalletMetadata<'a>>>,
runtime_apis: Vec<Diff<RuntimeApiMetadata<'a>>>,
}
impl<'a> MetadataDiff<'a> {
fn construct(metadata_1: &'a Metadata, metadata_2: &'a Metadata) -> MetadataDiff<'a> {
let pallets = pallet_differences(metadata_1, metadata_2);
let runtime_apis = runtime_api_differences(metadata_1, metadata_2);
MetadataDiff {
pallets,
runtime_apis,
}
}
fn is_empty(&self) -> bool {
self.pallets.is_empty() && self.runtime_apis.is_empty()
}
}
#[derive(Default)]
struct PalletDiff<'a> {
calls: Vec<Diff<&'a Variant<PortableForm>>>,
constants: Vec<Diff<&'a ConstantMetadata>>,
storage_entries: Vec<Diff<&'a StorageEntryMetadata>>,
}
impl<'a> PalletDiff<'a> {
fn construct(
pallet_metadata_1: &'a PalletMetadata<'a>,
pallet_metadata_2: &'a PalletMetadata<'a>,
) -> PalletDiff<'a> {
let calls = calls_differences(pallet_metadata_1, pallet_metadata_2);
let constants = constants_differences(pallet_metadata_1, pallet_metadata_2);
let storage_entries = storage_differences(pallet_metadata_1, pallet_metadata_2);
PalletDiff {
calls,
constants,
storage_entries,
}
}
}
struct StorageEntryDiff {
key_different: bool,
value_different: bool,
default_different: bool,
modifier_different: bool,
}
impl StorageEntryDiff {
fn construct(
storage_entry_1: &StorageEntryMetadata,
storage_entry_2: &StorageEntryMetadata,
metadata_1: &Metadata,
metadata_2: &Metadata,
) -> Self {
let value_1_ty_id = storage_entry_1.entry_type().value_ty();
let value_1_hash = metadata_1
.type_hash(value_1_ty_id)
.expect("type should be present");
let value_2_ty_id = storage_entry_2.entry_type().value_ty();
let value_2_hash = metadata_1
.type_hash(value_2_ty_id)
.expect("type should be present");
let value_different = value_1_hash != value_2_hash;
let key_1_hash = storage_entry_1
.entry_type()
.key_ty()
.map(|key_ty| {
metadata_1
.type_hash(key_ty)
.expect("type should be present")
})
.unwrap_or_default();
let key_2_hash = storage_entry_2
.entry_type()
.key_ty()
.map(|key_ty| {
metadata_2
.type_hash(key_ty)
.expect("type should be present")
})
.unwrap_or_default();
let key_different = key_1_hash != key_2_hash;
StorageEntryDiff {
key_different,
value_different,
default_different: storage_entry_1.default_bytes() != storage_entry_2.default_bytes(),
modifier_different: storage_entry_1.modifier() != storage_entry_2.modifier(),
}
}
fn to_strings(&self) -> Vec<&str> {
let mut strings = Vec::<&str>::new();
if self.key_different {
strings.push("key type");
}
if self.value_different {
strings.push("value type");
}
if self.modifier_different {
strings.push("modifier");
}
if self.default_different {
strings.push("default value");
}
strings
}
}
async fn get_metadata(opts: &Opts) -> color_eyre::Result<(Metadata, Metadata)> {
let bytes = opts.metadata_or_url_1.fetch().await?;
let entry_1_metadata: Metadata =
RuntimeMetadataPrefixed::decode(&mut &bytes[..])?.try_into()?;
let bytes = opts.metadata_or_url_2.fetch().await?;
let entry_2_metadata: Metadata =
RuntimeMetadataPrefixed::decode(&mut &bytes[..])?.try_into()?;
Ok((entry_1_metadata, entry_2_metadata))
}
fn storage_differences<'a>(
pallet_metadata_1: &'a PalletMetadata<'a>,
pallet_metadata_2: &'a PalletMetadata<'a>,
) -> Vec<Diff<&'a StorageEntryMetadata>> {
diff(
pallet_metadata_1
.storage()
.map(|s| s.entries())
.unwrap_or_default(),
pallet_metadata_2
.storage()
.map(|s| s.entries())
.unwrap_or_default(),
|e| {
pallet_metadata_1
.storage_hash(e.name())
.expect("storage entry should be present")
},
|e| {
pallet_metadata_2
.storage_hash(e.name())
.expect("storage entry should be present")
},
|e| e.name(),
)
}
fn calls_differences<'a>(
pallet_metadata_1: &'a PalletMetadata<'a>,
pallet_metadata_2: &'a PalletMetadata<'a>,
) -> Vec<Diff<&'a Variant<PortableForm>>> {
return diff(
pallet_metadata_1.call_variants().unwrap_or_default(),
pallet_metadata_2.call_variants().unwrap_or_default(),
|e| {
pallet_metadata_1
.call_hash(&e.name)
.expect("call should be present")
},
|e| {
pallet_metadata_2
.call_hash(&e.name)
.expect("call should be present")
},
|e| &e.name,
);
}
fn constants_differences<'a>(
pallet_metadata_1: &'a PalletMetadata<'a>,
pallet_metadata_2: &'a PalletMetadata<'a>,
) -> Vec<Diff<&'a ConstantMetadata>> {
diff(
pallet_metadata_1.constants(),
pallet_metadata_2.constants(),
|e| {
pallet_metadata_1
.constant_hash(e.name())
.expect("constant should be present")
},
|e| {
pallet_metadata_2
.constant_hash(e.name())
.expect("constant should be present")
},
|e| e.name(),
)
}
fn runtime_api_differences<'a>(
metadata_1: &'a Metadata,
metadata_2: &'a Metadata,
) -> Vec<Diff<RuntimeApiMetadata<'a>>> {
diff(
metadata_1.runtime_api_traits(),
metadata_2.runtime_api_traits(),
RuntimeApiMetadata::hash,
RuntimeApiMetadata::hash,
RuntimeApiMetadata::name,
)
}
fn pallet_differences<'a>(
metadata_1: &'a Metadata,
metadata_2: &'a Metadata,
) -> Vec<Diff<PalletMetadata<'a>>> {
diff(
metadata_1.pallets(),
metadata_2.pallets(),
PalletMetadata::hash,
PalletMetadata::hash,
PalletMetadata::name,
)
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Diff<T> {
Added(T),
Changed { from: T, to: T },
Removed(T),
}
fn diff<T, C: PartialEq, I: Hash + PartialEq + Eq + Ord>(
items_a: impl IntoIterator<Item = T>,
items_b: impl IntoIterator<Item = T>,
hash_fn_a: impl Fn(&T) -> C,
hash_fn_b: impl Fn(&T) -> C,
key_fn: impl Fn(&T) -> I,
) -> Vec<Diff<T>> {
let mut entries: HashMap<I, (Option<T>, Option<T>)> = HashMap::new();
for t1 in items_a {
let key = key_fn(&t1);
let (e1, _) = entries.entry(key).or_default();
*e1 = Some(t1);
}
for t2 in items_b {
let key = key_fn(&t2);
let (e1, e2) = entries.entry(key).or_default();
// skip all entries with the same hash:
if let Some(e1_inner) = e1 {
let e1_hash = hash_fn_a(e1_inner);
let e2_hash = hash_fn_b(&t2);
if e1_hash == e2_hash {
entries.remove(&key_fn(&t2));
continue;
}
}
*e2 = Some(t2);
}
// sort the values by key before returning
let mut diff_vec_with_keys: Vec<_> = entries.into_iter().collect();
diff_vec_with_keys.sort_by(|a, b| a.0.cmp(&b.0));
diff_vec_with_keys
.into_iter()
.map(|(_, tuple)| match tuple {
(None, None) => panic!("At least one value is inserted when the key exists; qed"),
(Some(old), None) => Diff::Removed(old),
(None, Some(new)) => Diff::Added(new),
(Some(old), Some(new)) => Diff::Changed { from: old, to: new },
})
.collect()
}
#[cfg(test)]
mod test {
use crate::commands::diff::{diff, Diff};
#[test]
fn test_diff_fn() {
let old_pallets = [("Babe", 7), ("Claims", 9), ("Balances", 23)];
let new_pallets = [("Claims", 9), ("Balances", 22), ("System", 3), ("NFTs", 5)];
let hash_fn = |e: &(&str, i32)| e.0.len() as i32 * e.1;
let differences = diff(old_pallets, new_pallets, hash_fn, hash_fn, |e| e.0);
let expected_differences = vec![
Diff::Removed(("Babe", 7)),
Diff::Changed {
from: ("Balances", 23),
to: ("Balances", 22),
},
Diff::Added(("NFTs", 5)),
Diff::Added(("System", 3)),
];
assert_eq!(differences, expected_differences);
}
}