-
Notifications
You must be signed in to change notification settings - Fork 254
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
V15 Metadata: Support accessing custom types #1106
Changes from 5 commits
09a045f
e2af2cb
72ad54b
8a9e76a
50da61c
e8f2140
00cbee4
f286b26
2f83d36
57be757
4a9000a
922fee2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -20,7 +20,7 @@ mod from_into; | |||||||||||||
mod utils; | ||||||||||||||
|
||||||||||||||
use scale_info::{form::PortableForm, PortableRegistry, Variant}; | ||||||||||||||
use std::collections::HashMap; | ||||||||||||||
use std::collections::{BTreeMap, HashMap}; | ||||||||||||||
use std::sync::Arc; | ||||||||||||||
use utils::ordered_map::OrderedMap; | ||||||||||||||
use utils::variant_index::VariantIndex; | ||||||||||||||
|
@@ -51,6 +51,8 @@ pub struct Metadata { | |||||||||||||
dispatch_error_ty: Option<u32>, | ||||||||||||||
/// 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, | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
impl Metadata { | ||||||||||||||
|
@@ -132,6 +134,11 @@ impl Metadata { | |||||||||||||
}) | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/// Returns custom user defined types | ||||||||||||||
pub fn custom(&self) -> &CustomMetadata { | ||||||||||||||
&self.custom | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/// Obtain a unique hash representing this metadata or specific parts of it. | ||||||||||||||
pub fn hasher(&self) -> MetadataHasher { | ||||||||||||||
MetadataHasher::new(self) | ||||||||||||||
|
@@ -631,6 +638,40 @@ pub struct RuntimeApiMethodParamMetadata { | |||||||||||||
pub ty: u32, | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/// Metadata of custom types with custom values, basically the same as `frame_metadata::v15::CustomMetadata<PortableForm>>`. | ||||||||||||||
#[derive(Debug, Clone)] | ||||||||||||||
pub struct CustomMetadata { | ||||||||||||||
pub(crate) map: BTreeMap<String, frame_metadata::v15::CustomValueMetadata<PortableForm>>, | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think the |
||||||||||||||
} | ||||||||||||||
|
||||||||||||||
impl CustomMetadata { | ||||||||||||||
/// Get a certain [CustomMetadataValue] by its name. | ||||||||||||||
pub fn get(&self, name: &str) -> Option<CustomMetadataValue<'_>> { | ||||||||||||||
self.map.get(name).map(|e| CustomMetadataValue { | ||||||||||||||
type_id: e.ty.id, | ||||||||||||||
data: &e.value, | ||||||||||||||
}) | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/// Basically the same as `frame_metadata::v15::CustomValueMetadata<PortableForm>>`, but borrowed. | ||||||||||||||
pub struct CustomMetadataValue<'a> { | ||||||||||||||
type_id: u32, | ||||||||||||||
data: &'a [u8], | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
impl<'a> CustomMetadataValue<'a> { | ||||||||||||||
/// the scale encoded value | ||||||||||||||
pub fn bytes(&self) -> &[u8] { | ||||||||||||||
&self.data | ||||||||||||||
} | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Just to tie the lifetime to that of the bytes and not that of this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yeah true, nice catch! |
||||||||||||||
|
||||||||||||||
/// the type id in the TypeRegistry | ||||||||||||||
pub fn type_id(&self) -> u32 { | ||||||||||||||
self.type_id | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// Support decoding metadata from the "wire" format directly into this. | ||||||||||||||
// Errors may be lost in the case that the metadata content is somehow invalid. | ||||||||||||||
impl codec::Decode for Metadata { | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo in the file name, should be |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
use crate::dynamic::DecodedValueThunk; | ||
use crate::metadata::DecodeWithMetadata; | ||
|
||
/// This represents the address of a custom value in in the metadata. | ||
/// Anything, that implements the [CustomValueAddress] trait can be used, to fetch | ||
/// custom values from the metadata. | ||
pub trait CustomValueAddress { | ||
/// The type of the custom value. | ||
type Target: DecodeWithMetadata; | ||
|
||
/// the name (key) by which the custom value can be accessed in the metadata. | ||
fn name(&self) -> &str; | ||
} | ||
|
||
impl CustomValueAddress for str { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe for consistency we just impl this on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wanted to use str directly, because of this interface: at<Address: CustomValueAddress + ?Sized>(&self, address: &Address) When I used &str in the trait implementation, this is expecting |
||
type Target = DecodedValueThunk; | ||
|
||
fn name(&self) -> &str { | ||
self | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,138 @@ | ||||||||||||||||||
use crate::client::OfflineClientT; | ||||||||||||||||||
use crate::custom_values::custom_value_adress::CustomValueAddress; | ||||||||||||||||||
use crate::error::MetadataError; | ||||||||||||||||||
use crate::metadata::DecodeWithMetadata; | ||||||||||||||||||
use crate::{Config, Error}; | ||||||||||||||||||
use derivative::Derivative; | ||||||||||||||||||
|
||||||||||||||||||
/// A client for accessing custom values stored in the metadata. | ||||||||||||||||||
#[derive(Derivative)] | ||||||||||||||||||
#[derivative(Clone(bound = "Client: Clone"))] | ||||||||||||||||||
pub struct CustomValuesClient<T, Client> { | ||||||||||||||||||
client: Client, | ||||||||||||||||||
_marker: std::marker::PhantomData<T>, | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
impl<T, Client> CustomValuesClient<T, Client> { | ||||||||||||||||||
/// Create a new [`ConstantsClient`]. | ||||||||||||||||||
pub fn new(client: Client) -> Self { | ||||||||||||||||||
Self { | ||||||||||||||||||
client, | ||||||||||||||||||
_marker: std::marker::PhantomData, | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
impl<T: Config, Client: OfflineClientT<T>> CustomValuesClient<T, Client> { | ||||||||||||||||||
/// get a [CustomMetadataValue] by the string key it is registered under | ||||||||||||||||||
pub fn at<Address: CustomValueAddress + ?Sized>( | ||||||||||||||||||
&self, | ||||||||||||||||||
address: &Address, | ||||||||||||||||||
) -> Result<Address::Target, Error> { | ||||||||||||||||||
let metadata = self.client.metadata(); | ||||||||||||||||||
let custom_value = metadata | ||||||||||||||||||
.custom() | ||||||||||||||||||
.get(address.name()) | ||||||||||||||||||
.ok_or_else(|| MetadataError::CustomValueNameNotFound(address.name().to_string()))?; | ||||||||||||||||||
|
||||||||||||||||||
let value = <Address::Target as DecodeWithMetadata>::decode_with_metadata( | ||||||||||||||||||
&mut custom_value.bytes(), | ||||||||||||||||||
custom_value.type_id(), | ||||||||||||||||||
&metadata, | ||||||||||||||||||
)?; | ||||||||||||||||||
Ok(value) | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
#[cfg(test)] | ||||||||||||||||||
mod tests { | ||||||||||||||||||
use std::collections::BTreeMap; | ||||||||||||||||||
|
||||||||||||||||||
use crate::custom_values::CustomValuesClient; | ||||||||||||||||||
use crate::rpc::types::RuntimeVersion; | ||||||||||||||||||
use crate::{Metadata, OfflineClient, SubstrateConfig}; | ||||||||||||||||||
use scale_decode::DecodeAsType; | ||||||||||||||||||
use scale_info::form::PortableForm; | ||||||||||||||||||
use scale_info::TypeInfo; | ||||||||||||||||||
use sp_core::Encode; | ||||||||||||||||||
|
||||||||||||||||||
#[derive(Debug, Clone, PartialEq, Eq, Encode, TypeInfo, DecodeAsType)] | ||||||||||||||||||
pub struct Person { | ||||||||||||||||||
age: u16, | ||||||||||||||||||
name: String, | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
fn mock_metadata() -> Metadata { | ||||||||||||||||||
let person_ty = scale_info::MetaType::new::<Person>(); | ||||||||||||||||||
let unit = scale_info::MetaType::new::<()>(); | ||||||||||||||||||
let mut types = scale_info::Registry::new(); | ||||||||||||||||||
let person_ty_id = types.register_type(&person_ty); | ||||||||||||||||||
let unit_id = types.register_type(&unit); | ||||||||||||||||||
let types: scale_info::PortableRegistry = types.into(); | ||||||||||||||||||
|
||||||||||||||||||
let person = Person { | ||||||||||||||||||
age: 42, | ||||||||||||||||||
name: "Neo".into(), | ||||||||||||||||||
}; | ||||||||||||||||||
|
||||||||||||||||||
let person_value_metadata: frame_metadata::v15::CustomValueMetadata<PortableForm> = | ||||||||||||||||||
frame_metadata::v15::CustomValueMetadata { | ||||||||||||||||||
ty: person_ty_id, | ||||||||||||||||||
value: person.encode(), | ||||||||||||||||||
}; | ||||||||||||||||||
|
||||||||||||||||||
let frame_metadata = frame_metadata::v15::RuntimeMetadataV15 { | ||||||||||||||||||
types, | ||||||||||||||||||
pallets: vec![], | ||||||||||||||||||
extrinsic: frame_metadata::v15::ExtrinsicMetadata { | ||||||||||||||||||
version: 0, | ||||||||||||||||||
address_ty: unit_id, | ||||||||||||||||||
call_ty: unit_id, | ||||||||||||||||||
signature_ty: unit_id, | ||||||||||||||||||
extra_ty: unit_id, | ||||||||||||||||||
signed_extensions: vec![], | ||||||||||||||||||
}, | ||||||||||||||||||
ty: unit_id, | ||||||||||||||||||
apis: vec![], | ||||||||||||||||||
outer_enums: frame_metadata::v15::OuterEnums { | ||||||||||||||||||
call_enum_ty: unit_id, | ||||||||||||||||||
event_enum_ty: unit_id, | ||||||||||||||||||
error_enum_ty: unit_id, | ||||||||||||||||||
}, | ||||||||||||||||||
custom: frame_metadata::v15::CustomMetadata { | ||||||||||||||||||
map: { | ||||||||||||||||||
let mut m = BTreeMap::new(); | ||||||||||||||||||
m.insert("Person".to_string(), person_value_metadata); | ||||||||||||||||||
m | ||||||||||||||||||
}, | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just FYI a nice alternate way to declare this is:
Suggested change
|
||||||||||||||||||
}, | ||||||||||||||||||
}; | ||||||||||||||||||
|
||||||||||||||||||
let metadata: subxt_metadata::Metadata = frame_metadata.try_into().unwrap(); | ||||||||||||||||||
Metadata::new(metadata) | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
#[test] | ||||||||||||||||||
fn test_decoding() { | ||||||||||||||||||
let client = OfflineClient::<SubstrateConfig>::new( | ||||||||||||||||||
Default::default(), | ||||||||||||||||||
RuntimeVersion { | ||||||||||||||||||
spec_version: 0, | ||||||||||||||||||
transaction_version: 0, | ||||||||||||||||||
other: Default::default(), | ||||||||||||||||||
}, | ||||||||||||||||||
mock_metadata(), | ||||||||||||||||||
); | ||||||||||||||||||
let custom_value_client = CustomValuesClient::new(client); | ||||||||||||||||||
assert!(custom_value_client.at("No one").is_err()); | ||||||||||||||||||
let person_decoded_value_thunk = custom_value_client.at("Person").unwrap(); | ||||||||||||||||||
let person: Person = person_decoded_value_thunk.as_type().unwrap(); | ||||||||||||||||||
assert_eq!( | ||||||||||||||||||
person, | ||||||||||||||||||
Person { | ||||||||||||||||||
age: 42, | ||||||||||||||||||
name: "Neo".into() | ||||||||||||||||||
} | ||||||||||||||||||
) | ||||||||||||||||||
} | ||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// 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. | ||
|
||
//! Types associated with accessing custom types | ||
|
||
mod custom_value_adress; | ||
mod custom_values_client; | ||
|
||
pub use custom_value_adress::CustomValueAddress; | ||
pub use custom_values_client::CustomValuesClient; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need this for all of the other types?