diff --git a/client/src/lib.rs b/client/src/lib.rs index a6c63018aa..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,13 +220,11 @@ 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(), - base_path: Default::default(), - informant_output_format: Default::default(), telemetry_endpoints: Default::default(), telemetry_external_transport: Default::default(), default_heap_pages: Default::default(), @@ -344,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, @@ -374,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/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 ccbea7e2b4..c6daa1e68a 100644 --- a/proc-macro/src/lib.rs +++ b/proc-macro/src/lib.rs @@ -30,13 +30,111 @@ 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 { 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() } 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, diff --git a/src/lib.rs b/src/lib.rs index dc10523c1a..23927e7df3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,6 @@ path_statements, patterns_in_fns_without_body, private_in_public, - missing_debug_implementations, unconditional_recursion, unused_allocation, unused_comparisons, @@ -36,8 +35,7 @@ while_true, trivial_casts, trivial_numeric_casts, - unused_extern_crates, - clippy::all + unused_extern_crates )] #![allow(clippy::type_complexity)] @@ -54,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; @@ -107,7 +108,6 @@ use crate::{ /// ClientBuilder for constructing a Client. #[derive(Default)] -#[allow(missing_debug_implementations)] pub struct ClientBuilder { _marker: std::marker::PhantomData, url: Option, @@ -166,7 +166,6 @@ impl ClientBuilder { } /// Client to interface with a substrate node. -#[allow(missing_debug_implementations)] pub struct Client { rpc: Rpc, genesis_hash: T::Hash, @@ -429,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 @@ -460,6 +494,7 @@ mod tests { use sp_runtime::MultiSignature; use substrate_subxt_client::{ DatabaseConfig, + KeystoreConfig, Role, SubxtClient, SubxtClientConfig, @@ -468,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 { @@ -477,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")) @@ -492,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 diff --git a/src/subscription.rs b/src/subscription.rs index c1dd36d91f..6208ce6f7b 100644 --- a/src/subscription.rs +++ b/src/subscription.rs @@ -33,7 +33,6 @@ use crate::{ /// Event subscription simplifies filtering a storage change set stream for /// events of interest. -#[allow(missing_debug_implementations)] pub struct EventSubscription { subscription: Subscription>, decoder: EventsDecoder, diff --git a/test-node/Cargo.toml b/test-node/Cargo.toml index 5325a3b634..e286e01eec 100644 --- a/test-node/Cargo.toml +++ b/test-node/Cargo.toml @@ -13,7 +13,7 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -futures = { version = "0.3.5", package = "futures" } +futures = "0.3.5" log = "0.4.8" structopt = "0.3.15" parking_lot = "0.11.0" diff --git a/test-node/runtime/src/lib.rs b/test-node/runtime/src/lib.rs index d8cc5318bd..23858fa174 100644 --- a/test-node/runtime/src/lib.rs +++ b/test-node/runtime/src/lib.rs @@ -235,8 +235,6 @@ impl system::Trait for Runtime { type OnKilledAccount = (); /// The data to be stored in an account. type AccountData = balances::AccountData; - /// The base call filter. - type BaseCallFilter = (); } impl aura::Trait for Runtime {