Skip to content

Commit

Permalink
Metadata difference command (#1015)
Browse files Browse the repository at this point in the history
* diffing pallets and runtime apis

* print diff

* clippy fix and format

* change formatting

* fmt

* diff working with storage details

* fix diff

* cargo fmt

* remove printing of node

* change strings

* handle parsing differently

* clippy fix

* cargo fmt

* more abstraction

* clippy fix and fmt

* add unit test and ordering

* fix small issue
  • Loading branch information
tadeohepperle authored Jun 21, 2023
1 parent b4eb406 commit 2a990ed
Show file tree
Hide file tree
Showing 9 changed files with 541 additions and 23 deletions.
464 changes: 464 additions & 0 deletions cli/src/commands/diff.rs

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion cli/src/commands/explore/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ pub enum PalletSubcommand {
Storage(StorageSubcommand),
}

/// cargo run -- explore --file=../artifacts/polkadot_metadata.scale
pub async fn run(opts: Opts, output: &mut impl std::io::Write) -> color_eyre::Result<()> {
// get the metadata
let bytes = opts.file_or_url.fetch().await?;
Expand Down
10 changes: 7 additions & 3 deletions cli/src/commands/explore/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub async fn explore_storage(
};

// if specified call storage entry wrong, show user the storage entries to choose from (but this time as an error):
let Some(storage) = storage_metadata.entries().find(|entry| entry.name().to_lowercase() == entry_name.to_lowercase()) else {
let Some(storage) = storage_metadata.entries().iter().find(|entry| entry.name().to_lowercase() == entry_name.to_lowercase()) else {
let storage_entries = print_available_storage_entries(storage_metadata, pallet_name);
let description = format!("Usage:\n subxt explore {pallet_name} storage <STORAGE_ENTRY>\n view details for a specific storage entry\n\n{storage_entries}");
return Err(eyre!("Storage entry \"{entry_name}\" not found in \"{pallet_name}\" pallet!\n\n{description}"));
Expand Down Expand Up @@ -164,14 +164,18 @@ fn print_available_storage_entries(
storage_metadata: &StorageMetadata,
pallet_name: &str,
) -> String {
if storage_metadata.entries().len() == 0 {
if storage_metadata.entries().is_empty() {
format!("No <STORAGE_ENTRY>'s available in the \"{pallet_name}\" pallet.")
} else {
let mut output = format!(
"Available <STORAGE_ENTRY>'s in the \"{}\" pallet:",
pallet_name
);
let mut strings: Vec<_> = storage_metadata.entries().map(|s| s.name()).collect();
let mut strings: Vec<_> = storage_metadata
.entries()
.iter()
.map(|s| s.name())
.collect();
strings.sort();
for entry in strings {
write!(output, "\n {}", entry).unwrap();
Expand Down
1 change: 1 addition & 0 deletions cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

pub mod codegen;
pub mod compatibility;
pub mod diff;
pub mod explore;
pub mod metadata;
pub mod version;
2 changes: 2 additions & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ enum Command {
Metadata(commands::metadata::Opts),
Codegen(commands::codegen::Opts),
Compatibility(commands::compatibility::Opts),
Diff(commands::diff::Opts),
Version(commands::version::Opts),
Explore(commands::explore::Opts),
}
Expand All @@ -28,6 +29,7 @@ async fn main() -> color_eyre::Result<()> {
Command::Metadata(opts) => commands::metadata::run(opts, &mut output).await,
Command::Codegen(opts) => commands::codegen::run(opts, &mut output).await,
Command::Compatibility(opts) => commands::compatibility::run(opts, &mut output).await,
Command::Diff(opts) => commands::diff::run(opts, &mut output).await,
Command::Version(opts) => commands::version::run(opts, &mut output),
Command::Explore(opts) => commands::explore::run(opts, &mut output).await,
}
Expand Down
27 changes: 26 additions & 1 deletion cli/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
use clap::Args;
use color_eyre::eyre;

use std::str::FromStr;
use std::{fs, io::Read, path::PathBuf};

use subxt_codegen::utils::{MetadataVersion, Uri};

pub mod type_description;
pub mod type_example;

/// The source of the metadata.
#[derive(Debug, Args)]
#[derive(Debug, Args, Clone)]
pub struct FileOrUrl {
/// The url of the substrate node to query for metadata for codegen.
#[clap(long, value_parser)]
Expand Down Expand Up @@ -94,3 +96,26 @@ pub fn with_indent(s: String, indent: usize) -> String {
.collect::<Vec<_>>()
.join("\n")
}

impl FromStr for FileOrUrl {
type Err = &'static str;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let path = std::path::Path::new(s);
if path.exists() {
Ok(FileOrUrl {
url: None,
file: Some(PathBuf::from(s)),
version: None,
})
} else {
Uri::from_str(s)
.map_err(|_| "no path or uri could be crated")
.map(|uri| FileOrUrl {
url: Some(uri),
file: None,
version: None,
})
}
}
}
7 changes: 4 additions & 3 deletions codegen/src/api/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ pub fn generate_storage(
should_gen_docs: bool,
) -> Result<TokenStream2, CodegenError> {
let Some(storage) = pallet.storage() else {
return Ok(quote!())
return Ok(quote!());
};

let storage_fns = storage
.entries()
.iter()
.map(|entry| {
generate_storage_entry_fns(type_gen, pallet, entry, crate_path, should_gen_docs)
})
Expand Down Expand Up @@ -104,7 +105,7 @@ fn generate_storage_entry_fns(
let pallet_name = pallet.name();
let storage_name = storage_entry.name();
let Some(storage_hash) = pallet.storage_hash(storage_name) else {
return Err(CodegenError::MissingStorageMetadata(pallet_name.into(), storage_name.into()))
return Err(CodegenError::MissingStorageMetadata(pallet_name.into(), storage_name.into()));
};

let fn_name = format_ident!("{}", storage_entry.name().to_snake_case());
Expand Down Expand Up @@ -157,7 +158,7 @@ fn generate_storage_entry_fns(
// so expose a function to create this entry, too:
let root_entry_fn = if is_map_type {
let fn_name_root = format_ident!("{}_root", fn_name);
quote! (
quote!(
#docs
pub fn #fn_name_root(
&self,
Expand Down
25 changes: 20 additions & 5 deletions metadata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mod from_into;
mod utils;

use scale_info::{form::PortableForm, PortableRegistry, Variant};
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use utils::ordered_map::OrderedMap;
use utils::variant_index::VariantIndex;
Expand Down Expand Up @@ -138,6 +138,16 @@ impl Metadata {
{
utils::retain::retain_metadata(self, pallet_filter, api_filter);
}

/// Get type hash for a type in the registry
pub fn type_hash(&self, id: u32) -> Option<[u8; 32]> {
self.types.resolve(id)?;
Some(crate::utils::validation::get_type_hash(
&self.types,
id,
&mut HashSet::<u32>::new(),
))
}
}

/// Metadata for a specific pallet.
Expand Down Expand Up @@ -303,8 +313,8 @@ impl StorageMetadata {
}

/// An iterator over the storage entries.
pub fn entries(&self) -> impl ExactSizeIterator<Item = &StorageEntryMetadata> {
self.entries.values().iter()
pub fn entries(&self) -> &[StorageEntryMetadata] {
self.entries.values()
}

/// Return a specific storage entry given its name.
Expand Down Expand Up @@ -387,7 +397,7 @@ pub enum StorageHasher {
}

/// Is the storage entry optional, or does it have a default value.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum StorageEntryModifier {
/// The storage entry returns an `Option<T>`, with `None` if the key is not present.
Optional,
Expand Down Expand Up @@ -490,7 +500,7 @@ pub struct RuntimeApiMetadata<'a> {

impl<'a> RuntimeApiMetadata<'a> {
/// Trait name.
pub fn name(&self) -> &str {
pub fn name(&self) -> &'a str {
&self.inner.name
}
/// Trait documentation.
Expand All @@ -509,6 +519,11 @@ impl<'a> RuntimeApiMetadata<'a> {
pub fn method_hash(&self, method_name: &str) -> Option<[u8; 32]> {
crate::utils::validation::get_runtime_api_hash(self, method_name)
}

/// Return a hash for the runtime API trait.
pub fn hash(&self) -> [u8; 32] {
crate::utils::validation::get_runtime_trait_hash(*self)
}
}

#[derive(Debug, Clone)]
Expand Down
27 changes: 17 additions & 10 deletions metadata/src/utils/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ fn get_type_def_hash(
}

/// Obtain the hash representation of a `scale_info::Type` identified by id.
fn get_type_hash(
pub fn get_type_hash(
registry: &PortableRegistry,
id: u32,
visited_ids: &mut HashSet<u32>,
Expand Down Expand Up @@ -283,7 +283,7 @@ fn get_runtime_method_hash(
}

/// Obtain the hash of all of a runtime API trait, including all of its methods.
fn get_runtime_trait_hash(trait_metadata: RuntimeApiMetadata) -> [u8; HASH_LEN] {
pub fn get_runtime_trait_hash(trait_metadata: RuntimeApiMetadata) -> [u8; HASH_LEN] {
let mut visited_ids = HashSet::new();
let trait_name = &*trait_metadata.inner.name;
let method_bytes = trait_metadata
Expand Down Expand Up @@ -379,14 +379,17 @@ pub fn get_pallet_hash(pallet: PalletMetadata) -> [u8; HASH_LEN] {
let storage_bytes = match pallet.storage() {
Some(storage) => {
let prefix_hash = hash(storage.prefix().as_bytes());
let entries_hash = storage.entries().fold([0u8; HASH_LEN], |bytes, entry| {
// We don't care what order the storage entries occur in, so XOR them together
// to make the order irrelevant.
xor(
bytes,
get_storage_entry_hash(registry, entry, &mut visited_ids),
)
});
let entries_hash = storage
.entries()
.iter()
.fold([0u8; HASH_LEN], |bytes, entry| {
// We don't care what order the storage entries occur in, so XOR them together
// to make the order irrelevant.
xor(
bytes,
get_storage_entry_hash(registry, entry, &mut visited_ids),
)
});
concat_and_hash2(&prefix_hash, &entries_hash)
}
None => [0u8; HASH_LEN],
Expand Down Expand Up @@ -496,6 +499,7 @@ mod tests {
struct A {
pub b: Box<B>,
}

#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
struct B {
Expand All @@ -507,6 +511,7 @@ mod tests {
#[derive(scale_info::TypeInfo)]
// TypeDef::Composite with TypeDef::Array with Typedef::Primitive.
struct AccountId32([u8; HASH_LEN]);

#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
// TypeDef::Variant.
Expand All @@ -525,6 +530,7 @@ mod tests {
// TypeDef::BitSequence.
BitSeq(BitVec<u8, Lsb0>),
}

#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
// Ensure recursive types and TypeDef variants are captured.
Expand All @@ -533,6 +539,7 @@ mod tests {
composite: AccountId32,
type_def: DigestItem,
}

#[allow(dead_code)]
#[derive(scale_info::TypeInfo)]
// Simulate a PalletCallMetadata.
Expand Down

0 comments on commit 2a990ed

Please sign in to comment.