mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-16 17:51:10 +00:00
Extend the new api.blocks() to be the primary way to subscribe and fetch blocks/extrinsics/events (#691)
* First pass adding functions to get blocks and extrinsics * cargo fmt and cache block events * prefix block hash with 0x * pin streams for better ergonomics and add an example of subscribing to blocks * remove unused var * standardise on _all, _best and _finalized for different block header subs * WIP center subscribing around blocks * Remove the event filtering/subscribing stuff * clippy * we need tokio, silly clippy * add extrinsic_index() call * Update subxt/src/blocks/block_types.rs Co-authored-by: Andrew Jones <ascjones@gmail.com> Co-authored-by: Andrew Jones <ascjones@gmail.com>
This commit is contained in:
@@ -1,208 +0,0 @@
|
||||
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! Subscribing to events.
|
||||
|
||||
use crate::{
|
||||
client::OnlineClientT,
|
||||
error::Error,
|
||||
events::EventsClient,
|
||||
Config,
|
||||
};
|
||||
use derivative::Derivative;
|
||||
use futures::{
|
||||
stream::BoxStream,
|
||||
Future,
|
||||
FutureExt,
|
||||
Stream,
|
||||
StreamExt,
|
||||
};
|
||||
use sp_runtime::traits::Header;
|
||||
use std::{
|
||||
marker::Unpin,
|
||||
task::Poll,
|
||||
};
|
||||
|
||||
pub use super::{
|
||||
EventDetails,
|
||||
EventFilter,
|
||||
Events,
|
||||
FilterEvents,
|
||||
};
|
||||
|
||||
/// A Subscription. This forms a part of the `EventSubscription` type handed back
|
||||
/// in codegen from `subscribe_finalized`, and is exposed to be used in codegen.
|
||||
#[doc(hidden)]
|
||||
pub type FinalizedEventSub<Header> = BoxStream<'static, Result<Header, Error>>;
|
||||
|
||||
/// A Subscription. This forms a part of the `EventSubscription` type handed back
|
||||
/// in codegen from `subscribe`, and is exposed to be used in codegen.
|
||||
#[doc(hidden)]
|
||||
pub type EventSub<Item> = BoxStream<'static, Result<Item, Error>>;
|
||||
|
||||
/// A subscription to events that implements [`Stream`], and returns [`Events`] objects for each block.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug(bound = "Sub: std::fmt::Debug, Client: std::fmt::Debug"))]
|
||||
pub struct EventSubscription<T: Config, Client, Sub> {
|
||||
finished: bool,
|
||||
client: Client,
|
||||
block_header_subscription: Sub,
|
||||
#[derivative(Debug = "ignore")]
|
||||
at: Option<std::pin::Pin<Box<dyn Future<Output = Result<Events<T>, Error>> + Send>>>,
|
||||
}
|
||||
|
||||
impl<T: Config, Client, Sub, E: Into<Error>> EventSubscription<T, Client, Sub>
|
||||
where
|
||||
Sub: Stream<Item = Result<T::Header, E>> + Unpin,
|
||||
{
|
||||
/// Create a new [`EventSubscription`] from a client and a subscription
|
||||
/// which returns block headers.
|
||||
pub fn new(client: Client, block_header_subscription: Sub) -> Self {
|
||||
EventSubscription {
|
||||
finished: false,
|
||||
client,
|
||||
block_header_subscription,
|
||||
at: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return only specific events matching the tuple of 1 or more event
|
||||
/// types that has been provided as the `Filter` type parameter.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use futures::StreamExt;
|
||||
/// use subxt::{OnlineClient, PolkadotConfig};
|
||||
///
|
||||
/// #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
/// pub mod polkadot {}
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
|
||||
///
|
||||
/// let mut events = api
|
||||
/// .events()
|
||||
/// .subscribe()
|
||||
/// .await
|
||||
/// .unwrap()
|
||||
/// .filter_events::<(
|
||||
/// polkadot::balances::events::Transfer,
|
||||
/// polkadot::balances::events::Deposit
|
||||
/// )>();
|
||||
///
|
||||
/// while let Some(ev) = events.next().await {
|
||||
/// let event_details = ev.unwrap();
|
||||
/// match event_details.event {
|
||||
/// (Some(transfer), None) => println!("Balance transfer event: {transfer:?}"),
|
||||
/// (None, Some(deposit)) => println!("Balance deposit event: {deposit:?}"),
|
||||
/// _ => unreachable!()
|
||||
/// }
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn filter_events<Filter: EventFilter>(
|
||||
self,
|
||||
) -> FilterEvents<'static, Self, T, Filter> {
|
||||
FilterEvents::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config, Client, Sub: Unpin> Unpin for EventSubscription<T, Client, Sub> {}
|
||||
|
||||
// We want `EventSubscription` to implement Stream. The below implementation is the rather verbose
|
||||
// way to roughly implement the following function:
|
||||
//
|
||||
// ```
|
||||
// fn subscribe_events<T: Config, Evs: Decode>(client: &'_ Client<T>, block_sub: Subscription<T::Header>) -> impl Stream<Item=Result<Events<'_, T, Evs>, Error>> + '_ {
|
||||
// use futures::StreamExt;
|
||||
// block_sub.then(move |block_header_res| async move {
|
||||
// use sp_runtime::traits::Header;
|
||||
// let block_header = block_header_res?;
|
||||
// let block_hash = block_header.hash();
|
||||
// at(client, block_hash).await
|
||||
// })
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// The advantage of this manual implementation is that we have a named type that we (and others)
|
||||
// can derive things on, store away, alias etc.
|
||||
impl<T, Client, Sub, E> Stream for EventSubscription<T, Client, Sub>
|
||||
where
|
||||
T: Config,
|
||||
Client: OnlineClientT<T>,
|
||||
Sub: Stream<Item = Result<T::Header, E>> + Unpin,
|
||||
E: Into<Error>,
|
||||
{
|
||||
type Item = Result<Events<T>, Error>;
|
||||
|
||||
fn poll_next(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
// We are finished; return None.
|
||||
if self.finished {
|
||||
return Poll::Ready(None)
|
||||
}
|
||||
|
||||
// If there isn't an `at` function yet that's busy resolving a block hash into
|
||||
// some event details, then poll the block header subscription to get one.
|
||||
if self.at.is_none() {
|
||||
match futures::ready!(self.block_header_subscription.poll_next_unpin(cx)) {
|
||||
None => {
|
||||
self.finished = true;
|
||||
return Poll::Ready(None)
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
self.finished = true;
|
||||
return Poll::Ready(Some(Err(e.into())))
|
||||
}
|
||||
Some(Ok(block_header)) => {
|
||||
// Note [jsdw]: We may be able to get rid of the per-item allocation
|
||||
// with https://github.com/oblique/reusable-box-future.
|
||||
let at = EventsClient::new(self.client.clone())
|
||||
.at(Some(block_header.hash()));
|
||||
self.at = Some(Box::pin(at));
|
||||
// Continue, so that we poll this function future we've just created.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, there will be an `at` function stored. Unwrap it and poll it to
|
||||
// completion to get our events, throwing it away as soon as it is ready.
|
||||
let at_fn = self
|
||||
.at
|
||||
.as_mut()
|
||||
.expect("'at' function should have been set above'");
|
||||
let events = futures::ready!(at_fn.poll_unpin(cx));
|
||||
self.at = None;
|
||||
Poll::Ready(Some(events))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
// Ensure `EventSubscription` can be sent; only actually a compile-time check.
|
||||
#[allow(unused)]
|
||||
fn check_sendability() {
|
||||
fn assert_send<T: Send>() {}
|
||||
assert_send::<
|
||||
EventSubscription<
|
||||
crate::SubstrateConfig,
|
||||
(),
|
||||
EventSub<<crate::SubstrateConfig as Config>::Header>,
|
||||
>,
|
||||
>();
|
||||
assert_send::<
|
||||
EventSubscription<
|
||||
crate::SubstrateConfig,
|
||||
(),
|
||||
FinalizedEventSub<<crate::SubstrateConfig as Config>::Header>,
|
||||
>,
|
||||
>();
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,7 @@
|
||||
use crate::{
|
||||
client::OnlineClientT,
|
||||
error::Error,
|
||||
events::{
|
||||
EventSub,
|
||||
EventSubscription,
|
||||
Events,
|
||||
FinalizedEventSub,
|
||||
},
|
||||
events::Events,
|
||||
Config,
|
||||
};
|
||||
use derivative::Derivative;
|
||||
@@ -44,6 +39,12 @@ where
|
||||
Client: OnlineClientT<T>,
|
||||
{
|
||||
/// Obtain events at some block hash.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// This call only supports blocks produced since the most recent
|
||||
/// runtime upgrade. You can attempt to retrieve events from older blocks,
|
||||
/// but may run into errors attempting to work with them.
|
||||
pub fn at(
|
||||
&self,
|
||||
block_hash: Option<T::Hash>,
|
||||
@@ -51,122 +52,30 @@ where
|
||||
// Clone and pass the client in like this so that we can explicitly
|
||||
// return a Future that's Send + 'static, rather than tied to &self.
|
||||
let client = self.client.clone();
|
||||
async move { at(client, block_hash).await }
|
||||
}
|
||||
async move {
|
||||
// If block hash is not provided, get the hash
|
||||
// for the latest block and use that.
|
||||
let block_hash = match block_hash {
|
||||
Some(hash) => hash,
|
||||
None => {
|
||||
client
|
||||
.rpc()
|
||||
.block_hash(None)
|
||||
.await?
|
||||
.expect("didn't pass a block number; qed")
|
||||
}
|
||||
};
|
||||
|
||||
/// Subscribe to all events from blocks.
|
||||
///
|
||||
/// **Note:** these blocks haven't necessarily been finalised yet; prefer
|
||||
/// [`EventsClient::subscribe_finalized()`] if that is important.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use futures::StreamExt;
|
||||
/// use subxt::{ OnlineClient, PolkadotConfig };
|
||||
///
|
||||
/// let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
|
||||
///
|
||||
/// let mut events = api.events().subscribe().await.unwrap();
|
||||
///
|
||||
/// while let Some(ev) = events.next().await {
|
||||
/// // Obtain all events from this block.
|
||||
/// let ev = ev.unwrap();
|
||||
/// // Print block hash.
|
||||
/// println!("Event at block hash {:?}", ev.block_hash());
|
||||
/// // Iterate over all events.
|
||||
/// let mut iter = ev.iter();
|
||||
/// while let Some(event_details) = iter.next() {
|
||||
/// println!("Event details {:?}", event_details);
|
||||
/// }
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn subscribe(
|
||||
&self,
|
||||
) -> impl Future<
|
||||
Output = Result<EventSubscription<T, Client, EventSub<T::Header>>, Error>,
|
||||
> + Send
|
||||
+ 'static
|
||||
where
|
||||
Client: Send + Sync + 'static,
|
||||
{
|
||||
let client = self.client.clone();
|
||||
async move { subscribe(client).await }
|
||||
}
|
||||
|
||||
/// Subscribe to events from finalized blocks. See [`EventsClient::subscribe()`] for details.
|
||||
pub fn subscribe_finalized(
|
||||
&self,
|
||||
) -> impl Future<
|
||||
Output = Result<
|
||||
EventSubscription<T, Client, FinalizedEventSub<T::Header>>,
|
||||
Error,
|
||||
>,
|
||||
> + Send
|
||||
+ 'static
|
||||
where
|
||||
Client: Send + Sync + 'static,
|
||||
{
|
||||
let client = self.client.clone();
|
||||
async move { subscribe_finalized(client).await }
|
||||
}
|
||||
}
|
||||
|
||||
async fn at<T, Client>(
|
||||
client: Client,
|
||||
block_hash: Option<T::Hash>,
|
||||
) -> Result<Events<T>, Error>
|
||||
where
|
||||
T: Config,
|
||||
Client: OnlineClientT<T>,
|
||||
{
|
||||
// If block hash is not provided, get the hash
|
||||
// for the latest block and use that.
|
||||
let block_hash = match block_hash {
|
||||
Some(hash) => hash,
|
||||
None => {
|
||||
client
|
||||
let event_bytes = client
|
||||
.rpc()
|
||||
.block_hash(None)
|
||||
.storage(&*system_events_key().0, Some(block_hash))
|
||||
.await?
|
||||
.expect("didn't pass a block number; qed")
|
||||
.map(|e| e.0)
|
||||
.unwrap_or_else(Vec::new);
|
||||
|
||||
Ok(Events::new(client.metadata(), block_hash, event_bytes))
|
||||
}
|
||||
};
|
||||
|
||||
let event_bytes = client
|
||||
.rpc()
|
||||
.storage(&*system_events_key().0, Some(block_hash))
|
||||
.await?
|
||||
.map(|e| e.0)
|
||||
.unwrap_or_else(Vec::new);
|
||||
|
||||
Ok(Events::new(client.metadata(), block_hash, event_bytes))
|
||||
}
|
||||
|
||||
async fn subscribe<T, Client>(
|
||||
client: Client,
|
||||
) -> Result<EventSubscription<T, Client, EventSub<T::Header>>, Error>
|
||||
where
|
||||
T: Config,
|
||||
Client: OnlineClientT<T>,
|
||||
{
|
||||
let block_subscription = client.blocks().subscribe_headers().await?;
|
||||
Ok(EventSubscription::new(client, Box::pin(block_subscription)))
|
||||
}
|
||||
|
||||
/// Subscribe to events from finalized blocks.
|
||||
async fn subscribe_finalized<T, Client>(
|
||||
client: Client,
|
||||
) -> Result<EventSubscription<T, Client, FinalizedEventSub<T::Header>>, Error>
|
||||
where
|
||||
T: Config,
|
||||
Client: OnlineClientT<T>,
|
||||
{
|
||||
let block_subscription = client.blocks().subscribe_finalized_headers().await?;
|
||||
Ok(EventSubscription::new(client, Box::pin(block_subscription)))
|
||||
}
|
||||
}
|
||||
|
||||
// The storage key needed to access events.
|
||||
|
||||
@@ -25,7 +25,7 @@ use std::sync::Arc;
|
||||
/// A collection of events obtained from a block, bundled with the necessary
|
||||
/// information needed to decode and iterate over them.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug(bound = ""))]
|
||||
#[derivative(Debug(bound = ""), Clone(bound = ""))]
|
||||
pub struct Events<T: Config> {
|
||||
metadata: Metadata,
|
||||
block_hash: T::Hash,
|
||||
@@ -83,6 +83,8 @@ impl<T: Config> Events<T> {
|
||||
/// Iterate over all of the events, using metadata to dynamically
|
||||
/// decode them as we go, and returning the raw bytes and other associated
|
||||
/// details. If an error occurs, all subsequent iterations return `None`.
|
||||
// Dev note: The returned iterator is 'static + Send so that we can box it up and make
|
||||
// use of it with our `FilterEvents` stuff.
|
||||
pub fn iter(
|
||||
&self,
|
||||
) -> impl Iterator<Item = Result<EventDetails, Error>> + Send + Sync + 'static {
|
||||
|
||||
@@ -1,403 +0,0 @@
|
||||
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! Filtering individual events from subscriptions.
|
||||
|
||||
use super::{
|
||||
Events,
|
||||
Phase,
|
||||
StaticEvent,
|
||||
};
|
||||
use crate::{
|
||||
Config,
|
||||
Error,
|
||||
};
|
||||
use futures::{
|
||||
Stream,
|
||||
StreamExt,
|
||||
};
|
||||
use std::{
|
||||
marker::Unpin,
|
||||
task::Poll,
|
||||
};
|
||||
|
||||
/// A stream which filters events based on the `Filter` param provided.
|
||||
/// If `Filter` is a 1-tuple of a single `Event` type, it will return every
|
||||
/// instance of that event as it's found. If `filter` is ` tuple of multiple
|
||||
/// `Event` types, it will return a corresponding tuple of `Option`s, where
|
||||
/// exactly one of these will be `Some(event)` each iteration.
|
||||
pub struct FilterEvents<'a, Sub: 'a, T: Config, Filter: EventFilter> {
|
||||
// A subscription; in order for the Stream impl to apply, this will
|
||||
// impl `Stream<Item = Result<Events<'a, T, Evs>, Error>> + Unpin + 'a`.
|
||||
sub: Sub,
|
||||
// Each time we get Events from our subscription, they are stored here
|
||||
// and iterated through in future stream iterations until exhausted.
|
||||
events: Option<
|
||||
Box<
|
||||
dyn Iterator<
|
||||
Item = Result<
|
||||
FilteredEventDetails<T::Hash, Filter::ReturnType>,
|
||||
Error,
|
||||
>,
|
||||
> + Send
|
||||
+ 'a,
|
||||
>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl<'a, Sub: 'a, T: Config, Filter: EventFilter> Unpin
|
||||
for FilterEvents<'a, Sub, T, Filter>
|
||||
{
|
||||
}
|
||||
|
||||
impl<'a, Sub: 'a, T: Config, Filter: EventFilter> FilterEvents<'a, Sub, T, Filter> {
|
||||
pub(crate) fn new(sub: Sub) -> Self {
|
||||
Self { sub, events: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Sub, T, Filter> Stream for FilterEvents<'a, Sub, T, Filter>
|
||||
where
|
||||
Sub: Stream<Item = Result<Events<T>, Error>> + Unpin + 'a,
|
||||
T: Config,
|
||||
Filter: EventFilter,
|
||||
{
|
||||
type Item = Result<FilteredEventDetails<T::Hash, Filter::ReturnType>, Error>;
|
||||
fn poll_next(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
loop {
|
||||
// Drain the current events we're iterating over first:
|
||||
if let Some(events_iter) = self.events.as_mut() {
|
||||
match events_iter.next() {
|
||||
Some(res) => return Poll::Ready(Some(res)),
|
||||
None => {
|
||||
self.events = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for new events to come in:
|
||||
match futures::ready!(self.sub.poll_next_unpin(cx)) {
|
||||
None => return Poll::Ready(None),
|
||||
Some(Err(e)) => return Poll::Ready(Some(Err(e))),
|
||||
Some(Ok(events)) => {
|
||||
self.events = Some(Filter::filter(events));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This is returned from the [`FilterEvents`] impl of [`Stream`]. It contains
|
||||
/// some type representing an event we've filtered on, along with couple of additional
|
||||
/// pieces of information about that event.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct FilteredEventDetails<BlockHash, Evs> {
|
||||
/// During which [`Phase`] was the event produced?
|
||||
pub phase: Phase,
|
||||
/// Hash of the block that this event came from.
|
||||
pub block_hash: BlockHash,
|
||||
/// A type containing an event that we've filtered on.
|
||||
/// Depending on the filter type, this may be a tuple
|
||||
/// or a single event.
|
||||
pub event: Evs,
|
||||
}
|
||||
|
||||
/// This trait is implemented for tuples of Event types; any such tuple (up to size 8) can be
|
||||
/// used to filter an event subscription to return only the specified events.
|
||||
pub trait EventFilter: private::Sealed {
|
||||
/// The type we'll be handed back from filtering.
|
||||
type ReturnType;
|
||||
/// Filter the events based on the type implementing this trait.
|
||||
fn filter<T: Config>(
|
||||
events: Events<T>,
|
||||
) -> Box<
|
||||
dyn Iterator<
|
||||
Item = Result<FilteredEventDetails<T::Hash, Self::ReturnType>, Error>,
|
||||
> + Send,
|
||||
>;
|
||||
}
|
||||
|
||||
// Prevent userspace implementations of the above trait; the interface is not considered stable
|
||||
// and is not a particularly nice API to work with (particularly because it involves boxing, which
|
||||
// would be nice to get rid of eventually).
|
||||
pub(crate) mod private {
|
||||
pub trait Sealed {}
|
||||
}
|
||||
|
||||
// A special case impl for searching for a tuple of exactly one event (in this case, we don't
|
||||
// need to return an `(Option<Event>,)`; we can just return `Event`.
|
||||
impl<Ev: StaticEvent> private::Sealed for (Ev,) {}
|
||||
impl<Ev: StaticEvent> EventFilter for (Ev,) {
|
||||
type ReturnType = Ev;
|
||||
fn filter<T: Config>(
|
||||
events: Events<T>,
|
||||
) -> Box<dyn Iterator<Item = Result<FilteredEventDetails<T::Hash, Ev>, Error>> + Send>
|
||||
{
|
||||
let block_hash = events.block_hash();
|
||||
let mut iter = events.iter();
|
||||
Box::new(std::iter::from_fn(move || {
|
||||
for ev in iter.by_ref() {
|
||||
// Forward any error immediately:
|
||||
let raw_event = match ev {
|
||||
Ok(ev) => ev,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
// Try decoding each type until we hit a match or an error:
|
||||
let ev = raw_event.as_event::<Ev>();
|
||||
if let Ok(Some(event)) = ev {
|
||||
// We found a match; return our tuple.
|
||||
return Some(Ok(FilteredEventDetails {
|
||||
phase: raw_event.phase(),
|
||||
block_hash,
|
||||
event,
|
||||
}))
|
||||
}
|
||||
if let Err(e) = ev {
|
||||
// We hit an error. Return it.
|
||||
return Some(Err(e.into()))
|
||||
}
|
||||
}
|
||||
None
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// A generalised impl for tuples of sizes greater than 1:
|
||||
macro_rules! impl_event_filter {
|
||||
($($ty:ident $idx:tt),+) => {
|
||||
impl <$($ty: StaticEvent),+> private::Sealed for ( $($ty,)+ ) {}
|
||||
impl <$($ty: StaticEvent),+> EventFilter for ( $($ty,)+ ) {
|
||||
type ReturnType = ( $(Option<$ty>,)+ );
|
||||
fn filter<T: Config>(
|
||||
events: Events<T>
|
||||
) -> Box<dyn Iterator<Item=Result<FilteredEventDetails<T::Hash,Self::ReturnType>, Error>> + Send> {
|
||||
let block_hash = events.block_hash();
|
||||
let mut iter = events.iter();
|
||||
Box::new(std::iter::from_fn(move || {
|
||||
let mut out: ( $(Option<$ty>,)+ ) = Default::default();
|
||||
for ev in iter.by_ref() {
|
||||
// Forward any error immediately:
|
||||
let raw_event = match ev {
|
||||
Ok(ev) => ev,
|
||||
Err(e) => return Some(Err(e))
|
||||
};
|
||||
// Try decoding each type until we hit a match or an error:
|
||||
$({
|
||||
let ev = raw_event.as_event::<$ty>();
|
||||
if let Ok(Some(ev)) = ev {
|
||||
// We found a match; return our tuple.
|
||||
out.$idx = Some(ev);
|
||||
return Some(Ok(FilteredEventDetails {
|
||||
phase: raw_event.phase(),
|
||||
block_hash,
|
||||
event: out
|
||||
}))
|
||||
}
|
||||
if let Err(e) = ev {
|
||||
// We hit an error. Return it.
|
||||
return Some(Err(e.into()))
|
||||
}
|
||||
})+
|
||||
}
|
||||
None
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_event_filter!(A 0, B 1);
|
||||
impl_event_filter!(A 0, B 1, C 2);
|
||||
impl_event_filter!(A 0, B 1, C 2, D 3);
|
||||
impl_event_filter!(A 0, B 1, C 2, D 3, E 4);
|
||||
impl_event_filter!(A 0, B 1, C 2, D 3, E 4, F 5);
|
||||
impl_event_filter!(A 0, B 1, C 2, D 3, E 4, F 5, G 6);
|
||||
impl_event_filter!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{
|
||||
super::events_type::test_utils::{
|
||||
event_record,
|
||||
events,
|
||||
metadata,
|
||||
},
|
||||
*,
|
||||
};
|
||||
use crate::{
|
||||
Config,
|
||||
Metadata,
|
||||
SubstrateConfig,
|
||||
};
|
||||
use codec::{
|
||||
Decode,
|
||||
Encode,
|
||||
};
|
||||
use futures::{
|
||||
stream,
|
||||
Stream,
|
||||
StreamExt,
|
||||
};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
// Some pretend events in a pallet
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
enum PalletEvents {
|
||||
A(EventA),
|
||||
B(EventB),
|
||||
C(EventC),
|
||||
}
|
||||
|
||||
// An event in our pallet that we can filter on.
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
struct EventA(u8);
|
||||
impl StaticEvent for EventA {
|
||||
const PALLET: &'static str = "Test";
|
||||
const EVENT: &'static str = "A";
|
||||
}
|
||||
|
||||
// An event in our pallet that we can filter on.
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
struct EventB(bool);
|
||||
impl StaticEvent for EventB {
|
||||
const PALLET: &'static str = "Test";
|
||||
const EVENT: &'static str = "B";
|
||||
}
|
||||
|
||||
// An event in our pallet that we can filter on.
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
struct EventC(u8, bool);
|
||||
impl StaticEvent for EventC {
|
||||
const PALLET: &'static str = "Test";
|
||||
const EVENT: &'static str = "C";
|
||||
}
|
||||
|
||||
// A stream of fake events for us to try filtering on.
|
||||
fn events_stream(
|
||||
metadata: Metadata,
|
||||
) -> impl Stream<Item = Result<Events<SubstrateConfig>, Error>> {
|
||||
stream::iter(vec![
|
||||
events::<PalletEvents>(
|
||||
metadata.clone(),
|
||||
vec![
|
||||
event_record(Phase::Initialization, PalletEvents::A(EventA(1))),
|
||||
event_record(Phase::ApplyExtrinsic(0), PalletEvents::B(EventB(true))),
|
||||
event_record(Phase::Finalization, PalletEvents::A(EventA(2))),
|
||||
],
|
||||
),
|
||||
events::<PalletEvents>(
|
||||
metadata.clone(),
|
||||
vec![event_record(
|
||||
Phase::ApplyExtrinsic(1),
|
||||
PalletEvents::B(EventB(false)),
|
||||
)],
|
||||
),
|
||||
events::<PalletEvents>(
|
||||
metadata,
|
||||
vec![
|
||||
event_record(Phase::ApplyExtrinsic(2), PalletEvents::B(EventB(true))),
|
||||
event_record(Phase::ApplyExtrinsic(3), PalletEvents::A(EventA(3))),
|
||||
],
|
||||
),
|
||||
])
|
||||
.map(Ok::<_, Error>)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn filter_one_event_from_stream() {
|
||||
let metadata = metadata::<PalletEvents>();
|
||||
|
||||
// Filter out fake event stream to select events matching `EventA` only.
|
||||
let actual: Vec<_> =
|
||||
FilterEvents::<_, SubstrateConfig, (EventA,)>::new(events_stream(metadata))
|
||||
.map(|e| e.unwrap())
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
let expected = vec![
|
||||
FilteredEventDetails {
|
||||
phase: Phase::Initialization,
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event: EventA(1),
|
||||
},
|
||||
FilteredEventDetails {
|
||||
phase: Phase::Finalization,
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event: EventA(2),
|
||||
},
|
||||
FilteredEventDetails {
|
||||
phase: Phase::ApplyExtrinsic(3),
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event: EventA(3),
|
||||
},
|
||||
];
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn filter_some_events_from_stream() {
|
||||
let metadata = metadata::<PalletEvents>();
|
||||
|
||||
// Filter out fake event stream to select events matching `EventA` or `EventB`.
|
||||
let actual: Vec<_> = FilterEvents::<_, SubstrateConfig, (EventA, EventB)>::new(
|
||||
events_stream(metadata),
|
||||
)
|
||||
.map(|e| e.unwrap())
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
let expected = vec![
|
||||
FilteredEventDetails {
|
||||
phase: Phase::Initialization,
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event: (Some(EventA(1)), None),
|
||||
},
|
||||
FilteredEventDetails {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event: (None, Some(EventB(true))),
|
||||
},
|
||||
FilteredEventDetails {
|
||||
phase: Phase::Finalization,
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event: (Some(EventA(2)), None),
|
||||
},
|
||||
FilteredEventDetails {
|
||||
phase: Phase::ApplyExtrinsic(1),
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event: (None, Some(EventB(false))),
|
||||
},
|
||||
FilteredEventDetails {
|
||||
phase: Phase::ApplyExtrinsic(2),
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event: (None, Some(EventB(true))),
|
||||
},
|
||||
FilteredEventDetails {
|
||||
phase: Phase::ApplyExtrinsic(3),
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event: (Some(EventA(3)), None),
|
||||
},
|
||||
];
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn filter_no_events_from_stream() {
|
||||
let metadata = metadata::<PalletEvents>();
|
||||
|
||||
// Filter out fake event stream to select events matching `EventC` (none exist).
|
||||
let actual: Vec<_> =
|
||||
FilterEvents::<_, SubstrateConfig, (EventC,)>::new(events_stream(metadata))
|
||||
.map(|e| e.unwrap())
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
assert_eq!(actual, vec![]);
|
||||
}
|
||||
}
|
||||
@@ -6,26 +6,14 @@
|
||||
//! The two main entry points into events are [`crate::OnlineClient::events()`]
|
||||
//! and calls like [crate::tx::TxProgress::wait_for_finalized_success()].
|
||||
|
||||
mod event_subscription;
|
||||
mod events_client;
|
||||
mod events_type;
|
||||
mod filter_events;
|
||||
|
||||
pub use event_subscription::{
|
||||
EventSub,
|
||||
EventSubscription,
|
||||
FinalizedEventSub,
|
||||
};
|
||||
pub use events_client::EventsClient;
|
||||
pub use events_type::{
|
||||
EventDetails,
|
||||
Events,
|
||||
};
|
||||
pub use filter_events::{
|
||||
EventFilter,
|
||||
FilterEvents,
|
||||
FilteredEventDetails,
|
||||
};
|
||||
|
||||
use codec::{
|
||||
Decode,
|
||||
|
||||
Reference in New Issue
Block a user