From c6350fcc11f25aee4f45fa414f8b0f29f2feec57 Mon Sep 17 00:00:00 2001 From: David Craven Date: Wed, 1 Jul 2020 16:27:14 +0200 Subject: [PATCH 1/3] Support authors api. (#134) --- client/src/lib.rs | 12 +++++-- src/lib.rs | 85 +++++++++++++++++++++++++++++++++++++++++++---- src/rpc.rs | 47 ++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 9 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index ee76bbab3b..3ca04c0376 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -43,12 +43,14 @@ use jsonrpsee::{ }; use sc_network::config::TransportConfig; pub use sc_service::{ - config::DatabaseConfig, + config::{ + DatabaseConfig, + KeystoreConfig, + }, Error as ServiceError, }; use sc_service::{ config::{ - KeystoreConfig, NetworkConfiguration, TaskType, }, @@ -119,6 +121,8 @@ pub struct SubxtClientConfig { pub copyright_start_year: i32, /// Database configuration. pub db: DatabaseConfig, + /// Keystore configuration. + pub keystore: KeystoreConfig, /// Service builder. pub builder: fn(Configuration) -> Result, /// Chain specification. @@ -216,7 +220,7 @@ fn start_subxt_client( }) .into(), database: config.db, - keystore: KeystoreConfig::InMemory, + keystore: config.keystore, max_runtime_instances: 8, announce_block: true, dev_key_seed: config.role.into(), @@ -342,6 +346,7 @@ mod tests { path: tmp.path().into(), cache_size: 64, }, + keystore: KeystoreConfig::InMemory, builder: test_node::service::new_light, chain_spec, role: Role::Light, @@ -372,6 +377,7 @@ mod tests { path: tmp.path().into(), cache_size: 128, }, + keystore: KeystoreConfig::InMemory, builder: test_node::service::new_full, chain_spec: test_node::chain_spec::development_config(), role: Role::Authority(AccountKeyring::Alice), diff --git a/src/lib.rs b/src/lib.rs index bce4734da3..23927e7df3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,9 +52,12 @@ use codec::Decode; use futures::future; use jsonrpsee::client::Subscription; use sc_rpc_api::state::ReadProof; -use sp_core::storage::{ - StorageChangeSet, - StorageKey, +use sp_core::{ + storage::{ + StorageChangeSet, + StorageKey, + }, + Bytes, }; pub use sp_runtime::traits::SignedExtension; use sp_version::RuntimeVersion; @@ -425,6 +428,41 @@ impl Client { let decoder = self.events_decoder::(); self.submit_and_watch_extrinsic(extrinsic, decoder).await } + + /// Insert a key into the keystore. + pub async fn insert_key( + &self, + key_type: String, + suri: String, + public: Bytes, + ) -> Result<(), Error> { + self.rpc.insert_key(key_type, suri, public).await + } + + /// Generate new session keys and returns the corresponding public keys. + pub async fn rotate_keys(&self) -> Result { + self.rpc.rotate_keys().await + } + + /// Checks if the keystore has private keys for the given session public keys. + /// + /// `session_keys` is the SCALE encoded session keys object from the runtime. + /// + /// Returns `true` iff all private keys could be found. + pub async fn has_session_keys(&self, session_keys: Bytes) -> Result { + self.rpc.has_session_keys(session_keys).await + } + + /// Checks if the keystore has private keys for the given public key and key type. + /// + /// Returns `true` if a private key could be found. + pub async fn has_key( + &self, + public_key: Bytes, + key_type: String, + ) -> Result { + self.rpc.has_key(public_key, key_type).await + } } /// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of @@ -456,6 +494,7 @@ mod tests { use sp_runtime::MultiSignature; use substrate_subxt_client::{ DatabaseConfig, + KeystoreConfig, Role, SubxtClient, SubxtClientConfig, @@ -464,7 +503,9 @@ mod tests { pub(crate) type TestRuntime = crate::NodeTemplateRuntime; - pub(crate) async fn test_client() -> (Client, TempDir) { + pub(crate) async fn test_client_with( + key: AccountKeyring, + ) -> (Client, TempDir) { env_logger::try_init().ok(); let tmp = TempDir::new("subxt-").expect("failed to create tempdir"); let config = SubxtClientConfig { @@ -473,12 +514,16 @@ mod tests { author: "substrate subxt", copyright_start_year: 2020, db: DatabaseConfig::RocksDb { - path: tmp.path().into(), + path: tmp.path().join("db"), cache_size: 128, }, + keystore: KeystoreConfig::Path { + path: tmp.path().join("keystore"), + password: None, + }, builder: test_node::service::new_full, chain_spec: test_node::chain_spec::development_config(), - role: Role::Authority(AccountKeyring::Alice), + role: Role::Authority(key), }; let client = ClientBuilder::new() .set_client(SubxtClient::new(config).expect("Error creating subxt client")) @@ -488,6 +533,34 @@ mod tests { (client, tmp) } + pub(crate) async fn test_client() -> (Client, TempDir) { + test_client_with(AccountKeyring::Alice).await + } + + #[async_std::test] + async fn test_insert_key() { + // Bob is not an authority, so block production should be disabled. + let (client, _tmp) = test_client_with(AccountKeyring::Bob).await; + let mut blocks = client.subscribe_blocks().await.unwrap(); + // get the genesis block. + assert_eq!(blocks.next().await.number, 0); + let public = AccountKeyring::Alice.public().as_array_ref().to_vec(); + client + .insert_key( + "aura".to_string(), + "//Alice".to_string(), + public.clone().into(), + ) + .await + .unwrap(); + assert!(client + .has_key(public.clone().into(), "aura".to_string()) + .await + .unwrap()); + // Alice is an authority, so blocks should be produced. + assert_eq!(blocks.next().await.number, 1); + } + #[async_std::test] async fn test_tx_transfer_balance() { let mut signer = PairSigner::new(AccountKeyring::Alice.pair()); diff --git a/src/rpc.rs b/src/rpc.rs index 95e176ae32..08662d0ad6 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -396,6 +396,53 @@ impl Rpc { } unreachable!() } + + /// Insert a key into the keystore. + pub async fn insert_key( + &self, + key_type: String, + suri: String, + public: Bytes, + ) -> Result<(), Error> { + let params = Params::Array(vec![ + to_json_value(key_type)?, + to_json_value(suri)?, + to_json_value(public)?, + ]); + self.client.request("author_insertKey", params).await?; + Ok(()) + } + + /// Generate new session keys and returns the corresponding public keys. + pub async fn rotate_keys(&self) -> Result { + Ok(self + .client + .request("author_rotateKeys", Params::None) + .await?) + } + + /// Checks if the keystore has private keys for the given session public keys. + /// + /// `session_keys` is the SCALE encoded session keys object from the runtime. + /// + /// Returns `true` iff all private keys could be found. + pub async fn has_session_keys(&self, session_keys: Bytes) -> Result { + let params = Params::Array(vec![to_json_value(session_keys)?]); + Ok(self.client.request("author_hasSessionKeys", params).await?) + } + + /// Checks if the keystore has private keys for the given public key and key type. + /// + /// Returns `true` if a private key could be found. + pub async fn has_key( + &self, + public_key: Bytes, + key_type: String, + ) -> Result { + let params = + Params::Array(vec![to_json_value(public_key)?, to_json_value(key_type)?]); + Ok(self.client.request("author_hasKey", params).await?) + } } /// Captures data for when an extrinsic is successfully included in a block From 536c54c84373b4610ee797d8d23a582937e42a48 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 7 Jul 2020 09:19:44 +0200 Subject: [PATCH 2/3] Document the #[module] macro (#135) * Document the #[module] macro * Obey the fmt * Review feedback * More docs * Tweak wording * tweak wording * whitespace * Tweak docstring --- proc-macro/src/lib.rs | 52 ++++++++++++++++++++++++++++++++++++++++ proc-macro/src/module.rs | 44 +++++++++++++++++++++++++++++++++- src/events.rs | 2 +- 3 files changed, 96 insertions(+), 2 deletions(-) diff --git a/proc-macro/src/lib.rs b/proc-macro/src/lib.rs index ccbea7e2b4..cecdfe397f 100644 --- a/proc-macro/src/lib.rs +++ b/proc-macro/src/lib.rs @@ -30,6 +30,58 @@ use synstructure::{ Structure, }; +/// Register type sizes for [EventsDecoder](struct.EventsDecoder.html) and set the `MODULE`. +/// +/// The `module` macro registers the type sizes of the associated types of a trait so that [EventsDecoder](struct.EventsDecoder.html) +/// can decode events of that type when received from Substrate. It also sets the `MODULE` constant +/// to the name of the trait (must match the name of the Substrate pallet) that enables the [Call](), [Event]() and [Store]() macros to work. +/// +/// If you do not want an associated type to be registered, likely because you never expect it as part of a response payload to be decoded, use `#[module(ignore)]` on the type. +/// +/// Example: +/// +/// ```ignore +/// #[module] +/// pub trait Herd: Husbandry { +/// type Hooves: HoofCounter; +/// type Wool: WoollyAnimal; +/// #[module(ignore)] +/// type Digestion: EnergyProducer + std::fmt::Debug; +/// } +/// ``` +/// +/// The above will produce the following code: +/// +/// ```ignore +/// pub trait Herd: Husbandry { +/// type Hooves: HoofCounter; +/// type Wool: WoollyAnimal; +/// #[module(ignore)] +/// type Digestion: EnergyProducer + std::fmt::Debug; +/// } +/// +/// const MODULE: &str = "Herd"; +/// +/// // `EventsDecoder` extension trait. +/// pub trait HerdEventsDecoder { +/// // Registers this modules types. +/// fn with_herd(&mut self); +/// } +/// +/// impl HerdEventsDecoder for +/// substrate_subxt::EventsDecoder +/// { +/// fn with_herd(&mut self) { +/// self.with_husbandry(); +/// self.register_type_size::("Hooves"); +/// self.register_type_size::("Wool"); +/// } +/// } +/// ``` +/// +/// The following type sizes are registered by default: `bool, u8, u32, AccountId, AccountIndex, +/// AuthorityId, AuthorityIndex, AuthorityWeight, BlockNumber, DispatchInfo, Hash, Kind, +/// MemberCount, PhantomData, PropIndex, ProposalIndex, ReferendumIndex, SessionIndex, VoteThreshold` #[proc_macro_attribute] #[proc_macro_error] pub fn module(args: TokenStream, input: TokenStream) -> TokenStream { diff --git a/proc-macro/src/module.rs b/proc-macro/src/module.rs index 5dddfd5899..b1d989235b 100644 --- a/proc-macro/src/module.rs +++ b/proc-macro/src/module.rs @@ -69,7 +69,7 @@ fn events_decoder_trait_name(module: &syn::Ident) -> syn::Ident { fn with_module_ident(module: &syn::Ident) -> syn::Ident { format_ident!("with_{}", module.to_string().to_snake_case()) } - +/// Attribute macro that registers the type sizes used by the module; also sets the `MODULE` constant. pub fn module(_args: TokenStream, tokens: TokenStream) -> TokenStream { let input: Result = syn::parse2(tokens.clone()); let input = if let Ok(input) = input { @@ -187,4 +187,46 @@ mod tests { let result = module(attr, input); utils::assert_proc_macro(result, expected); } + + #[test] + fn test_herd() { + let attr = quote!(#[module]); + let input = quote! { + pub trait Herd: Husbandry { + type Hoves: u8; + type Wool: bool; + #[module(ignore)] + type Digestion: EnergyProducer + fmt::Debug; + } + }; + let expected = quote! { + pub trait Herd: Husbandry { + type Hoves: u8; + type Wool: bool; + #[module(ignore)] + type Digestion: EnergyProducer + fmt::Debug; + } + + const MODULE: &str = "Herd"; + + /// `EventsDecoder` extension trait. + pub trait HerdEventsDecoder { + /// Registers this modules types. + fn with_herd(&mut self); + } + + impl HerdEventsDecoder for + substrate_subxt::EventsDecoder + { + fn with_herd(&mut self) { + self.with_husbandry(); + self.register_type_size::("Hoves"); + self.register_type_size::("Wool"); + } + } + }; + + let result = module(attr, input); + utils::assert_proc_macro(result, expected); + } } diff --git a/src/events.rs b/src/events.rs index b226947e4a..d5f75c9b8c 100644 --- a/src/events.rs +++ b/src/events.rs @@ -59,7 +59,7 @@ pub struct RawEvent { pub data: Vec, } -/// Event decoder. +/// Events decoder. #[derive(Debug)] pub struct EventsDecoder { metadata: Metadata, From a4ba74ed729dca14dcaa90005265c8fd51f63849 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 7 Jul 2020 09:20:58 +0200 Subject: [PATCH 3/3] Document the `Call` derive macro (#137) * Document the `Call` derive macro * Obey the fmt * Update proc-macro/src/lib.rs Co-authored-by: Andrew Jones * Review feedback Co-authored-by: Andrew Jones --- examples/submit_and_watch.rs | 5 +++- proc-macro/src/lib.rs | 48 +++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/examples/submit_and_watch.rs b/examples/submit_and_watch.rs index 81701f7383..c75137f994 100644 --- a/examples/submit_and_watch.rs +++ b/examples/submit_and_watch.rs @@ -16,7 +16,10 @@ use sp_keyring::AccountKeyring; use substrate_subxt::{ - balances::*, + balances::{ + TransferCallExt, + TransferEventExt, + }, ClientBuilder, DefaultNodeRuntime, PairSigner, diff --git a/proc-macro/src/lib.rs b/proc-macro/src/lib.rs index cecdfe397f..c6daa1e68a 100644 --- a/proc-macro/src/lib.rs +++ b/proc-macro/src/lib.rs @@ -88,7 +88,53 @@ pub fn module(args: TokenStream, input: TokenStream) -> TokenStream { module::module(args.into(), input.into()).into() } -decl_derive!([Call] => #[proc_macro_error] call); +decl_derive!( + [Call] => + /// Derive macro that implements [substrate_subxt::Call](../substrate_subxt/trait.Call.html) for your struct + /// and defines&implements the calls as an extension trait. + /// + /// Use the `Call` derive macro in tandem with the [#module](../substrate_subxt/attr.module.html) macro to extend + /// your struct to enable calls to substrate and to decode events. The struct maps to the corresponding Substrate runtime call, e.g.: + /// + /// ```ignore + /// decl_module! { + /// /* … */ + /// pub fn fun_stuff(origin, something: Vec) -> DispatchResult { /* … */ } + /// /* … */ + /// } + ///``` + /// + /// Implements [substrate_subxt::Call](../substrate_subxt/trait.Call.html) and adds an extension trait that + /// provides two methods named as your struct. + /// + /// Example: + /// ```rust,ignore + /// pub struct MyRuntime; + /// + /// impl System for MyRuntime { /* … */ } + /// impl Balances for MyRuntime { /* … */ } + /// + /// #[module] + /// pub trait MyTrait: System + Balances {} + /// + /// #[derive(Call)] + /// pub struct FunStuffCall { + /// /// Runtime marker. + /// pub _runtime: PhantomData, + /// /// The argument passed to the call.. + /// pub something: Vec, + /// } + /// ``` + /// + /// When building a [Client](../substrate_subxt/struct.Client.html) parameterised to `MyRuntime`, you have access to + /// two new methods: `fun_stuff()` and `fun_stuff_and_watch()` by way of the derived `FunStuffExt` + /// trait. The `_and_watch` variant makes the call and waits for the result. The fields of the + /// input struct become arguments to the calls (ignoring the marker field). + /// + /// Under the hood the implementation calls [submit()](../substrate_subxt/struct.Client.html#method.submit) and + /// [watch()](../substrate_subxt/struct.Client.html#method.watch) respectively. + #[proc_macro_error] call +); fn call(s: Structure) -> TokenStream { call::call(s).into() }