diff --git a/metadata/src/lib.rs b/metadata/src/lib.rs index b0186cab66..4ebc68a44e 100644 --- a/metadata/src/lib.rs +++ b/metadata/src/lib.rs @@ -22,8 +22,8 @@ mod utils; use scale_info::{form::PortableForm, PortableRegistry, Variant}; use std::collections::HashMap; use std::sync::Arc; -use utils::ordered_map::OrderedMap; use utils::variant_index::VariantIndex; +use utils::{ordered_map::OrderedMap, validation::outer_enum_hashes::OuterEnumHashes}; type ArcStr = Arc; @@ -163,7 +163,7 @@ impl Metadata { Some(crate::utils::validation::get_type_hash( &self.types, id, - &mut HashMap::new(), + &OuterEnumHashes::empty(), )) } @@ -324,7 +324,7 @@ impl<'a> PalletMetadata<'a> { /// Return a hash for the entire pallet. pub fn hash(&self) -> [u8; HASH_LEN] { - crate::utils::validation::get_pallet_hash(*self) + crate::utils::validation::get_pallet_hash(*self, &OuterEnumHashes::empty()) } } @@ -643,7 +643,7 @@ impl<'a> RuntimeApiMetadata<'a> { /// Return a hash for the runtime API trait. pub fn hash(&self) -> [u8; HASH_LEN] { - crate::utils::validation::get_runtime_trait_hash(*self) + crate::utils::validation::get_runtime_trait_hash(*self, &OuterEnumHashes::empty()) } } @@ -766,8 +766,7 @@ impl<'a> CustomValueMetadata<'a> { /// Calculates the hash for the CustomValueMetadata. pub fn hash(&self) -> [u8; HASH_LEN] { - let mut cache = HashMap::new(); - get_custom_value_hash(self, &mut cache) + get_custom_value_hash(self, &OuterEnumHashes::empty()) } } diff --git a/metadata/src/utils/validation.rs b/metadata/src/utils/validation.rs index 6656c6dbfc..81c50b4593 100644 --- a/metadata/src/utils/validation.rs +++ b/metadata/src/utils/validation.rs @@ -5,15 +5,18 @@ //! Utility functions for metadata validation. use crate::{ - CustomMetadata, CustomValueMetadata, ExtrinsicMetadata, Metadata, OuterEnumsMetadata, - PalletMetadata, RuntimeApiMetadata, RuntimeApiMethodMetadata, StorageEntryMetadata, - StorageEntryType, + CustomMetadata, CustomValueMetadata, ExtrinsicMetadata, Metadata, PalletMetadata, + RuntimeApiMetadata, RuntimeApiMethodMetadata, StorageEntryMetadata, StorageEntryType, }; +use outer_enum_hashes::OuterEnumHashes; use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, TypeDefVariant, Variant}; use std::collections::HashMap; +pub mod outer_enum_hashes; + // The number of bytes our `hash` function produces. pub(crate) const HASH_LEN: usize = 32; +pub type Hash = [u8; HASH_LEN]; /// Internal byte representation for various metadata types utilized for /// generating deterministic hashes between different rust versions. @@ -30,13 +33,13 @@ enum TypeBeingHashed { } /// Hashing function utilized internally. -fn hash(data: &[u8]) -> [u8; HASH_LEN] { +fn hash(data: &[u8]) -> Hash { sp_core_hashing::twox_256(data) } /// XOR two hashes together. Only use this when you don't care about the order /// of the things you're hashing together. -fn xor(a: [u8; HASH_LEN], b: [u8; HASH_LEN]) -> [u8; HASH_LEN] { +fn xor(a: Hash, b: Hash) -> Hash { let mut out = [0u8; HASH_LEN]; for (idx, (a, b)) in a.into_iter().zip(b).enumerate() { out[idx] = a ^ b; @@ -52,7 +55,7 @@ macro_rules! count_idents { } macro_rules! concat_and_hash_n { ($name:ident($($arg:ident)+)) => { - fn $name($($arg: &[u8; HASH_LEN]),+) -> [u8; HASH_LEN] { + fn $name($($arg: &Hash),+) -> Hash { let mut out = [0u8; HASH_LEN * count_idents!($($arg)+)]; let mut start = 0; $( @@ -75,7 +78,8 @@ fn get_field_hash( registry: &PortableRegistry, field: &Field, cache: &mut HashMap, -) -> [u8; HASH_LEN] { + outer_enum_hashes: &OuterEnumHashes, +) -> Hash { let field_name_bytes = match &field.name { Some(name) => hash(name.as_bytes()), None => [0u8; HASH_LEN], @@ -83,7 +87,7 @@ fn get_field_hash( concat_and_hash2( &field_name_bytes, - &get_type_hash(registry, field.ty.id, cache), + &get_type_hash_recurse(registry, field.ty.id, cache, outer_enum_hashes), ) } @@ -92,12 +96,16 @@ fn get_variant_hash( registry: &PortableRegistry, var: &Variant, cache: &mut HashMap, -) -> [u8; HASH_LEN] { + outer_enum_hashes: &OuterEnumHashes, +) -> Hash { let variant_name_bytes = hash(var.name.as_bytes()); let variant_field_bytes = var.fields.iter().fold([0u8; HASH_LEN], |bytes, field| { // EncodeAsType and DecodeAsType don't care about variant field ordering, // so XOR the fields to ensure that it doesn't matter. - xor(bytes, get_field_hash(registry, field, cache)) + xor( + bytes, + get_field_hash(registry, field, cache, outer_enum_hashes), + ) }); concat_and_hash2(&variant_name_bytes, &variant_field_bytes) @@ -108,7 +116,8 @@ fn get_type_def_variant_hash( variant: &TypeDefVariant, only_these_variants: Option<&[&str]>, cache: &mut HashMap, -) -> [u8; HASH_LEN] { + outer_enum_hashes: &OuterEnumHashes, +) -> Hash { let variant_id_bytes = [TypeBeingHashed::Variant as u8; HASH_LEN]; let variant_field_bytes = variant.variants.iter().fold([0u8; HASH_LEN], |bytes, var| { // With EncodeAsType and DecodeAsType we no longer care which order the variants are in, @@ -117,9 +126,11 @@ fn get_type_def_variant_hash( .as_ref() .map(|only_these_variants| only_these_variants.contains(&var.name.as_str())) .unwrap_or(true); - if should_hash { - xor(bytes, get_variant_hash(registry, var, cache)) + xor( + bytes, + get_variant_hash(registry, var, cache, outer_enum_hashes), + ) } else { bytes } @@ -132,7 +143,8 @@ fn get_type_def_hash( registry: &PortableRegistry, ty_def: &TypeDef, cache: &mut HashMap, -) -> [u8; HASH_LEN] { + outer_enum_hashes: &OuterEnumHashes, +) -> Hash { match ty_def { TypeDef::Composite(composite) => { let composite_id_bytes = [TypeBeingHashed::Composite as u8; HASH_LEN]; @@ -143,14 +155,19 @@ fn get_type_def_hash( .fold([0u8; HASH_LEN], |bytes, field| { // With EncodeAsType and DecodeAsType we no longer care which order the fields are in, // as long as all of the names+types are there. XOR to not care about ordering. - xor(bytes, get_field_hash(registry, field, cache)) + xor( + bytes, + get_field_hash(registry, field, cache, outer_enum_hashes), + ) }); concat_and_hash2(&composite_id_bytes, &composite_field_bytes) } - TypeDef::Variant(variant) => get_type_def_variant_hash(registry, variant, None, cache), + TypeDef::Variant(variant) => { + get_type_def_variant_hash(registry, variant, None, cache, outer_enum_hashes) + } TypeDef::Sequence(sequence) => concat_and_hash2( &[TypeBeingHashed::Sequence as u8; HASH_LEN], - &get_type_hash(registry, sequence.type_param.id, cache), + &get_type_hash_recurse(registry, sequence.type_param.id, cache, outer_enum_hashes), ), TypeDef::Array(array) => { // Take length into account too; different length must lead to different hash. @@ -162,13 +179,16 @@ fn get_type_def_hash( }; concat_and_hash2( &array_id_bytes, - &get_type_hash(registry, array.type_param.id, cache), + &get_type_hash_recurse(registry, array.type_param.id, cache, outer_enum_hashes), ) } TypeDef::Tuple(tuple) => { let mut bytes = hash(&[TypeBeingHashed::Tuple as u8]); for field in &tuple.fields { - bytes = concat_and_hash2(&bytes, &get_type_hash(registry, field.id, cache)); + bytes = concat_and_hash2( + &bytes, + &get_type_hash_recurse(registry, field.id, cache, outer_enum_hashes), + ); } bytes } @@ -178,12 +198,12 @@ fn get_type_def_hash( } TypeDef::Compact(compact) => concat_and_hash2( &[TypeBeingHashed::Compact as u8; HASH_LEN], - &get_type_hash(registry, compact.type_param.id, cache), + &get_type_hash_recurse(registry, compact.type_param.id, cache, outer_enum_hashes), ), TypeDef::BitSequence(bitseq) => concat_and_hash3( &[TypeBeingHashed::BitSequence as u8; HASH_LEN], - &get_type_hash(registry, bitseq.bit_order_type.id, cache), - &get_type_hash(registry, bitseq.bit_store_type.id, cache), + &get_type_hash_recurse(registry, bitseq.bit_order_type.id, cache, outer_enum_hashes), + &get_type_hash_recurse(registry, bitseq.bit_store_type.id, cache, outer_enum_hashes), ), } } @@ -194,11 +214,11 @@ pub enum CachedHash { /// hash not known yet, but computation has already started Recursive, /// hash of the type, computation was finished - Hash([u8; HASH_LEN]), + Hash(Hash), } impl CachedHash { - fn hash(&self) -> [u8; HASH_LEN] { + fn hash(&self) -> Hash { match &self { CachedHash::Hash(hash) => *hash, CachedHash::Recursive => [123; HASH_LEN], // some magical value @@ -207,11 +227,33 @@ impl CachedHash { } /// Obtain the hash representation of a `scale_info::Type` identified by id. +/// +/// Hashes of the outer enums (call, event, error) should be computed prior to this +/// and passed in as the `outer_enum_hashes` argument. Whenever a type is encountered that +/// is one of the outer enums, the procomputed hash is used instead of computing a new one. +/// +/// The reason for this unintuitive behavior is that we sometimes want to trim the outer enum types +/// beforehand to only include certain pallets, which affects their hash values. pub fn get_type_hash( + registry: &PortableRegistry, + id: u32, + outer_enum_hashes: &OuterEnumHashes, +) -> Hash { + get_type_hash_recurse(registry, id, &mut HashMap::new(), outer_enum_hashes) +} + +/// Obtain the hash representation of a `scale_info::Type` identified by id. +fn get_type_hash_recurse( registry: &PortableRegistry, id: u32, cache: &mut HashMap, -) -> [u8; HASH_LEN] { + outer_enum_hashes: &OuterEnumHashes, +) -> Hash { + // If the type is part of precomputed outer enum hashes, the respective hash is used instead: + if let Some(hash) = outer_enum_hashes.resolve(id) { + return hash; + } + // Guard against recursive types, with a 2 step caching approach: // if the cache has an entry for the id, just return a hash derived from it. // if the type has not been seen yet, mark it with `CachedHash::Recursive` in the cache and proceed to `get_type_def_hash()`. @@ -225,7 +267,6 @@ pub fn get_type_hash( // type_id -> not contained = We haven't seen the type yet. // -> `CachedHash::Recursive` = We have seen the type but hash calculation for it hasn't finished yet. // -> `CachedHash::Hash(hash)` = Hash calculation for the type was completed. - if let Some(cached_hash) = cache.get(&id) { return cached_hash.hash(); } @@ -233,7 +274,7 @@ pub fn get_type_hash( let ty = registry .resolve(id) .expect("Type ID provided by the metadata is registered; qed"); - let type_hash = get_type_def_hash(registry, &ty.type_def, cache); + let type_hash = get_type_def_hash(registry, &ty.type_def, cache, outer_enum_hashes); cache.insert(id, CachedHash::Hash(type_hash)); type_hash } @@ -242,14 +283,13 @@ pub fn get_type_hash( fn get_extrinsic_hash( registry: &PortableRegistry, extrinsic: &ExtrinsicMetadata, -) -> [u8; HASH_LEN] { - let mut cache = HashMap::::new(); - + outer_enum_hashes: &OuterEnumHashes, +) -> Hash { // Get the hashes of the extrinsic type. - let address_hash = get_type_hash(registry, extrinsic.address_ty, &mut cache); + let address_hash = get_type_hash(registry, extrinsic.address_ty, outer_enum_hashes); // The `RuntimeCall` type is intentionally omitted and hashed by the outer enums instead. - let signature_hash = get_type_hash(registry, extrinsic.signature_ty, &mut cache); - let extra_hash = get_type_hash(registry, extrinsic.extra_ty, &mut cache); + let signature_hash = get_type_hash(registry, extrinsic.signature_ty, outer_enum_hashes); + let extra_hash = get_type_hash(registry, extrinsic.extra_ty, outer_enum_hashes); let mut bytes = concat_and_hash4( &address_hash, @@ -262,53 +302,20 @@ fn get_extrinsic_hash( bytes = concat_and_hash4( &bytes, &hash(signed_extension.identifier.as_bytes()), - &get_type_hash(registry, signed_extension.extra_ty, &mut cache), - &get_type_hash(registry, signed_extension.additional_ty, &mut cache), + &get_type_hash(registry, signed_extension.extra_ty, outer_enum_hashes), + &get_type_hash(registry, signed_extension.additional_ty, outer_enum_hashes), ) } bytes } -/// Obtain the hash representation of the `frame_metadata::v15::OuterEnums`. -fn get_outer_enums_hash( - registry: &PortableRegistry, - enums: &OuterEnumsMetadata, - only_these_variants: Option<&[&str]>, -) -> [u8; HASH_LEN] { - /// Hash the provided enum type. - fn get_enum_hash( - registry: &PortableRegistry, - id: u32, - only_these_variants: Option<&[&str]>, - ) -> [u8; HASH_LEN] { - let ty = registry - .types - .get(id as usize) - .expect("Metadata should contain enum type in registry"); - - if let TypeDef::Variant(variant) = &ty.ty.type_def { - get_type_def_variant_hash(registry, variant, only_these_variants, &mut HashMap::new()) - } else { - get_type_hash(registry, id, &mut HashMap::new()) - } - } - - let call_hash = get_enum_hash(registry, enums.call_enum_ty, only_these_variants); - - let event_hash = get_enum_hash(registry, enums.event_enum_ty, only_these_variants); - - let error_hash = get_enum_hash(registry, enums.error_enum_ty, only_these_variants); - - concat_and_hash3(&call_hash, &event_hash, &error_hash) -} - /// Get the hash corresponding to a single storage entry. fn get_storage_entry_hash( registry: &PortableRegistry, entry: &StorageEntryMetadata, - cache: &mut HashMap, -) -> [u8; HASH_LEN] { + outer_enum_hashes: &OuterEnumHashes, +) -> Hash { let mut bytes = concat_and_hash3( &hash(entry.name.as_bytes()), // Cloning 'entry.modifier' should essentially be a copy. @@ -318,7 +325,7 @@ fn get_storage_entry_hash( match &entry.entry_type { StorageEntryType::Plain(ty) => { - concat_and_hash2(&bytes, &get_type_hash(registry, *ty, cache)) + concat_and_hash2(&bytes, &get_type_hash(registry, *ty, outer_enum_hashes)) } StorageEntryType::Map { hashers, @@ -331,8 +338,8 @@ fn get_storage_entry_hash( } concat_and_hash3( &bytes, - &get_type_hash(registry, *key_ty, cache), - &get_type_hash(registry, *value_ty, cache), + &get_type_hash(registry, *key_ty, outer_enum_hashes), + &get_type_hash(registry, *value_ty, outer_enum_hashes), ) } } @@ -343,8 +350,8 @@ fn get_runtime_method_hash( registry: &PortableRegistry, trait_name: &str, method_metadata: &RuntimeApiMethodMetadata, - cache: &mut HashMap, -) -> [u8; HASH_LEN] { + outer_enum_hashes: &OuterEnumHashes, +) -> Hash { // The trait name is part of the runtime API call that is being // generated for this method. Therefore the trait name is strongly // connected to the method in the same way as a parameter is @@ -358,21 +365,23 @@ fn get_runtime_method_hash( bytes = concat_and_hash3( &bytes, &hash(input.name.as_bytes()), - &get_type_hash(registry, input.ty, cache), + &get_type_hash(registry, input.ty, outer_enum_hashes), ); } bytes = concat_and_hash2( &bytes, - &get_type_hash(registry, method_metadata.output_ty, cache), + &get_type_hash(registry, method_metadata.output_ty, outer_enum_hashes), ); bytes } /// Obtain the hash of all of a runtime API trait, including all of its methods. -pub fn get_runtime_trait_hash(trait_metadata: RuntimeApiMetadata) -> [u8; HASH_LEN] { - let mut cache = HashMap::new(); +pub fn get_runtime_trait_hash( + trait_metadata: RuntimeApiMetadata, + outer_enum_hashes: &OuterEnumHashes, +) -> Hash { let trait_name = &*trait_metadata.inner.name; let method_bytes = trait_metadata .methods() @@ -387,7 +396,7 @@ pub fn get_runtime_trait_hash(trait_metadata: RuntimeApiMetadata) -> [u8; HASH_L trait_metadata.types, trait_name, method_metadata, - &mut cache, + outer_enum_hashes, ), ) }); @@ -395,12 +404,17 @@ pub fn get_runtime_trait_hash(trait_metadata: RuntimeApiMetadata) -> [u8; HASH_L concat_and_hash2(&hash(trait_name.as_bytes()), &method_bytes) } -pub fn get_custom_metadata_hash(custom_metadata: &CustomMetadata) -> [u8; HASH_LEN] { - let mut cache = HashMap::new(); +fn get_custom_metadata_hash( + custom_metadata: &CustomMetadata, + outer_enum_hashes: &OuterEnumHashes, +) -> Hash { custom_metadata .iter() .fold([0u8; HASH_LEN], |bytes, custom_value| { - xor(bytes, get_custom_value_hash(&custom_value, &mut cache)) + xor( + bytes, + get_custom_value_hash(&custom_value, outer_enum_hashes), + ) }) } @@ -410,50 +424,56 @@ pub fn get_custom_metadata_hash(custom_metadata: &CustomMetadata) -> [u8; HASH_L /// only the name and bytes are used for hashing. pub fn get_custom_value_hash( custom_value: &CustomValueMetadata, - cache: &mut HashMap, -) -> [u8; HASH_LEN] { + outer_enum_hashes: &OuterEnumHashes, +) -> Hash { let name_hash = hash(custom_value.name.as_bytes()); if custom_value.types.resolve(custom_value.type_id()).is_none() { hash(&name_hash) } else { concat_and_hash2( &name_hash, - &get_type_hash(custom_value.types, custom_value.type_id(), cache), + &get_type_hash( + custom_value.types, + custom_value.type_id(), + outer_enum_hashes, + ), ) } } /// Obtain the hash for a specific storage item, or an error if it's not found. -pub fn get_storage_hash(pallet: &PalletMetadata, entry_name: &str) -> Option<[u8; HASH_LEN]> { +pub fn get_storage_hash(pallet: &PalletMetadata, entry_name: &str) -> Option { let storage = pallet.storage()?; let entry = storage.entry_by_name(entry_name)?; - let hash = get_storage_entry_hash(pallet.types, entry, &mut HashMap::new()); + let hash = get_storage_entry_hash(pallet.types, entry, &OuterEnumHashes::empty()); Some(hash) } /// Obtain the hash for a specific constant, or an error if it's not found. -pub fn get_constant_hash(pallet: &PalletMetadata, constant_name: &str) -> Option<[u8; HASH_LEN]> { +pub fn get_constant_hash(pallet: &PalletMetadata, constant_name: &str) -> Option { let constant = pallet.constant_by_name(constant_name)?; // We only need to check that the type of the constant asked for matches. - let bytes = get_type_hash(pallet.types, constant.ty, &mut HashMap::new()); + let bytes = get_type_hash(pallet.types, constant.ty, &OuterEnumHashes::empty()); Some(bytes) } /// Obtain the hash for a specific call, or an error if it's not found. -pub fn get_call_hash(pallet: &PalletMetadata, call_name: &str) -> Option<[u8; HASH_LEN]> { +pub fn get_call_hash(pallet: &PalletMetadata, call_name: &str) -> Option { let call_variant = pallet.call_variant_by_name(call_name)?; // hash the specific variant representing the call we are interested in. - let hash = get_variant_hash(pallet.types, call_variant, &mut HashMap::new()); + let hash = get_variant_hash( + pallet.types, + call_variant, + &mut HashMap::new(), + &OuterEnumHashes::empty(), + ); Some(hash) } /// Obtain the hash of a specific runtime API function, or an error if it's not found. -pub fn get_runtime_api_hash( - runtime_apis: &RuntimeApiMetadata, - method_name: &str, -) -> Option<[u8; HASH_LEN]> { +pub fn get_runtime_api_hash(runtime_apis: &RuntimeApiMetadata, method_name: &str) -> Option { let trait_name = &*runtime_apis.inner.name; let method_metadata = runtime_apis.method_by_name(method_name)?; @@ -461,25 +481,24 @@ pub fn get_runtime_api_hash( runtime_apis.types, trait_name, method_metadata, - &mut HashMap::new(), + &OuterEnumHashes::empty(), )) } /// Obtain the hash representation of a `frame_metadata::v15::PalletMetadata`. -pub fn get_pallet_hash(pallet: PalletMetadata) -> [u8; HASH_LEN] { - let mut cache = HashMap::::new(); +pub fn get_pallet_hash(pallet: PalletMetadata, outer_enum_hashes: &OuterEnumHashes) -> Hash { let registry = pallet.types; let call_bytes = match pallet.call_ty_id() { - Some(calls) => get_type_hash(registry, calls, &mut cache), + Some(calls) => get_type_hash(registry, calls, outer_enum_hashes), None => [0u8; HASH_LEN], }; let event_bytes = match pallet.event_ty_id() { - Some(event) => get_type_hash(registry, event, &mut cache), + Some(event) => get_type_hash(registry, event, outer_enum_hashes), None => [0u8; HASH_LEN], }; let error_bytes = match pallet.error_ty_id() { - Some(error) => get_type_hash(registry, error, &mut cache), + Some(error) => get_type_hash(registry, error, outer_enum_hashes), None => [0u8; HASH_LEN], }; let constant_bytes = pallet.constants().fold([0u8; HASH_LEN], |bytes, constant| { @@ -487,7 +506,7 @@ pub fn get_pallet_hash(pallet: PalletMetadata) -> [u8; HASH_LEN] { // of (constantName, constantType) to make the order we see them irrelevant. let constant_hash = concat_and_hash2( &hash(constant.name.as_bytes()), - &get_type_hash(registry, constant.ty(), &mut cache), + &get_type_hash(registry, constant.ty(), outer_enum_hashes), ); xor(bytes, constant_hash) }); @@ -500,7 +519,10 @@ pub fn get_pallet_hash(pallet: PalletMetadata) -> [u8; HASH_LEN] { .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 cache)) + xor( + bytes, + get_storage_entry_hash(registry, entry, outer_enum_hashes), + ) }); concat_and_hash2(&prefix_hash, &entries_hash) } @@ -560,9 +582,13 @@ impl<'a> MetadataHasher<'a> { } /// Hash the given metadata. - pub fn hash(&self) -> [u8; HASH_LEN] { + pub fn hash(&self) -> Hash { let metadata = self.metadata; + // Get the hashes of outer enums, considering only `specific_pallets` (if any are set). + // If any of the typed that represent outer enums are encountered later, hashes from `top_level_enum_hashes` can be substituted. + let outer_enum_hashes = OuterEnumHashes::new(metadata, self.specific_pallets.as_deref()); + let pallet_hash = metadata.pallets().fold([0u8; HASH_LEN], |bytes, pallet| { // If specific pallets are given, only include this pallet if it is in the specific pallets. let should_hash = self @@ -573,7 +599,7 @@ impl<'a> MetadataHasher<'a> { // We don't care what order the pallets are seen in, so XOR their // hashes together to be order independent. if should_hash { - xor(bytes, get_pallet_hash(pallet)) + xor(bytes, get_pallet_hash(pallet, &outer_enum_hashes)) } else { bytes } @@ -591,24 +617,19 @@ impl<'a> MetadataHasher<'a> { // We don't care what order the runtime APIs are seen in, so XOR their // hashes together to be order independent. if should_hash { - xor(bytes, get_runtime_trait_hash(api)) + xor(bytes, get_runtime_trait_hash(api, &outer_enum_hashes)) } else { bytes } }); - let extrinsic_hash = get_extrinsic_hash(&metadata.types, &metadata.extrinsic); + let extrinsic_hash = + get_extrinsic_hash(&metadata.types, &metadata.extrinsic, &outer_enum_hashes); let runtime_hash = - get_type_hash(&metadata.types, metadata.runtime_ty(), &mut HashMap::new()); - let outer_enums_hash = get_outer_enums_hash( - &metadata.types, - &metadata.outer_enums(), - self.specific_pallets.as_deref(), - ); - + get_type_hash(&metadata.types, metadata.runtime_ty(), &outer_enum_hashes); let custom_values_hash = self .include_custom_values - .then(|| get_custom_metadata_hash(&metadata.custom())) + .then(|| get_custom_metadata_hash(&metadata.custom(), &outer_enum_hashes)) .unwrap_or_default(); concat_and_hash6( @@ -616,7 +637,7 @@ impl<'a> MetadataHasher<'a> { &apis_hash, &extrinsic_hash, &runtime_hash, - &outer_enums_hash, + &outer_enum_hashes.combined_hash(), &custom_values_hash, ) } @@ -646,7 +667,7 @@ mod tests { #[allow(dead_code)] #[derive(scale_info::TypeInfo)] // TypeDef::Composite with TypeDef::Array with Typedef::Primitive. - struct AccountId32([u8; HASH_LEN]); + struct AccountId32(Hash); #[allow(dead_code)] #[derive(scale_info::TypeInfo)] @@ -849,10 +870,11 @@ mod tests { let registry: PortableRegistry = registry.into(); let mut cache = HashMap::new(); + let ignored_enums = &OuterEnumHashes::empty(); - let a_hash = get_type_hash(®istry, a_type_id, &mut cache); - let a_hash2 = get_type_hash(®istry, a_type_id, &mut cache); - let b_hash = get_type_hash(®istry, b_type_id, &mut cache); + let a_hash = get_type_hash_recurse(®istry, a_type_id, &mut cache, ignored_enums); + let a_hash2 = get_type_hash_recurse(®istry, a_type_id, &mut cache, ignored_enums); + let b_hash = get_type_hash_recurse(®istry, b_type_id, &mut cache, ignored_enums); let CachedHash::Hash(a_cache_hash) = cache[&a_type_id] else { panic!() @@ -1080,4 +1102,144 @@ mod tests { to_hash(meta_type::()) ); } + + use frame_metadata::v15::{ + PalletEventMetadata, PalletStorageMetadata, StorageEntryMetadata, StorageEntryModifier, + }; + + fn metadata_with_pallet_events() -> Metadata { + #[allow(dead_code)] + #[derive(scale_info::TypeInfo)] + struct FirstEvent { + s: String, + } + + #[allow(dead_code)] + #[derive(scale_info::TypeInfo)] + struct SecondEvent { + n: u8, + } + + #[allow(dead_code)] + #[derive(scale_info::TypeInfo)] + enum Events { + First(FirstEvent), + Second(SecondEvent), + } + + #[allow(dead_code)] + #[derive(scale_info::TypeInfo)] + enum Errors { + First(DispatchError), + Second(DispatchError), + } + + #[allow(dead_code)] + #[derive(scale_info::TypeInfo)] + enum Calls { + First(u8), + Second(u8), + } + + #[allow(dead_code)] + enum DispatchError { + A, + B, + C, + } + + impl scale_info::TypeInfo for DispatchError { + type Identity = DispatchError; + + fn type_info() -> scale_info::Type { + scale_info::Type { + path: scale_info::Path { + segments: vec!["sp_runtime", "DispatchError"], + }, + type_params: vec![], + type_def: TypeDef::Variant(TypeDefVariant { variants: vec![] }), + docs: vec![], + } + } + } + + let pallets = vec![ + v15::PalletMetadata { + name: "First", + index: 0, + calls: Some(v15::PalletCallMetadata { + ty: meta_type::(), + }), + storage: Some(PalletStorageMetadata { + prefix: "___", + entries: vec![StorageEntryMetadata { + name: "Hello", + modifier: StorageEntryModifier::Optional, + // Note: This is the important part here: + // The Events type will be trimmed down and this trimming needs to be reflected + // when the hash of this storage item is computed. + ty: frame_metadata::v14::StorageEntryType::Plain(meta_type::>()), + default: vec![], + docs: vec![], + }], + }), + event: Some(PalletEventMetadata { + ty: meta_type::(), + }), + constants: vec![], + error: None, + docs: vec![], + }, + v15::PalletMetadata { + name: "Second", + index: 1, + calls: Some(v15::PalletCallMetadata { + ty: meta_type::(), + }), + storage: None, + event: Some(PalletEventMetadata { + ty: meta_type::(), + }), + constants: vec![], + error: None, + docs: vec![], + }, + ]; + + v15::RuntimeMetadataV15::new( + pallets, + build_default_extrinsic(), + meta_type::<()>(), + vec![], + v15::OuterEnums { + call_enum_ty: meta_type::(), + event_enum_ty: meta_type::(), + error_enum_ty: meta_type::(), + }, + v15::CustomMetadata { + map: Default::default(), + }, + ) + .try_into() + .expect("can build valid metadata") + } + + #[test] + fn hash_comparison_trimmed_metadata() { + // trim the metadata: + let metadata = metadata_with_pallet_events(); + let trimmed_metadata = { + let mut m = metadata.clone(); + m.retain(|e| e == "First", |_| true); + m + }; + + // test that the hashes are the same: + let hash = MetadataHasher::new(&metadata) + .only_these_pallets(&["First"]) + .hash(); + let hash_trimmed = MetadataHasher::new(&trimmed_metadata).hash(); + + assert_eq!(hash, hash_trimmed); + } } diff --git a/metadata/src/utils/validation/outer_enum_hashes.rs b/metadata/src/utils/validation/outer_enum_hashes.rs new file mode 100644 index 0000000000..33bd4f52c1 --- /dev/null +++ b/metadata/src/utils/validation/outer_enum_hashes.rs @@ -0,0 +1,87 @@ +//! Hash representations of the `frame_metadata::v15::OuterEnums`. + +use std::collections::HashMap; + +use scale_info::{PortableRegistry, TypeDef}; + +use crate::{ + utils::validation::{get_type_def_variant_hash, get_type_hash}, + Metadata, +}; + +use super::{concat_and_hash3, Hash, HASH_LEN}; + +/// Hash representations of the `frame_metadata::v15::OuterEnums`. +pub struct OuterEnumHashes { + call_hash: (u32, Hash), + error_hash: (u32, Hash), + event_hash: (u32, Hash), +} + +impl OuterEnumHashes { + /// Constructs new `OuterEnumHashes` from metadata. If `only_these_variants` is set, the enums are stripped down to only these variants, before their hashes are calculated. + pub fn new(metadata: &Metadata, only_these_variants: Option<&[&str]>) -> Self { + fn get_enum_hash( + registry: &PortableRegistry, + id: u32, + only_these_variants: Option<&[&str]>, + ) -> Hash { + let ty = registry + .types + .get(id as usize) + .expect("Metadata should contain enum type in registry"); + + if let TypeDef::Variant(variant) = &ty.ty.type_def { + get_type_def_variant_hash( + registry, + variant, + only_these_variants, + &mut HashMap::new(), + // ignored, because not computed yet... + &OuterEnumHashes::empty(), + ) + } else { + get_type_hash(registry, id, &OuterEnumHashes::empty()) + } + } + let enums = &metadata.outer_enums; + + let call_hash = get_enum_hash(metadata.types(), enums.call_enum_ty, only_these_variants); + let event_hash = get_enum_hash(metadata.types(), enums.event_enum_ty, only_these_variants); + let error_hash = get_enum_hash(metadata.types(), enums.error_enum_ty, only_these_variants); + + Self { + call_hash: (enums.call_enum_ty, call_hash), + error_hash: (enums.error_enum_ty, error_hash), + event_hash: (enums.event_enum_ty, event_hash), + } + } + + /// Constructs empty `OuterEnumHashes` with type ids that are never a real type id. + /// Can be used as a placeholder when outer enum hashes are required but should be ignored. + pub fn empty() -> Self { + Self { + call_hash: (u32::MAX, [0; HASH_LEN]), + error_hash: (u32::MAX, [0; HASH_LEN]), + event_hash: (u32::MAX, [0; HASH_LEN]), + } + } + + /// Returns a combined hash of the top level enums. + pub fn combined_hash(&self) -> Hash { + concat_and_hash3(&self.call_hash.1, &self.error_hash.1, &self.event_hash.1) + } + + /// Checks if a type is one of the 3 top level enum types. If so, returns Some(hash). + /// + /// This is useful, because top level enums are sometimes stripped down to only certain pallets. + /// The hashes of these stripped down types are stored in this struct. + pub fn resolve(&self, id: u32) -> Option<[u8; HASH_LEN]> { + match id { + e if e == self.error_hash.0 => Some(self.error_hash.1), + e if e == self.event_hash.0 => Some(self.event_hash.1), + e if e == self.call_hash.0 => Some(self.call_hash.1), + _ => None, + } + } +}