Skip to content
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

Merged
merged 12 commits into from
Aug 11, 2023
2 changes: 2 additions & 0 deletions metadata/src/from_into/v15.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use std::collections::HashMap;
// Converting from V15 metadata into our Subxt repr.
mod from_v15 {
use super::*;
use crate::CustomMetadata;
Copy link
Collaborator

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?


impl TryFrom<v15::RuntimeMetadataV15> for Metadata {
type Error = TryFromError;
Expand Down Expand Up @@ -93,6 +94,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 },
})
}
}
Expand Down
43 changes: 42 additions & 1 deletion metadata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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>>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the pub(crate) is necessary (since sub-modules can access stuff in parent ones anyway, and it's not needed anywhere else)

}

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
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn bytes(&self) -> &[u8] {
&self.data
}
pub fn bytes(&self) -> &'a [u8] {
&self.data
}

Just to tie the lifetime to that of the bytes and not that of this CustomMetadataValue instance

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 {
Expand Down
2 changes: 1 addition & 1 deletion subxt/src/book/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// see LICENSE for license details.

// Dev note; I used the following command to normalize and wrap comments:
// rustfmt +nightly --config wrap_comments=true,comment_width=100,normalize_comments=true subxt/src/book/mod.rs
// rustfmt +nightly --config wrap_comments=true,comment_width=100,normalize_comments=true subxt/src/book/custom_values
// It messed up comments in code blocks though, so be prepared to go and fix those.

//! # The Subxt Guide
Expand Down
12 changes: 12 additions & 0 deletions subxt/src/client/offline_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

use crate::custom_values::CustomValuesClient;
use crate::{
blocks::BlocksClient, constants::ConstantsClient, events::EventsClient,
rpc::types::RuntimeVersion, runtime_api::RuntimeApiClient, storage::StorageClient,
tx::TxClient, Config, Metadata,
};
use derivative::Derivative;

use std::sync::Arc;

/// A trait representing a client that can perform
Expand Down Expand Up @@ -49,6 +51,11 @@ pub trait OfflineClientT<T: Config>: Clone + Send + Sync + 'static {
fn runtime_api(&self) -> RuntimeApiClient<T, Self> {
RuntimeApiClient::new(self.clone())
}

/// Work this custom types.
fn custom_values(&self) -> CustomValuesClient<T, Self> {
CustomValuesClient::new(self.clone())
}
}

/// A client that is capable of performing offline-only operations.
Expand Down Expand Up @@ -121,6 +128,11 @@ impl<T: Config> OfflineClient<T> {
pub fn constants(&self) -> ConstantsClient<T, Self> {
<Self as OfflineClientT<T>>::constants(self)
}

/// Access custom types
pub fn custom_values(&self) -> CustomValuesClient<T, Self> {
<Self as OfflineClientT<T>>::custom_values(self)
}
}

impl<T: Config> OfflineClientT<T> for OfflineClient<T> {
Expand Down
8 changes: 8 additions & 0 deletions subxt/src/client/online_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// see LICENSE for license details.

use super::{OfflineClient, OfflineClientT};
use crate::custom_values::CustomValuesClient;
use crate::{
blocks::BlocksClient,
constants::ConstantsClient,
Expand All @@ -18,7 +19,9 @@ use crate::{
Config, Metadata,
};
use derivative::Derivative;

use futures::future;

use std::sync::{Arc, RwLock};

/// A trait representing a client that can perform
Expand Down Expand Up @@ -292,6 +295,11 @@ impl<T: Config> OnlineClient<T> {
<Self as OfflineClientT<T>>::constants(self)
}

/// Access custom types.
pub fn custom_values(&self) -> CustomValuesClient<T, Self> {
<Self as OfflineClientT<T>>::custom_values(self)
}

/// Work with blocks.
pub fn blocks(&self) -> BlocksClient<T, Self> {
<Self as OfflineClientT<T>>::blocks(self)
Expand Down
21 changes: 21 additions & 0 deletions subxt/src/custom_values/custom_value_adress.rs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in the file name, should be custom_value_address.rs :)

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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe for consistency we just impl this on &str so that we don't need the + ?Sized bound below? I'm a bit torn; it doesn't really matter either way afaik :)

Copy link
Contributor Author

@tadeohepperle tadeohepperle Aug 9, 2023

Choose a reason for hiding this comment

The 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 &&str to be passed as address. And then it cannot be easily used as e.g. custom_value_client.at("Foo") anymore.

type Target = DecodedValueThunk;

fn name(&self) -> &str {
self
}
}
138 changes: 138 additions & 0 deletions subxt/src/custom_values/custom_values_client.rs
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
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just FYI a nice alternate way to declare this is:

Suggested change
map: {
let mut m = BTreeMap::new();
m.insert("Person".to_string(), person_value_metadata);
m
},
map: BTreeMap::from_iter([
("Person".to_string(), person_value_metadata)
]),

},
};

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()
}
)
}
}
11 changes: 11 additions & 0 deletions subxt/src/custom_values/mod.rs
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;
8 changes: 8 additions & 0 deletions subxt/src/dynamic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,12 @@ impl DecodedValueThunk {
)?;
Ok(val)
}
/// decode the `DecodedValueThunk` into a concrete type.
pub fn as_type<T: DecodeAsType>(&self) -> Result<T, scale_decode::Error> {
T::decode_as_type(
&mut &self.scale_bytes[..],
self.type_id,
self.metadata.types(),
)
}
}
3 changes: 3 additions & 0 deletions subxt/src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,4 +234,7 @@ pub enum MetadataError {
/// The generated interface used is not compatible with the node.
#[error("The generated code is not compatible with the node")]
IncompatibleCodegen,
/// Custom value not found.
#[error("Custom value with name {0} not found")]
CustomValueNameNotFound(String),
}
1 change: 1 addition & 0 deletions subxt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub mod blocks;
pub mod client;
pub mod config;
pub mod constants;
pub mod custom_values;
pub mod dynamic;
pub mod error;
pub mod events;
Expand Down
1 change: 1 addition & 0 deletions subxt/src/metadata/metadata_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// see LICENSE for license details.

use crate::error::MetadataError;

use std::sync::Arc;

/// A cheaply clone-able representation of the runtime metadata received from a node.
Expand Down