Introduce Metadata type (#974)

* WIP new Metadata type

* Finish basic Metadata impl inc hashing and validation

* remove caching from metadata; can add that higher up

* remove caches

* update retain to use Metadata

* clippy fixes

* update codegen to use Metadata

* clippy

* WIP fixing subxt lib

* WIP fixing tests, rebuild artifacts, fix OrderedMap::retain

* get --all-targets compiling

* move DispatchError type lookup back to being optional

* cargo clippy

* fix docs

* re-use VariantIndex to get variants

* add docs and enforce docs on metadata crate

* fix docs

* add test and fix docs

* cargo fmt

* address review comments

* update lockfiles

* ExactSizeIter so we can ask for len() of things (and hopefully soon is_empty()
This commit is contained in:
James Wilson
2023-05-25 10:35:21 +01:00
committed by GitHub
parent f344d0dd4d
commit b9f5419095
64 changed files with 6818 additions and 5719 deletions
-92
View File
@@ -1,92 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use parking_lot::RwLock;
use std::{borrow::Cow, collections::HashMap};
/// A cache with the simple goal of storing 32 byte hashes against root+item keys
#[derive(Default, Debug)]
pub struct HashCache {
inner: RwLock<HashMap<RootItemKey<'static>, [u8; 32]>>,
}
impl HashCache {
/// get a hash out of the cache by its root and item key. If the item doesn't exist,
/// run the function provided to obtain a hash to insert (or bail with some error on failure).
pub fn get_or_insert<F, E>(&self, root: &str, item: &str, f: F) -> Result<[u8; 32], E>
where
F: FnOnce() -> Result<[u8; 32], E>,
{
let maybe_hash = self
.inner
.read()
.get(&RootItemKey::new(root, item))
.copied();
if let Some(hash) = maybe_hash {
return Ok(hash);
}
let hash = f()?;
self.inner
.write()
.insert(RootItemKey::new(root.to_string(), item.to_string()), hash);
Ok(hash)
}
}
/// This exists so that we can look items up in the cache using &strs, without having to allocate
/// Strings first (as you'd have to do to construct something like an `&(String,String)` key).
#[derive(Debug, PartialEq, Eq, Hash)]
struct RootItemKey<'a> {
pallet: Cow<'a, str>,
item: Cow<'a, str>,
}
impl<'a> RootItemKey<'a> {
fn new(pallet: impl Into<Cow<'a, str>>, item: impl Into<Cow<'a, str>>) -> Self {
RootItemKey {
pallet: pallet.into(),
item: item.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hash_cache_validation() {
let cache = HashCache::default();
let pallet = "System";
let item = "Account";
let mut call_number = 0;
let value = cache.get_or_insert(pallet, item, || -> Result<[u8; 32], ()> {
call_number += 1;
Ok([0; 32])
});
assert_eq!(
cache
.inner
.read()
.get(&RootItemKey::new(pallet, item))
.unwrap(),
&value.unwrap()
);
assert_eq!(value.unwrap(), [0; 32]);
assert_eq!(call_number, 1);
// Further calls must be hashed.
let value = cache.get_or_insert(pallet, item, || -> Result<[u8; 32], ()> {
call_number += 1;
Ok([0; 32])
});
assert_eq!(call_number, 1);
assert_eq!(value.unwrap(), [0; 32]);
}
}
-14
View File
@@ -1,14 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
/// Locate an item of a known type in the metadata.
/// We should already know that the item we're looking
/// for is a call or event for instance, and then with this,
/// we can dig up details for that item in the metadata.
pub trait MetadataLocation {
/// The pallet in which the item lives.
fn pallet(&self) -> &str;
/// The name of the item.
fn item(&self) -> &str;
}
+19 -870
View File
@@ -2,888 +2,37 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::hash_cache::HashCache;
use codec::Error as CodecError;
use frame_metadata::{
v15::PalletConstantMetadata, v15::RuntimeMetadataV15, v15::StorageEntryMetadata,
RuntimeMetadata, RuntimeMetadataPrefixed, META_RESERVED,
};
use parking_lot::RwLock;
use scale_info::{form::PortableForm, PortableRegistry, Type};
use std::{collections::HashMap, convert::TryFrom, sync::Arc};
use std::sync::Arc;
/// Metadata error originated from inspecting the internal representation of the runtime metadata.
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum MetadataError {
/// Module is not in metadata.
#[error("Pallet not found")]
PalletNotFound,
/// Pallet is not in metadata.
#[error("Pallet index {0} not found")]
PalletIndexNotFound(u8),
/// Call is not in metadata.
#[error("Call not found")]
CallNotFound,
/// Event is not in metadata.
#[error("Pallet {0}, Event {0} not found")]
EventNotFound(u8, u8),
/// Extrinsic is not in metadata.
#[error("Pallet {0}, Extrinsic {0} not found")]
ExtrinsicNotFound(u8, u8),
/// Event is not in metadata.
#[error("Pallet {0}, Error {0} not found")]
ErrorNotFound(u8, u8),
/// Runtime function is not in metadata.
#[error("Runtime function not found")]
RuntimeFnNotFound,
/// Storage is not in metadata.
#[error("Storage not found")]
StorageNotFound,
/// Storage type does not match requested type.
#[error("Storage type error")]
StorageTypeError,
/// Default error.
#[error("Failed to decode default: {0}")]
DefaultError(CodecError),
/// Failure to decode constant value.
#[error("Failed to decode constant value: {0}")]
ConstantValueError(CodecError),
/// Constant is not in metadata.
#[error("Constant not found")]
ConstantNotFound,
/// Type is not in metadata.
#[error("Type {0} missing from type registry")]
TypeNotFound(u32),
/// Runtime constant metadata is incompatible with the static one.
#[error("Pallet {0} Constant {0} has incompatible metadata")]
IncompatibleConstantMetadata(String, String),
/// Runtime call metadata is incompatible with the static one.
#[error("Pallet {0} Call {0} has incompatible metadata")]
IncompatibleCallMetadata(String, String),
/// Runtime storage metadata is incompatible with the static one.
#[error("Pallet {0} Storage {0} has incompatible metadata")]
IncompatibleStorageMetadata(String, String),
/// Runtime API metadata is incompatible with the static one.
#[error("Runtime API Trait {0} Method {0} has incompatible metadata")]
IncompatibleRuntimeApiMetadata(String, String),
/// Runtime metadata is not fully compatible with the static one.
#[error("Node metadata is not fully compatible")]
IncompatibleMetadata,
}
// We hide the innards behind an Arc so that it's easy to clone and share.
#[derive(Debug)]
struct MetadataInner {
metadata: RuntimeMetadataV15,
// Events are hashed by pallet an error index (decode oriented)
events: HashMap<(u8, u8), EventMetadata>,
// Extrinsics are hashed by pallet an error index (decode oriented)
extrinsics: HashMap<(u8, u8), ExtrinsicMetadata>,
// Errors are hashed by pallet and error index (decode oriented)
errors: HashMap<(u8, u8), ErrorMetadata>,
// Other pallet details are hashed by pallet name.
pallets: HashMap<String, PalletMetadata>,
// Type of the DispatchError type, which is what comes back if
// an extrinsic fails.
dispatch_error_ty: Option<u32>,
// Runtime API metadata
runtime_apis: HashMap<String, RuntimeFnMetadata>,
// The hashes uniquely identify parts of the metadata; different
// hashes mean some type difference exists between static and runtime
// versions. We cache them here to avoid recalculating:
cached_metadata_hash: RwLock<Option<[u8; 32]>>,
cached_call_hashes: HashCache,
cached_constant_hashes: HashCache,
cached_storage_hashes: HashCache,
cached_runtime_hashes: HashCache,
}
/// A representation of the runtime metadata received from a node.
/// A cheaply clone-able representation of the runtime metadata received from a node.
#[derive(Clone, Debug)]
pub struct Metadata {
inner: Arc<MetadataInner>,
inner: Arc<subxt_metadata::Metadata>,
}
impl std::ops::Deref for Metadata {
type Target = subxt_metadata::Metadata;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl Metadata {
/// Returns a reference to [`RuntimeFnMetadata`].
pub fn runtime_fn(&self, name: &str) -> Result<&RuntimeFnMetadata, MetadataError> {
self.inner
.runtime_apis
.get(name)
.ok_or(MetadataError::RuntimeFnNotFound)
}
/// Returns a reference to [`PalletMetadata`].
pub fn pallet(&self, name: &str) -> Result<&PalletMetadata, MetadataError> {
self.inner
.pallets
.get(name)
.ok_or(MetadataError::PalletNotFound)
}
/// Returns the metadata for the event at the given pallet and event indices.
pub fn event(
&self,
pallet_index: u8,
event_index: u8,
) -> Result<&EventMetadata, MetadataError> {
let event = self
.inner
.events
.get(&(pallet_index, event_index))
.ok_or(MetadataError::EventNotFound(pallet_index, event_index))?;
Ok(event)
}
/// Returns the metadata for the extrinsic at the given pallet and call indices.
pub fn extrinsic(
&self,
pallet_index: u8,
call_index: u8,
) -> Result<&ExtrinsicMetadata, MetadataError> {
let event = self
.inner
.extrinsics
.get(&(pallet_index, call_index))
.ok_or(MetadataError::ExtrinsicNotFound(pallet_index, call_index))?;
Ok(event)
}
/// Returns the metadata for the error at the given pallet and error indices.
pub fn error(
&self,
pallet_index: u8,
error_index: u8,
) -> Result<&ErrorMetadata, MetadataError> {
let error = self
.inner
.errors
.get(&(pallet_index, error_index))
.ok_or(MetadataError::ErrorNotFound(pallet_index, error_index))?;
Ok(error)
}
/// Return the DispatchError type ID if it exists.
pub fn dispatch_error_ty(&self) -> Option<u32> {
self.inner.dispatch_error_ty
}
/// Return the type registry embedded within the metadata.
pub fn types(&self) -> &PortableRegistry {
&self.inner.metadata.types
}
/// Resolve a type definition.
pub fn resolve_type(&self, id: u32) -> Option<&Type<PortableForm>> {
self.inner.metadata.types.resolve(id)
}
/// Return the runtime metadata.
pub fn runtime_metadata(&self) -> &RuntimeMetadataV15 {
&self.inner.metadata
}
/// Obtain the unique hash for a specific storage entry.
pub fn storage_hash(&self, pallet: &str, storage: &str) -> Result<[u8; 32], MetadataError> {
self.inner
.cached_storage_hashes
.get_or_insert(pallet, storage, || {
subxt_metadata::get_storage_hash(&self.inner.metadata, pallet, storage).map_err(
|e| match e {
subxt_metadata::NotFound::Root => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Item => MetadataError::StorageNotFound,
},
)
})
}
/// Obtain the unique hash for a constant.
pub fn constant_hash(&self, pallet: &str, constant: &str) -> Result<[u8; 32], MetadataError> {
self.inner
.cached_constant_hashes
.get_or_insert(pallet, constant, || {
subxt_metadata::get_constant_hash(&self.inner.metadata, pallet, constant).map_err(
|e| match e {
subxt_metadata::NotFound::Root => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Item => MetadataError::ConstantNotFound,
},
)
})
}
/// Obtain the unique hash for a call.
pub fn call_hash(&self, pallet: &str, function: &str) -> Result<[u8; 32], MetadataError> {
self.inner
.cached_call_hashes
.get_or_insert(pallet, function, || {
subxt_metadata::get_call_hash(&self.inner.metadata, pallet, function).map_err(|e| {
match e {
subxt_metadata::NotFound::Root => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Item => MetadataError::CallNotFound,
}
})
})
}
/// Obtain the unique hash for a runtime API function.
pub fn runtime_api_hash(
&self,
trait_name: &str,
method_name: &str,
) -> Result<[u8; 32], MetadataError> {
self.inner
.cached_runtime_hashes
.get_or_insert(trait_name, method_name, || {
subxt_metadata::get_runtime_api_hash(&self.inner.metadata, trait_name, method_name)
.map_err(|_| MetadataError::RuntimeFnNotFound)
})
}
/// Obtain the unique hash for this metadata.
pub fn metadata_hash<T: AsRef<str>>(&self, pallets: &[T]) -> [u8; 32] {
if let Some(hash) = *self.inner.cached_metadata_hash.read() {
return hash;
pub(crate) fn new(md: subxt_metadata::Metadata) -> Self {
Metadata {
inner: Arc::new(md),
}
let hash = subxt_metadata::MetadataHasher::new()
.only_these_pallets(pallets)
.hash(self.runtime_metadata());
*self.inner.cached_metadata_hash.write() = Some(hash);
hash
}
}
/// Metadata for a specific runtime API function.
#[derive(Clone, Debug)]
pub struct RuntimeFnMetadata {
/// The trait name of the runtime function.
trait_name: String,
/// The method name of the runtime function.
method_name: String,
/// The parameter name and type IDs interpreted as `scale_info::Field`
/// for ease of decoding.
fields: Vec<scale_info::Field<scale_info::form::PortableForm>>,
/// The type ID of the return type.
return_id: u32,
}
impl RuntimeFnMetadata {
/// Get the parameters as fields.
pub fn fields(&self) -> &[scale_info::Field<scale_info::form::PortableForm>] {
&self.fields
}
/// Return the trait name of the runtime function.
pub fn trait_name(&self) -> &str {
&self.trait_name
}
/// Return the method name of the runtime function.
pub fn method_name(&self) -> &str {
&self.method_name
}
/// Get the type ID of the return type.
pub fn return_id(&self) -> u32 {
self.return_id
impl From<subxt_metadata::Metadata> for Metadata {
fn from(md: subxt_metadata::Metadata) -> Self {
Metadata::new(md)
}
}
/// Metadata for a specific pallet.
#[derive(Clone, Debug)]
pub struct PalletMetadata {
index: u8,
name: String,
call_metadata: HashMap<String, CallMetadata>,
call_ty_id: Option<u32>,
event_ty_id: Option<u32>,
storage: HashMap<String, StorageEntryMetadata<PortableForm>>,
constants: HashMap<String, PalletConstantMetadata<PortableForm>>,
}
impl PalletMetadata {
/// Get the name of the pallet.
pub fn name(&self) -> &str {
&self.name
}
/// Get the index of this pallet.
pub fn index(&self) -> u8 {
self.index
}
/// If calls exist for this pallet, this returns the type ID of the variant
/// representing the different possible calls.
pub fn call_ty_id(&self) -> Option<u32> {
self.call_ty_id
}
/// If events exist for this pallet, this returns the type ID of the variant
/// representing the different possible events.
pub fn event_ty_id(&self) -> Option<u32> {
self.event_ty_id
}
/// Attempt to resolve a call into an index in this pallet, failing
/// if the call is not found in this pallet.
pub fn call(&self, function: &str) -> Result<&CallMetadata, MetadataError> {
let fn_index = self
.call_metadata
.get(function)
.ok_or(MetadataError::CallNotFound)?;
Ok(fn_index)
}
/// Return [`StorageEntryMetadata`] given some storage key.
pub fn storage(&self, key: &str) -> Result<&StorageEntryMetadata<PortableForm>, MetadataError> {
self.storage.get(key).ok_or(MetadataError::StorageNotFound)
}
/// Get a constant's metadata by name.
pub fn constant(
&self,
key: &str,
) -> Result<&PalletConstantMetadata<PortableForm>, MetadataError> {
self.constants
.get(key)
.ok_or(MetadataError::ConstantNotFound)
}
}
#[derive(Clone, Debug)]
pub struct CallMetadata {
call_index: u8,
fields: Vec<scale_info::Field<scale_info::form::PortableForm>>,
}
impl CallMetadata {
/// Index of this call.
pub fn index(&self) -> u8 {
self.call_index
}
/// The names, type names & types of each field in the call data.
pub fn fields(&self) -> &[scale_info::Field<scale_info::form::PortableForm>] {
&self.fields
}
}
/// Metadata for specific events.
#[derive(Clone, Debug)]
pub struct EventMetadata {
// The pallet name is shared across every event, so put it
// behind an Arc to avoid lots of needless clones of it existing.
pallet: Arc<str>,
event: String,
fields: Vec<scale_info::Field<scale_info::form::PortableForm>>,
docs: Vec<String>,
}
impl EventMetadata {
/// Get the name of the pallet from which the event was emitted.
pub fn pallet(&self) -> &str {
&self.pallet
}
/// Get the name of the pallet event which was emitted.
pub fn event(&self) -> &str {
&self.event
}
/// The names, type names & types of each field in the event.
pub fn fields(&self) -> &[scale_info::Field<scale_info::form::PortableForm>] {
&self.fields
}
/// Documentation for this event.
pub fn docs(&self) -> &[String] {
&self.docs
}
}
/// Metadata for specific extrinsics.
#[derive(Clone, Debug)]
pub struct ExtrinsicMetadata {
// The pallet name is shared across every extrinsic, so put it
// behind an Arc to avoid lots of needless clones of it existing.
pallet: Arc<str>,
call: String,
fields: Vec<scale_info::Field<scale_info::form::PortableForm>>,
docs: Vec<String>,
}
impl ExtrinsicMetadata {
/// Get the name of the pallet from which the extrinsic was emitted.
pub fn pallet(&self) -> &str {
&self.pallet
}
/// Get the name of the extrinsic call.
pub fn call(&self) -> &str {
&self.call
}
/// The names, type names & types of each field in the extrinsic.
pub fn fields(&self) -> &[scale_info::Field<scale_info::form::PortableForm>] {
&self.fields
}
/// Documentation for this extrinsic.
pub fn docs(&self) -> &[String] {
&self.docs
}
}
/// Details about a specific runtime error.
#[derive(Clone, Debug)]
pub struct ErrorMetadata {
// The pallet name is shared across every event, so put it
// behind an Arc to avoid lots of needless clones of it existing.
pallet: Arc<str>,
error: String,
docs: Vec<String>,
}
impl ErrorMetadata {
/// Get the name of the pallet from which the error originates.
pub fn pallet(&self) -> &str {
&self.pallet
}
/// The name of the error.
pub fn error(&self) -> &str {
&self.error
}
/// Documentation for the error.
pub fn docs(&self) -> &[String] {
&self.docs
}
}
/// Error originated from converting a runtime metadata [RuntimeMetadataPrefixed] to
/// the internal [Metadata] representation.
#[derive(Debug, thiserror::Error)]
pub enum InvalidMetadataError {
/// Invalid prefix
#[error("Invalid prefix")]
InvalidPrefix,
/// Invalid version
#[error("Invalid version")]
InvalidVersion,
/// Type missing from type registry
#[error("Type {0} missing from type registry")]
MissingType(u32),
/// Type missing extrinsic "Call" type
#[error("Missing extrinsic Call type")]
MissingCallType,
/// The extrinsic variant expected to contain a single field.
#[error("Extrinsic variant at index {0} expected to contain a single field")]
InvalidExtrinsicVariant(u8),
/// Type was not a variant/enum type
#[error("Type {0} was not a variant/enum type")]
TypeDefNotVariant(u32),
}
impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
type Error = InvalidMetadataError;
fn try_from(metadata: RuntimeMetadataPrefixed) -> Result<Self, Self::Error> {
if metadata.0 != META_RESERVED {
return Err(InvalidMetadataError::InvalidPrefix);
}
let metadata = match metadata.1 {
RuntimeMetadata::V14(v14) => subxt_metadata::metadata_v14_to_latest(v14),
RuntimeMetadata::V15(v15) => v15,
_ => return Err(InvalidMetadataError::InvalidVersion),
};
let runtime_apis: HashMap<String, RuntimeFnMetadata> = metadata
.apis
.iter()
.flat_map(|trait_metadata| {
let trait_name = &trait_metadata.name;
trait_metadata
.methods
.iter()
.map(|method_metadata| {
// Function named used by substrate to identify the runtime call.
let fn_name = format!("{}_{}", trait_name, method_metadata.name);
// Parameters mapped as `scale_info::Field` to allow dynamic decoding.
let fields: Vec<_> = method_metadata
.inputs
.iter()
.map(|input| {
let name = input.name.clone();
let ty = input.ty.id;
scale_info::Field {
name: Some(name),
ty: ty.into(),
type_name: None,
docs: Default::default(),
}
})
.collect();
let return_id = method_metadata.output.id;
let metadata = RuntimeFnMetadata {
fields,
return_id,
trait_name: trait_name.clone(),
method_name: method_metadata.name.clone(),
};
(fn_name, metadata)
})
.collect::<Vec<_>>()
})
.collect();
let get_type_def_variant = |type_id: u32| {
let ty = metadata
.types
.resolve(type_id)
.ok_or(InvalidMetadataError::MissingType(type_id))?;
if let scale_info::TypeDef::Variant(var) = &ty.type_def {
Ok(var)
} else {
Err(InvalidMetadataError::TypeDefNotVariant(type_id))
}
};
let pallets = metadata
.pallets
.iter()
.map(|pallet| {
let call_ty_id = pallet.calls.as_ref().map(|c| c.ty.id);
let event_ty_id = pallet.event.as_ref().map(|e| e.ty.id);
let call_metadata = pallet.calls.as_ref().map_or(Ok(HashMap::new()), |call| {
let type_def_variant = get_type_def_variant(call.ty.id)?;
let call_indexes = type_def_variant
.variants
.iter()
.map(|v| {
(
v.name.clone(),
CallMetadata {
call_index: v.index,
fields: v.fields.clone(),
},
)
})
.collect();
Ok(call_indexes)
})?;
let storage = pallet.storage.as_ref().map_or(HashMap::new(), |storage| {
storage
.entries
.iter()
.map(|entry| (entry.name.clone(), entry.clone()))
.collect()
});
let constants = pallet
.constants
.iter()
.map(|constant| (constant.name.clone(), constant.clone()))
.collect();
let pallet_metadata = PalletMetadata {
index: pallet.index,
name: pallet.name.to_string(),
call_metadata,
call_ty_id,
event_ty_id,
storage,
constants,
};
Ok((pallet.name.to_string(), pallet_metadata))
})
.collect::<Result<_, _>>()?;
let mut events = HashMap::<(u8, u8), EventMetadata>::new();
for pallet in &metadata.pallets {
if let Some(event) = &pallet.event {
let pallet_name: Arc<str> = pallet.name.to_string().into();
let event_type_id = event.ty.id;
let event_variant = get_type_def_variant(event_type_id)?;
for variant in &event_variant.variants {
events.insert(
(pallet.index, variant.index),
EventMetadata {
pallet: pallet_name.clone(),
event: variant.name.clone(),
fields: variant.fields.clone(),
docs: variant.docs.clone(),
},
);
}
}
}
let mut errors = HashMap::<(u8, u8), ErrorMetadata>::new();
for pallet in &metadata.pallets {
if let Some(error) = &pallet.error {
let pallet_name: Arc<str> = pallet.name.to_string().into();
let error_variant = get_type_def_variant(error.ty.id)?;
for variant in &error_variant.variants {
errors.insert(
(pallet.index, variant.index),
ErrorMetadata {
pallet: pallet_name.clone(),
error: variant.name.clone(),
docs: variant.docs.clone(),
},
);
}
}
}
let dispatch_error_ty = metadata
.types
.types
.iter()
.find(|ty| ty.ty.path.segments == ["sp_runtime", "DispatchError"])
.map(|ty| ty.id);
let extrinsic_ty = metadata
.types
.resolve(metadata.extrinsic.ty.id)
.ok_or(InvalidMetadataError::MissingType(metadata.extrinsic.ty.id))?;
let Some(call_id) = extrinsic_ty.type_params
.iter()
.find(|ty| ty.name == "Call")
.and_then(|ty| ty.ty)
.map(|ty| ty.id) else {
return Err(InvalidMetadataError::MissingCallType);
};
let call_type_variants = get_type_def_variant(call_id)?;
let mut extrinsics = HashMap::<(u8, u8), ExtrinsicMetadata>::new();
for variant in &call_type_variants.variants {
let pallet_name: Arc<str> = variant.name.to_string().into();
let pallet_index = variant.index;
// Pallet variants must contain one single call variant.
// In the following form:
//
// enum RuntimeCall {
// Pallet(pallet_call)
// }
if variant.fields.len() != 1 {
return Err(InvalidMetadataError::InvalidExtrinsicVariant(pallet_index));
}
let Some(ty) = variant.fields.first() else {
return Err(InvalidMetadataError::InvalidExtrinsicVariant(pallet_index));
};
// Get the call variant.
let call_type_variant = get_type_def_variant(ty.ty.id)?;
for variant in &call_type_variant.variants {
extrinsics.insert(
(pallet_index, variant.index),
ExtrinsicMetadata {
pallet: pallet_name.clone(),
call: variant.name.to_string(),
fields: variant.fields.clone(),
docs: variant.docs.clone(),
},
);
}
}
Ok(Metadata {
inner: Arc::new(MetadataInner {
metadata,
pallets,
events,
extrinsics,
errors,
dispatch_error_ty,
runtime_apis,
cached_metadata_hash: Default::default(),
cached_call_hashes: Default::default(),
cached_constant_hashes: Default::default(),
cached_storage_hashes: Default::default(),
cached_runtime_hashes: Default::default(),
}),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use frame_metadata::v15::{
ExtrinsicMetadata, PalletCallMetadata, PalletMetadata, PalletStorageMetadata,
StorageEntryModifier, StorageEntryType,
};
use scale_info::{meta_type, TypeInfo};
fn load_metadata() -> Metadata {
// Extrinsic needs to contain at least the generic type parameter "Call"
// for the metadata to be valid.
// The "Call" type from the metadata is used to decode extrinsics.
// In reality, the extrinsic type has "Call", "Address", "Extra", "Signature" generic types.
#[allow(unused)]
#[derive(TypeInfo)]
struct ExtrinsicType<Call> {
call: Call,
}
// Because this type is used to decode extrinsics, we expect this to be a TypeDefVariant.
// Each pallet must contain one single variant.
#[allow(unused)]
#[derive(TypeInfo)]
enum RuntimeCall {
PalletName(Pallet),
}
// The calls of the pallet.
#[allow(unused)]
#[derive(TypeInfo)]
enum Pallet {
#[allow(unused)]
SomeCall,
}
#[allow(dead_code)]
#[allow(non_camel_case_types)]
#[derive(TypeInfo)]
enum Call {
fill_block { param: u128 },
}
let storage = PalletStorageMetadata {
prefix: "System",
entries: vec![StorageEntryMetadata {
name: "Account",
modifier: StorageEntryModifier::Optional,
ty: StorageEntryType::Plain(meta_type::<u32>()),
default: vec![0],
docs: vec![],
}],
};
let constant = PalletConstantMetadata {
name: "BlockWeights",
ty: meta_type::<u32>(),
value: vec![1, 2, 3],
docs: vec![],
};
let pallet = PalletMetadata {
index: 0,
name: "System",
calls: Some(PalletCallMetadata {
ty: meta_type::<Call>(),
}),
storage: Some(storage),
constants: vec![constant],
event: None,
error: None,
docs: vec![],
};
let metadata = RuntimeMetadataV15::new(
vec![pallet],
ExtrinsicMetadata {
ty: meta_type::<ExtrinsicType<RuntimeCall>>(),
version: 0,
signed_extensions: vec![],
},
meta_type::<()>(),
vec![],
);
let prefixed = RuntimeMetadataPrefixed::from(metadata);
Metadata::try_from(prefixed)
.expect("Cannot translate runtime metadata to internal Metadata")
}
#[test]
fn metadata_inner_cache() {
// Note: Dependency on test_runtime can be removed if complex metadata
// is manually constructed.
let metadata = load_metadata();
let hash = metadata.metadata_hash(&["System"]);
// Check inner caching.
assert_eq!(metadata.inner.cached_metadata_hash.read().unwrap(), hash);
// The cache `metadata.inner.cached_metadata_hash` is already populated from
// the previous call. Therefore, changing the pallets argument must not
// change the methods behavior.
let hash_old = metadata.metadata_hash(&["no-pallet"]);
assert_eq!(hash_old, hash);
}
#[test]
fn metadata_call_inner_cache() {
let metadata = load_metadata();
let hash = metadata.call_hash("System", "fill_block");
let mut call_number = 0;
let hash_cached = metadata.inner.cached_call_hashes.get_or_insert(
"System",
"fill_block",
|| -> Result<[u8; 32], MetadataError> {
call_number += 1;
Ok([0; 32])
},
);
// Check function is never called (e.i, value fetched from cache).
assert_eq!(call_number, 0);
assert_eq!(hash.unwrap(), hash_cached.unwrap());
}
#[test]
fn metadata_constant_inner_cache() {
let metadata = load_metadata();
let hash = metadata.constant_hash("System", "BlockWeights");
let mut call_number = 0;
let hash_cached = metadata.inner.cached_constant_hashes.get_or_insert(
"System",
"BlockWeights",
|| -> Result<[u8; 32], MetadataError> {
call_number += 1;
Ok([0; 32])
},
);
// Check function is never called (e.i, value fetched from cache).
assert_eq!(call_number, 0);
assert_eq!(hash.unwrap(), hash_cached.unwrap());
}
#[test]
fn metadata_storage_inner_cache() {
let metadata = load_metadata();
let hash = metadata.storage_hash("System", "Account");
let mut call_number = 0;
let hash_cached = metadata.inner.cached_storage_hashes.get_or_insert(
"System",
"Account",
|| -> Result<[u8; 32], MetadataError> {
call_number += 1;
Ok([0; 32])
},
);
// Check function is never called (e.i, value fetched from cache).
assert_eq!(call_number, 0);
assert_eq!(hash.unwrap(), hash_cached.unwrap());
impl codec::Decode for Metadata {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
subxt_metadata::Metadata::decode(input).map(Metadata::new)
}
}
+4 -9
View File
@@ -5,15 +5,10 @@
//! Types representing the metadata obtained from a node.
mod decode_encode_traits;
mod hash_cache;
mod metadata_location;
mod metadata_type;
pub use metadata_location::MetadataLocation;
pub use metadata_type::{
ErrorMetadata, EventMetadata, ExtrinsicMetadata, InvalidMetadataError, Metadata, MetadataError,
PalletMetadata, RuntimeFnMetadata,
};
pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata};
pub use metadata_type::Metadata;
// Expose metadata types under a sub module in case somebody needs to reference them:
pub use subxt_metadata as types;