Convert to std futures (#58)

* WIP

* Begin converting rpc layer to use std futures and jsonrpsee

* Convert metadata to async/await

* Convert block_hash to async/await

* Convert more methods to async/await

* Remove sp_rpc

* Fix more compilation errors

* Remove connect

* Starting to convert subscription functions

* Use jsonrpsee branch from PR for public client types

* Implement subscribe events with jsonrpsee subscription

* Converting subscriptions and wait_for_block_events

* WIP converting lib methods to async

* Use shared client reference directly for rpc call

`rpc_api!` macro currently only supports RawClient (which cannot be shared).
Also supports named params only which is not currently compatible with substrate rpd which accepts only positional params.

* Use &self instead of &mut self for shared Client

* Convert submit_and_watch to async/await

* Convert more Client fns to async

* Pin some trait futures

* Add serde error

* Fix client creation

* Fix client request compiler errors

* Unify metadata errors

* Add WS handshake error variant

* Fix some more compiler errors

* Fix more compiler errors

* Convert submit_extrinsic to async

* Convert submit and submit_and_watch

* Add Send + Sync constraints

* Clone clients

* Fix EventArg conversion error

* Fix remaining warnings/errors

* Replace deny warnings with specific lints

* Infallable subscription loops

* Use jsonrpsee wss branch

* Fix example

* Start to fix up tests

* Make contracts tests compile

* Make some more tests pass

* Fix up remaining tests

* Fmt

* Use correct event storage key type

* Fix finding events

* Use master jsonrpsee
This commit is contained in:
Andrew Jones
2020-01-20 12:00:08 +00:00
committed by GitHub
parent 691244fef2
commit 253a7d8b0b
10 changed files with 544 additions and 507 deletions
+4 -4
View File
@@ -13,10 +13,11 @@ include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE"]
[dependencies] [dependencies]
log = "0.4" log = "0.4"
thiserror = "1.0" thiserror = "1.0"
futures = "0.1" futures = "0.3"
jsonrpc-core-client = { version = "14.0", features = ["ws"] } jsonrpsee = { git = "https://github.com/paritytech/jsonrpsee/", features = ["ws"] }
num-traits = { version = "0.2", default-features = false } num-traits = { version = "0.2", default-features = false }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
url = "1.7" url = "1.7"
codec = { package = "parity-scale-codec", version = "1.1", default-features = false, features = ["derive", "full"] } codec = { package = "parity-scale-codec", version = "1.1", default-features = false, features = ["derive", "full"] }
@@ -26,14 +27,13 @@ sp-runtime = { git = "https://github.com/paritytech/substrate/", package = "sp-r
sp-version = { git = "https://github.com/paritytech/substrate/", package = "sp-version" } sp-version = { git = "https://github.com/paritytech/substrate/", package = "sp-version" }
pallet-indices = { git = "https://github.com/paritytech/substrate/", package = "pallet-indices" } pallet-indices = { git = "https://github.com/paritytech/substrate/", package = "pallet-indices" }
hex = "0.4.0" hex = "0.4.0"
sc-rpc-api = { git = "https://github.com/paritytech/substrate/", package = "sc-rpc-api" }
sp-rpc = { git = "https://github.com/paritytech/substrate/", package = "sp-rpc" } sp-rpc = { git = "https://github.com/paritytech/substrate/", package = "sp-rpc" }
sp-core = { git = "https://github.com/paritytech/substrate/", package = "sp-core" } sp-core = { git = "https://github.com/paritytech/substrate/", package = "sp-core" }
sp-transaction-pool = { git = "https://github.com/paritytech/substrate/", package = "sp-transaction-pool" } sp-transaction-pool = { git = "https://github.com/paritytech/substrate/", package = "sp-transaction-pool" }
[dev-dependencies] [dev-dependencies]
async-std = "1.2.0"
env_logger = "0.7" env_logger = "0.7"
tokio = "0.1"
wabt = "0.9" wabt = "0.9"
frame-system = { git = "https://github.com/paritytech/substrate/", package = "frame-system" } frame-system = { git = "https://github.com/paritytech/substrate/", package = "frame-system" }
node-runtime = { git = "https://github.com/paritytech/substrate/", package = "node-runtime" } node-runtime = { git = "https://github.com/paritytech/substrate/", package = "node-runtime" }
+17 -13
View File
@@ -14,37 +14,41 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with substrate-subxt. If not, see <http://www.gnu.org/licenses/>. // along with substrate-subxt. If not, see <http://www.gnu.org/licenses/>.
use futures::future::Future;
use sp_keyring::AccountKeyring; use sp_keyring::AccountKeyring;
use substrate_subxt::{ use substrate_subxt::{
balances, balances,
system::System, system::System,
DefaultNodeRuntime as Runtime, DefaultNodeRuntime as Runtime,
ExtrinsicSuccess,
}; };
type AccountId = <Runtime as System>::AccountId; type AccountId = <Runtime as System>::AccountId;
type Balance = <Runtime as balances::Balances>::Balance; type Balance = <Runtime as balances::Balances>::Balance;
fn main() { fn main() {
env_logger::init(); let result: Result<ExtrinsicSuccess<_>, Box<dyn std::error::Error + 'static>> =
let signer = AccountKeyring::Alice.pair(); async_std::task::block_on(async move {
env_logger::init();
let dest = AccountKeyring::Bob.to_account_id(); let signer = AccountKeyring::Alice.pair();
let fut = substrate_subxt::ClientBuilder::<Runtime>::new() let dest = AccountKeyring::Bob.to_account_id();
.build()
.and_then(|cli| cli.xt(signer, None)) let cli = substrate_subxt::ClientBuilder::<Runtime>::new()
.and_then(move |xt| { .build()
xt.watch() .await?;
let xt = cli.xt(signer, None).await?;
let xt_result = xt
.watch()
.events_decoder(|decoder| { .events_decoder(|decoder| {
// for any primitive event with no type size registered // for any primitive event with no type size registered
decoder.register_type_size::<(u64, u64)>("IdentificationTuple") decoder.register_type_size::<(u64, u64)>("IdentificationTuple")
}) })
.submit(balances::transfer::<Runtime>(dest.clone().into(), 10_000)) .submit(balances::transfer::<Runtime>(dest.clone().into(), 10_000))
.await?;
Ok(xt_result)
}); });
match result {
let mut rt = tokio::runtime::Runtime::new().unwrap();
match rt.block_on(fut) {
Ok(extrinsic_success) => { Ok(extrinsic_success) => {
match extrinsic_success match extrinsic_success
.find_event::<(AccountId, AccountId, Balance, Balance)>( .find_event::<(AccountId, AccountId, Balance, Balance)>(
@@ -57,6 +61,6 @@ fn main() {
None => println!("Failed to find Contracts::CodeStored Event"), None => println!("Failed to find Contracts::CodeStored Event"),
} }
} }
Err(err) => println!("Error: {}", err), Err(err) => println!("Error: {:?}", err),
} }
} }
+11 -8
View File
@@ -14,7 +14,10 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with substrate-subxt. If not, see <http://www.gnu.org/licenses/>. // along with substrate-subxt. If not, see <http://www.gnu.org/licenses/>.
use jsonrpc_core_client::RpcError; use jsonrpsee::{
client::RequestError,
ws::WsNewDnsError,
};
use sp_core::crypto::SecretStringError; use sp_core::crypto::SecretStringError;
use sp_runtime::transaction_validity::TransactionValidityError; use sp_runtime::transaction_validity::TransactionValidityError;
@@ -34,7 +37,13 @@ pub enum Error {
Codec(#[from] codec::Error), Codec(#[from] codec::Error),
/// Rpc error. /// Rpc error.
#[error("Rpc error: {0}")] #[error("Rpc error: {0}")]
Rpc(RpcError), Rpc(#[from] RequestError),
/// Error that can happen during the initial websocket handshake
#[error("Rpc error: {0}")]
WsHandshake(#[from] WsNewDnsError),
/// Serde serialization error
#[error("Serde json error: {0}")]
Serialization(#[from] serde_json::error::Error),
/// Secret string error. /// Secret string error.
#[error("Secret String Error")] #[error("Secret String Error")]
SecretString(SecretStringError), SecretString(SecretStringError),
@@ -52,12 +61,6 @@ pub enum Error {
Other(String), Other(String),
} }
impl From<RpcError> for Error {
fn from(error: RpcError) -> Self {
Error::Rpc(error)
}
}
impl From<SecretStringError> for Error { impl From<SecretStringError> for Error {
fn from(error: SecretStringError) -> Self { fn from(error: SecretStringError) -> Self {
Error::SecretString(error) Error::SecretString(error)
+2 -2
View File
@@ -205,14 +205,14 @@ impl<T: System + Balances + 'static> EventsDecoder<T> {
for _ in 0..len { for _ in 0..len {
// decode EventRecord // decode EventRecord
let phase = Phase::decode(input)?; let phase = Phase::decode(input)?;
let module_variant = input.read_byte()? as u8; let module_variant = input.read_byte()?;
let module = self.metadata.module_with_events(module_variant)?; let module = self.metadata.module_with_events(module_variant)?;
let event = if module.name() == "System" { let event = if module.name() == "System" {
let system_event = SystemEvent::decode(input)?; let system_event = SystemEvent::decode(input)?;
RuntimeEvent::System(system_event) RuntimeEvent::System(system_event)
} else { } else {
let event_variant = input.read_byte()? as u8; let event_variant = input.read_byte()?;
let event_metadata = module.event(event_variant)?; let event_metadata = module.event(event_variant)?;
let mut event_data = Vec::<u8>::new(); let mut event_data = Vec::<u8>::new();
+24 -7
View File
@@ -16,7 +16,10 @@
//! Implements support for the pallet_balances module. //! Implements support for the pallet_balances module.
use std::fmt::Debug; use std::{
fmt::Debug,
pin::Pin,
};
use futures::future::{ use futures::future::{
self, self,
@@ -74,17 +77,26 @@ pub trait BalancesStore {
fn free_balance( fn free_balance(
&self, &self,
account_id: <Self::Balances as System>::AccountId, account_id: <Self::Balances as System>::AccountId,
) -> Box<dyn Future<Item = <Self::Balances as Balances>::Balance, Error = Error> + Send>; ) -> Pin<
Box<
dyn Future<Output = Result<<Self::Balances as Balances>::Balance, Error>>
+ Send,
>,
>;
} }
impl<T: Balances + 'static, S: 'static> BalancesStore for Client<T, S> { impl<T: Balances + Sync + Send + 'static, S: 'static> BalancesStore for Client<T, S> {
type Balances = T; type Balances = T;
fn free_balance( fn free_balance(
&self, &self,
account_id: <Self::Balances as System>::AccountId, account_id: <Self::Balances as System>::AccountId,
) -> Box<dyn Future<Item = <Self::Balances as Balances>::Balance, Error = Error> + Send> ) -> Pin<
{ Box<
dyn Future<Output = Result<<Self::Balances as Balances>::Balance, Error>>
+ Send,
>,
> {
let free_balance_map = || { let free_balance_map = || {
Ok(self Ok(self
.metadata() .metadata()
@@ -96,9 +108,14 @@ impl<T: Balances + 'static, S: 'static> BalancesStore for Client<T, S> {
}; };
let map = match free_balance_map() { let map = match free_balance_map() {
Ok(map) => map, Ok(map) => map,
Err(err) => return Box::new(future::err(err)), Err(err) => return Box::pin(future::err(err)),
}; };
Box::new(self.fetch_or(map.key(account_id), None, map.default())) let client = self.clone();
Box::pin(async move {
client
.fetch_or(map.key(account_id), None, map.default())
.await
})
} }
} }
+38 -49
View File
@@ -139,11 +139,7 @@ pub fn call<T: Contracts>(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use codec::{ use codec::Codec;
Codec,
Error as CodecError,
};
use futures::Future;
use sp_core::Pair; use sp_core::Pair;
use sp_keyring::AccountKeyring; use sp_keyring::AccountKeyring;
use sp_runtime::traits::{ use sp_runtime::traits::{
@@ -154,7 +150,7 @@ mod tests {
use super::events; use super::events;
use crate::{ use crate::{
frame::contracts::MODULE, frame::contracts::MODULE,
tests::test_setup, tests::test_client,
Balances, Balances,
Client, Client,
DefaultNodeRuntime as Runtime, DefaultNodeRuntime as Runtime,
@@ -164,17 +160,16 @@ mod tests {
type AccountId = <Runtime as System>::AccountId; type AccountId = <Runtime as System>::AccountId;
fn put_code<T, P, S>( async fn put_code<T, P, S>(
client: &Client<T, S>, client: &Client<T, S>,
signer: P, signer: P,
) -> impl Future<Item = Option<Result<T::Hash, CodecError>>, Error = Error> ) -> Result<T::Hash, Error>
where where
T: System + Balances + Send + Sync, T: System + Balances + Send + Sync,
T::Address: From<T::AccountId>, T::Address: From<T::AccountId>,
P: Pair, P: Pair,
P::Signature: Codec, P::Signature: Codec,
S: 'static, S: Verify + Codec + From<P::Signature> + 'static,
S: Verify + Codec + From<P::Signature>,
S::Signer: From<P::Public> + IdentifyAccount<AccountId = T::AccountId>, S::Signer: From<P::Public> + IdentifyAccount<AccountId = T::AccountId>,
{ {
const CONTRACT: &str = r#" const CONTRACT: &str = r#"
@@ -185,70 +180,64 @@ mod tests {
"#; "#;
let wasm = wabt::wat2wasm(CONTRACT).expect("invalid wabt"); let wasm = wabt::wat2wasm(CONTRACT).expect("invalid wabt");
client.xt(signer, None).and_then(|xt| { let xt = client.xt(signer, None).await?;
xt.watch()
.submit(super::put_code(500_000, wasm)) let result = xt.watch().submit(super::put_code(500_000, wasm)).await?;
.map(|result| result.find_event::<T::Hash>(MODULE, events::CODE_STORED)) let code_hash = result
}) .find_event::<T::Hash>(MODULE, events::CODE_STORED)
.ok_or(Error::Other("Failed to find CodeStored event".into()))??;
Ok(code_hash)
} }
#[test] #[test]
#[ignore] // requires locally running substrate node #[ignore] // requires locally running substrate node
fn tx_put_code() { fn tx_put_code() {
let (mut rt, client) = test_setup(); env_logger::try_init().ok();
let code_hash: Result<_, Error> = async_std::task::block_on(async move {
let signer = AccountKeyring::Alice.pair(); let signer = AccountKeyring::Alice.pair();
let code_hash = rt.block_on(put_code(&client, signer)).unwrap(); let client = test_client().await;
let code_hash = put_code(&client, signer).await?;
Ok(code_hash)
});
assert!( assert!(
code_hash.is_some(), code_hash.is_ok(),
"Contracts CodeStored event should be present" "Contracts CodeStored event should be received and decoded"
);
assert!(
code_hash.unwrap().is_ok(),
"CodeStored Hash should decode successfully"
); );
} }
#[test] #[test]
#[ignore] // requires locally running substrate node #[ignore] // requires locally running substrate node
fn tx_instantiate() { fn tx_instantiate() {
let (mut rt, client) = test_setup(); env_logger::try_init().ok();
let result: Result<_, Error> = async_std::task::block_on(async move {
let signer = AccountKeyring::Alice.pair();
let client = test_client().await;
let signer = AccountKeyring::Alice.pair(); let code_hash = put_code(&client, signer.clone()).await?;
let code_hash = rt
.block_on(put_code(&client, signer.clone()))
.unwrap()
.unwrap()
.unwrap();
println!("{:?}", code_hash); println!("{:?}", code_hash);
let instantiate = client.xt(signer, None).and_then(move |xt| { let xt = client.xt(signer, None).await?;
xt.watch() let result = xt
.watch()
.submit(super::instantiate::<Runtime>( .submit(super::instantiate::<Runtime>(
100_000_000_000_000, 100_000_000_000_000,
500_000, 500_000,
code_hash, code_hash,
Vec::new(), Vec::new(),
)) ))
.map(|result| { .await?;
result.find_event::<(AccountId, AccountId)>( let event = result
MODULE, .find_event::<(AccountId, AccountId)>(MODULE, events::INSTANTIATED)
events::INSTANTIATED, .ok_or(Error::Other("Failed to find Instantiated event".into()))??;
) Ok(event)
})
}); });
let result = rt.block_on(instantiate).unwrap();
assert!( assert!(
result.is_some(), result.is_ok(),
"Contracts Instantiated event should be present" "Contracts CodeStored event should be received and decoded"
);
assert!(
result.unwrap().is_ok(),
"Instantiated Event should decode successfully"
); );
} }
} }
+21 -10
View File
@@ -16,16 +16,13 @@
//! Implements support for the frame_system module. //! Implements support for the frame_system module.
use std::fmt::Debug;
use codec::Codec; use codec::Codec;
use frame_support::Parameter;
use futures::future::{ use futures::future::{
self, self,
Future, Future,
}; };
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use frame_support::Parameter;
use sp_runtime::traits::{ use sp_runtime::traits::{
Bounded, Bounded,
CheckEqual, CheckEqual,
@@ -39,6 +36,10 @@ use sp_runtime::traits::{
SimpleArithmetic, SimpleArithmetic,
SimpleBitOps, SimpleBitOps,
}; };
use std::{
fmt::Debug,
pin::Pin,
};
use crate::{ use crate::{
error::Error, error::Error,
@@ -123,17 +124,22 @@ pub trait SystemStore {
fn account_nonce( fn account_nonce(
&self, &self,
account_id: <Self::System as System>::AccountId, account_id: <Self::System as System>::AccountId,
) -> Box<dyn Future<Item = <Self::System as System>::Index, Error = Error> + Send>; ) -> Pin<
Box<dyn Future<Output = Result<<Self::System as System>::Index, Error>> + Send>,
>;
} }
impl<T: System + Balances + 'static, S: 'static> SystemStore for Client<T, S> { impl<T: System + Balances + Sync + Send + 'static, S: 'static> SystemStore
for Client<T, S>
{
type System = T; type System = T;
fn account_nonce( fn account_nonce(
&self, &self,
account_id: <Self::System as System>::AccountId, account_id: <Self::System as System>::AccountId,
) -> Box<dyn Future<Item = <Self::System as System>::Index, Error = Error> + Send> ) -> Pin<
{ Box<dyn Future<Output = Result<<Self::System as System>::Index, Error>> + Send>,
> {
let account_nonce_map = || { let account_nonce_map = || {
Ok(self Ok(self
.metadata .metadata
@@ -143,9 +149,14 @@ impl<T: System + Balances + 'static, S: 'static> SystemStore for Client<T, S> {
}; };
let map = match account_nonce_map() { let map = match account_nonce_map() {
Ok(map) => map, Ok(map) => map,
Err(err) => return Box::new(future::err(err)), Err(err) => return Box::pin(future::err(err)),
}; };
Box::new(self.fetch_or(map.key(account_id), None, map.default())) let client = self.clone();
Box::pin(async move {
client
.fetch_or(map.key(account_id), None, map.default())
.await
})
} }
} }
+187 -158
View File
@@ -17,8 +17,27 @@
//! A library to **sub**mit e**xt**rinsics to a //! A library to **sub**mit e**xt**rinsics to a
//! [substrate](https://github.com/paritytech/substrate) node via RPC. //! [substrate](https://github.com/paritytech/substrate) node via RPC.
#![deny(missing_docs)] #![deny(
#![deny(warnings)] bad_style,
const_err,
improper_ctypes,
missing_docs,
non_shorthand_field_patterns,
no_mangle_generic_items,
overflowing_literals,
path_statements,
patterns_in_fns_without_body,
plugin_as_library,
private_in_public,
unconditional_recursion,
unused_allocation,
unused_comparisons,
unused_parens,
while_true,
trivial_casts,
trivial_numeric_casts,
unused_extern_crates
)]
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use std::{ use std::{
@@ -31,13 +50,8 @@ use codec::{
Decode, Decode,
Encode, Encode,
}; };
use futures::future::{ use futures::future;
self, use jsonrpsee::client::Subscription;
Either,
Future,
IntoFuture,
};
use jsonrpc_core_client::transports::ws;
use sp_core::{ use sp_core::{
storage::{ storage::{
StorageChangeSet, StorageChangeSet,
@@ -54,7 +68,6 @@ use sp_runtime::{
MultiSignature, MultiSignature,
}; };
use sp_version::RuntimeVersion; use sp_version::RuntimeVersion;
use url::Url;
mod error; mod error;
mod events; mod events;
@@ -93,20 +106,15 @@ use self::{
rpc::{ rpc::{
BlockNumber, BlockNumber,
ChainBlock, ChainBlock,
MapStream,
Rpc, Rpc,
}, },
}; };
fn connect<T: System>(url: &Url) -> impl Future<Item = Rpc<T>, Error = Error> {
ws::connect(url).map_err(Into::into)
}
/// ClientBuilder for constructing a Client. /// ClientBuilder for constructing a Client.
#[derive(Default)] #[derive(Default)]
pub struct ClientBuilder<T: System, S = MultiSignature> { pub struct ClientBuilder<T: System, S = MultiSignature> {
_marker: std::marker::PhantomData<(T, S)>, _marker: std::marker::PhantomData<(T, S)>,
url: Option<Url>, url: Option<String>,
} }
impl<T: System, S> ClientBuilder<T, S> { impl<T: System, S> ClientBuilder<T, S> {
@@ -119,35 +127,35 @@ impl<T: System, S> ClientBuilder<T, S> {
} }
/// Set the substrate rpc address. /// Set the substrate rpc address.
pub fn set_url(mut self, url: Url) -> Self { pub fn set_url(mut self, url: &str) -> Self {
self.url = Some(url); self.url = Some(url.to_string());
self self
} }
/// Creates a new Client. /// Creates a new Client.
pub fn build(self) -> impl Future<Item = Client<T, S>, Error = Error> { pub async fn build(self) -> Result<Client<T, S>, Error> {
let url = self.url.unwrap_or_else(|| { let url = self.url.unwrap_or("ws://127.0.0.1:9944".to_string());
Url::parse("ws://127.0.0.1:9944").expect("Is valid url; qed") let rpc = Rpc::connect_ws(&url).await?;
});
connect::<T>(&url).and_then(|rpc| { let (metadata, genesis_hash, runtime_version) = future::join3(
rpc.metadata() rpc.metadata(),
.join3(rpc.genesis_hash(), rpc.runtime_version(None)) rpc.genesis_hash(),
.map(|(metadata, genesis_hash, runtime_version)| { rpc.runtime_version(None),
Client { )
url, .await;
genesis_hash, Ok(Client {
metadata, rpc,
runtime_version, genesis_hash: genesis_hash?,
_marker: PhantomData, metadata: metadata?,
} runtime_version: runtime_version?,
}) _marker: PhantomData,
}) })
} }
} }
/// Client to interface with a substrate node. /// Client to interface with a substrate node.
pub struct Client<T: System, S = MultiSignature> { pub struct Client<T: System, S = MultiSignature> {
url: Url, rpc: Rpc<T>,
genesis_hash: T::Hash, genesis_hash: T::Hash,
metadata: Metadata, metadata: Metadata,
runtime_version: RuntimeVersion, runtime_version: RuntimeVersion,
@@ -157,7 +165,7 @@ pub struct Client<T: System, S = MultiSignature> {
impl<T: System, S> Clone for Client<T, S> { impl<T: System, S> Clone for Client<T, S> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
url: self.url.clone(), rpc: self.rpc.clone(),
genesis_hash: self.genesis_hash, genesis_hash: self.genesis_hash,
metadata: self.metadata.clone(), metadata: self.metadata.clone(),
runtime_version: self.runtime_version.clone(), runtime_version: self.runtime_version.clone(),
@@ -166,120 +174,136 @@ impl<T: System, S> Clone for Client<T, S> {
} }
} }
impl<T: System + Balances + 'static, S: 'static> Client<T, S> { impl<T: System + Balances + Sync + Send + 'static, S: 'static> Client<T, S> {
fn connect(&self) -> impl Future<Item = Rpc<T>, Error = Error> {
connect(&self.url)
}
/// Returns the chain metadata. /// Returns the chain metadata.
pub fn metadata(&self) -> &Metadata { pub fn metadata(&self) -> &Metadata {
&self.metadata &self.metadata
} }
/// Fetch a StorageKey. /// Fetch a StorageKey.
pub fn fetch<V: Decode>( pub async fn fetch<V: Decode>(
&self, &self,
key: StorageKey, key: StorageKey,
hash: Option<T::Hash>, hash: Option<T::Hash>,
) -> impl Future<Item = Option<V>, Error = Error> { ) -> Result<Option<V>, Error> {
self.connect().and_then(move |rpc| rpc.storage::<V>(key, hash)) self.rpc.storage::<V>(key, hash).await
} }
/// Fetch a StorageKey or return the default. /// Fetch a StorageKey or return the default.
pub fn fetch_or<V: Decode>( pub async fn fetch_or<V: Decode>(
&self, &self,
key: StorageKey, key: StorageKey,
hash: Option<T::Hash>, hash: Option<T::Hash>,
default: V, default: V,
) -> impl Future<Item = V, Error = Error> { ) -> Result<V, Error> {
self.fetch(key, hash).map(|value| value.unwrap_or(default)) let result = self.fetch(key, hash).await?;
Ok(result.unwrap_or(default))
} }
/// Fetch a StorageKey or return the default. /// Fetch a StorageKey or return the default.
pub fn fetch_or_default<V: Decode + Default>( pub async fn fetch_or_default<V: Decode + Default>(
&self, &self,
key: StorageKey, key: StorageKey,
hash: Option<T::Hash>, hash: Option<T::Hash>,
) -> impl Future<Item = V, Error = Error> { ) -> Result<V, Error> {
self.fetch(key, hash).map(|value| value.unwrap_or_default()) let result = self.fetch(key, hash).await?;
Ok(result.unwrap_or_default())
} }
/// Get a block hash. By default returns the latest block hash /// Get a block hash. By default returns the latest block hash
pub fn block_hash( pub async fn block_hash(
&self, &self,
height: Option<BlockNumber<T>>, block_number: Option<BlockNumber<T>>,
) -> impl Future<Item = Option<T::Hash>, Error = Error> { ) -> Result<Option<T::Hash>, Error> {
self.connect() let hash = self.rpc.block_hash(block_number).await?;
.and_then(|rpc| rpc.block_hash(height.map(|h| h))) Ok(hash)
} }
/// Get a block hash of the latest finalized block /// Get a block hash of the latest finalized block
pub fn finalized_head(&self) -> impl Future<Item = T::Hash, Error = Error> { pub async fn finalized_head(&self) -> Result<T::Hash, Error> {
self.connect().and_then(|rpc| rpc.finalized_head()) let head = self.rpc.finalized_head().await?;
Ok(head)
} }
/// Get a block /// Get a block
pub fn block<H>( pub async fn block<H>(&self, hash: Option<H>) -> Result<Option<ChainBlock<T>>, Error>
&self,
hash: Option<H>,
) -> impl Future<Item = Option<ChainBlock<T>>, Error = Error>
where where
H: Into<T::Hash> + 'static, H: Into<T::Hash> + 'static,
{ {
self.connect() let block = self.rpc.block(hash.map(|h| h.into())).await?;
.and_then(|rpc| rpc.block(hash.map(|h| h.into()))) Ok(block)
}
/// Create and submit an extrinsic and return corresponding Hash if successful
pub async fn submit_extrinsic<E: Encode>(
&self,
extrinsic: E,
) -> Result<T::Hash, Error> {
let xt_hash = self.rpc.submit_extrinsic(extrinsic).await?;
Ok(xt_hash)
}
/// Create and submit an extrinsic and return corresponding Event if successful
pub async fn submit_and_watch_extrinsic<E: Encode + 'static>(
self,
extrinsic: E,
decoder: EventsDecoder<T>,
) -> Result<ExtrinsicSuccess<T>, Error> {
let success = self
.rpc
.submit_and_watch_extrinsic(extrinsic, decoder)
.await?;
Ok(success)
} }
/// Subscribe to events. /// Subscribe to events.
pub fn subscribe_events( pub async fn subscribe_events(
&self, &self,
) -> impl Future<Item = MapStream<StorageChangeSet<T::Hash>>, Error = Error> { ) -> Result<Subscription<StorageChangeSet<T::Hash>>, Error> {
self.connect().and_then(|rpc| rpc.subscribe_events()) let events = self.rpc.subscribe_events().await?;
Ok(events)
} }
/// Subscribe to new blocks. /// Subscribe to new blocks.
pub fn subscribe_blocks( pub async fn subscribe_blocks(&self) -> Result<Subscription<T::Header>, Error> {
&self, let headers = self.rpc.subscribe_blocks().await?;
) -> impl Future<Item = MapStream<T::Header>, Error = Error> { Ok(headers)
self.connect().and_then(|rpc| rpc.subscribe_blocks())
} }
/// Subscribe to finalized blocks. /// Subscribe to finalized blocks.
pub fn subscribe_finalized_blocks( pub async fn subscribe_finalized_blocks(
&self, &self,
) -> impl Future<Item = MapStream<T::Header>, Error = Error> { ) -> Result<Subscription<T::Header>, Error> {
self.connect() let headers = self.rpc.subscribe_finalized_blocks().await?;
.and_then(|rpc| rpc.subscribe_finalized_blocks()) Ok(headers)
} }
/// Create a transaction builder for a private key. /// Create a transaction builder for a private key.
pub fn xt<P>( pub async fn xt<P>(
&self, &self,
signer: P, signer: P,
nonce: Option<T::Index>, nonce: Option<T::Index>,
) -> impl Future<Item = XtBuilder<T, P, S>, Error = Error> ) -> Result<XtBuilder<T, P, S>, Error>
where where
P: Pair, P: Pair,
P::Signature: Codec, P::Signature: Codec,
S: Verify, S: Verify,
S::Signer: From<P::Public> + IdentifyAccount<AccountId = T::AccountId>, S::Signer: From<P::Public> + IdentifyAccount<AccountId = T::AccountId>,
{ {
let client = self.clone();
let account_id = S::Signer::from(signer.public()).into_account(); let account_id = S::Signer::from(signer.public()).into_account();
match nonce { let nonce = match nonce {
Some(nonce) => Either::A(future::ok(nonce)), Some(nonce) => nonce,
None => Either::B(self.account_nonce(account_id)), None => self.account_nonce(account_id).await?,
} };
.map(|nonce| {
let genesis_hash = client.genesis_hash; let genesis_hash = self.genesis_hash;
let runtime_version = client.runtime_version.clone(); let runtime_version = self.runtime_version.clone();
XtBuilder { Ok(XtBuilder {
client, client: self.clone(),
nonce, nonce,
runtime_version, runtime_version,
genesis_hash, genesis_hash,
signer, signer,
}
}) })
} }
} }
@@ -294,7 +318,7 @@ pub struct XtBuilder<T: System, P, S> {
signer: P, signer: P,
} }
impl<T: System + Balances + 'static, P, S: 'static> XtBuilder<T, P, S> impl<T: System + Balances + Send + Sync + 'static, P, S: 'static> XtBuilder<T, P, S>
where where
P: Pair, P: Pair,
{ {
@@ -365,17 +389,10 @@ where
} }
/// Submits a transaction to the chain. /// Submits a transaction to the chain.
pub fn submit<C: Encode>( pub async fn submit<C: Encode>(&self, call: Call<C>) -> Result<T::Hash, Error> {
&self, let extrinsic = self.create_and_sign(call)?;
call: Call<C>, let xt_hash = self.client.submit_extrinsic(extrinsic).await?;
) -> impl Future<Item = T::Hash, Error = Error> { Ok(xt_hash)
let cli = self.client.connect();
self.create_and_sign(call)
.into_future()
.map_err(Into::into)
.and_then(move |extrinsic| {
cli.and_then(move |rpc| rpc.submit_extrinsic(extrinsic))
})
} }
/// Submits transaction to the chain and watch for events. /// Submits transaction to the chain and watch for events.
@@ -422,24 +439,17 @@ where
} }
/// Submits transaction to the chain and watch for events. /// Submits transaction to the chain and watch for events.
pub fn submit<C: Encode>( pub async fn submit<C: Encode>(
self, self,
call: Call<C>, call: Call<C>,
) -> impl Future<Item = ExtrinsicSuccess<T>, Error = Error> { ) -> Result<ExtrinsicSuccess<T>, Error> {
let cli = self.client.connect(); let decoder = self.decoder?;
let decoder = self.decoder.into_future().map_err(Into::into); let extrinsic = self.builder.create_and_sign(call)?;
let xt_success = self
self.builder .client
.create_and_sign(call) .submit_and_watch_extrinsic(extrinsic, decoder)
.into_future() .await?;
.map_err(Into::into) Ok(xt_success)
.join(decoder)
.and_then(move |(extrinsic, decoder)| {
decoder.check_missing_type_sizes();
cli.and_then(move |rpc| {
rpc.submit_and_watch_extrinsic(extrinsic, decoder)
})
})
} }
} }
@@ -458,7 +468,6 @@ impl codec::Encode for Encoded {
mod tests { mod tests {
use codec::Encode; use codec::Encode;
use frame_support::StorageMap; use frame_support::StorageMap;
use futures::stream::Stream;
use sp_core::storage::StorageKey; use sp_core::storage::StorageKey;
use sp_keyring::AccountKeyring; use sp_keyring::AccountKeyring;
@@ -469,6 +478,7 @@ mod tests {
BalancesStore, BalancesStore,
}, },
DefaultNodeRuntime as Runtime, DefaultNodeRuntime as Runtime,
Error,
}; };
type Index = <Runtime as System>::Index; type Index = <Runtime as System>::Index;
@@ -476,85 +486,104 @@ mod tests {
type Address = <Runtime as System>::Address; type Address = <Runtime as System>::Address;
type Balance = <Runtime as Balances>::Balance; type Balance = <Runtime as Balances>::Balance;
pub(crate) fn test_setup() -> (tokio::runtime::Runtime, Client<Runtime>) { pub(crate) async fn test_client() -> Client<Runtime> {
env_logger::try_init().ok(); ClientBuilder::<Runtime>::new()
let mut rt = tokio::runtime::Runtime::new().unwrap(); .build()
let client_future = ClientBuilder::<Runtime>::new().build(); .await
let client = rt.block_on(client_future).unwrap(); .expect("Error creating client")
(rt, client)
} }
#[test] #[test]
#[ignore] // requires locally running substrate node #[ignore] // requires locally running substrate node
fn test_tx_transfer_balance() { fn test_tx_transfer_balance() {
let (mut rt, client) = test_setup(); env_logger::try_init().ok();
let transfer = async_std::task::block_on(async move {
let signer = AccountKeyring::Alice.pair();
let dest = AccountKeyring::Bob.to_account_id();
let signer = AccountKeyring::Alice.pair(); let client = test_client().await;
let mut xt = rt.block_on(client.xt(signer, None)).unwrap(); let mut xt = client.xt(signer, None).await?;
let _ = xt
.submit(balances::transfer::<Runtime>(dest.clone().into(), 10_000))
.await?;
let dest = AccountKeyring::Bob.to_account_id(); // check that nonce is handled correctly
let transfer = xt.increment_nonce()
xt.submit(balances::transfer::<Runtime>(dest.clone().into(), 10_000)); .submit(balances::transfer::<Runtime>(dest.clone().into(), 10_000))
rt.block_on(transfer).unwrap(); .await
});
// check that nonce is handled correctly assert!(transfer.is_ok())
let transfer = xt
.increment_nonce()
.submit(balances::transfer::<Runtime>(dest.clone().into(), 10_000));
rt.block_on(transfer).unwrap();
} }
#[test] #[test]
#[ignore] // requires locally running substrate node #[ignore] // requires locally running substrate node
fn test_getting_hash() { fn test_getting_hash() {
let (mut rt, client) = test_setup(); let result: Result<_, Error> = async_std::task::block_on(async move {
rt.block_on(client.block_hash(None)).unwrap(); let client = test_client().await;
let block_hash = client.block_hash(None).await?;
Ok(block_hash)
});
assert!(result.is_ok())
} }
#[test] #[test]
#[ignore] // requires locally running substrate node #[ignore] // requires locally running substrate node
fn test_getting_block() { fn test_getting_block() {
let (mut rt, client) = test_setup(); let result: Result<_, Error> = async_std::task::block_on(async move {
rt.block_on(client.block_hash(None).and_then(move |h| client.block(h))) let client = test_client().await;
.unwrap(); let block_hash = client.block_hash(None).await?;
let block = client.block(block_hash).await?;
Ok(block)
});
assert!(result.is_ok())
} }
#[test] #[test]
#[ignore] // requires locally running substrate node #[ignore] // requires locally running substrate node
fn test_state_read_free_balance() { fn test_state_read_free_balance() {
let (mut rt, client) = test_setup(); let result: Result<_, Error> = async_std::task::block_on(async move {
let account = AccountKeyring::Alice.to_account_id();
let client = test_client().await;
let balance = client.free_balance(account.into()).await?;
Ok(balance)
});
let account = AccountKeyring::Alice.to_account_id(); assert!(result.is_ok())
rt.block_on(client.free_balance(account.into())).unwrap();
} }
#[test] #[test]
#[ignore] // requires locally running substrate node #[ignore] // requires locally running substrate node
fn test_chain_subscribe_blocks() { fn test_chain_subscribe_blocks() {
let (mut rt, client) = test_setup(); let result: Result<_, Error> = async_std::task::block_on(async move {
let client = test_client().await;
let mut blocks = client.subscribe_blocks().await?;
let block = blocks.next().await;
Ok(block)
});
let stream = rt.block_on(client.subscribe_blocks()).unwrap(); assert!(result.is_ok())
let (_header, _) = rt
.block_on(stream.into_future().map_err(|(e, _)| e))
.unwrap();
} }
#[test] #[test]
#[ignore] // requires locally running substrate node #[ignore] // requires locally running substrate node
fn test_chain_subscribe_finalized_blocks() { fn test_chain_subscribe_finalized_blocks() {
let (mut rt, client) = test_setup(); let result: Result<_, Error> = async_std::task::block_on(async move {
let client = test_client().await;
let mut blocks = client.subscribe_finalized_blocks().await?;
let block = blocks.next().await;
Ok(block)
});
let stream = rt.block_on(client.subscribe_finalized_blocks()).unwrap(); assert!(result.is_ok())
let (_header, _) = rt
.block_on(stream.into_future().map_err(|(e, _)| e))
.unwrap();
} }
#[test] #[test]
#[ignore] // requires locally running substrate node #[ignore] // requires locally running substrate node
fn test_chain_read_metadata() { fn test_chain_read_metadata() {
let (_, client) = test_setup(); let client = async_std::task::block_on(test_client());
let balances = client.metadata().module_with_calls("Balances").unwrap(); let balances = client.metadata().module_with_calls("Balances").unwrap();
let dest = sp_keyring::AccountKeyring::Bob.to_account_id(); let dest = sp_keyring::AccountKeyring::Bob.to_account_id();
+20 -12
View File
@@ -41,6 +41,8 @@ use crate::Encoded;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum MetadataError { pub enum MetadataError {
#[error("Error converting substrate metadata: {0}")]
Conversion(#[from] ConversionError),
#[error("Module not found")] #[error("Module not found")]
ModuleNotFound(String), ModuleNotFound(String),
#[error("Module with events not found")] #[error("Module with events not found")]
@@ -284,14 +286,14 @@ pub enum EventArg {
} }
impl FromStr for EventArg { impl FromStr for EventArg {
type Err = Error; type Err = ConversionError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with("Vec<") { if s.starts_with("Vec<") {
if s.ends_with('>') { if s.ends_with('>') {
Ok(EventArg::Vec(Box::new(s[4..s.len() - 1].parse()?))) Ok(EventArg::Vec(Box::new(s[4..s.len() - 1].parse()?)))
} else { } else {
Err(Error::InvalidEventArg( Err(ConversionError::InvalidEventArg(
s.to_string(), s.to_string(),
"Expected closing `>` for `Vec`", "Expected closing `>` for `Vec`",
)) ))
@@ -305,7 +307,7 @@ impl FromStr for EventArg {
} }
Ok(EventArg::Tuple(args)) Ok(EventArg::Tuple(args))
} else { } else {
Err(Error::InvalidEventArg( Err(ConversionError::InvalidEventArg(
s.to_string(), s.to_string(),
"Expecting closing `)` for tuple", "Expecting closing `)` for tuple",
)) ))
@@ -333,24 +335,28 @@ impl EventArg {
} }
} }
#[derive(Debug)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum ConversionError {
#[error("Invalid prefix")]
InvalidPrefix, InvalidPrefix,
#[error("Invalid version")]
InvalidVersion, InvalidVersion,
#[error("Expected DecodeDifferent::Decoded")]
ExpectedDecoded, ExpectedDecoded,
#[error("Invalid event arg {0}")]
InvalidEventArg(String, &'static str), InvalidEventArg(String, &'static str),
} }
impl TryFrom<RuntimeMetadataPrefixed> for Metadata { impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
type Error = Error; type Error = MetadataError;
fn try_from(metadata: RuntimeMetadataPrefixed) -> Result<Self, Self::Error> { fn try_from(metadata: RuntimeMetadataPrefixed) -> Result<Self, Self::Error> {
if metadata.0 != META_RESERVED { if metadata.0 != META_RESERVED {
return Err(Error::InvalidPrefix) return Err(ConversionError::InvalidPrefix.into())
} }
let meta = match metadata.1 { let meta = match metadata.1 {
RuntimeMetadata::V10(meta) => meta, RuntimeMetadata::V10(meta) => meta,
_ => return Err(Error::InvalidVersion), _ => return Err(ConversionError::InvalidVersion.into()),
}; };
let mut modules = HashMap::new(); let mut modules = HashMap::new();
let mut modules_with_calls = HashMap::new(); let mut modules_with_calls = HashMap::new();
@@ -417,16 +423,18 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
} }
} }
fn convert<B: 'static, O: 'static>(dd: DecodeDifferent<B, O>) -> Result<O, Error> { fn convert<B: 'static, O: 'static>(
dd: DecodeDifferent<B, O>,
) -> Result<O, ConversionError> {
match dd { match dd {
DecodeDifferent::Decoded(value) => Ok(value), DecodeDifferent::Decoded(value) => Ok(value),
_ => Err(Error::ExpectedDecoded), _ => Err(ConversionError::ExpectedDecoded),
} }
} }
fn convert_event( fn convert_event(
event: frame_metadata::EventMetadata, event: frame_metadata::EventMetadata,
) -> Result<ModuleEventMetadata, Error> { ) -> Result<ModuleEventMetadata, ConversionError> {
let name = convert(event.name)?; let name = convert(event.name)?;
let mut arguments = Vec::new(); let mut arguments = Vec::new();
for arg in convert(event.arguments)? { for arg in convert(event.arguments)? {
@@ -440,7 +448,7 @@ fn convert_entry(
module_prefix: String, module_prefix: String,
storage_prefix: String, storage_prefix: String,
entry: frame_metadata::StorageEntryMetadata, entry: frame_metadata::StorageEntryMetadata,
) -> Result<StorageMetadata, Error> { ) -> Result<StorageMetadata, ConversionError> {
let default = convert(entry.default)?; let default = convert(entry.default)?;
Ok(StorageMetadata { Ok(StorageMetadata {
module_prefix, module_prefix,
+220 -244
View File
@@ -21,35 +21,26 @@ use codec::{
Encode, Encode,
Error as CodecError, Error as CodecError,
}; };
use futures::{ use jsonrpsee::{
future::{ client::Subscription,
self, core::common::{
Future, to_value as to_json_value,
IntoFuture, Params,
},
stream::{
self,
Stream,
}, },
Client,
}; };
use jsonrpc_core_client::{
RpcChannel,
TypedSubscriptionStream,
};
use num_traits::bounds::Bounded; use num_traits::bounds::Bounded;
use frame_metadata::RuntimeMetadataPrefixed; use frame_metadata::RuntimeMetadataPrefixed;
use sc_rpc_api::{
author::AuthorClient,
chain::ChainClient,
state::StateClient,
};
use sp_core::{ use sp_core::{
storage::{ storage::{
StorageChangeSet, StorageChangeSet,
StorageData,
StorageKey, StorageKey,
}, },
twox_128, twox_128,
Bytes,
}; };
use sp_rpc::{ use sp_rpc::{
list::ListOrValue, list::ListOrValue,
@@ -64,6 +55,7 @@ use sp_runtime::{
}; };
use sp_transaction_pool::TransactionStatus; use sp_transaction_pool::TransactionStatus;
use sp_version::RuntimeVersion; use sp_version::RuntimeVersion;
use std::marker::PhantomData;
use crate::{ use crate::{
error::Error, error::Error,
@@ -83,265 +75,251 @@ use crate::{
metadata::Metadata, metadata::Metadata,
}; };
pub type ChainBlock<T> = SignedBlock<Block<<T as System>::Header, <T as System>::Extrinsic>>; pub type ChainBlock<T> =
SignedBlock<Block<<T as System>::Header, <T as System>::Extrinsic>>;
pub type BlockNumber<T> = NumberOrHex<<T as System>::BlockNumber>; pub type BlockNumber<T> = NumberOrHex<<T as System>::BlockNumber>;
/// Client for substrate rpc interfaces /// Client for substrate rpc interfaces
#[derive(Clone)]
pub struct Rpc<T: System> { pub struct Rpc<T: System> {
state: StateClient<T::Hash>, client: Client,
chain: ChainClient<T::BlockNumber, T::Hash, T::Header, ChainBlock<T>>, marker: std::marker::PhantomData<T>,
author: AuthorClient<T::Hash, T::Hash>,
} }
/// Allows connecting to all inner interfaces on the same RpcChannel impl<T> Rpc<T>
impl<T: System> From<RpcChannel> for Rpc<T> { where
fn from(channel: RpcChannel) -> Self { T: System,
Self { {
state: channel.clone().into(), pub async fn connect_ws(url: &str) -> Result<Self, Error> {
chain: channel.clone().into(), let raw_client = jsonrpsee::ws::ws_raw_client(&url).await?;
author: channel.into(), Ok(Rpc {
} client: raw_client.into(),
marker: PhantomData,
})
} }
}
impl<T: System> Rpc<T> {
/// Fetch a storage key /// Fetch a storage key
pub fn storage<V: Decode>( pub async fn storage<V: Decode>(
&self, &self,
key: StorageKey, key: StorageKey,
hash: Option<T::Hash>, hash: Option<T::Hash>,
) -> impl Future<Item = Option<V>, Error = Error> { ) -> Result<Option<V>, Error> {
self.state // todo: update jsonrpsee::rpc_api! macro to accept shared Client (currently only RawClient)
.storage(key, hash) // until then we manually construct params here and in other methods
.map_err(Into::into) let params = Params::Array(vec![to_json_value(key)?, to_json_value(hash)?]);
.and_then(|data| { let data: Option<StorageData> =
match data { self.client.request("state_getStorage", params).await?;
Some(data) => { match data {
let value = Decode::decode(&mut &data.0[..])?; Some(data) => {
Ok(Some(value)) let value = Decode::decode(&mut &data.0[..])?;
} Ok(Some(value))
None => Ok(None), }
} None => Ok(None),
}) }
} }
/// Fetch the genesis hash /// Fetch the genesis hash
pub fn genesis_hash(&self) -> impl Future<Item = T::Hash, Error = Error> { pub async fn genesis_hash(&self) -> Result<T::Hash, Error> {
let block_zero = T::BlockNumber::min_value(); let block_zero = Some(ListOrValue::Value(NumberOrHex::Number(
self.chain T::BlockNumber::min_value(),
.block_hash(Some(ListOrValue::Value(NumberOrHex::Number(block_zero)))) )));
.map_err(Into::into) let params = Params::Array(vec![to_json_value(block_zero)?]);
.and_then(|list_or_value| { let list_or_value: ListOrValue<Option<T::Hash>> =
future::result(match list_or_value { self.client.request("chain_getBlockHash", params).await?;
ListOrValue::Value(genesis_hash) => { match list_or_value {
genesis_hash.ok_or_else(|| "Genesis hash not found".into()) ListOrValue::Value(genesis_hash) => {
} genesis_hash.ok_or_else(|| "Genesis hash not found".into())
ListOrValue::List(_) => Err("Expected a Value, got a List".into()), }
}) ListOrValue::List(_) => Err("Expected a Value, got a List".into()),
}) }
} }
/// Fetch the metadata /// Fetch the metadata
pub fn metadata(&self) -> impl Future<Item = Metadata, Error = Error> { pub async fn metadata(&self) -> Result<Metadata, Error> {
self.state let bytes: Bytes = self
.metadata(None) .client
.map_err(Into::into) .request("state_getMetadata", Params::None)
.and_then(|bytes| { .await?;
let result = Decode::decode(&mut &bytes[..]) let meta: RuntimeMetadataPrefixed = Decode::decode(&mut &bytes[..])?;
.map_err(Into::into) let metadata: Metadata = meta.try_into()?;
.and_then(|meta: RuntimeMetadataPrefixed| { Ok(metadata)
meta.try_into().map_err(|err| format!("{:?}", err).into())
});
future::result(result)
})
} }
/// Get a block hash, returns hash of latest block by default /// Get a block hash, returns hash of latest block by default
pub fn block_hash( pub async fn block_hash(
&self, &self,
block_number: Option<BlockNumber<T>>, block_number: Option<BlockNumber<T>>,
) -> impl Future<Item = Option<T::Hash>, Error = Error> { ) -> Result<Option<T::Hash>, Error> {
self.chain let block_number = block_number.map(|bn| ListOrValue::Value(bn));
.block_hash(block_number.map(|bn| ListOrValue::Value(bn))) let params = Params::Array(vec![to_json_value(block_number)?]);
.map_err(Into::into) let list_or_value = self.client.request("chain_getBlockHash", params).await?;
.and_then(|list_or_value| { match list_or_value {
match list_or_value { ListOrValue::Value(hash) => Ok(hash),
ListOrValue::Value(hash) => Ok(hash), ListOrValue::List(_) => Err("Expected a Value, got a List".into()),
ListOrValue::List(_) => Err("Expected a Value, got a List".into()), }
}
})
} }
/// Get a block hash of the latest finalized block /// Get a block hash of the latest finalized block
pub fn finalized_head(&self) -> impl Future<Item = T::Hash, Error = Error> { pub async fn finalized_head(&self) -> Result<T::Hash, Error> {
self.chain.finalized_head().map_err(Into::into) let hash = self
.client
.request("chain_getFinalizedHead", Params::None)
.await?;
Ok(hash)
} }
/// Get a Block /// Get a Block
pub fn block( pub async fn block(
&self, &self,
hash: Option<T::Hash>, hash: Option<T::Hash>,
) -> impl Future<Item = Option<ChainBlock<T>>, Error = Error> { ) -> Result<Option<ChainBlock<T>>, Error> {
self.chain.block(hash).map_err(Into::into) let params = Params::Array(vec![to_json_value(hash)?]);
let block = self.client.request("chain_getBlock", params).await?;
Ok(block)
} }
/// Fetch the runtime version /// Fetch the runtime version
pub fn runtime_version( pub async fn runtime_version(
&self, &self,
at: Option<T::Hash>, at: Option<T::Hash>,
) -> impl Future<Item = RuntimeVersion, Error = Error> { ) -> Result<RuntimeVersion, Error> {
self.state.runtime_version(at).map_err(Into::into) let params = Params::Array(vec![to_json_value(at)?]);
let version = self
.client
.request("state_getRuntimeVersion", params)
.await?;
Ok(version)
} }
} }
type MapClosure<T> = Box<dyn Fn(T) -> T + Send>;
pub type MapStream<T> = stream::Map<TypedSubscriptionStream<T>, MapClosure<T>>;
impl<T: System + Balances + 'static> Rpc<T> { impl<T: System + Balances + 'static> Rpc<T> {
/// Subscribe to substrate System Events /// Subscribe to substrate System Events
pub fn subscribe_events( pub async fn subscribe_events(
&self, &self,
) -> impl Future<Item = MapStream<StorageChangeSet<<T as System>::Hash>>, Error = Error> ) -> Result<Subscription<StorageChangeSet<<T as System>::Hash>>, Error> {
{
let mut storage_key = twox_128(b"System").to_vec(); let mut storage_key = twox_128(b"System").to_vec();
storage_key.extend(twox_128(b"Events").to_vec()); storage_key.extend(twox_128(b"Events").to_vec());
log::debug!("Events storage key {:?}", hex::encode(&storage_key)); log::debug!("Events storage key {:?}", hex::encode(&storage_key));
let closure: MapClosure<StorageChangeSet<<T as System>::Hash>> = let keys = Some(vec![StorageKey(storage_key)]);
Box::new(|event| { let params = Params::Array(vec![to_json_value(keys)?]);
log::debug!(
"Event {:?}", let subscription = self
event .client
.changes .subscribe("state_subscribeStorage", params, "state_unsubscribeStorage")
.iter() .await?;
.map(|(k, v)| { Ok(subscription)
(hex::encode(&k.0), v.as_ref().map(|v| hex::encode(&v.0)))
})
.collect::<Vec<_>>()
);
event
});
self.state
.subscribe_storage(Some(vec![StorageKey(storage_key)]))
.map(|stream: TypedSubscriptionStream<_>| stream.map(closure))
.map_err(Into::into)
} }
/// Subscribe to blocks. /// Subscribe to blocks.
pub fn subscribe_blocks( pub async fn subscribe_blocks(&self) -> Result<Subscription<T::Header>, Error> {
&self, let subscription = self
) -> impl Future<Item = MapStream<T::Header>, Error = Error> { .client
let closure: MapClosure<T::Header> = Box::new(|event| { .subscribe(
log::info!("New block {:?}", event); "chain_subscribeNewHeads",
event Params::None,
}); "chain_subscribeNewHeads",
self.chain )
.subscribe_new_heads() .await?;
.map(|stream| stream.map(closure))
.map_err(Into::into) Ok(subscription)
} }
/// Subscribe to finalized blocks. /// Subscribe to finalized blocks.
pub fn subscribe_finalized_blocks( pub async fn subscribe_finalized_blocks(
&self, &self,
) -> impl Future<Item = MapStream<T::Header>, Error = Error> { ) -> Result<Subscription<T::Header>, Error> {
let closure: MapClosure<T::Header> = Box::new(|event| { let subscription = self
log::info!("Finalized block {:?}", event); .client
event .subscribe(
}); "chain_subscribeFinalizedHeads",
self.chain Params::None,
.subscribe_finalized_heads() "chain_subscribeFinalizedHeads",
.map(|stream| stream.map(closure)) )
.map_err(Into::into) .await?;
Ok(subscription)
} }
/// Create and submit an extrinsic and return corresponding Hash if successful /// Create and submit an extrinsic and return corresponding Hash if successful
pub fn submit_extrinsic<E>( pub async fn submit_extrinsic<E: Encode>(
self, &self,
extrinsic: E, extrinsic: E,
) -> impl Future<Item = T::Hash, Error = Error> ) -> Result<T::Hash, Error> {
where let bytes: Bytes = extrinsic.encode().into();
E: Encode, let params = Params::Array(vec![to_json_value(bytes)?]);
{ let xt_hash = self
self.author .client
.submit_extrinsic(extrinsic.encode().into()) .request("author_submitExtrinsic", params)
.map_err(Into::into) .await?;
Ok(xt_hash)
}
pub async fn watch_extrinsic<E: Encode>(
&self,
extrinsic: E,
) -> Result<Subscription<TransactionStatus<T::Hash, T::Hash>>, Error> {
let bytes: Bytes = extrinsic.encode().into();
let params = Params::Array(vec![to_json_value(bytes)?]);
let subscription = self
.client
.subscribe(
"author_submitAndWatchExtrinsic",
params,
"author_unwatchExtrinsic",
)
.await?;
Ok(subscription)
} }
/// Create and submit an extrinsic and return corresponding Event if successful /// Create and submit an extrinsic and return corresponding Event if successful
pub fn submit_and_watch_extrinsic<E: 'static>( pub async fn submit_and_watch_extrinsic<E: Encode + 'static>(
self, self,
extrinsic: E, extrinsic: E,
decoder: EventsDecoder<T>, decoder: EventsDecoder<T>,
) -> impl Future<Item = ExtrinsicSuccess<T>, Error = Error> ) -> Result<ExtrinsicSuccess<T>, Error> {
where let ext_hash = T::Hashing::hash_of(&extrinsic);
E: Encode, log::info!("Submitting Extrinsic `{:?}`", ext_hash);
{
let events = self.subscribe_events().map_err(Into::into);
events.and_then(move |events| {
let ext_hash = T::Hashing::hash_of(&extrinsic);
log::info!("Submitting Extrinsic `{:?}`", ext_hash);
let chain = self.chain.clone(); let events_sub = self.subscribe_events().await?;
self.author let mut xt_sub = self.watch_extrinsic(extrinsic).await?;
.watch_extrinsic(extrinsic.encode().into())
.map_err(Into::into)
.and_then(|stream| {
stream
.filter_map(|status| {
log::info!("received status {:?}", status);
match status {
// ignore in progress extrinsic for now
TransactionStatus::Future
| TransactionStatus::Ready
| TransactionStatus::Broadcast(_) => None,
TransactionStatus::InBlock(block_hash) => {
Some(Ok(block_hash))
}
TransactionStatus::Usurped(_) => {
Some(Err("Extrinsic Usurped".into()))
}
TransactionStatus::Dropped => {
Some(Err("Extrinsic Dropped".into()))
}
TransactionStatus::Invalid => {
Some(Err("Extrinsic Invalid".into()))
}
}
})
.into_future()
.map_err(|(e, _)| e.into())
.and_then(|(result, _)| {
log::info!("received result {:?}", result);
result while let status = xt_sub.next().await {
.ok_or_else(|| Error::from("Stream terminated")) log::info!("received status {:?}", status);
.and_then(|r| r) match status {
.into_future() // ignore in progress extrinsic for now
}) TransactionStatus::Future
}) | TransactionStatus::Ready
.and_then(move |bh| { | TransactionStatus::Broadcast(_) => continue,
log::info!("Fetching block {:?}", bh); TransactionStatus::InBlock(block_hash) => {
chain log::info!("Fetching block {:?}", block_hash);
.block(Some(bh)) let block = self.block(Some(block_hash)).await?;
.map(move |b| (bh, b)) return match block {
.map_err(Into::into) Some(signed_block) => {
}) log::info!(
.and_then(|(h, b)| { "Found block {:?}, with {} extrinsics",
b.ok_or_else(|| format!("Failed to find block {:?}", h).into()) block_hash,
.map(|b| (h, b)) signed_block.block.extrinsics.len()
.into_future() );
}) wait_for_block_events(
.and_then(move |(bh, sb)| { decoder,
log::info!( ext_hash,
"Found block {:?}, with {} extrinsics", signed_block,
bh, block_hash,
sb.block.extrinsics.len() events_sub,
); )
.await
wait_for_block_events(decoder, ext_hash, sb, bh, events) }
}) None => {
}) Err(format!("Failed to find block {:?}", block_hash).into())
}
}
}
TransactionStatus::Usurped(_) => return Err("Extrinsic Usurped".into()),
TransactionStatus::Dropped => return Err("Extrinsic Dropped".into()),
TransactionStatus::Invalid => return Err("Extrinsic Invalid".into()),
}
}
unreachable!()
} }
} }
@@ -399,13 +377,13 @@ impl<T: System> ExtrinsicSuccess<T> {
} }
/// Waits for events for the block triggered by the extrinsic /// Waits for events for the block triggered by the extrinsic
pub fn wait_for_block_events<T: System + Balances + 'static>( pub async fn wait_for_block_events<T: System + Balances + 'static>(
decoder: EventsDecoder<T>, decoder: EventsDecoder<T>,
ext_hash: T::Hash, ext_hash: T::Hash,
signed_block: ChainBlock<T>, signed_block: ChainBlock<T>,
block_hash: T::Hash, block_hash: T::Hash,
events_stream: MapStream<StorageChangeSet<T::Hash>>, events_subscription: Subscription<StorageChangeSet<T::Hash>>,
) -> impl Future<Item = ExtrinsicSuccess<T>, Error = Error> { ) -> Result<ExtrinsicSuccess<T>, Error> {
let ext_index = signed_block let ext_index = signed_block
.block .block
.extrinsics .extrinsics
@@ -415,43 +393,41 @@ pub fn wait_for_block_events<T: System + Balances + 'static>(
hash == ext_hash hash == ext_hash
}) })
.ok_or_else(|| { .ok_or_else(|| {
format!("Failed to find Extrinsic with hash {:?}", ext_hash).into() Error::Other(format!("Failed to find Extrinsic with hash {:?}", ext_hash))
}) })?;
.into_future();
events_stream let mut subscription = events_subscription;
.filter(move |event| event.block == block_hash) while let change_set = subscription.next().await {
.into_future() // only interested in events for the given block
.map_err(|(e, _)| e.into()) if change_set.block != block_hash {
.join(ext_index) continue
.and_then(move |((change_set, _), ext_index)| { }
let events = match change_set { let mut events = Vec::new();
None => Vec::new(), for (_key, data) in change_set.changes {
Some(change_set) => { if let Some(data) = data {
let mut events = Vec::new(); match decoder.decode_events(&mut &data.0[..]) {
for (_key, data) in change_set.changes { Ok(raw_events) => {
if let Some(data) = data { for (phase, event) in raw_events {
match decoder.decode_events(&mut &data.0[..]) { if let Phase::ApplyExtrinsic(i) = phase {
Ok(raw_events) => { if i as usize == ext_index {
for (phase, event) in raw_events { events.push(event)
if let Phase::ApplyExtrinsic(i) = phase {
if i as usize == ext_index {
events.push(event)
}
}
}
} }
Err(err) => return future::err(err.into()),
} }
} }
} }
events Err(err) => return Err(err.into()),
} }
}; }
future::ok(ExtrinsicSuccess { }
return if events.len() > 0 {
Ok(ExtrinsicSuccess {
block: block_hash, block: block_hash,
extrinsic: ext_hash, extrinsic: ext_hash,
events, events,
}) })
}) } else {
Err(format!("No events found for block {}", block_hash).into())
}
}
unreachable!()
} }