Codegen for custom values in metadata (#1117)

* work in progress

* add custom types access

* nit

* custom values client

* adjust light client

* adjust doc comments

* adjust book for custom values in code gen

* format and check docs

* work in progress

* add custom types access

* nit

* custom values client

* adjust light client

* codegen and validation

* adjust docs

* use ignore in docs in book

* change iter implementation

* use validation hash and other codegen changes

* add ui test for custom values codegen

* allow 'latest' metadata to be returned from the fallback code (#1127)

* nits

* fix validation check

* fix comments

* nits

---------

Co-authored-by: James Wilson <james@jsdw.me>
This commit is contained in:
Tadeo Hepperle
2023-08-24 09:50:44 +02:00
committed by GitHub
parent a8dbd9d6d5
commit b413e5e84e
12 changed files with 354 additions and 43 deletions
+5 -5
View File
@@ -5,10 +5,10 @@
use super::TryFromError;
use crate::utils::variant_index::VariantIndex;
use crate::{
utils::ordered_map::OrderedMap, ArcStr, ConstantMetadata, CustomMetadata, ExtrinsicMetadata,
Metadata, OuterEnumsMetadata, PalletMetadataInner, RuntimeApiMetadataInner,
RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata, SignedExtensionMetadata,
StorageEntryMetadata, StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata,
utils::ordered_map::OrderedMap, ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata,
OuterEnumsMetadata, PalletMetadataInner, RuntimeApiMetadataInner, RuntimeApiMethodMetadata,
RuntimeApiMethodParamMetadata, SignedExtensionMetadata, StorageEntryMetadata,
StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata,
};
use frame_metadata::v15;
use scale_info::form::PortableForm;
@@ -93,7 +93,7 @@ mod from_v15 {
event_enum_ty: m.outer_enums.event_enum_ty.id,
error_enum_ty: m.outer_enums.error_enum_ty.id,
},
custom: CustomMetadata { map: m.custom.map },
custom: m.custom,
})
}
}
+63 -21
View File
@@ -20,13 +20,14 @@ mod from_into;
mod utils;
use scale_info::{form::PortableForm, PortableRegistry, Variant};
use std::collections::{BTreeMap, HashMap};
use std::collections::HashMap;
use std::sync::Arc;
use utils::ordered_map::OrderedMap;
use utils::variant_index::VariantIndex;
type ArcStr = Arc<str>;
use crate::utils::validation::{get_custom_value_hash, HASH_LEN};
pub use from_into::TryFromError;
pub use utils::validation::MetadataHasher;
@@ -52,7 +53,7 @@ pub struct Metadata {
/// Details about each of the runtime API traits.
apis: OrderedMap<ArcStr, RuntimeApiMetadataInner>,
/// Allows users to add custom types to the metadata. A map that associates a string key to a `CustomValueMetadata`.
custom: CustomMetadata,
custom: frame_metadata::v15::CustomMetadata<PortableForm>,
}
impl Metadata {
@@ -135,8 +136,11 @@ impl Metadata {
}
/// Returns custom user defined types
pub fn custom(&self) -> &CustomMetadata {
&self.custom
pub fn custom(&self) -> CustomMetadata<'_> {
CustomMetadata {
types: self.types(),
inner: &self.custom,
}
}
/// Obtain a unique hash representing this metadata or specific parts of it.
@@ -154,7 +158,7 @@ impl Metadata {
}
/// Get type hash for a type in the registry
pub fn type_hash(&self, id: u32) -> Option<[u8; 32]> {
pub fn type_hash(&self, id: u32) -> Option<[u8; HASH_LEN]> {
self.types.resolve(id)?;
Some(crate::utils::validation::get_type_hash(
&self.types,
@@ -265,22 +269,22 @@ impl<'a> PalletMetadata<'a> {
}
/// Return a hash for the storage entry, or None if it was not found.
pub fn storage_hash(&self, entry_name: &str) -> Option<[u8; 32]> {
pub fn storage_hash(&self, entry_name: &str) -> Option<[u8; HASH_LEN]> {
crate::utils::validation::get_storage_hash(self, entry_name)
}
/// Return a hash for the constant, or None if it was not found.
pub fn constant_hash(&self, constant_name: &str) -> Option<[u8; 32]> {
pub fn constant_hash(&self, constant_name: &str) -> Option<[u8; HASH_LEN]> {
crate::utils::validation::get_constant_hash(self, constant_name)
}
/// Return a hash for the call, or None if it was not found.
pub fn call_hash(&self, call_name: &str) -> Option<[u8; 32]> {
pub fn call_hash(&self, call_name: &str) -> Option<[u8; HASH_LEN]> {
crate::utils::validation::get_call_hash(self, call_name)
}
/// Return a hash for the entire pallet.
pub fn hash(&self) -> [u8; 32] {
pub fn hash(&self) -> [u8; HASH_LEN] {
crate::utils::validation::get_pallet_hash(*self)
}
}
@@ -577,12 +581,12 @@ impl<'a> RuntimeApiMetadata<'a> {
self.inner.methods.get_by_key(name)
}
/// Return a hash for the constant, or None if it was not found.
pub fn method_hash(&self, method_name: &str) -> Option<[u8; 32]> {
pub fn method_hash(&self, method_name: &str) -> Option<[u8; HASH_LEN]> {
crate::utils::validation::get_runtime_api_hash(self, method_name)
}
/// Return a hash for the runtime API trait.
pub fn hash(&self) -> [u8; 32] {
pub fn hash(&self) -> [u8; HASH_LEN] {
crate::utils::validation::get_runtime_trait_hash(*self)
}
}
@@ -640,36 +644,74 @@ pub struct RuntimeApiMethodParamMetadata {
/// Metadata of custom types with custom values, basically the same as `frame_metadata::v15::CustomMetadata<PortableForm>>`.
#[derive(Debug, Clone)]
pub struct CustomMetadata {
map: BTreeMap<String, frame_metadata::v15::CustomValueMetadata<PortableForm>>,
pub struct CustomMetadata<'a> {
types: &'a PortableRegistry,
inner: &'a frame_metadata::v15::CustomMetadata<PortableForm>,
}
impl CustomMetadata {
/// Get a certain [CustomMetadataValue] by its name.
pub fn get(&self, name: &str) -> Option<CustomMetadataValue<'_>> {
self.map.get(name).map(|e| CustomMetadataValue {
impl<'a> CustomMetadata<'a> {
/// Get a certain [CustomValueMetadata] by its name.
pub fn get(&self, name: &str) -> Option<CustomValueMetadata<'a>> {
self.inner
.map
.get_key_value(name)
.map(|(name, e)| CustomValueMetadata {
types: self.types,
type_id: e.ty.id,
data: &e.value,
name,
})
}
/// Iterates over names (keys) and associated custom values
pub fn iter(&self) -> impl Iterator<Item = CustomValueMetadata> {
self.inner.map.iter().map(|(name, e)| CustomValueMetadata {
types: self.types,
type_id: e.ty.id,
data: &e.value,
name: name.as_ref(),
})
}
/// Access the underlying type registry.
pub fn types(&self) -> &PortableRegistry {
self.types
}
}
/// Basically the same as `frame_metadata::v15::CustomValueMetadata<PortableForm>>`, but borrowed.
pub struct CustomMetadataValue<'a> {
pub struct CustomValueMetadata<'a> {
types: &'a PortableRegistry,
type_id: u32,
data: &'a [u8],
name: &'a str,
}
impl<'a> CustomMetadataValue<'a> {
/// the scale encoded value
impl<'a> CustomValueMetadata<'a> {
/// The scale encoded value
pub fn bytes(&self) -> &'a [u8] {
self.data
}
/// the type id in the TypeRegistry
/// The type id in the TypeRegistry
pub fn type_id(&self) -> u32 {
self.type_id
}
/// The name under which the custom value is registered.
pub fn name(&self) -> &str {
self.name
}
/// Calculates the hash for the CustomValueMetadata.
///
/// # Panics
///
/// Panics if `self.type_id` is not registered in the provided type registry
pub fn hash(&self) -> [u8; HASH_LEN] {
let mut cache = HashMap::new();
get_custom_value_hash(self, &mut cache)
}
}
// Support decoding metadata from the "wire" format directly into this.
+48 -7
View File
@@ -5,14 +5,15 @@
//! Utility functions for metadata validation.
use crate::{
ExtrinsicMetadata, Metadata, OuterEnumsMetadata, PalletMetadata, RuntimeApiMetadata,
RuntimeApiMethodMetadata, StorageEntryMetadata, StorageEntryType,
CustomMetadata, CustomValueMetadata, ExtrinsicMetadata, Metadata, OuterEnumsMetadata,
PalletMetadata, RuntimeApiMetadata, RuntimeApiMethodMetadata, StorageEntryMetadata,
StorageEntryType,
};
use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, TypeDefVariant, Variant};
use std::collections::HashMap;
// The number of bytes our `hash` function produces.
const HASH_LEN: usize = 32;
pub(crate) const HASH_LEN: usize = 32;
/// Internal byte representation for various metadata types utilized for
/// generating deterministic hashes between different rust versions.
@@ -67,6 +68,7 @@ concat_and_hash_n!(concat_and_hash2(a b));
concat_and_hash_n!(concat_and_hash3(a b c));
concat_and_hash_n!(concat_and_hash4(a b c d));
concat_and_hash_n!(concat_and_hash5(a b c d e));
concat_and_hash_n!(concat_and_hash6(a b c d e f));
/// Obtain the hash representation of a `scale_info::Field`.
fn get_field_hash(
@@ -393,6 +395,27 @@ 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();
custom_metadata
.iter()
.fold([0u8; HASH_LEN], |bytes, custom_value| {
xor(bytes, get_custom_value_hash(&custom_value, &mut cache))
})
}
/// Obtain the hash of some custom value in the metadata including it's name/key.
pub fn get_custom_value_hash(
custom_value: &CustomValueMetadata,
cache: &mut HashMap<u32, CachedHash>,
) -> [u8; HASH_LEN] {
concat_and_hash3(
&hash(custom_value.name.as_bytes()),
&get_type_hash(custom_value.types, custom_value.type_id(), cache),
&hash(custom_value.bytes()),
)
}
/// 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]> {
let storage = pallet.storage()?;
@@ -494,6 +517,7 @@ pub struct MetadataHasher<'a> {
metadata: &'a Metadata,
specific_pallets: Option<Vec<&'a str>>,
specific_runtime_apis: Option<Vec<&'a str>>,
include_custom_values: bool,
}
impl<'a> MetadataHasher<'a> {
@@ -503,6 +527,7 @@ impl<'a> MetadataHasher<'a> {
metadata,
specific_pallets: None,
specific_runtime_apis: None,
include_custom_values: true,
}
}
@@ -522,6 +547,12 @@ impl<'a> MetadataHasher<'a> {
self
}
/// Do not hash the custom values
pub fn ignore_custom_values(&mut self) -> &mut Self {
self.include_custom_values = false;
self
}
/// Hash the given metadata.
pub fn hash(&self) -> [u8; HASH_LEN] {
let metadata = self.metadata;
@@ -554,7 +585,7 @@ 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, xor(bytes, get_runtime_trait_hash(api)))
xor(bytes, get_runtime_trait_hash(api))
} else {
bytes
}
@@ -569,12 +600,18 @@ impl<'a> MetadataHasher<'a> {
self.specific_pallets.as_deref(),
);
concat_and_hash5(
let custom_values_hash = self
.include_custom_values
.then(|| get_custom_metadata_hash(&metadata.custom()))
.unwrap_or_default();
concat_and_hash6(
&pallet_hash,
&apis_hash,
&extrinsic_hash,
&runtime_hash,
&outer_enums_hash,
&custom_values_hash,
)
}
}
@@ -811,8 +848,12 @@ mod tests {
let a_hash2 = get_type_hash(&registry, a_type_id, &mut cache);
let b_hash = get_type_hash(&registry, b_type_id, &mut cache);
let CachedHash::Hash(a_cache_hash) = cache[&a_type_id] else { panic!() };
let CachedHash::Hash(b_cache_hash) = cache[&b_type_id] else { panic!() };
let CachedHash::Hash(a_cache_hash) = cache[&a_type_id] else {
panic!()
};
let CachedHash::Hash(b_cache_hash) = cache[&b_type_id] else {
panic!()
};
assert_eq!(a_hash, a_cache_hash);
assert_eq!(b_hash, b_cache_hash);
+3 -5
View File
@@ -21,7 +21,7 @@ impl VariantIndex {
/// Build indexes from the optional variant ID.
pub fn build(variant_id: Option<u32>, types: &PortableRegistry) -> Self {
let Some(variants) = Self::get(variant_id, types) else {
return Self::empty()
return Self::empty();
};
let mut by_name = HashMap::new();
@@ -47,11 +47,9 @@ impl VariantIndex {
variant_id: Option<u32>,
types: &PortableRegistry,
) -> Option<&[Variant<PortableForm>]> {
let Some(variant_id) = variant_id else {
return None
};
let variant_id = variant_id?;
let TypeDef::Variant(v) = &types.resolve(variant_id)?.type_def else {
return None
return None;
};
Some(&v.variants)
}