diff --git a/examples/submit_and_watch.rs b/examples/submit_and_watch.rs new file mode 100644 index 0000000000..ce4bef2f6e --- /dev/null +++ b/examples/submit_and_watch.rs @@ -0,0 +1,54 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// This file is part of substrate-subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with substrate-subxt. If not, see . + +use futures::future::Future; +use substrate_subxt::{balances, system::System, DefaultNodeRuntime as Runtime}; +use sp_keyring::AccountKeyring; + +type AccountId = ::AccountId; +type Balance = ::Balance; + +fn main() { + env_logger::init(); + let signer = AccountKeyring::Alice.pair(); + + let dest = AccountKeyring::Bob.to_account_id(); + + let fut = substrate_subxt::ClientBuilder::::new() + .build() + .and_then(|cli| cli.xt(signer, None)) + .and_then(move |xt| { + xt.watch() + .events_decoder(|decoder| { + // for any primitive event with no type size registered + decoder.register_type_size::<(u64, u64)>("IdentificationTuple") + }) + .submit(balances::transfer::(dest.clone().into(), 10_000)) + }); + + let mut rt = tokio::runtime::Runtime::new().unwrap(); + match rt.block_on(fut) { + Ok(extrinsic_success) => { + match extrinsic_success.find_event::<(AccountId, AccountId, Balance, Balance)>("Balances", "Transfer") { + Some(Ok((_from, _to, value, _fees))) => + println!("Balance transfer success: value: {:?}", value), + Some(Err(err)) => println!("Failed to decode code hash: {}", err), + None => println!("Failed to find Contracts::CodeStored Event"), + } + }, + Err(err) => println!("Error: {}", err) + } +} diff --git a/src/events.rs b/src/events.rs index dfb1dd50e9..af096088be 100644 --- a/src/events.rs +++ b/src/events.rs @@ -43,9 +43,9 @@ use crate::{ Metadata, MetadataError, }, + Phase, System, SystemEvent, - Phase, }; /// Top level Event that can be produced by a substrate runtime @@ -72,18 +72,10 @@ pub enum EventsError { CodecError(#[from] CodecError), #[error("Metadata error: {0:?}")] Metadata(#[from] MetadataError), - #[error("Type Sizes Missing: {0:?}")] - TypeSizesMissing(Vec), #[error("Type Sizes Unavailable: {0:?}")] TypeSizeUnavailable(String), } -impl From> for EventsError { - fn from(error: Vec) -> Self { - EventsError::TypeSizesMissing(error) - } -} - pub struct EventsDecoder { metadata: Metadata, // todo: [AJ] borrow? type_sizes: HashMap, @@ -120,16 +112,6 @@ impl TryFrom for EventsDecoder { // VoteThreshold enum index decoder.register_type_size::("VoteThreshold")?; - // Ignore these unregistered types, which are not fixed size primitives - decoder.check_missing_type_sizes(vec![ - "DispatchInfo", - "DispatchError", - "Result<(), DispatchError>", - "OpaqueTimeSlot", - // FIXME: determine type size for the following if necessary/possible - "IdentificationTuple", - "AuthorityList", - ])?; Ok(decoder) } } @@ -148,31 +130,28 @@ impl EventsDecoder { } } - fn check_missing_type_sizes>( - &self, - ignore: I, - ) -> Result<(), Vec> { + pub fn check_missing_type_sizes(&self) { let mut missing = HashSet::new(); - let mut ignore_set = HashSet::new(); - ignore_set.extend(ignore); for module in self.metadata.modules_with_events() { for event in module.events() { for arg in event.arguments() { for primitive in arg.primitives() { - if !self.type_sizes.contains_key(&primitive) - && !ignore_set.contains(primitive.as_str()) + if module.name() != "System" + && !self.type_sizes.contains_key(&primitive) && !primitive.contains("PhantomData") { - missing.insert(primitive); + missing.insert(format!("{}::{}::{}", module.name(), event.name, primitive)); } } } } } - if missing.is_empty() { - Ok(()) - } else { - Err(missing.into_iter().collect()) + if missing.len() > 0 { + log::warn!( + "The following primitive types do not have registered sizes: {:?} \ + If any of these events are received, an error will occur since we cannot decode them", + missing + ); } } diff --git a/src/frame/contracts.rs b/src/frame/contracts.rs index d9b3a1a9b6..55415bfe3f 100644 --- a/src/frame/contracts.rs +++ b/src/frame/contracts.rs @@ -186,7 +186,8 @@ mod tests { let wasm = wabt::wat2wasm(CONTRACT).expect("invalid wabt"); client.xt(signer, None).and_then(|xt| { - xt.submit_and_watch(super::put_code(500_000, wasm)) + xt.watch() + .submit(super::put_code(500_000, wasm)) .map(|result| result.find_event::(MODULE, events::CODE_STORED)) }) } @@ -224,15 +225,19 @@ mod tests { println!("{:?}", code_hash); let instantiate = client.xt(signer, None).and_then(move |xt| { - xt.submit_and_watch(super::instantiate::( - 100_000_000_000_000, - 500_000, - code_hash, - Vec::new(), - )) - .map(|result| { - result.find_event::<(AccountId, AccountId)>(MODULE, events::INSTANTIATED) - }) + xt.watch() + .submit(super::instantiate::( + 100_000_000_000_000, + 500_000, + code_hash, + Vec::new(), + )) + .map(|result| { + result.find_event::<(AccountId, AccountId)>( + MODULE, + events::INSTANTIATED, + ) + }) }); let result = rt.block_on(instantiate).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 000cfbe0f3..0c33676782 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,7 +72,7 @@ pub use self::{ runtimes::*, }; use self::{ - events::EventsDecoder, + events::{EventsDecoder, EventsError}, extrinsic::{ DefaultExtra, SignedExtra, @@ -80,9 +80,9 @@ use self::{ frame::{ balances::Balances, system::{ + Phase, System, SystemEvent, - Phase, SystemStore, }, }, @@ -209,8 +209,7 @@ impl Client { /// Get a block hash of the latest finalized block pub fn finalized_head(&self) -> impl Future { - self.connect() - .and_then(|rpc| rpc.finalized_head()) + self.connect().and_then(|rpc| rpc.finalized_head()) } /// Get a block @@ -280,6 +279,7 @@ impl Client { } /// Transaction builder. +#[derive(Clone)] pub struct XtBuilder { client: Client, nonce: T::Index, @@ -373,21 +373,58 @@ where } /// Submits transaction to the chain and watch for events. - pub fn submit_and_watch( - &self, + pub fn watch(self) -> EventsSubscriber { + let metadata = self.client.metadata().clone(); + let decoder = EventsDecoder::try_from(metadata).map_err(Into::into); + EventsSubscriber { + client: self.client.clone(), + builder: self, + decoder, + } + } +} + +/// Submits an extrinsic and subscribes to the triggered events +pub struct EventsSubscriber { + client: Client, + builder: XtBuilder, + decoder: Result, EventsError>, +} + +impl + EventsSubscriber +where + P: Pair, + S: Verify + Codec + From, + S::Signer: From + IdentifyAccount, + T::Address: From, +{ + /// Access the events decoder for registering custom type sizes + pub fn events_decoder) -> Result>(self, f: F) -> Self { + let mut this = self; + if let Ok(ref mut decoder) = this.decoder { + if let Err(err) = f(decoder) { + this.decoder = Err(err) + } + } + this + } + + /// Submits transaction to the chain and watch for events. + pub fn submit( + self, call: Call, ) -> impl Future, Error = Error> { let cli = self.client.connect(); - let metadata = self.client.metadata().clone(); - let decoder = EventsDecoder::try_from(metadata) - .into_future() - .map_err(Into::into); + let decoder = self.decoder.into_future().map_err(Into::into); - self.create_and_sign(call) + self.builder + .create_and_sign(call) .into_future() .map_err(Into::into) .join(decoder) .and_then(move |(extrinsic, decoder)| { + decoder.check_missing_type_sizes(); cli.and_then(move |rpc| { rpc.submit_and_watch_extrinsic(extrinsic, decoder) }) diff --git a/src/metadata.rs b/src/metadata.rs index 60065feaf9..d84978fc61 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -244,7 +244,7 @@ impl StorageMap { .chain(&encoded_key) .cloned() .collect::>() - }, + } StorageHasher::Blake2_256 => sp_core::blake2_256(&encoded_key).to_vec(), StorageHasher::Twox128 => sp_core::twox_128(&encoded_key).to_vec(), StorageHasher::Twox256 => sp_core::twox_256(&encoded_key).to_vec(), diff --git a/src/rpc.rs b/src/rpc.rs index 808ebd98f1..8d823e07a4 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -170,9 +170,7 @@ impl Rpc { /// Get a block hash of the latest finalized block pub fn finalized_head(&self) -> impl Future { - self.chain - .finalized_head() - .map_err(Into::into) + self.chain.finalized_head().map_err(Into::into) } /// Get a Block