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

Codegen for custom values in metadata #1117

Merged
merged 34 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
09a045f
work in progress
tadeohepperle Aug 2, 2023
e2af2cb
add custom types access
tadeohepperle Aug 3, 2023
72ad54b
nit
tadeohepperle Aug 8, 2023
8a9e76a
Merge branch 'master' into tadeo-hepperle-v15-custom-type-map
tadeohepperle Aug 8, 2023
50da61c
custom values client
tadeohepperle Aug 9, 2023
e8f2140
adjust light client
tadeohepperle Aug 9, 2023
00cbee4
Merge remote-tracking branch 'origin' into tadeo-hepperle-v15-custom-…
tadeohepperle Aug 9, 2023
f286b26
adjust doc comments
tadeohepperle Aug 10, 2023
2f83d36
adjust book for custom values in code gen
tadeohepperle Aug 10, 2023
57be757
format and check docs
tadeohepperle Aug 10, 2023
4a9000a
Merge branch 'master' into tadeo-hepperle-v15-custom-type-map
tadeohepperle Aug 10, 2023
34fa447
work in progress
tadeohepperle Aug 2, 2023
2bb420e
add custom types access
tadeohepperle Aug 3, 2023
4280a8e
nit
tadeohepperle Aug 8, 2023
59ac6cc
custom values client
tadeohepperle Aug 9, 2023
8afa2bf
adjust light client
tadeohepperle Aug 9, 2023
c73ace1
codegen and validation
tadeohepperle Aug 10, 2023
c9e2b5b
adjust docs
tadeohepperle Aug 10, 2023
99bf804
Merge branch 'tadeo-hepperle-v15-custom-type-map' into tadeo-hepperle…
tadeohepperle Aug 10, 2023
922fee2
use ignore in docs in book
tadeohepperle Aug 11, 2023
e1f7889
Merge branch 'tadeo-hepperle-v15-custom-type-map' into tadeo-hepperle…
tadeohepperle Aug 11, 2023
b4c91c4
change iter implementation
tadeohepperle Aug 11, 2023
b131b60
Merge branch 'master' into tadeo-hepperle-custom-values-codegen
tadeohepperle Aug 11, 2023
56bdcd0
Merge branch 'master' into tadeo-hepperle-custom-values-codegen
tadeohepperle Aug 11, 2023
7d871d0
use validation hash and other codegen changes
tadeohepperle Aug 11, 2023
f348cb5
Merge branch 'master' into tadeo-hepperle-custom-values-codegen
tadeohepperle Aug 21, 2023
fba3902
add ui test for custom values codegen
tadeohepperle Aug 21, 2023
959f24e
allow 'latest' metadata to be returned from the fallback code (#1127)
jsdw Aug 21, 2023
41513c0
nits
tadeohepperle Aug 22, 2023
175bbd5
Merge branch 'master' into tadeo-hepperle-custom-values-codegen
tadeohepperle Aug 22, 2023
529fd2f
fix validation check
tadeohepperle Aug 22, 2023
a291c60
Merge branch 'master' into tadeo-hepperle-custom-values-codegen
tadeohepperle Aug 23, 2023
1915ae9
fix comments
tadeohepperle Aug 23, 2023
3a93bda
nits
tadeohepperle Aug 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions codegen/src/api/custom_values.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// 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 std::collections::HashSet;

use crate::{types::TypeGenerator, CratePath};
use heck::ToSnakeCase as _;
use subxt_metadata::Metadata;

use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};

/// Generate the custom values mod, if there are any custom values in the metadata. Else returns None.
pub fn generate_custom_values(
metadata: &Metadata,
type_gen: &TypeGenerator,
types_mod_ident: &syn::Ident,
crate_path: &CratePath,
) -> Option<TokenStream2> {
let mut fn_names_taken = HashSet::new();
let custom_value_fns: Vec<_> = metadata
.custom()
.iter()
.filter_map(|(key, custom)| {
generate_custom_value_fn(
key,
custom.type_id(),
type_gen,
crate_path,
&mut fn_names_taken,
)
})
.collect();

(!custom_value_fns.is_empty()).then(|| {
quote!(
pub fn custom() -> custom::CustomValuesApi {
custom::CustomValuesApi
}

pub mod custom{
use super::#types_mod_ident;

pub struct CustomValuesApi;

impl CustomValuesApi {
#( #custom_value_fns )*
}
}


)
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: I guess either this or the below could be removed

Suggested change
(!custom_value_fns.is_empty()).then(|| {
quote!(
pub fn custom() -> custom::CustomValuesApi {
custom::CustomValuesApi
}
pub mod custom{
use super::#types_mod_ident;
pub struct CustomValuesApi;
impl CustomValuesApi {
#( #custom_value_fns )*
}
}
)
});

Copy link
Member

@niklasad1 niklasad1 Aug 11, 2023

Choose a reason for hiding this comment

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

I agree with Alex that if custom_value_fns.is_empty() is easier for me to understand here but I always struggle to understand then/then_some on bool's


if custom_value_fns.is_empty() {
None
} else {
Some(quote!(
pub fn custom() -> custom::CustomValuesApi {
Copy link
Collaborator

@lexnv lexnv Aug 11, 2023

Choose a reason for hiding this comment

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

With this implementation, I wonder if the metadata doesn't expose any custom values, then the users will not have the fn custom() exposed.

Wouldn't it be more consistent to have the pub fn custom() either way? Similar to how we expose pub fn tx() -> TransactionApi, then it would be an implementation detail that the CustomValuesApi doesn't expose anything.

Another benefit of mimicking the API would be that we don't have to collect the let custom_value_fns: Vec<_> values, saving an allocation here and there, since quote! works for iterators as well.

custom::CustomValuesApi
}

pub mod custom{
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would this work without placing the CustomValuesApi under the pub mod custom module?

Then ConstantsApi, StorageApi, TxApi would be placed on the same indentation/path level as our newly added CustomValuesApi

use super::#types_mod_ident;

pub struct CustomValuesApi;

impl CustomValuesApi {
#( #custom_value_fns )*
}
}


Copy link
Collaborator

Choose a reason for hiding this comment

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

Some extra spaces here

Suggested change

))
}
}

/// Generates runtime functions for the given API metadata.
/// Returns None, if the name would not make for a valid identifier.
fn generate_custom_value_fn(
name: &str,
type_id: u32,
type_gen: &TypeGenerator,
crate_path: &CratePath,
fn_names_taken: &mut HashSet<String>,
) -> Option<TokenStream2> {
// names are transformed to snake case to make for good function identifiers.
let fn_name = name.to_snake_case();
// Skip elements where the fn name is already occupied. E.g. if you have custom values with names "Foo" and "foo" in the metadata.
if fn_names_taken.contains(&fn_name) {
return None;
}
let fn_name_ident = format_ident!("{fn_name}");
fn_names_taken.insert(fn_name);

let return_ty = type_gen.resolve_type_path(type_id);

Some(quote!(
pub fn #fn_name_ident() -> #crate_path::custom_values::StaticAddress<#return_ty> {
#crate_path::custom_values::StaticAddress::new(#name)
}
))
}
10 changes: 10 additions & 0 deletions codegen/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@

mod calls;
mod constants;
mod custom_values;
mod errors;
mod events;
mod runtime_apis;
mod storage;

use subxt_metadata::Metadata;

use self::custom_values::generate_custom_values;

use super::DerivesRegistry;
use crate::error::CodegenError;
use crate::{
Expand Down Expand Up @@ -469,6 +472,11 @@ impl RuntimeGenerator {
let event_path = type_gen.resolve_type_path(self.metadata.outer_enums().event_enum_ty());
let error_path = type_gen.resolve_type_path(self.metadata.outer_enums().error_enum_ty());

// if there are no custom values this will be empty:
let custom_values_mod_and_fn =
generate_custom_values(&self.metadata, &type_gen, types_mod_ident, &crate_path)
.unwrap_or_default();

Ok(quote! {
#( #item_mod_attrs )*
#[allow(dead_code, unused_imports, non_camel_case_types)]
Expand Down Expand Up @@ -521,6 +529,8 @@ impl RuntimeGenerator {

#apis_mod

#custom_values_mod_and_fn

pub struct ConstantsApi;
impl ConstantsApi {
#(
Expand Down
24 changes: 19 additions & 5 deletions metadata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -641,26 +641,40 @@ 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 {
/// custom values accessible via a name.
map: BTreeMap<String, frame_metadata::v15::CustomValueMetadata<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 {
/// Get a certain [CustomValueMetadata] by its name.
pub fn get(&self, name: &str) -> Option<CustomValueMetadata<'_>> {
self.map.get(name).map(|e| CustomValueMetadata {
type_id: e.ty.id,
data: &e.value,
})
}

/// Iterates over names (keys) and associated custom values
pub fn iter(&self) -> impl Iterator<Item = (&str, CustomValueMetadata)> {
self.map.iter().map(|(name, el)| {
(
name.as_ref(),
CustomValueMetadata {
type_id: el.ty.id,
data: &el.value,
},
)
})
}
}

/// Basically the same as `frame_metadata::v15::CustomValueMetadata<PortableForm>>`, but borrowed.
pub struct CustomMetadataValue<'a> {
pub struct CustomValueMetadata<'a> {
type_id: u32,
data: &'a [u8],
}

impl<'a> CustomMetadataValue<'a> {
impl<'a> CustomValueMetadata<'a> {
/// the scale encoded value
pub fn bytes(&self) -> &'a [u8] {
self.data
Expand Down
47 changes: 43 additions & 4 deletions metadata/src/utils/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
//! 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;
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -393,6 +395,35 @@ 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,
registry: &PortableRegistry,
) -> [u8; HASH_LEN] {
let mut cache = HashMap::new();
custom_metadata
.iter()
.fold([0u8; HASH_LEN], |bytes, (key, custom_value)| {
xor(
bytes,
get_custom_value_hash(registry, key, 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(
registry: &PortableRegistry,
name: &str,
metadata: CustomValueMetadata,
cache: &mut HashMap<u32, CachedHash>,
) -> [u8; HASH_LEN] {
concat_and_hash3(
&hash(name.as_bytes()),
&get_type_hash(registry, metadata.type_id(), cache),
&hash(metadata.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()?;
Expand Down Expand Up @@ -494,6 +525,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> {
Expand All @@ -503,6 +535,7 @@ impl<'a> MetadataHasher<'a> {
metadata,
specific_pallets: None,
specific_runtime_apis: None,
include_custom_values: true,
}
}

Expand Down Expand Up @@ -554,7 +587,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
}
Expand All @@ -569,12 +602,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(), &metadata.types))
.unwrap_or_default();

concat_and_hash6(
&pallet_hash,
&apis_hash,
&extrinsic_hash,
&runtime_hash,
&outer_enums_hash,
&custom_values_hash,
)
}
}
Expand Down
27 changes: 27 additions & 0 deletions subxt/src/custom_values/custom_value_address.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use std::marker::PhantomData;

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.
/// The trait is implemented by [str] for dynamic loopup and [StaticAddress] for static queries.
pub trait CustomValueAddress {
/// The type of the custom value.
type Target: DecodeWithMetadata;
Expand All @@ -19,3 +22,27 @@ impl CustomValueAddress for str {
self
}
}

/// A static address to a custom value.
pub struct StaticAddress<R> {
name: &'static str,
phantom: PhantomData<R>,
}

impl<R> StaticAddress<R> {
/// Creates a new StaticAddress.
pub fn new(name: &'static str) -> Self {
StaticAddress {
name,
phantom: PhantomData,
}
}
}

impl<R: DecodeWithMetadata> CustomValueAddress for StaticAddress<R> {
type Target = R;

fn name(&self) -> &str {
self.name
}
}
2 changes: 1 addition & 1 deletion subxt/src/custom_values/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
mod custom_value_address;
mod custom_values_client;

pub use custom_value_address::CustomValueAddress;
pub use custom_value_address::{CustomValueAddress, StaticAddress};
pub use custom_values_client::CustomValuesClient;