From 489650c9c4172b756f9fc6c300bb1573b589b1c7 Mon Sep 17 00:00:00 2001 From: Niklas Date: Thu, 3 Feb 2022 18:09:47 +0100 Subject: [PATCH] introduce jsonrpsee proc macros API --- subxt/Cargo.toml | 2 +- subxt/src/client.rs | 20 +- subxt/src/config.rs | 8 +- subxt/src/rpc.rs | 364 ++++++++++++------------------ subxt/src/storage.rs | 64 ++++-- subxt/src/subscription.rs | 16 +- subxt/src/transaction.rs | 35 +-- subxt/tests/integration/client.rs | 26 ++- 8 files changed, 261 insertions(+), 274 deletions(-) diff --git a/subxt/Cargo.toml b/subxt/Cargo.toml index bffd622a3b..54e1b393ad 100644 --- a/subxt/Cargo.toml +++ b/subxt/Cargo.toml @@ -20,7 +20,7 @@ chameleon = "0.1.0" scale-info = { version = "1.0.0", features = ["bit-vec"] } futures = "0.3.13" hex = "0.4.3" -jsonrpsee = { version = "0.8.0", features = ["async-client", "client-ws-transport"] } +jsonrpsee = { version = "0.8.0", features = ["macros", "async-client", "client-ws-transport"] } log = "0.4.14" num-traits = { version = "0.2.14", default-features = false } serde = { version = "1.0.124", features = ["derive"] } diff --git a/subxt/src/client.rs b/subxt/src/client.rs index 6dddfdf986..8cdd980e4f 100644 --- a/subxt/src/client.rs +++ b/subxt/src/client.rs @@ -31,6 +31,7 @@ use crate::{ Rpc, RpcClient, RuntimeVersion, + SubxtRpcApiClient, SystemProperties, }, storage::StorageClient, @@ -89,14 +90,21 @@ impl ClientBuilder { crate::rpc::build_ws_client(url).await? }; let rpc = Rpc::new(client); - let (metadata, genesis_hash, runtime_version, properties) = future::join4( - rpc.metadata(), - rpc.genesis_hash(), - rpc.runtime_version(None), - rpc.system_properties(), + let (metadata_bytes, genesis_hash, runtime_version, properties) = future::join4( + SubxtRpcApiClient::::metadata(&*rpc), + SubxtRpcApiClient::::genesis_hash(&*rpc), + SubxtRpcApiClient::::runtime_version( + &*rpc, None, + ), + SubxtRpcApiClient::::system_properties( + &*rpc, + ), ) .await; - let metadata = metadata?; + let metadata_bytes = metadata_bytes?; + let meta: frame_metadata::RuntimeMetadataPrefixed = + Decode::decode(&mut &metadata_bytes[..])?; + let metadata: Metadata = meta.try_into()?; let events_decoder = EventsDecoder::new(metadata.clone()); diff --git a/subxt/src/config.rs b/subxt/src/config.rs index dd7b727790..15ed084795 100644 --- a/subxt/src/config.rs +++ b/subxt/src/config.rs @@ -78,7 +78,13 @@ pub trait Config: 'static { type Signature: Verify + Encode + Send + Sync + 'static; /// Extrinsic type within blocks. - type Extrinsic: Parameter + Extrinsic + Debug + MaybeSerializeDeserialize; + // TODO(niklasad1): I have no idea if this ok or not ^^ + type Extrinsic: Parameter + + Extrinsic + + Debug + + MaybeSerializeDeserialize + + Send + + Sync; } /// Parameter trait copied from `substrate::frame_support` diff --git a/subxt/src/rpc.rs b/subxt/src/rpc.rs index f3bb891e62..ede7b83db9 100644 --- a/subxt/src/rpc.rs +++ b/subxt/src/rpc.rs @@ -23,29 +23,22 @@ use std::{ collections::HashMap, + ops::Deref, sync::Arc, }; use crate::{ error::BasicError, - storage::StorageKeyPrefix, subscription::{ EventStorageSubscription, FinalizedEventStorageSubscription, SystemEvents, }, Config, - Metadata, }; -use codec::{ - Decode, - Encode, -}; -use core::{ - convert::TryInto, - marker::PhantomData, -}; -use frame_metadata::RuntimeMetadataPrefixed; +use codec::Encode; +use core::marker::PhantomData; +use jsonrpsee::core::RpcResult; pub use jsonrpsee::{ client_transport::ws::{ InvalidUri, @@ -67,6 +60,7 @@ pub use jsonrpsee::{ Error as RpcError, JsonValue, }, + proc_macros::rpc, rpc_params, }; use serde::{ @@ -87,6 +81,132 @@ use sp_runtime::generic::{ SignedBlock, }; +/// subxt RPC API. +#[rpc(client)] +pub trait SubxtRpcApi { + /// Fetch a storage key + #[method(name = "state_getStorage")] + async fn storage( + &self, + key: &StorageKey, + hash: Option, + ) -> RpcResult>; + + /// Returns the keys with prefix with pagination support. + /// Up to `count` keys will be returned. + /// If `start_key` is passed, return next keys in storage in lexicographic order. + #[method(name = "state_getKeysPaged")] + async fn storage_keys_paged( + &self, + prefix: Option, + count: u32, + start_key: Option, + hash: Option, + ) -> RpcResult>; + + /// Query historical storage entries + #[method(name = "state_queryStorage")] + async fn query_storage( + &self, + keys: Vec, + from: Hash, + to: Option, + ) -> RpcResult>>; + + /// Query historical storage entries + #[method(name = "state_queryStorageAt")] + async fn query_storage_at( + &self, + keys: &[StorageKey], + at: Option, + ) -> RpcResult>>; + + /// Fetch the genesis hash + #[method(name = "chain_getBlockHash")] + async fn genesis_hash(&self) -> RpcResult; + + /// Fetch the metadata as bytes. + #[method(name = "state_getMetadata")] + async fn metadata(&self) -> RpcResult; + + /// Fetch system properties + #[method(name = "system_properties")] + async fn system_properties(&self) -> RpcResult; + + /// Fetch system chain + #[method(name = "system_chain")] + async fn system_chain(&self) -> RpcResult; + + /// Fetch system name + #[method(name = "system_name")] + async fn system_name(&self) -> RpcResult; + + /// Fetch system version + #[method(name = "system_version")] + async fn system_version(&self) -> RpcResult; + + /// Fetch the runtime version + #[method(name = "state_getRuntimeVersion")] + async fn runtime_version(&self, at: Option) -> RpcResult; + + /// Get a header + #[method(name = "state_getRuntimeVersion")] + async fn header(&self, hash: Option) -> RpcResult>; + + /// Get a block hash, returns hash of latest block by default + #[method(name = "chain_getBlockHash")] + async fn block_hash( + &self, + block_number: Option, + ) -> RpcResult>; + + /// Get a block hash of the latest finalized block + #[method(name = "chain_getFinalizedHead")] + async fn finalized_head(&self) -> RpcResult; + + /// Get proof of storage entries at a specific block's state. + #[method(name = "state_getReadProof")] + async fn read_proof( + &self, + keys: Vec, + hash: Option, + ) -> RpcResult>; + + /// Get a Block + #[method(name = "chain_getBlock")] + async fn block( + &self, + hash: Option, + ) -> RpcResult>>>; + + /// Insert a key into the keystore. + #[method(name = "author_insertKey")] + async fn insert_key( + &self, + key_type: String, + suri: String, + public: Bytes, + ) -> RpcResult<()>; + + /// Generate new session keys and returns the corresponding public keys. + #[method(name = "author_rotateKeys")] + async fn rotate_keys(&self) -> RpcResult; + + /// 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. + #[method(name = "author_hasSessionKeys")] + async fn has_session_keys(&self, session_keys: Bytes) -> RpcResult; + + /// Checks if the keystore has private keys for the given public key and key type. + /// + /// Returns `true` if a private key could be found. + #[method(name = "author_hasKey")] + async fn has_key(&self, public_key: Bytes, key_type: String) -> RpcResult; +} + /// A number type that can be serialized both as a number or a string that encodes a number in a /// string. /// @@ -228,6 +348,14 @@ impl Clone for Rpc { } } +impl Deref for Rpc { + type Target = RpcClient; + + fn deref(&self) -> &Self::Target { + &*self.client + } +} + impl Rpc { /// Create a new [`Rpc`] pub fn new(client: RpcClient) -> Self { @@ -237,175 +365,6 @@ impl Rpc { } } - /// Fetch a storage key - pub async fn storage( - &self, - key: &StorageKey, - hash: Option, - ) -> Result, BasicError> { - let params = rpc_params![key, hash]; - let data = self.client.request("state_getStorage", params).await?; - Ok(data) - } - - /// Returns the keys with prefix with pagination support. - /// Up to `count` keys will be returned. - /// If `start_key` is passed, return next keys in storage in lexicographic order. - pub async fn storage_keys_paged( - &self, - prefix: Option, - count: u32, - start_key: Option, - hash: Option, - ) -> Result, BasicError> { - let prefix = prefix.map(|p| p.to_storage_key()); - let params = rpc_params![prefix, count, start_key, hash]; - let data = self.client.request("state_getKeysPaged", params).await?; - Ok(data) - } - - /// Query historical storage entries - pub async fn query_storage( - &self, - keys: Vec, - from: T::Hash, - to: Option, - ) -> Result>, BasicError> { - let params = rpc_params![keys, from, to]; - self.client - .request("state_queryStorage", params) - .await - .map_err(Into::into) - } - - /// Query historical storage entries - pub async fn query_storage_at( - &self, - keys: &[StorageKey], - at: Option, - ) -> Result>, BasicError> { - let params = rpc_params![keys, at]; - self.client - .request("state_queryStorageAt", params) - .await - .map_err(Into::into) - } - - /// Fetch the genesis hash - pub async fn genesis_hash(&self) -> Result { - let block_zero = Some(ListOrValue::Value(NumberOrHex::Number(0))); - let params = rpc_params![block_zero]; - let list_or_value: ListOrValue> = - self.client.request("chain_getBlockHash", params).await?; - match list_or_value { - ListOrValue::Value(genesis_hash) => { - genesis_hash.ok_or_else(|| "Genesis hash not found".into()) - } - ListOrValue::List(_) => Err("Expected a Value, got a List".into()), - } - } - - /// Fetch the metadata - pub async fn metadata(&self) -> Result { - let bytes: Bytes = self - .client - .request("state_getMetadata", rpc_params![]) - .await?; - let meta: RuntimeMetadataPrefixed = Decode::decode(&mut &bytes[..])?; - let metadata: Metadata = meta.try_into()?; - Ok(metadata) - } - - /// Fetch system properties - pub async fn system_properties(&self) -> Result { - Ok(self - .client - .request("system_properties", rpc_params![]) - .await?) - } - - /// Fetch system chain - pub async fn system_chain(&self) -> Result { - Ok(self.client.request("system_chain", rpc_params![]).await?) - } - - /// Fetch system name - pub async fn system_name(&self) -> Result { - Ok(self.client.request("system_name", rpc_params![]).await?) - } - - /// Fetch system version - pub async fn system_version(&self) -> Result { - Ok(self.client.request("system_version", rpc_params![]).await?) - } - - /// Get a header - pub async fn header( - &self, - hash: Option, - ) -> Result, BasicError> { - let params = rpc_params![hash]; - let header = self.client.request("chain_getHeader", params).await?; - Ok(header) - } - - /// Get a block hash, returns hash of latest block by default - pub async fn block_hash( - &self, - block_number: Option, - ) -> Result, BasicError> { - let block_number = block_number.map(ListOrValue::Value); - let params = rpc_params![block_number]; - let list_or_value = self.client.request("chain_getBlockHash", params).await?; - match list_or_value { - ListOrValue::Value(hash) => Ok(hash), - ListOrValue::List(_) => Err("Expected a Value, got a List".into()), - } - } - - /// Get a block hash of the latest finalized block - pub async fn finalized_head(&self) -> Result { - let hash = self - .client - .request("chain_getFinalizedHead", rpc_params![]) - .await?; - Ok(hash) - } - - /// Get a Block - pub async fn block( - &self, - hash: Option, - ) -> Result>, BasicError> { - let params = rpc_params![hash]; - let block = self.client.request("chain_getBlock", params).await?; - Ok(block) - } - - /// Get proof of storage entries at a specific block's state. - pub async fn read_proof( - &self, - keys: Vec, - hash: Option, - ) -> Result, BasicError> { - let params = rpc_params![keys, hash]; - let proof = self.client.request("state_getReadProof", params).await?; - Ok(proof) - } - - /// Fetch the runtime version - pub async fn runtime_version( - &self, - at: Option, - ) -> Result { - let params = rpc_params![at]; - let version = self - .client - .request("state_getRuntimeVersion", params) - .await?; - Ok(version) - } - /// Subscribe to System Events that are imported into blocks. /// /// *WARNING* these may not be included in the finalized chain, use @@ -496,51 +455,6 @@ impl Rpc { .await?; Ok(subscription) } - - /// Insert a key into the keystore. - pub async fn insert_key( - &self, - key_type: String, - suri: String, - public: Bytes, - ) -> Result<(), BasicError> { - let params = rpc_params![key_type, suri, 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", rpc_params![]) - .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 = rpc_params![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 = rpc_params![public_key, key_type]; - Ok(self.client.request("author_hasKey", params).await?) - } } /// Build WS RPC client from URL diff --git a/subxt/src/storage.rs b/subxt/src/storage.rs index 3d1b6f11e2..3995c39669 100644 --- a/subxt/src/storage.rs +++ b/subxt/src/storage.rs @@ -35,7 +35,10 @@ use crate::{ Metadata, MetadataError, }, - rpc::Rpc, + rpc::{ + Rpc, + SubxtRpcApiClient, + }, Config, StorageHasher, }; @@ -164,7 +167,14 @@ impl<'a, T: Config> StorageClient<'a, T> { key: StorageKey, hash: Option, ) -> Result, BasicError> { - if let Some(data) = self.rpc.storage(&key, hash).await? { + if let Some(data) = + SubxtRpcApiClient::::storage( + &*self.rpc.client, + &key, + hash, + ) + .await? + { Ok(Some(Decode::decode(&mut &data.0[..])?)) } else { Ok(None) @@ -177,7 +187,13 @@ impl<'a, T: Config> StorageClient<'a, T> { key: StorageKey, hash: Option, ) -> Result, BasicError> { - self.rpc.storage(&key, hash).await + SubxtRpcApiClient::::storage( + &*self.rpc.client, + &key, + hash, + ) + .await + .map_err(Into::into) } /// Fetch a StorageKey with an optional block hash. @@ -215,7 +231,14 @@ impl<'a, T: Config> StorageClient<'a, T> { from: T::Hash, to: Option, ) -> Result>, BasicError> { - self.rpc.query_storage(keys, from, to).await + SubxtRpcApiClient::::query_storage( + &*self.rpc.client, + keys, + from, + to, + ) + .await + .map_err(Into::into) } /// Fetch up to `count` keys for a storage map in lexicographic order. @@ -227,10 +250,15 @@ impl<'a, T: Config> StorageClient<'a, T> { start_key: Option, hash: Option, ) -> Result, BasicError> { - let prefix = StorageKeyPrefix::new::(); - let keys = self - .rpc - .storage_keys_paged(Some(prefix), count, start_key, hash) + let prefix = StorageKeyPrefix::new::().to_storage_key(); + let keys = + SubxtRpcApiClient::::storage_keys_paged( + &*self.rpc.client, + Some(prefix), + count, + start_key, + hash, + ) .await?; Ok(keys) } @@ -243,10 +271,12 @@ impl<'a, T: Config> StorageClient<'a, T> { let hash = if let Some(hash) = hash { hash } else { - self.rpc - .block_hash(None) - .await? - .expect("didn't pass a block number; qed") + SubxtRpcApiClient::::block_hash( + &*self.rpc.client, + None, + ) + .await? + .expect("didn't pass a block number; qed") }; Ok(KeyIter { client: self.clone(), @@ -287,10 +317,12 @@ impl<'a, T: Config, F: StorageEntry> KeyIter<'a, T, F> { self.start_key = keys.last().cloned(); - let change_sets = self - .client - .rpc - .query_storage_at(&keys, Some(self.hash)) + let change_sets = + SubxtRpcApiClient::::query_storage_at( + &*self.client.rpc.client, + &keys, + Some(self.hash), + ) .await?; for change_set in change_sets { for (k, v) in change_set.changes { diff --git a/subxt/src/subscription.rs b/subxt/src/subscription.rs index 307bfea222..e70b714547 100644 --- a/subxt/src/subscription.rs +++ b/subxt/src/subscription.rs @@ -20,7 +20,10 @@ use crate::{ EventsDecoder, RawEvent, }, - rpc::Rpc, + rpc::{ + Rpc, + SubxtRpcApiClient, + }, Config, Event, Phase, @@ -210,10 +213,13 @@ impl FinalizedEventStorageSubscription { read_subscription_response("HeaderSubscription", &mut self.subscription) .await?; self.storage_changes.extend( - self.rpc - .query_storage_at(&[self.storage_key.clone()], Some(header.hash())) - .await - .ok()?, + SubxtRpcApiClient::::query_storage_at( + &*self.rpc.client, + &[self.storage_key.clone()], + Some(header.hash()), + ) + .await + .ok()?, ); } } diff --git a/subxt/src/transaction.rs b/subxt/src/transaction.rs index ff0d3132cb..568f1e680a 100644 --- a/subxt/src/transaction.rs +++ b/subxt/src/transaction.rs @@ -31,7 +31,10 @@ use crate::{ RuntimeError, TransactionError, }, - rpc::SubstrateTransactionStatus, + rpc::{ + SubstrateTransactionStatus, + SubxtRpcApiClient, + }, subscription::SystemEvents, Config, Phase, @@ -389,12 +392,12 @@ impl<'client, T: Config, E: Decode> TransactionInBlock<'client, T, E> { /// **Note:** This has to download block details from the node and decode events /// from them. pub async fn fetch_events(&self) -> Result, BasicError> { - let block = self - .client - .rpc() - .block(Some(self.block_hash)) - .await? - .ok_or(BasicError::Transaction(TransactionError::BlockHashNotFound))?; + let block = SubxtRpcApiClient::::block( + &*self.client.rpc().client, + Some(self.block_hash), + ) + .await? + .ok_or(BasicError::Transaction(TransactionError::BlockHashNotFound))?; let extrinsic_idx = block.block.extrinsics .iter() @@ -406,16 +409,14 @@ impl<'client, T: Config, E: Decode> TransactionInBlock<'client, T, E> { // extrinsic, the extrinsic should be in there somewhere.. .ok_or(BasicError::Transaction(TransactionError::BlockHashNotFound))?; - let raw_events = self - .client - .rpc() - .storage( - &StorageKey::from(SystemEvents::new()), - Some(self.block_hash), - ) - .await? - .map(|s| s.0) - .unwrap_or_else(Vec::new); + let raw_events = SubxtRpcApiClient::::storage( + &*self.client.rpc().client, + &StorageKey::from(SystemEvents::new()), + Some(self.block_hash), + ) + .await? + .map(|s| s.0) + .unwrap_or_else(Vec::new); let events = self .client diff --git a/subxt/tests/integration/client.rs b/subxt/tests/integration/client.rs index 47676fb6ee..b8c1bbbda5 100644 --- a/subxt/tests/integration/client.rs +++ b/subxt/tests/integration/client.rs @@ -19,6 +19,11 @@ use crate::{ test_node_process_with, utils::node_runtime::system, }; +use subxt::{ + rpc::SubxtRpcApiClient, + Config, + DefaultConfig, +}; use sp_core::storage::{ well_known_keys, @@ -127,7 +132,22 @@ async fn test_iter() { async fn fetch_system_info() { let node_process = test_node_process().await; let client = node_process.client(); - assert_eq!(client.rpc().system_chain().await.unwrap(), "Development"); - assert_eq!(client.rpc().system_name().await.unwrap(), "Substrate Node"); - assert!(!client.rpc().system_version().await.unwrap().is_empty()); + let rpc = client.rpc().client.clone(); + + type Hash = ::Hash; + + assert_eq!( + SubxtRpcApiClient::::system_chain(&*rpc) + .await + .unwrap(), + "Development" + ); + assert_eq!( + SubxtRpcApiClient::::system_name(&*rpc).await.unwrap(), + "Substrate Node" + ); + assert!(!SubxtRpcApiClient::::system_version(&*rpc) + .await + .unwrap() + .is_empty()); }