Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand the subscribe_and_watch example #361

Merged
merged 3 commits into from
Dec 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions examples/submit_and_watch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ pub mod polkadot {}
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();

simple_transfer().await?;
simple_transfer_separate_events().await?;
handle_transfer_events().await?;

Ok(())
}

/// This is the highest level approach to using this API. We use `wait_for_finalized_success`
/// to wait for the transaction to make it into a finalized block, and also ensure that the
/// transaction was successful according to the associated events.
async fn simple_transfer() -> Result<(), Box<dyn std::error::Error>> {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id().into();

Expand Down Expand Up @@ -62,3 +73,124 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}
Ok(())
}

/// This is very similar to `simple_transfer`, except to show that we can handle
/// waiting for the transaction to be finalized separately from obtaining and checking
/// for success on the events.
async fn simple_transfer_separate_events() -> Result<(), Box<dyn std::error::Error>> {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id().into();

let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<polkadot::DefaultConfig>>();

let balance_transfer = api
.tx()
.balances()
.transfer(dest, 10_000)
.sign_and_submit_then_watch(&signer)
.await?
.wait_for_finalized()
.await?;

// Now we know it's been finalized, we can get hold of a couple of
// details, including events. Calling `wait_for_finalized_success` is
// equivalent to calling `wait_for_finalized` and then `wait_for_success`:
let _events = balance_transfer.wait_for_success().await?;

// Alternately, we could just `fetch_events`, which grabs all of the events like
// the above, but does not check for success, and leaves it up to you:
let events = balance_transfer.fetch_events().await?;

let failed_event =
events.find_first_event::<polkadot::system::events::ExtrinsicFailed>()?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth mentioning how find_first_event could return error?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's worth an explicit shout out in the example really, but I added this note to the docs for the relevant methods because it wasn't clear enough there :)


if let Some(_ev) = failed_event {
// We found a failed event; the transfer didn't succeed.
println!("Balance transfer failed");
} else {
// We didn't find a failed event; the transfer succeeded. Find
// more details about it to report..
let transfer_event =
events.find_first_event::<polkadot::balances::events::Transfer>()?;
if let Some(event) = transfer_event {
println!("Balance transfer success: value: {:?}", event.2);
} else {
println!("Failed to find Balances::Transfer Event");
}
}

Ok(())
}

/// If we need more visibility into the state of the transaction, we can also ditch
/// `wait_for_finalized` entirely and stream the transaction progress events, handling
/// them more manually.
async fn handle_transfer_events() -> Result<(), Box<dyn std::error::Error>> {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id().into();

let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<polkadot::DefaultConfig>>();

let mut balance_transfer_progress = api
.tx()
.balances()
.transfer(dest, 10_000)
.sign_and_submit_then_watch(&signer)
.await?;

while let Some(ev) = balance_transfer_progress.next().await? {
use subxt::TransactionStatus::*;

// Made it into a block, but not finalized.
if let InBlock(details) = ev {
println!(
"Transaction {:?} made it into block {:?}",
details.extrinsic_hash(),
details.block_hash()
);

let events = details.wait_for_success().await?;
let transfer_event =
events.find_first_event::<polkadot::balances::events::Transfer>()?;

if let Some(event) = transfer_event {
println!(
"Balance transfer is now in block (but not finalized): value: {:?}",
event.2
);
} else {
println!("Failed to find Balances::Transfer Event");
}
}
// Finalized!
else if let Finalized(details) = ev {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wasn't aware of this syntax but I prefer matching regardless :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aah, I kept flicking between a match and if let here, but since I only wanted to check for two states I thoughtI'd save a level of indentation with if let :)

println!(
"Transaction {:?} is finalized in block {:?}",
details.extrinsic_hash(),
details.block_hash()
);

let events = details.wait_for_success().await?;
let transfer_event =
events.find_first_event::<polkadot::balances::events::Transfer>()?;

if let Some(event) = transfer_event {
println!("Balance transfer success: value: {:?}", event.2);
} else {
println!("Failed to find Balances::Transfer Event");
}
}
// Report other statuses we see.
else {
println!("Current transaction status: {:?}", ev);
}
}

Ok(())
}
8 changes: 5 additions & 3 deletions src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,15 +414,17 @@ impl<T: Config> TransactionEvents<T> {
&self.events
}

/// Find all of the events matching the event type provided as a generic parameter.
/// Find all of the events matching the event type provided as a generic parameter. This
/// will return an error if a matching event is found but cannot be properly decoded.
pub fn find_events<E: crate::Event>(&self) -> Result<Vec<E>, Error> {
self.events
.iter()
.filter_map(|e| e.as_event::<E>().map_err(Into::into).transpose())
.collect()
}

/// Find the first event that matches the event type provided as a generic parameter.
/// Find the first event that matches the event type provided as a generic parameter. This
/// will return an error if a matching event is found but cannot be properly decoded.
///
/// Use [`TransactionEvents::find_events`], or iterate over [`TransactionEvents`] yourself
/// if you'd like to handle multiple events of the same type.
Expand All @@ -436,7 +438,7 @@ impl<T: Config> TransactionEvents<T> {
}

/// Find an event. Returns true if it was found.
pub fn has_event<E: crate::Event>(self) -> Result<bool, Error> {
pub fn has_event<E: crate::Event>(&self) -> Result<bool, Error> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Ok(self.find_first_event::<E>()?.is_some())
}
}
Expand Down