Metadata V16: Implement support for Pallet View Functions (#1981)

* Support Pallet View Functions in Subxt

* fmt

* clippy

* Move a little view function logic to subxt_core

* clippy

* Add back check that prob isnt needed

* avoid vec macro in core

* Add view funciton test and apply various fixes to get it working

* Add test for dynamic view fn call and fix issues

* clippy

* fix test-runtime

* fmt

* remove export

* avoid vec for nostd core

* use const instead of fn for view fn call name

* Update to support latest unstable metadata

* Update metadata stripping tests for new v16 version
This commit is contained in:
James Wilson
2025-04-24 14:42:07 +01:00
committed by GitHub
parent 21b3f52191
commit 4524590821
28 changed files with 875 additions and 108 deletions
+1 -1
View File
@@ -68,7 +68,7 @@ impl TryFrom<v14::RuntimeMetadataV14> for Metadata {
error_ty: p.error.map(|e| e.ty.id),
error_variant_index,
constants: constants.collect(),
view_functions: vec![],
view_functions: Default::default(),
associated_types: Default::default(),
docs: vec![],
},
+1 -1
View File
@@ -63,7 +63,7 @@ impl TryFrom<v15::RuntimeMetadataV15> for Metadata {
error_ty: p.error.map(|e| e.ty.id),
error_variant_index,
constants: constants.collect(),
view_functions: vec![],
view_functions: Default::default(),
associated_types: Default::default(),
docs: p.docs,
},
+16 -12
View File
@@ -7,10 +7,9 @@ use super::TryFromError;
use crate::utils::variant_index::VariantIndex;
use crate::{
utils::ordered_map::OrderedMap, ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata,
MethodParamMetadata, OuterEnumsMetadata, PalletMetadataInner, PalletViewFunctionMetadataInner,
RuntimeApiMetadataInner, RuntimeApiMethodMetadataInner, StorageEntryMetadata,
StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata,
TransactionExtensionMetadataInner,
MethodParamMetadata, OuterEnumsMetadata, PalletMetadataInner, RuntimeApiMetadataInner,
RuntimeApiMethodMetadataInner, StorageEntryMetadata, StorageEntryModifier, StorageEntryType,
StorageHasher, StorageMetadata, TransactionExtensionMetadataInner, ViewFunctionMetadataInner,
};
use frame_metadata::{v15, v16};
use hashbrown::HashMap;
@@ -41,10 +40,10 @@ impl TryFrom<v16::RuntimeMetadataV16> for Metadata {
let name: ArcStr = c.name.clone().into();
(name.clone(), from_constant_metadata(name, c))
});
let view_functions = p
.view_functions
.into_iter()
.map(from_view_function_metadata);
let view_functions = p.view_functions.into_iter().map(|v| {
let name: ArcStr = v.name.clone().into();
(name.clone(), from_view_function_metadata(name, v))
});
let call_variant_index = VariantIndex::build(p.calls.as_ref().map(|c| c.ty.id), &types);
let error_variant_index =
@@ -133,7 +132,11 @@ fn from_transaction_extension_metadata(
fn from_extrinsic_metadata(value: v16::ExtrinsicMetadata<PortableForm>) -> ExtrinsicMetadata {
ExtrinsicMetadata {
supported_versions: value.versions,
transaction_extensions_by_version: value.transaction_extensions_by_version,
transaction_extensions_by_version: value
.transaction_extensions_by_version
.into_iter()
.map(|(version, idxs)| (version, idxs.into_iter().map(|idx| idx.0).collect()))
.collect(),
transaction_extensions: value
.transaction_extensions
.into_iter()
@@ -241,10 +244,11 @@ fn from_runtime_api_method_metadata(
}
fn from_view_function_metadata(
name: ArcStr,
s: v16::PalletViewFunctionMetadata<PortableForm>,
) -> PalletViewFunctionMetadataInner {
PalletViewFunctionMetadataInner {
name: s.name,
) -> ViewFunctionMetadataInner {
ViewFunctionMetadataInner {
name,
query_id: s.id,
inputs: s
.inputs
+53 -14
View File
@@ -216,6 +216,20 @@ impl Metadata {
})
}
/// Access a view function given its query ID, if any.
pub fn view_function_by_query_id(
&'_ self,
query_id: &[u8; 32],
) -> Option<ViewFunctionMetadata<'_>> {
// Dev note: currently, we only have pallet view functions, and here
// we just do a naive thing of iterating over the pallets to find the one
// we're looking for. Eventually we should construct a separate map of view
// functions for easy querying here.
self.pallets()
.flat_map(|p| p.view_functions())
.find(|vf| vf.query_id() == query_id)
}
/// Returns custom user defined types
pub fn custom(&self) -> CustomMetadata<'_> {
CustomMetadata {
@@ -293,12 +307,29 @@ impl<'a> PalletMetadata<'a> {
)
}
/// Does this pallet have any view functions?
pub fn has_view_functions(&self) -> bool {
!self.inner.view_functions.is_empty()
}
/// Return an iterator over the View Functions in this pallet, if any.
pub fn view_functions(&self) -> impl ExactSizeIterator<Item = PalletViewFunctionMetadata<'a>> {
pub fn view_functions(&self) -> impl ExactSizeIterator<Item = ViewFunctionMetadata<'a>> {
self.inner
.view_functions
.values()
.iter()
.map(|vf: &'a _| PalletViewFunctionMetadata {
.map(|vf: &'a _| ViewFunctionMetadata {
inner: vf,
types: self.types,
})
}
/// Return the view function with a given name, if any
pub fn view_function_by_name(&self, name: &str) -> Option<ViewFunctionMetadata<'a>> {
self.inner
.view_functions
.get_by_key(name)
.map(|vf: &'a _| ViewFunctionMetadata {
inner: vf,
types: self.types,
})
@@ -404,7 +435,7 @@ struct PalletMetadataInner {
/// Map from constant name to constant details.
constants: OrderedMap<ArcStr, ConstantMetadata>,
/// Details about each of the pallet view functions.
view_functions: Vec<PalletViewFunctionMetadataInner>,
view_functions: OrderedMap<ArcStr, ViewFunctionMetadataInner>,
/// Mapping from associated type to type ID describing its shape.
associated_types: BTreeMap<String, u32>,
/// Pallet documentation.
@@ -832,41 +863,48 @@ struct RuntimeApiMethodMetadataInner {
docs: Vec<String>,
}
/// Metadata for the available pallet View Functions.
/// Metadata for the available View Functions. Currently these exist only
/// at the pallet level, but eventually they could exist at the runtime level too.
#[derive(Debug, Clone, Copy)]
pub struct PalletViewFunctionMetadata<'a> {
inner: &'a PalletViewFunctionMetadataInner,
pub struct ViewFunctionMetadata<'a> {
inner: &'a ViewFunctionMetadataInner,
types: &'a PortableRegistry,
}
impl PalletViewFunctionMetadata<'_> {
impl<'a> ViewFunctionMetadata<'a> {
/// Method name.
pub fn name(&self) -> &str {
pub fn name(&self) -> &'a str {
&self.inner.name
}
/// Query ID. This is used to query the function. Roughly, it is constructed by doing
/// `twox_128(pallet_name) ++ twox_128("fn_name(fnarg_types) -> return_ty")` .
pub fn query_id(&self) -> [u8; 32] {
self.inner.query_id
pub fn query_id(&self) -> &'a [u8; 32] {
&self.inner.query_id
}
/// Method documentation.
pub fn docs(&self) -> &[String] {
pub fn docs(&self) -> &'a [String] {
&self.inner.docs
}
/// Method inputs.
pub fn inputs(&self) -> impl ExactSizeIterator<Item = &MethodParamMetadata> {
pub fn inputs(&self) -> impl ExactSizeIterator<Item = &'a MethodParamMetadata> {
self.inner.inputs.iter()
}
/// Method return type.
pub fn output_ty(&self) -> u32 {
self.inner.output_ty
}
/// Return a hash for the method. The query ID of a view function validates it to some
/// degree, but only takes type _names_ into account. This hash takes into account the
/// actual _shape_ of each argument and the return type.
pub fn hash(&self) -> [u8; HASH_LEN] {
crate::utils::validation::get_view_function_hash(self)
}
}
#[derive(Debug, Clone)]
struct PalletViewFunctionMetadataInner {
struct ViewFunctionMetadataInner {
/// View function name.
name: String,
name: ArcStr,
/// View function query ID.
query_id: [u8; 32],
/// Input types.
@@ -966,6 +1004,7 @@ impl codec::Decode for Metadata {
let metadata = match metadata.1 {
frame_metadata::RuntimeMetadata::V14(md) => md.try_into(),
frame_metadata::RuntimeMetadata::V15(md) => md.try_into(),
frame_metadata::RuntimeMetadata::V16(md) => md.try_into(),
_ => return Err("Cannot try_into() to Metadata: unsupported metadata version".into()),
};
+6 -6
View File
@@ -6,8 +6,8 @@
use crate::{
CustomMetadata, CustomValueMetadata, ExtrinsicMetadata, Metadata, PalletMetadata,
PalletViewFunctionMetadata, RuntimeApiMetadata, RuntimeApiMethodMetadata, StorageEntryMetadata,
StorageEntryType,
RuntimeApiMetadata, RuntimeApiMethodMetadata, StorageEntryMetadata, StorageEntryType,
ViewFunctionMetadata,
};
use alloc::vec::Vec;
use hashbrown::HashMap;
@@ -406,12 +406,12 @@ pub fn get_runtime_apis_hash(trait_metadata: RuntimeApiMetadata) -> Hash {
})
}
/// Obtain the hash of a specific pallet view function, or an error if it's not found.
pub fn get_pallet_view_function_hash(view_function: &PalletViewFunctionMetadata) -> Hash {
/// Obtain the hash of a specific view function, or an error if it's not found.
pub fn get_view_function_hash(view_function: &ViewFunctionMetadata) -> Hash {
let registry = view_function.types;
// The Query ID is `twox_128(pallet_name) ++ twox_128("fn_name(fnarg_types) -> return_ty")`.
let mut bytes = view_function.query_id();
let mut bytes = *view_function.query_id();
// This only takes type _names_ into account, so we beef this up by combining with actual
// type hashes, in a similar approach to runtime APIs..
@@ -439,7 +439,7 @@ fn get_pallet_view_functions_hash(pallet_metadata: &PalletMetadata) -> Hash {
// be identical regardless. For this, we can just XOR the hashes for each method
// together; we'll get the same output whichever order they are XOR'd together in,
// so long as each individual method is the same.
xor(bytes, get_pallet_view_function_hash(&method_metadata))
xor(bytes, get_view_function_hash(&method_metadata))
})
}