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