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:
James Wilson
2022-11-01 16:53:35 +01:00
committed by GitHub
parent 52d4762d13
commit 33a9ec91af
25 changed files with 646 additions and 1279 deletions
-208
View File
@@ -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>,
>,
>();
}
}
+27 -118
View File
@@ -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.
+3 -1
View File
@@ -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 {
-403
View File
@@ -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![]);
}
}
-12
View File
@@ -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,