-
Notifications
You must be signed in to change notification settings - Fork 281
New Event Subscription API #442
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
Changes from 22 commits
18b95a4
18482d8
644b638
3443e6c
589d1a6
8627117
9c409a6
72e66a9
1273c7e
63a4e1a
9ac234f
9a40790
d9bc620
457cd6e
b85acf1
9d99ddb
ea71f74
5b1ed7b
221135c
4d0085d
1d202b3
a74c8ed
e20d171
aa96d8d
18ca3be
4056f29
ef96407
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| // Copyright 2019-2022 Parity Technologies (UK) Ltd. | ||
| // This file is part of 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 subxt. If not, see <http://www.gnu.org/licenses/>. | ||
|
|
||
| //! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.13-82616422d0-aarch64-macos. | ||
| //! | ||
| //! E.g. | ||
| //! ```bash | ||
| //! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.13/polkadot" --output /usr/local/bin/polkadot --location | ||
| //! polkadot --dev --tmp | ||
| //! ``` | ||
| use futures::StreamExt; | ||
| use sp_keyring::AccountKeyring; | ||
| use std::time::Duration; | ||
| use subxt::{ | ||
| ClientBuilder, | ||
| DefaultConfig, | ||
| DefaultExtra, | ||
| PairSigner, | ||
| }; | ||
|
|
||
| #[subxt::subxt(runtime_metadata_path = "examples/polkadot_metadata.scale")] | ||
| pub mod polkadot {} | ||
|
|
||
| /// Subscribe to all events, and then manually look through them and | ||
| /// pluck out the events that we care about. | ||
| #[async_std::main] | ||
| async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
| env_logger::init(); | ||
|
|
||
| // Subscribe to any events that occur: | ||
| let api = ClientBuilder::new() | ||
| .build() | ||
| .await? | ||
| .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>>(); | ||
| let mut event_sub = api.events().subscribe().await?; | ||
dvdplm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // While this subscription is active, balance transfers are made somewhere: | ||
| async_std::task::spawn(async { | ||
| let signer = PairSigner::new(AccountKeyring::Alice.pair()); | ||
| let api = ClientBuilder::new() | ||
| .build() | ||
| .await | ||
| .unwrap() | ||
| .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>>(); | ||
|
|
||
| let mut transfer_amount = 1_000_000_000; | ||
|
|
||
| // Make small balance transfers from Alice to Bob in a loop: | ||
| loop { | ||
| api.tx() | ||
| .balances() | ||
| .transfer(AccountKeyring::Bob.to_account_id().into(), transfer_amount) | ||
| .sign_and_submit(&signer) | ||
| .await | ||
| .unwrap(); | ||
|
|
||
| async_std::task::sleep(Duration::from_secs(10)).await; | ||
| transfer_amount += 100_000_000; | ||
| } | ||
| }); | ||
|
|
||
| // Our subscription will see the events emitted as a result of this: | ||
| while let Some(events) = event_sub.next().await { | ||
| let events = events?; | ||
dvdplm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| let block_hash = events.block_hash(); | ||
|
|
||
| // We can iterate, statically decoding all events if we want: | ||
| println!("All events in block {block_hash:?}:"); | ||
| println!(" Static event details:"); | ||
| for event in events.iter() { | ||
| let event = event?; | ||
| println!(" {event:?}"); | ||
| } | ||
|
|
||
| // Or we can dynamically decode events: | ||
| println!(" Dynamic event details: {block_hash:?}:"); | ||
dvdplm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| for event in events.iter_raw() { | ||
| let event = event?; | ||
| let is_balance_transfer = event | ||
| .as_event::<polkadot::balances::events::Transfer>()? | ||
| .is_some(); | ||
| let pallet = event.pallet; | ||
| let variant = event.variant; | ||
| println!( | ||
| " {pallet}::{variant} (is balance transfer? {is_balance_transfer})" | ||
| ); | ||
| } | ||
|
|
||
| // Or we can dynamically find the first transfer event, ignoring any others: | ||
| let transfer_event = | ||
| events.find_first_event::<polkadot::balances::events::Transfer>()?; | ||
|
|
||
| if let Some(ev) = transfer_event { | ||
| println!(" - Balance transfer success: value: {:?}", ev.amount); | ||
| } else { | ||
| println!(" - No balance transfer event found in this block"); | ||
| } | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| // Copyright 2019-2022 Parity Technologies (UK) Ltd. | ||
| // This file is part of 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 subxt. If not, see <http://www.gnu.org/licenses/>. | ||
|
|
||
| //! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.13-82616422d0-aarch64-macos. | ||
| //! | ||
| //! E.g. | ||
| //! ```bash | ||
| //! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.13/polkadot" --output /usr/local/bin/polkadot --location | ||
| //! polkadot --dev --tmp | ||
| //! ``` | ||
| use futures::{ | ||
| future, | ||
| stream, | ||
| StreamExt, | ||
| }; | ||
| use sp_keyring::AccountKeyring; | ||
| use std::time::Duration; | ||
| use subxt::{ | ||
| ClientBuilder, | ||
| DefaultConfig, | ||
| DefaultExtra, | ||
| PairSigner, | ||
| }; | ||
|
|
||
| #[subxt::subxt(runtime_metadata_path = "examples/polkadot_metadata.scale")] | ||
| pub mod polkadot {} | ||
|
|
||
| /// Subscribe to all events, and then manually look through them and | ||
| /// pluck out the events that we care about. | ||
| #[async_std::main] | ||
| async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
| env_logger::init(); | ||
|
|
||
| // Subscribe to any events that occur: | ||
| let api = ClientBuilder::new() | ||
| .build() | ||
| .await? | ||
| .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>>(); | ||
|
|
||
| // Subscribe to just balance transfer events, making use of `flat_map` and | ||
| // `filter_map` from the StreamExt trait to filter everything else out. | ||
| let mut transfer_events = api | ||
| .events() | ||
| .subscribe() | ||
| .await? | ||
| // Ignore errors returning events: | ||
| .filter_map(|events| future::ready(events.ok())) | ||
| // Map events to just those we care about: | ||
| .flat_map(|events| { | ||
| let transfer_events = events | ||
| .find::<polkadot::balances::events::Transfer>() | ||
| .collect::<Vec<_>>(); | ||
| stream::iter(transfer_events) | ||
| }); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if these two are common enough usage patterns that it would make sense to have them packaged up into a utility method of some sort. It would be nice to be able to do
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had that exact thought; it felt like it would be a nice API to add and I did ponder it, but I wasn’t sure enough on how people actually would use events to know whether to go ahead and add it or wait for a feature request to come in :)
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (I think one thing I started pondering was; how likely is it that people would care about plucking out more than one event type, and then, should I try to support that (harder to do) here to avoid people creating multiple subscriptions? In fact, a general optimisation would be to have only one shared subscription in the background for some of these things to make that more palettable...)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In most cases I would use such API since I only care about one event. However here is a case where I need two events: https://github.com/paritytech/cargo-contract/blob/0e9ffe78947f458c89c5ee0ef25234a146b797d9/src/cmd/extrinsics/instantiate.rs#L246. Maybe tuples of event types would be one idea?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tuples would be the ideal way to provide the type params to the function, but the return type would need to be more sum-type in nature as we'll get back just one of the possible types each iteration. I have a couple of thoughts, but I think it'd be good to merge this first and then I can add this functionality in a separate PR so that it can be given a little more thought!
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Absolutely! Defo not a job for this PR was just brainstorming here since it was brought up.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hehe, I suppose I was going to just add it if it was simple enough (ie the one-event variation)! Thanks for your thoughts; very useful :)) |
||
|
|
||
| // While this subscription is active, we imagine some balance transfers are made somewhere else: | ||
| async_std::task::spawn(async { | ||
| let signer = PairSigner::new(AccountKeyring::Alice.pair()); | ||
| let api = ClientBuilder::new() | ||
| .build() | ||
| .await | ||
| .unwrap() | ||
| .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>>(); | ||
|
|
||
| // Make small balance transfers from Alice to Bob in a loop: | ||
| loop { | ||
| api.tx() | ||
| .balances() | ||
| .transfer(AccountKeyring::Bob.to_account_id().into(), 1_000_000_000) | ||
| .sign_and_submit(&signer) | ||
| .await | ||
| .unwrap(); | ||
| async_std::task::sleep(Duration::from_secs(10)).await; | ||
| } | ||
| }); | ||
|
|
||
| // Our subscription will see all of the transfer events emitted as a result of this: | ||
| while let Some(transfer_event) = transfer_events.next().await { | ||
| println!("Balance transfer event: {transfer_event:?}"); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.