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

wip: Remove subxt Config associated types by relying on enriched metadata (v16) #1566

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
20 changes: 19 additions & 1 deletion codegen/src/api/custom_values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::collections::HashSet;
use subxt_metadata::{CustomValueMetadata, Metadata};

use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
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(
Expand All @@ -19,6 +19,18 @@ pub fn generate_custom_values(
) -> TokenStream2 {
let mut fn_names_taken = HashSet::new();
let custom = metadata.custom();
let custom_types = custom.iter().map(|custom| {
let name = format_ident!("{}", custom.name());

let Ok(ty) = type_gen.resolve_type_path(custom.type_id()) else {
return quote! {};
};
let ty = ty.to_token_stream(type_gen.settings());
quote! {
pub type #name = #ty;
}
});

let custom_values_fns = custom.iter().filter_map(|custom_value| {
generate_custom_value_fn(custom_value, type_gen, crate_path, &mut fn_names_taken)
});
Expand All @@ -29,6 +41,12 @@ pub fn generate_custom_values(
impl CustomValuesApi {
#(#custom_values_fns)*
}

pub mod custom_types {
pub use super::*;

#(#custom_types)*
}
}
}

Expand Down
22 changes: 15 additions & 7 deletions core/src/config/polkadot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,22 @@ pub use primitive_types::{H256, U256};
pub enum PolkadotConfig {}

impl Config for PolkadotConfig {
type Hash = <SubstrateConfig as Config>::Hash;
type AccountId = <SubstrateConfig as Config>::AccountId;
type Address = MultiAddress<Self::AccountId, ()>;
type Signature = <SubstrateConfig as Config>::Signature;
type Hasher = <SubstrateConfig as Config>::Hasher;
type Header = <SubstrateConfig as Config>::Header;
// coming from: System::Config
type Hash = <SubstrateConfig as Config>::Hash; // Done
type AccountId = <SubstrateConfig as Config>::AccountId; // Done
type Hasher = <SubstrateConfig as Config>::Hasher; // Done

// coming from <runtime as traits::Block>::Extrinsic type
type Address = MultiAddress<Self::AccountId, ()>; // Done
type Signature = <SubstrateConfig as Config>::Signature; // Done

// coming from <runtime as traits::Block>::Header type
type Header = <SubstrateConfig as Config>::Header; // Done

type ExtrinsicParams = PolkadotExtrinsicParams<Self>;
type AssetId = u32;

// coming from Assets::Config (interested in foreign Assets specifically)
type AssetId = u32; // Done
}

/// A struct representing the signed extra and additional parameters required
Expand Down
63 changes: 63 additions & 0 deletions subxt/examples/metadata_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, SubstrateConfig};
use subxt_core::config::{Config, DefaultExtrinsicParams};
use subxt_signer::sr25519::dev;

// Generate an interface that we can use from the node's metadata.
#[subxt::subxt(runtime_metadata_insecure_url = "ws://localhost:9999")]
pub mod polkadot {}

// Derives aren't strictly needed, they just make developer life easier.
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum MetadataConfig {}

impl Config for MetadataConfig {
// Extracted from metadata directly:
type Hash = polkadot::custom_types::Hash;
type AccountId = polkadot::custom_types::AccountId;
type AssetId = polkadot::custom_types::AssetId;
type Address = polkadot::custom_types::Address;

// Present in metadata but this PoC needs to add
// fn specific per name of type to impl the hashing fn.
Comment on lines +25 to +26
Copy link
Collaborator

Choose a reason for hiding this comment

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

The thought I had for this sort of thing was originally going to be more like:

type Hasher = DynamicHasher

And then this hasher type (DynamicHasher here) is given the metadata when it tries to hash something, and can inspect the name of the hashing function and do the correct hashing logic itself.

In either case though, you need it to line up with the type Hash I think, which would be interesting/messy to do. It might be that we can infer and generate the Hash type based on what we see that the Hasher is though, rather than trying to extract it from the metadata. So maybe you'd end up with:

// DynamicHasher would spit out hashes like this, and these would encode
// to their inner bytes only (ignoring the variant byte):
enum DynamicHash {
    // an enum that supports the common hash types we know about
    BlakeTwo256([u8; 32]),
    TwoX128([u8; 16])
}


// ...
type Hash = DynamicHash;
type Hasher = DynamicHasher;

But we could theoretically do it all with codegen instead and extend the Subxt macro so that we can provide the necessary config types or override existing ones, but by default it will codegen relevant types like Hasher and Hash based on the metadata!

This would have the advantage of being faster/more correct and the disadvantage of needing more different Config impls for different chains rather than potentially being able to rely on one Config impl for multiple chains (or customising it for one chain).

Something to ponder :)

type Hasher = <SubstrateConfig as Config>::Hasher;
// Present in metadata but this PoC needs to impl the header
// trait to make use of this.
type Header = <SubstrateConfig as Config>::Header;
// Same story, present in md but needs subxt::tx::Signer<T>.
// type Signature = polkadot::custom_types::Signature;
type Signature = <SubstrateConfig as Config>::Signature;

// Not exposed in metadata, seems like heavily involved with
// code functionality which cannot safely be expressed in the
// metadata.
type ExtrinsicParams = DefaultExtrinsicParams<Self>;
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new API client, configured to talk to nodes.
let api = OnlineClient::<MetadataConfig>::from_insecure_url("ws://localhost:9999").await?;

// Build a balance transfer extrinsic.
let dest = dev::bob().public_key().into();
let balance_transfer_tx = polkadot::tx().balances().transfer_allow_death(dest, 10_000);

// Submit the balance transfer extrinsic from Alice, and wait for it to be successful
// and in a finalized block. We get back the extrinsic events if all is well.
let from = dev::alice();
let events = api
.tx()
.sign_and_submit_then_watch_default(&balance_transfer_tx, &from)
.await?
.wait_for_finalized_success()
.await?;

// Find a Transfer event and print it.
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
if let Some(event) = transfer_event {
println!("Balance transfer success: {event:?}");
}

Ok(())
}
Loading