mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-08 13:38:11 +00:00
fc5a18aaa0
* integrate scale-typegen, remove types mod * reintroduce default substitutes and derives * support runtime_types only again * generating polkadot.rs ok * update scale-typegen to discrete error types * scale-typegen-api-changes * add note about UncheckedExtrinsic in default substitutes * add resursive attributes and derives * adjust example where Clone bound recursive * move scale-typegen dependency to workspace * expose default typegen settings * lightclient: Fix wasm socket closure called after being dropped (#1289) * lightclient: Close wasm socket while dropping from connecting state Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Construct one time only closures Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * testing: Enable console logs for lightclient WASM testing Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Separate wakes and check connectivity on poll_read Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Close the socket depending on internal state Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Revert "lightclient: Separate wakes and check connectivity on poll_read" This reverts commit 866094001d4c0b119a80ed681a74b323f74eae1b. * lightclient: Return pending if socket is opening from poll_read Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Close the socket on `poll_close` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Reset closures on Drop to avoid recursive invokation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * lightclient: Close the socket if not already closing Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> --------- Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * workflows: Install rustup component for building substrate (#1295) Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * cli: Command to fetch chainSpec and optimise its size (#1278) * cli: Add chainSpec command Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * cli/chainSpec: Move to dedicated module Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * cli: Compute the state root hash Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * cli: Remove code substitutes Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * artifacts: Update polkadot.json Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * scripts: Generate the chain spec Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * cli: Remove testing artifacts Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * cli: Fix clippy Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * cli: Apply rustfmt Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * cli: Introduce feature flag for smoldot dependency Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * cli: Rename chain-spec to chain-spec-pruning Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * scripts: Update chain-spec command Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> --------- Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * remove comments and unused args * Update substrate- and signer-related dependencies (#1297) * update crypto dependencies, adjust keypair * add scale_info::TypeInfo derive in some places * add multi signature derive * fix lock file * fix lock file again :| * adjust to new interface in scale-typegen * use released scale typegen * reintroduce type aliases * introduce type aliases again using scale-typegen * cargo fmt and clippy * reconcile changes with master branch * update polkadot.rs * bump scale-typgen to fix substitution * implemented Alex suggestions, regenerated polkadot.rs (did not change) * resolve conflicts in Cargo.lock * make expect messages more clear * correct typos --------- Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
464 lines
16 KiB
Rust
464 lines
16 KiB
Rust
use clap::Args;
|
|
use codec::Decode;
|
|
|
|
use frame_metadata::RuntimeMetadataPrefixed;
|
|
use std::collections::HashMap;
|
|
use std::hash::Hash;
|
|
|
|
use crate::utils::{validate_url_security, 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,
|
|
/// Allow insecure URLs e.g. URLs starting with ws:// or http:// without SSL encryption
|
|
#[clap(long, short)]
|
|
allow_insecure: bool,
|
|
}
|
|
|
|
pub async fn run(opts: Opts, output: &mut impl std::io::Write) -> color_eyre::Result<()> {
|
|
validate_url_security(opts.metadata_or_url_1.url.as_ref(), opts.allow_insecure)?;
|
|
validate_url_security(opts.metadata_or_url_2.url.as_ref(), opts.allow_insecure)?;
|
|
|
|
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 is in metadata; qed");
|
|
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 is in metadata; qed");
|
|
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 is in metadata; qed")
|
|
})
|
|
.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 is in metadata; qed")
|
|
})
|
|
.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 is in metadata; qed")
|
|
},
|
|
|e| {
|
|
pallet_metadata_2
|
|
.storage_hash(e.name())
|
|
.expect("storage entry is in metadata; qed")
|
|
},
|
|
|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 is in metadata; qed")
|
|
},
|
|
|e| {
|
|
pallet_metadata_2
|
|
.call_hash(&e.name)
|
|
.expect("call is in metadata; qed")
|
|
},
|
|
|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 is in metadata; qed")
|
|
},
|
|
|e| {
|
|
pallet_metadata_2
|
|
.constant_hash(e.name())
|
|
.expect("constant is in metadata; qed")
|
|
},
|
|
|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);
|
|
}
|
|
}
|