mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-24 18:15:48 +00:00
Rework Subxt API to support offline and dynamic transactions (#593)
* WIP API changes * debug impls * Get main crate compiling with first round of changes * Some tidy up * Add WithExtrinsicParams, and have SubstrateConfig + PolkadotConfig, not DefaultConfig * move transaction into extrinsic folder * Add runtime updates back to OnlineClient * rework to be 'client first' to fit better with storage + events * add support for events to Client * tidy dupe trait bound * Wire storage into client, but need to remove static reliance * various tidy up and start stripping codegen to remove bits we dont need now * First pass updating calls and constants codegen * WIP storage client updates * First pass migrated runtime storage over to new format * pass over codegen to generate StorageAddresses and throw other stuff out * don't need a Call trait any more * shuffle things around a bit * Various proc_macro fixes to get 'cargo check' working * organise what's exposed from subxt * Get first example working; balance_transfer_with_params * get balance_transfer example compiling * get concurrent_storage_requests.rs example compiling * get fetch_all_accounts example compiling * get a bunch more of the examples compiling * almost get final example working; type mismatch to look into * wee tweaks * move StorageAddress to separate file * pass Defaultable/Iterable info to StorageAddress in codegen * fix storage validation ne, and partial run through example code * Remove static iteration and strip a generic param from everything * fix doc tests in subxt crate * update test utils and start fixing frame tests * fix frame staking tests * fix the rest of the test compile issues, Borrow on storage values * cargo fmt * remove extra logging during tests * Appease clippy and no more need for into_iter on events * cargo fmt * fix dryRun tests by waiting for blocks * wait for blocks instead of sleeping or other test hacks * cargo fmt * Fix doc links * Traitify StorageAddress * remove out-of-date doc comments * optimise decoding storage a little * cleanup tx stuff, trait for TxPayload, remove Err type param and decode at runtime * clippy fixes * fix doc links * fix doc example * constant address trait for consistency * fix a typo and remove EncodeWithMetadata stuff * Put EventDetails behind a proper interface and allow decoding into top level event, too * fix docs * tweak StorageAddress docs * re-export StorageAddress at root for consistency * fix clippy things * Add support for dynamic values * fix double encoding of storage map key after refactor * clippy fix * Fixes and add a dynamic usage example (needs new scale_value release) * bump scale_value version * cargo fmt * Tweak event bits * cargo fmt * Add a test and bump scale-value to 0.4.0 to support this * remove unnecessary vec from dynamic example * Various typo/grammar fixes Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> * Address PR nits * Undo accidental rename in changelog * Small PR nits/tidyups * fix tests; codegen change against latest substrate * tweak storage address util names * move error decoding to DecodeError and expose * impl some basic traits on the extrinsic param builder Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
This commit is contained in:
@@ -5,18 +5,14 @@
|
||||
//! Subscribing to events.
|
||||
|
||||
use crate::{
|
||||
error::BasicError,
|
||||
Client,
|
||||
client::OnlineClientT,
|
||||
error::Error,
|
||||
events::EventsClient,
|
||||
Config,
|
||||
};
|
||||
use codec::Decode;
|
||||
use derivative::Derivative;
|
||||
use futures::{
|
||||
future::Either,
|
||||
stream::{
|
||||
self,
|
||||
BoxStream,
|
||||
},
|
||||
stream::BoxStream,
|
||||
Future,
|
||||
FutureExt,
|
||||
Stream,
|
||||
@@ -30,112 +26,16 @@ use std::{
|
||||
};
|
||||
|
||||
pub use super::{
|
||||
at,
|
||||
EventDetails,
|
||||
EventFilter,
|
||||
Events,
|
||||
FilterEvents,
|
||||
RawEventDetails,
|
||||
};
|
||||
|
||||
/// Subscribe to events from blocks.
|
||||
///
|
||||
/// **Note:** these blocks haven't necessarily been finalised yet; prefer
|
||||
/// [`Events::subscribe_finalized()`] if that is important.
|
||||
///
|
||||
/// **Note:** This function is hidden from the documentation
|
||||
/// and is exposed only to be called via the codegen. It may
|
||||
/// break between minor releases.
|
||||
#[doc(hidden)]
|
||||
pub async fn subscribe<T: Config, Evs: Decode + 'static>(
|
||||
client: &Client<T>,
|
||||
) -> Result<EventSubscription<EventSub<T::Header>, T, Evs>, BasicError> {
|
||||
let block_subscription = client.rpc().subscribe_blocks().await?;
|
||||
Ok(EventSubscription::new(client, block_subscription))
|
||||
}
|
||||
|
||||
/// Subscribe to events from finalized blocks.
|
||||
///
|
||||
/// **Note:** This function is hidden from the documentation
|
||||
/// and is exposed only to be called via the codegen. It may
|
||||
/// break between minor releases.
|
||||
#[doc(hidden)]
|
||||
pub async fn subscribe_finalized<T: Config, Evs: Decode + 'static>(
|
||||
client: &Client<T>,
|
||||
) -> Result<EventSubscription<FinalizedEventSub<T::Header>, T, Evs>, BasicError> {
|
||||
// fetch the last finalised block details immediately, so that we'll get
|
||||
// events for each block after this one.
|
||||
let last_finalized_block_hash = client.rpc().finalized_head().await?;
|
||||
let last_finalized_block_number = client
|
||||
.rpc()
|
||||
.header(Some(last_finalized_block_hash))
|
||||
.await?
|
||||
.map(|h| (*h.number()).into());
|
||||
|
||||
// Fill in any gaps between the block above and the finalized blocks reported.
|
||||
let block_subscription = subscribe_to_block_headers_filling_in_gaps(
|
||||
client,
|
||||
last_finalized_block_number,
|
||||
client.rpc().subscribe_finalized_blocks().await?,
|
||||
);
|
||||
|
||||
Ok(EventSubscription::new(client, Box::pin(block_subscription)))
|
||||
}
|
||||
|
||||
/// Take a subscription that returns block headers, and if any block numbers are missed out
|
||||
/// betweem the block number provided and what's returned from the subscription, we fill in
|
||||
/// the gaps and get hold of all intermediate block headers.
|
||||
///
|
||||
/// **Note:** This is exposed so that we can run integration tests on it, but otherwise
|
||||
/// should not be used directly and may break between minor releases.
|
||||
#[doc(hidden)]
|
||||
pub fn subscribe_to_block_headers_filling_in_gaps<'a, S, E, T: Config>(
|
||||
client: &'a Client<T>,
|
||||
mut last_block_num: Option<u64>,
|
||||
sub: S,
|
||||
) -> impl Stream<Item = Result<T::Header, BasicError>> + Send + 'a
|
||||
where
|
||||
S: Stream<Item = Result<T::Header, E>> + Send + 'a,
|
||||
E: Into<BasicError> + Send + 'static,
|
||||
{
|
||||
sub.flat_map(move |s| {
|
||||
// Get the header, or return a stream containing just the error. Our EventSubscription
|
||||
// stream will return `None` as soon as it hits an error like this.
|
||||
let header = match s {
|
||||
Ok(header) => header,
|
||||
Err(e) => return Either::Left(stream::once(async { Err(e.into()) })),
|
||||
};
|
||||
|
||||
// We want all previous details up to, but not including this current block num.
|
||||
let end_block_num = (*header.number()).into();
|
||||
|
||||
// This is one after the last block we returned details for last time.
|
||||
let start_block_num = last_block_num.map(|n| n + 1).unwrap_or(end_block_num);
|
||||
|
||||
// Iterate over all of the previous blocks we need headers for, ignoring the current block
|
||||
// (which we already have the header info for):
|
||||
let previous_headers = stream::iter(start_block_num..end_block_num)
|
||||
.then(move |n| {
|
||||
async move {
|
||||
let hash = client.rpc().block_hash(Some(n.into())).await?;
|
||||
let header = client.rpc().header(hash).await?;
|
||||
Ok::<_, BasicError>(header)
|
||||
}
|
||||
})
|
||||
.filter_map(|h| async { h.transpose() });
|
||||
|
||||
// On the next iteration, we'll get details starting just after this end block.
|
||||
last_block_num = Some(end_block_num);
|
||||
|
||||
// Return a combination of any previous headers plus the new header.
|
||||
Either::Right(previous_headers.chain(stream::once(async { Ok(header) })))
|
||||
})
|
||||
}
|
||||
|
||||
/// A `jsonrpsee` 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<'a, Header> = BoxStream<'a, Result<Header, BasicError>>;
|
||||
pub type FinalizedEventSub<Header> = BoxStream<'static, Result<Header, Error>>;
|
||||
|
||||
/// A `jsonrpsee` Subscription. This forms a part of the `EventSubscription` type handed back
|
||||
/// in codegen from `subscribe`, and is exposed to be used in codegen.
|
||||
@@ -144,52 +44,80 @@ pub type EventSub<Item> = Subscription<Item>;
|
||||
|
||||
/// A subscription to events that implements [`Stream`], and returns [`Events`] objects for each block.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug(bound = "Sub: std::fmt::Debug"))]
|
||||
pub struct EventSubscription<'a, Sub, T: Config, Evs: 'static> {
|
||||
#[derivative(Debug(bound = "Sub: std::fmt::Debug, Client: std::fmt::Debug"))]
|
||||
pub struct EventSubscription<T: Config, Client, Sub> {
|
||||
finished: bool,
|
||||
client: &'a Client<T>,
|
||||
client: Client,
|
||||
block_header_subscription: Sub,
|
||||
#[derivative(Debug = "ignore")]
|
||||
at: Option<
|
||||
std::pin::Pin<
|
||||
Box<dyn Future<Output = Result<Events<T, Evs>, BasicError>> + Send + 'a>,
|
||||
>,
|
||||
>,
|
||||
_event_type: std::marker::PhantomData<Evs>,
|
||||
at: Option<std::pin::Pin<Box<dyn Future<Output = Result<Events<T>, Error>> + Send>>>,
|
||||
}
|
||||
|
||||
impl<'a, Sub, T: Config, Evs: Decode, E: Into<BasicError>>
|
||||
EventSubscription<'a, Sub, T, Evs>
|
||||
impl<T: Config, Client, Sub, E: Into<Error>> EventSubscription<T, Client, Sub>
|
||||
where
|
||||
Sub: Stream<Item = Result<T::Header, E>> + Unpin + 'a,
|
||||
Sub: Stream<Item = Result<T::Header, E>> + Unpin,
|
||||
{
|
||||
fn new(client: &'a Client<T>, block_header_subscription: Sub) -> Self {
|
||||
/// 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,
|
||||
_event_type: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return only specific events matching the tuple of 1 or more event
|
||||
/// types that has been provided as the `Filter` type parameter.
|
||||
pub fn filter_events<Filter: EventFilter>(self) -> FilterEvents<'a, Self, T, Filter> {
|
||||
///
|
||||
/// # 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<'a, T: Config, Sub: Unpin, Evs: Decode> Unpin
|
||||
for EventSubscription<'a, Sub, T, Evs>
|
||||
{
|
||||
}
|
||||
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>, BasicError>> + '_ {
|
||||
// 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;
|
||||
@@ -202,14 +130,14 @@ impl<'a, T: Config, Sub: Unpin, Evs: Decode> Unpin
|
||||
//
|
||||
// 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<'a, Sub, T, Evs, E> Stream for EventSubscription<'a, Sub, T, Evs>
|
||||
impl<T, Client, Sub, E> Stream for EventSubscription<T, Client, Sub>
|
||||
where
|
||||
T: Config,
|
||||
Evs: Decode,
|
||||
Sub: Stream<Item = Result<T::Header, E>> + Unpin + 'a,
|
||||
E: Into<BasicError>,
|
||||
Client: OnlineClientT<T>,
|
||||
Sub: Stream<Item = Result<T::Header, E>> + Unpin,
|
||||
E: Into<Error>,
|
||||
{
|
||||
type Item = Result<Events<T, Evs>, BasicError>;
|
||||
type Item = Result<Events<T>, Error>;
|
||||
|
||||
fn poll_next(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
@@ -235,7 +163,9 @@ where
|
||||
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.
|
||||
self.at = Some(Box::pin(at(self.client, block_header.hash())));
|
||||
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.
|
||||
}
|
||||
}
|
||||
@@ -263,16 +193,16 @@ mod test {
|
||||
fn assert_send<T: Send>() {}
|
||||
assert_send::<
|
||||
EventSubscription<
|
||||
EventSub<<crate::DefaultConfig as Config>::Header>,
|
||||
crate::DefaultConfig,
|
||||
crate::SubstrateConfig,
|
||||
(),
|
||||
EventSub<<crate::SubstrateConfig as Config>::Header>,
|
||||
>,
|
||||
>();
|
||||
assert_send::<
|
||||
EventSubscription<
|
||||
FinalizedEventSub<<crate::DefaultConfig as Config>::Header>,
|
||||
crate::DefaultConfig,
|
||||
crate::SubstrateConfig,
|
||||
(),
|
||||
FinalizedEventSub<<crate::SubstrateConfig as Config>::Header>,
|
||||
>,
|
||||
>();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
// 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.
|
||||
|
||||
use crate::{
|
||||
client::OnlineClientT,
|
||||
error::Error,
|
||||
events::{
|
||||
EventSub,
|
||||
EventSubscription,
|
||||
Events,
|
||||
FinalizedEventSub,
|
||||
},
|
||||
Config,
|
||||
};
|
||||
use derivative::Derivative;
|
||||
use futures::{
|
||||
future::Either,
|
||||
stream,
|
||||
Stream,
|
||||
StreamExt,
|
||||
};
|
||||
use sp_core::{
|
||||
storage::StorageKey,
|
||||
twox_128,
|
||||
};
|
||||
use sp_runtime::traits::Header;
|
||||
use std::future::Future;
|
||||
|
||||
/// A client for working with events.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Clone(bound = "Client: Clone"))]
|
||||
pub struct EventsClient<T, Client> {
|
||||
client: Client,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, Client> EventsClient<T, Client> {
|
||||
/// Create a new [`EventsClient`].
|
||||
pub fn new(client: Client) -> Self {
|
||||
Self {
|
||||
client,
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Client> EventsClient<T, Client>
|
||||
where
|
||||
T: Config,
|
||||
Client: OnlineClientT<T>,
|
||||
{
|
||||
/// Obtain events at some block hash.
|
||||
pub fn at(
|
||||
&self,
|
||||
block_hash: Option<T::Hash>,
|
||||
) -> impl Future<Output = Result<Events<T>, Error>> + Send + 'static {
|
||||
// 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 }
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
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
|
||||
.rpc()
|
||||
.block_hash(None)
|
||||
.await?
|
||||
.expect("didn't pass a block number; qed")
|
||||
}
|
||||
};
|
||||
|
||||
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.rpc().subscribe_blocks().await?;
|
||||
Ok(EventSubscription::new(client, 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>,
|
||||
{
|
||||
// fetch the last finalised block details immediately, so that we'll get
|
||||
// events for each block after this one.
|
||||
let last_finalized_block_hash = client.rpc().finalized_head().await?;
|
||||
let last_finalized_block_number = client
|
||||
.rpc()
|
||||
.header(Some(last_finalized_block_hash))
|
||||
.await?
|
||||
.map(|h| (*h.number()).into());
|
||||
|
||||
let sub = client.rpc().subscribe_finalized_blocks().await?;
|
||||
|
||||
// Fill in any gaps between the block above and the finalized blocks reported.
|
||||
let block_subscription = subscribe_to_block_headers_filling_in_gaps(
|
||||
client.clone(),
|
||||
last_finalized_block_number,
|
||||
sub,
|
||||
);
|
||||
|
||||
Ok(EventSubscription::new(client, Box::pin(block_subscription)))
|
||||
}
|
||||
|
||||
/// Note: This is exposed for testing but is not considered stable and may change
|
||||
/// without notice in a patch release.
|
||||
#[doc(hidden)]
|
||||
pub fn subscribe_to_block_headers_filling_in_gaps<T, Client, S, E>(
|
||||
client: Client,
|
||||
mut last_block_num: Option<u64>,
|
||||
sub: S,
|
||||
) -> impl Stream<Item = Result<T::Header, Error>> + Send
|
||||
where
|
||||
T: Config,
|
||||
Client: OnlineClientT<T> + Send + Sync,
|
||||
S: Stream<Item = Result<T::Header, E>> + Send,
|
||||
E: Into<Error> + Send + 'static,
|
||||
{
|
||||
sub.flat_map(move |s| {
|
||||
let client = client.clone();
|
||||
|
||||
// Get the header, or return a stream containing just the error. Our EventSubscription
|
||||
// stream will return `None` as soon as it hits an error like this.
|
||||
let header = match s {
|
||||
Ok(header) => header,
|
||||
Err(e) => return Either::Left(stream::once(async { Err(e.into()) })),
|
||||
};
|
||||
|
||||
// We want all previous details up to, but not including this current block num.
|
||||
let end_block_num = (*header.number()).into();
|
||||
|
||||
// This is one after the last block we returned details for last time.
|
||||
let start_block_num = last_block_num.map(|n| n + 1).unwrap_or(end_block_num);
|
||||
|
||||
// Iterate over all of the previous blocks we need headers for, ignoring the current block
|
||||
// (which we already have the header info for):
|
||||
let previous_headers = stream::iter(start_block_num..end_block_num)
|
||||
.then(move |n| {
|
||||
let client = client.clone();
|
||||
async move {
|
||||
let hash = client.rpc().block_hash(Some(n.into())).await?;
|
||||
let header = client.rpc().header(hash).await?;
|
||||
Ok::<_, Error>(header)
|
||||
}
|
||||
})
|
||||
.filter_map(|h| async { h.transpose() });
|
||||
|
||||
// On the next iteration, we'll get details starting just after this end block.
|
||||
last_block_num = Some(end_block_num);
|
||||
|
||||
// Return a combination of any previous headers plus the new header.
|
||||
Either::Right(previous_headers.chain(stream::once(async { Ok(header) })))
|
||||
})
|
||||
}
|
||||
|
||||
// The storage key needed to access events.
|
||||
fn system_events_key() -> StorageKey {
|
||||
let mut storage_key = twox_128(b"System").to_vec();
|
||||
storage_key.extend(twox_128(b"Events").to_vec());
|
||||
StorageKey(storage_key)
|
||||
}
|
||||
+208
-435
@@ -4,13 +4,15 @@
|
||||
|
||||
//! A representation of a block of events.
|
||||
|
||||
use crate::{
|
||||
error::BasicError,
|
||||
Client,
|
||||
Config,
|
||||
Event,
|
||||
Metadata,
|
||||
use super::{
|
||||
Phase,
|
||||
StaticEvent,
|
||||
};
|
||||
use crate::{
|
||||
dynamic::DecodedValue,
|
||||
error::Error,
|
||||
Config,
|
||||
Metadata,
|
||||
};
|
||||
use codec::{
|
||||
Compact,
|
||||
@@ -19,77 +21,48 @@ use codec::{
|
||||
Input,
|
||||
};
|
||||
use derivative::Derivative;
|
||||
use parking_lot::RwLock;
|
||||
use sp_core::{
|
||||
storage::StorageKey,
|
||||
twox_128,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Obtain events at some block hash. The generic parameter is what we
|
||||
/// will attempt to decode each event into if using [`Events::iter()`],
|
||||
/// and is expected to be the outermost event enum that contains all of
|
||||
/// the possible events across all pallets.
|
||||
///
|
||||
/// **Note:** This function is hidden from the documentation
|
||||
/// and is exposed only to be called via the codegen. Thus, prefer to use
|
||||
/// `api.events().at(block_hash)` over calling this directly.
|
||||
#[doc(hidden)]
|
||||
pub async fn at<T: Config, Evs: Decode>(
|
||||
client: &'_ Client<T>,
|
||||
block_hash: T::Hash,
|
||||
) -> Result<Events<T, Evs>, BasicError> {
|
||||
let mut event_bytes = client
|
||||
.rpc()
|
||||
.storage(&system_events_key(), Some(block_hash))
|
||||
.await?
|
||||
.map(|s| s.0)
|
||||
.unwrap_or_else(Vec::new);
|
||||
|
||||
// event_bytes is a SCALE encoded vector of events. So, pluck the
|
||||
// compact encoded length from the front, leaving the remaining bytes
|
||||
// for our iterating to decode.
|
||||
//
|
||||
// Note: if we get no bytes back, avoid an error reading vec length
|
||||
// and default to 0 events.
|
||||
let cursor = &mut &*event_bytes;
|
||||
let num_events = <Compact<u32>>::decode(cursor).unwrap_or(Compact(0)).0;
|
||||
let event_bytes_len = event_bytes.len();
|
||||
let remaining_len = cursor.len();
|
||||
event_bytes.drain(0..event_bytes_len - remaining_len);
|
||||
|
||||
Ok(Events {
|
||||
metadata: client.metadata(),
|
||||
block_hash,
|
||||
event_bytes,
|
||||
num_events,
|
||||
_event_type: std::marker::PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
// The storage key needed to access events.
|
||||
fn system_events_key() -> StorageKey {
|
||||
let mut storage_key = twox_128(b"System").to_vec();
|
||||
storage_key.extend(twox_128(b"Events").to_vec());
|
||||
StorageKey(storage_key)
|
||||
}
|
||||
|
||||
/// 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 = ""))]
|
||||
pub struct Events<T: Config, Evs> {
|
||||
metadata: Arc<RwLock<Metadata>>,
|
||||
pub struct Events<T: Config> {
|
||||
metadata: Metadata,
|
||||
block_hash: T::Hash,
|
||||
// Note; raw event bytes are prefixed with a Compact<u32> containing
|
||||
// the number of events to be decoded. We should have stripped that off
|
||||
// before storing the bytes here.
|
||||
event_bytes: Vec<u8>,
|
||||
event_bytes: Arc<[u8]>,
|
||||
num_events: u32,
|
||||
_event_type: std::marker::PhantomData<Evs>,
|
||||
}
|
||||
|
||||
impl<'a, T: Config, Evs: Decode> Events<T, Evs> {
|
||||
impl<T: Config> Events<T> {
|
||||
pub(crate) fn new(
|
||||
metadata: Metadata,
|
||||
block_hash: T::Hash,
|
||||
mut event_bytes: Vec<u8>,
|
||||
) -> Self {
|
||||
// event_bytes is a SCALE encoded vector of events. So, pluck the
|
||||
// compact encoded length from the front, leaving the remaining bytes
|
||||
// for our iterating to decode.
|
||||
//
|
||||
// Note: if we get no bytes back, avoid an error reading vec length
|
||||
// and default to 0 events.
|
||||
let cursor = &mut &*event_bytes;
|
||||
let num_events = <Compact<u32>>::decode(cursor).unwrap_or(Compact(0)).0;
|
||||
let event_bytes_len = event_bytes.len();
|
||||
let remaining_len = cursor.len();
|
||||
event_bytes.drain(0..event_bytes_len - remaining_len);
|
||||
|
||||
Self {
|
||||
metadata,
|
||||
block_hash,
|
||||
event_bytes: event_bytes.into(),
|
||||
num_events,
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of events.
|
||||
pub fn len(&self) -> u32 {
|
||||
self.num_events
|
||||
@@ -106,83 +79,23 @@ impl<'a, T: Config, Evs: Decode> Events<T, Evs> {
|
||||
self.block_hash
|
||||
}
|
||||
|
||||
/// Iterate over the events, statically decoding them as we go.
|
||||
/// If an event is encountered that cannot be statically decoded,
|
||||
/// a [`codec::Error`] will be returned.
|
||||
///
|
||||
/// If the generated code does not know about all of the pallets that exist
|
||||
/// in the runtime being targeted, it may not know about all of the
|
||||
/// events either, and so this method should be avoided in favout of [`Events::iter_raw()`],
|
||||
/// which uses runtime metadata to skip over unknown events.
|
||||
/// 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`.
|
||||
pub fn iter(
|
||||
&self,
|
||||
) -> impl Iterator<Item = Result<EventDetails<Evs>, BasicError>> + '_ {
|
||||
let event_bytes = &self.event_bytes;
|
||||
) -> impl Iterator<Item = Result<EventDetails, Error>> + Send + Sync + 'static {
|
||||
let event_bytes = self.event_bytes.clone();
|
||||
let num_events = self.num_events;
|
||||
|
||||
let metadata = self.metadata.clone();
|
||||
let mut pos = 0;
|
||||
let mut index = 0;
|
||||
std::iter::from_fn(move || {
|
||||
let cursor = &mut &event_bytes[pos..];
|
||||
let start_len = cursor.len();
|
||||
|
||||
if start_len == 0 || self.num_events == index {
|
||||
None
|
||||
} else {
|
||||
let mut decode_one_event = || -> Result<_, BasicError> {
|
||||
let phase = Phase::decode(cursor)?;
|
||||
let ev = Evs::decode(cursor)?;
|
||||
let _topics = Vec::<T::Hash>::decode(cursor)?;
|
||||
Ok((phase, ev))
|
||||
};
|
||||
match decode_one_event() {
|
||||
Ok((phase, event)) => {
|
||||
// Skip over decoded bytes in next iteration:
|
||||
pos += start_len - cursor.len();
|
||||
// Gather the event details before incrementing the index for the next iter.
|
||||
let res = Some(Ok(EventDetails {
|
||||
phase,
|
||||
index,
|
||||
event,
|
||||
}));
|
||||
index += 1;
|
||||
res
|
||||
}
|
||||
Err(e) => {
|
||||
// By setting the position to the "end" of the event bytes,
|
||||
// the cursor len will become 0 and the iterator will return `None`
|
||||
// from now on:
|
||||
pos = event_bytes.len();
|
||||
Some(Err(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
///
|
||||
/// This method is safe to use even if you do not statically know about
|
||||
/// all of the possible events; it splits events up using the metadata
|
||||
/// obtained at runtime, which does.
|
||||
pub fn iter_raw(
|
||||
&self,
|
||||
) -> impl Iterator<Item = Result<RawEventDetails, BasicError>> + '_ {
|
||||
let event_bytes = &self.event_bytes;
|
||||
|
||||
let metadata = {
|
||||
let metadata = self.metadata.read();
|
||||
metadata.clone()
|
||||
};
|
||||
|
||||
let mut pos = 0;
|
||||
let mut index = 0;
|
||||
std::iter::from_fn(move || {
|
||||
let cursor = &mut &event_bytes[pos..];
|
||||
let start_len = cursor.len();
|
||||
|
||||
if start_len == 0 || self.num_events == index {
|
||||
if start_len == 0 || num_events == index {
|
||||
None
|
||||
} else {
|
||||
match decode_raw_event_details::<T>(&metadata, index, cursor) {
|
||||
@@ -206,62 +119,11 @@ impl<'a, T: Config, Evs: Decode> Events<T, Evs> {
|
||||
})
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
///
|
||||
/// This method is safe to use even if you do not statically know about
|
||||
/// all of the possible events; it splits events up using the metadata
|
||||
/// obtained at runtime, which does.
|
||||
///
|
||||
/// Unlike [`Events::iter_raw()`] this consumes `self`, which can be useful
|
||||
/// if you need to store the iterator somewhere and avoid lifetime issues.
|
||||
pub fn into_iter_raw(
|
||||
self,
|
||||
) -> impl Iterator<Item = Result<RawEventDetails, BasicError>> + 'a {
|
||||
let mut pos = 0;
|
||||
let mut index = 0;
|
||||
let metadata = {
|
||||
let metadata = self.metadata.read();
|
||||
metadata.clone()
|
||||
};
|
||||
|
||||
std::iter::from_fn(move || {
|
||||
let cursor = &mut &self.event_bytes[pos..];
|
||||
let start_len = cursor.len();
|
||||
|
||||
if start_len == 0 || self.num_events == index {
|
||||
None
|
||||
} else {
|
||||
match decode_raw_event_details::<T>(&metadata, index, cursor) {
|
||||
Ok(raw_event) => {
|
||||
// Skip over decoded bytes in next iteration:
|
||||
pos += start_len - cursor.len();
|
||||
// Increment the index:
|
||||
index += 1;
|
||||
// Return the event details:
|
||||
Some(Ok(raw_event))
|
||||
}
|
||||
Err(e) => {
|
||||
// By setting the position to the "end" of the event bytes,
|
||||
// the cursor len will become 0 and the iterator will return `None`
|
||||
// from now on:
|
||||
pos = self.event_bytes.len();
|
||||
Some(Err(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterate through the events using metadata to dynamically decode and skip
|
||||
/// them, and return only those which should decode to the provided `Ev` type.
|
||||
/// If an error occurs, all subsequent iterations return `None`.
|
||||
///
|
||||
/// **Note:** This method internally uses [`Events::iter_raw()`], so it is safe to
|
||||
/// use even if you do not statically know about all of the possible events.
|
||||
pub fn find<Ev: Event>(&self) -> impl Iterator<Item = Result<Ev, BasicError>> + '_ {
|
||||
self.iter_raw().filter_map(|ev| {
|
||||
pub fn find<Ev: StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> + '_ {
|
||||
self.iter().filter_map(|ev| {
|
||||
ev.and_then(|ev| ev.as_event::<Ev>().map_err(Into::into))
|
||||
.transpose()
|
||||
})
|
||||
@@ -269,67 +131,147 @@ impl<'a, T: Config, Evs: Decode> Events<T, Evs> {
|
||||
|
||||
/// Iterate through the events using metadata to dynamically decode and skip
|
||||
/// them, and return the first event found which decodes to the provided `Ev` type.
|
||||
///
|
||||
/// **Note:** This method internally uses [`Events::iter_raw()`], so it is safe to
|
||||
/// use even if you do not statically know about all of the possible events.
|
||||
pub fn find_first<Ev: Event>(&self) -> Result<Option<Ev>, BasicError> {
|
||||
pub fn find_first<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
|
||||
self.find::<Ev>().next().transpose()
|
||||
}
|
||||
|
||||
/// Find an event that decodes to the type provided. Returns true if it was found.
|
||||
///
|
||||
/// **Note:** This method internally uses [`Events::iter_raw()`], so it is safe to
|
||||
/// use even if you do not statically know about all of the possible events.
|
||||
pub fn has<Ev: crate::Event>(&self) -> Result<bool, BasicError> {
|
||||
pub fn has<Ev: StaticEvent>(&self) -> Result<bool, Error> {
|
||||
Ok(self.find::<Ev>().next().transpose()?.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
/// A decoded event and associated details.
|
||||
/// The event details.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct EventDetails<Evs> {
|
||||
/// During which [`Phase`] was the event produced?
|
||||
pub phase: Phase,
|
||||
/// What index is this event in the stored events for this block.
|
||||
pub index: u32,
|
||||
/// The event itself.
|
||||
pub event: Evs,
|
||||
pub struct EventDetails {
|
||||
phase: Phase,
|
||||
index: u32,
|
||||
pallet: String,
|
||||
variant: String,
|
||||
bytes: Vec<u8>,
|
||||
// Dev note: this is here because we've pretty much had to generate it
|
||||
// anyway, but expect it to be generated on the fly in future versions,
|
||||
// and so don't expose it.
|
||||
fields: Vec<(Option<String>, DecodedValue)>,
|
||||
}
|
||||
|
||||
/// A Value which has been decoded from some raw bytes.
|
||||
pub type DecodedValue = scale_value::Value<scale_value::scale::TypeId>;
|
||||
|
||||
/// The raw bytes for an event with associated details about
|
||||
/// where and when it was emitted.
|
||||
/// The raw data associated with some event.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct RawEventDetails {
|
||||
pub struct EventDetailParts {
|
||||
/// When was the event produced?
|
||||
pub phase: Phase,
|
||||
/// What index is this event in the stored events for this block.
|
||||
pub index: u32,
|
||||
/// The name of the pallet from whence the Event originated.
|
||||
pub pallet: String,
|
||||
/// The index of the pallet from whence the Event originated.
|
||||
pub pallet_index: u8,
|
||||
/// The name of the pallet Event variant.
|
||||
/// The name of the pallet's Event variant.
|
||||
pub variant: String,
|
||||
/// The index of the pallet Event variant.
|
||||
pub variant_index: u8,
|
||||
/// The bytes representing the fields contained within the event.
|
||||
/// All of the bytes representing this event, including the pallet
|
||||
/// and variant index that the event originated from.
|
||||
pub bytes: Vec<u8>,
|
||||
/// Generic values representing each field of the event.
|
||||
pub fields: Vec<DecodedValue>,
|
||||
}
|
||||
|
||||
impl RawEventDetails {
|
||||
/// Attempt to decode this [`RawEventDetails`] into a specific event.
|
||||
pub fn as_event<E: Event>(&self) -> Result<Option<E>, CodecError> {
|
||||
impl EventDetails {
|
||||
/// Return the raw data associated with this event. Useful if you want
|
||||
/// ownership over parts of the event data.
|
||||
pub fn parts(self) -> EventDetailParts {
|
||||
EventDetailParts {
|
||||
phase: self.phase,
|
||||
index: self.index,
|
||||
pallet: self.pallet,
|
||||
variant: self.variant,
|
||||
bytes: self.bytes,
|
||||
}
|
||||
}
|
||||
|
||||
/// When was the event produced?
|
||||
pub fn phase(&self) -> Phase {
|
||||
self.phase
|
||||
}
|
||||
|
||||
/// What index is this event in the stored events for this block.
|
||||
pub fn index(&self) -> u32 {
|
||||
self.index
|
||||
}
|
||||
|
||||
/// The index of the pallet that the event originated from.
|
||||
pub fn pallet_index(&self) -> u8 {
|
||||
// Note: never panics because we set the first two bytes
|
||||
// in `decode_event_details` to build this.
|
||||
self.bytes[0]
|
||||
}
|
||||
|
||||
/// The index of the event variant that the event originated from.
|
||||
pub fn variant_index(&self) -> u8 {
|
||||
// Note: never panics because we set the first two bytes
|
||||
// in `decode_event_details` to build this.
|
||||
self.bytes[1]
|
||||
}
|
||||
|
||||
/// The name of the pallet from whence the Event originated.
|
||||
pub fn pallet_name(&self) -> &str {
|
||||
&self.pallet
|
||||
}
|
||||
|
||||
/// The name of the pallet's Event variant.
|
||||
pub fn variant_name(&self) -> &str {
|
||||
&self.variant
|
||||
}
|
||||
|
||||
/// Return the bytes representing this event, which include the pallet
|
||||
/// and variant index that the event originated from.
|
||||
pub fn bytes(&self) -> &[u8] {
|
||||
&self.bytes
|
||||
}
|
||||
|
||||
/// Return the bytes representing the fields stored in this event.
|
||||
pub fn field_bytes(&self) -> &[u8] {
|
||||
&self.bytes[2..]
|
||||
}
|
||||
|
||||
/// Decode and provide the event fields back in the form of a composite
|
||||
/// type, which represents either the named or unnamed fields that were
|
||||
/// present.
|
||||
// Dev note: if we can optimise Value decoding to avoid allocating
|
||||
// while working through events, or if the event structure changes
|
||||
// to allow us to skip over them, we'll no longer keep a copy of the
|
||||
// decoded events in the event, and the actual decoding will happen
|
||||
// when this method is called. This is why we return an owned vec and
|
||||
// not a reference.
|
||||
pub fn field_values(&self) -> scale_value::Composite<scale_value::scale::TypeId> {
|
||||
if self.fields.is_empty() {
|
||||
scale_value::Composite::Unnamed(vec![])
|
||||
} else if self.fields[0].0.is_some() {
|
||||
let named = self
|
||||
.fields
|
||||
.iter()
|
||||
.map(|(n, f)| (n.clone().unwrap_or_default(), f.clone()))
|
||||
.collect();
|
||||
scale_value::Composite::Named(named)
|
||||
} else {
|
||||
let unnamed = self.fields.iter().map(|(_n, f)| f.clone()).collect();
|
||||
scale_value::Composite::Unnamed(unnamed)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to decode these [`EventDetails`] into a specific static event.
|
||||
/// This targets the fields within the event directly. You can also attempt to
|
||||
/// decode the entirety of the event type (including the pallet and event
|
||||
/// variants) using [`EventDetails::as_root_event()`].
|
||||
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, CodecError> {
|
||||
if self.pallet == E::PALLET && self.variant == E::EVENT {
|
||||
Ok(Some(E::decode(&mut &self.bytes[..])?))
|
||||
Ok(Some(E::decode(&mut &self.bytes[2..])?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to decode these [`EventDetails`] into a root event type (which includes
|
||||
/// the pallet and event enum variants as well as the event fields). A compatible
|
||||
/// type for this is exposed via static codegen as a root level `Event` type.
|
||||
pub fn as_root_event<E: Decode>(&self) -> Result<E, CodecError> {
|
||||
E::decode(&mut &self.bytes[..])
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to dynamically decode a single event from our events input.
|
||||
@@ -337,7 +279,7 @@ fn decode_raw_event_details<T: Config>(
|
||||
metadata: &Metadata,
|
||||
index: u32,
|
||||
input: &mut &[u8],
|
||||
) -> Result<RawEventDetails, BasicError> {
|
||||
) -> Result<EventDetails, Error> {
|
||||
// Decode basic event details:
|
||||
let phase = Phase::decode(input)?;
|
||||
let pallet_index = input.read_byte()?;
|
||||
@@ -358,19 +300,20 @@ fn decode_raw_event_details<T: Config>(
|
||||
event_metadata.event()
|
||||
);
|
||||
|
||||
// Use metadata to figure out which bytes belong to this event:
|
||||
let mut event_bytes = Vec::new();
|
||||
// Use metadata to figure out which bytes belong to this event.
|
||||
// the event bytes also include the pallet/variant index so that, if we
|
||||
// like, we can decode them quite easily into a top level event type.
|
||||
let mut event_bytes = vec![pallet_index, variant_index];
|
||||
let mut event_fields = Vec::new();
|
||||
for arg in event_metadata.variant().fields() {
|
||||
let type_id = arg.ty().id();
|
||||
for (name, type_id) in event_metadata.fields() {
|
||||
let all_bytes = *input;
|
||||
// consume some bytes for each event field, moving the cursor forward:
|
||||
let value = scale_value::scale::decode_as_type(
|
||||
input,
|
||||
type_id,
|
||||
*type_id,
|
||||
&metadata.runtime_metadata().types,
|
||||
)?;
|
||||
event_fields.push(value);
|
||||
event_fields.push((name.clone(), value));
|
||||
// count how many bytes were consumed based on remaining length:
|
||||
let consumed_len = all_bytes.len() - input.len();
|
||||
// move those consumed bytes to the output vec unaltered:
|
||||
@@ -382,12 +325,10 @@ fn decode_raw_event_details<T: Config>(
|
||||
let topics = Vec::<T::Hash>::decode(input)?;
|
||||
tracing::debug!("topics: {:?}", topics);
|
||||
|
||||
Ok(RawEventDetails {
|
||||
Ok(EventDetails {
|
||||
phase,
|
||||
index,
|
||||
pallet_index,
|
||||
pallet: event_metadata.pallet().to_string(),
|
||||
variant_index,
|
||||
variant: event_metadata.event().to_string(),
|
||||
bytes: event_bytes,
|
||||
fields: event_fields,
|
||||
@@ -400,8 +341,7 @@ pub(crate) mod test_utils {
|
||||
use super::*;
|
||||
use crate::{
|
||||
Config,
|
||||
DefaultConfig,
|
||||
Phase,
|
||||
SubstrateConfig,
|
||||
};
|
||||
use codec::Encode;
|
||||
use frame_metadata::{
|
||||
@@ -431,7 +371,7 @@ pub(crate) mod test_utils {
|
||||
pub struct EventRecord<E: Encode> {
|
||||
phase: Phase,
|
||||
event: AllEvents<E>,
|
||||
topics: Vec<<DefaultConfig as Config>::Hash>,
|
||||
topics: Vec<<SubstrateConfig as Config>::Hash>,
|
||||
}
|
||||
|
||||
/// Build an EventRecord, which encoded events in the format expected
|
||||
@@ -474,9 +414,9 @@ pub(crate) mod test_utils {
|
||||
/// Build an `Events` object for test purposes, based on the details provided,
|
||||
/// and with a default block hash.
|
||||
pub fn events<E: Decode + Encode>(
|
||||
metadata: Arc<RwLock<Metadata>>,
|
||||
metadata: Metadata,
|
||||
event_records: Vec<EventRecord<E>>,
|
||||
) -> Events<DefaultConfig, AllEvents<E>> {
|
||||
) -> Events<SubstrateConfig> {
|
||||
let num_events = event_records.len() as u32;
|
||||
let mut event_bytes = Vec::new();
|
||||
for ev in event_records {
|
||||
@@ -487,17 +427,16 @@ pub(crate) mod test_utils {
|
||||
|
||||
/// Much like [`events`], but takes pre-encoded events and event count, so that we can
|
||||
/// mess with the bytes in tests if we need to.
|
||||
pub fn events_raw<E: Decode + Encode>(
|
||||
metadata: Arc<RwLock<Metadata>>,
|
||||
pub fn events_raw(
|
||||
metadata: Metadata,
|
||||
event_bytes: Vec<u8>,
|
||||
num_events: u32,
|
||||
) -> Events<DefaultConfig, AllEvents<E>> {
|
||||
) -> Events<SubstrateConfig> {
|
||||
Events {
|
||||
block_hash: <DefaultConfig as Config>::Hash::default(),
|
||||
event_bytes,
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event_bytes: event_bytes.into(),
|
||||
metadata,
|
||||
num_events,
|
||||
_event_type: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -509,18 +448,16 @@ mod tests {
|
||||
event_record,
|
||||
events,
|
||||
events_raw,
|
||||
AllEvents,
|
||||
},
|
||||
*,
|
||||
};
|
||||
use crate::Phase;
|
||||
use codec::Encode;
|
||||
use scale_info::TypeInfo;
|
||||
use scale_value::Value;
|
||||
|
||||
/// Build a fake wrapped metadata.
|
||||
fn metadata<E: TypeInfo + 'static>() -> Arc<RwLock<Metadata>> {
|
||||
Arc::new(RwLock::new(test_utils::metadata::<E>()))
|
||||
fn metadata<E: TypeInfo + 'static>() -> Metadata {
|
||||
test_utils::metadata::<E>()
|
||||
}
|
||||
|
||||
/// [`RawEventDetails`] can be annoying to test, because it contains
|
||||
@@ -542,170 +479,42 @@ mod tests {
|
||||
pub fn assert_raw_events_match(
|
||||
// Just for convenience, pass in the metadata type constructed
|
||||
// by the `metadata` function above to simplify caller code.
|
||||
metadata: &Arc<RwLock<Metadata>>,
|
||||
actual: RawEventDetails,
|
||||
metadata: &Metadata,
|
||||
actual: EventDetails,
|
||||
expected: TestRawEventDetails,
|
||||
) {
|
||||
let metadata = metadata.read();
|
||||
let types = &metadata.runtime_metadata().types;
|
||||
|
||||
// Make sure that the bytes handed back line up with the fields handed back;
|
||||
// encode the fields back into bytes and they should be equal.
|
||||
let mut actual_bytes = vec![];
|
||||
for field in &actual.fields {
|
||||
for (_name, field) in &actual.fields {
|
||||
scale_value::scale::encode_as_type(
|
||||
field.clone(),
|
||||
field,
|
||||
field.context,
|
||||
types,
|
||||
&mut actual_bytes,
|
||||
)
|
||||
.expect("should be able to encode properly");
|
||||
}
|
||||
assert_eq!(actual_bytes, actual.bytes);
|
||||
assert_eq!(actual_bytes, actual.field_bytes());
|
||||
|
||||
let actual_fields_no_context: Vec<_> = actual
|
||||
.fields
|
||||
.into_iter()
|
||||
.map(|f| f.remove_context())
|
||||
.field_values()
|
||||
.into_values()
|
||||
.map(|value| value.remove_context())
|
||||
.collect();
|
||||
|
||||
// Check each of the other fields:
|
||||
assert_eq!(actual.phase, expected.phase);
|
||||
assert_eq!(actual.index, expected.index);
|
||||
assert_eq!(actual.pallet, expected.pallet);
|
||||
assert_eq!(actual.pallet_index, expected.pallet_index);
|
||||
assert_eq!(actual.variant, expected.variant);
|
||||
assert_eq!(actual.variant_index, expected.variant_index);
|
||||
assert_eq!(actual.phase(), expected.phase);
|
||||
assert_eq!(actual.index(), expected.index);
|
||||
assert_eq!(actual.pallet_name(), expected.pallet);
|
||||
assert_eq!(actual.pallet_index(), expected.pallet_index);
|
||||
assert_eq!(actual.variant_name(), expected.variant);
|
||||
assert_eq!(actual.variant_index(), expected.variant_index);
|
||||
assert_eq!(actual_fields_no_context, expected.fields);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn statically_decode_single_event() {
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
enum Event {
|
||||
A(u8),
|
||||
}
|
||||
|
||||
// Create fake metadata that knows about our single event, above:
|
||||
let metadata = metadata::<Event>();
|
||||
// Encode our events in the format we expect back from a node, and
|
||||
// construct an Events object to iterate them:
|
||||
let events = events::<Event>(
|
||||
metadata,
|
||||
vec![event_record(Phase::Finalization, Event::A(1))],
|
||||
);
|
||||
|
||||
let event_details: Vec<EventDetails<AllEvents<Event>>> =
|
||||
events.iter().collect::<Result<_, _>>().unwrap();
|
||||
assert_eq!(
|
||||
event_details,
|
||||
vec![EventDetails {
|
||||
index: 0,
|
||||
phase: Phase::Finalization,
|
||||
event: AllEvents::Test(Event::A(1))
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn statically_decode_multiple_events() {
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
enum Event {
|
||||
A(u8),
|
||||
B(bool),
|
||||
}
|
||||
|
||||
// Create fake metadata that knows about our single event, above:
|
||||
let metadata = metadata::<Event>();
|
||||
|
||||
// Encode our events in the format we expect back from a node, and
|
||||
// construst an Events object to iterate them:
|
||||
let events = events::<Event>(
|
||||
metadata,
|
||||
vec![
|
||||
event_record(Phase::Initialization, Event::A(1)),
|
||||
event_record(Phase::ApplyExtrinsic(123), Event::B(true)),
|
||||
event_record(Phase::Finalization, Event::A(234)),
|
||||
],
|
||||
);
|
||||
|
||||
let event_details: Vec<EventDetails<AllEvents<Event>>> =
|
||||
events.iter().collect::<Result<_, _>>().unwrap();
|
||||
assert_eq!(
|
||||
event_details,
|
||||
vec![
|
||||
EventDetails {
|
||||
index: 0,
|
||||
phase: Phase::Initialization,
|
||||
event: AllEvents::Test(Event::A(1))
|
||||
},
|
||||
EventDetails {
|
||||
index: 1,
|
||||
phase: Phase::ApplyExtrinsic(123),
|
||||
event: AllEvents::Test(Event::B(true))
|
||||
},
|
||||
EventDetails {
|
||||
index: 2,
|
||||
phase: Phase::Finalization,
|
||||
event: AllEvents::Test(Event::A(234))
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn statically_decode_multiple_events_until_error() {
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
enum Event {
|
||||
A(u8),
|
||||
B(bool),
|
||||
}
|
||||
|
||||
// Create fake metadata that knows about our single event, above:
|
||||
let metadata = metadata::<Event>();
|
||||
|
||||
// Encode 2 events:
|
||||
let mut event_bytes = vec![];
|
||||
event_record(Phase::Initialization, Event::A(1)).encode_to(&mut event_bytes);
|
||||
event_record(Phase::ApplyExtrinsic(123), Event::B(true))
|
||||
.encode_to(&mut event_bytes);
|
||||
|
||||
// Push a few naff bytes to the end (a broken third event):
|
||||
event_bytes.extend_from_slice(&[3, 127, 45, 0, 2]);
|
||||
|
||||
// Encode our events in the format we expect back from a node, and
|
||||
// construst an Events object to iterate them:
|
||||
let events = events_raw::<Event>(
|
||||
metadata,
|
||||
event_bytes,
|
||||
3, // 2 "good" events, and then it'll hit the naff bytes.
|
||||
);
|
||||
|
||||
let mut events_iter = events.iter();
|
||||
assert_eq!(
|
||||
events_iter.next().unwrap().unwrap(),
|
||||
EventDetails {
|
||||
index: 0,
|
||||
phase: Phase::Initialization,
|
||||
event: AllEvents::Test(Event::A(1))
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
events_iter.next().unwrap().unwrap(),
|
||||
EventDetails {
|
||||
index: 1,
|
||||
phase: Phase::ApplyExtrinsic(123),
|
||||
event: AllEvents::Test(Event::B(true))
|
||||
}
|
||||
);
|
||||
|
||||
// We'll hit an error trying to decode the third event:
|
||||
assert!(events_iter.next().unwrap().is_err());
|
||||
// ... and then "None" from then on.
|
||||
assert!(events_iter.next().is_none());
|
||||
assert!(events_iter.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dynamically_decode_single_event() {
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
@@ -724,7 +533,7 @@ mod tests {
|
||||
vec![event_record(Phase::ApplyExtrinsic(123), event)],
|
||||
);
|
||||
|
||||
let mut event_details = events.iter_raw();
|
||||
let mut event_details = events.iter();
|
||||
assert_raw_events_match(
|
||||
&metadata,
|
||||
event_details.next().unwrap().unwrap(),
|
||||
@@ -736,7 +545,7 @@ mod tests {
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
fields: vec![
|
||||
Value::uint(1u8),
|
||||
Value::u128(1),
|
||||
Value::bool(true),
|
||||
Value::unnamed_composite(vec![Value::string("Hi")]),
|
||||
],
|
||||
@@ -771,7 +580,7 @@ mod tests {
|
||||
],
|
||||
);
|
||||
|
||||
let mut event_details = events.iter_raw();
|
||||
let mut event_details = events.iter();
|
||||
|
||||
assert_raw_events_match(
|
||||
&metadata,
|
||||
@@ -783,7 +592,7 @@ mod tests {
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
fields: vec![Value::uint(1u8)],
|
||||
fields: vec![Value::u128(1)],
|
||||
},
|
||||
);
|
||||
assert_raw_events_match(
|
||||
@@ -809,7 +618,7 @@ mod tests {
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
fields: vec![Value::uint(234u8)],
|
||||
fields: vec![Value::u128(234)],
|
||||
},
|
||||
);
|
||||
assert!(event_details.next().is_none());
|
||||
@@ -837,13 +646,13 @@ mod tests {
|
||||
|
||||
// Encode our events in the format we expect back from a node, and
|
||||
// construst an Events object to iterate them:
|
||||
let events = events_raw::<Event>(
|
||||
let events = events_raw(
|
||||
metadata.clone(),
|
||||
event_bytes,
|
||||
3, // 2 "good" events, and then it'll hit the naff bytes.
|
||||
);
|
||||
|
||||
let mut events_iter = events.iter_raw();
|
||||
let mut events_iter = events.iter();
|
||||
assert_raw_events_match(
|
||||
&metadata,
|
||||
events_iter.next().unwrap().unwrap(),
|
||||
@@ -854,7 +663,7 @@ mod tests {
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
fields: vec![Value::uint(1u8)],
|
||||
fields: vec![Value::u128(1)],
|
||||
},
|
||||
);
|
||||
assert_raw_events_match(
|
||||
@@ -895,20 +704,8 @@ mod tests {
|
||||
vec![event_record(Phase::Finalization, Event::A(1))],
|
||||
);
|
||||
|
||||
// Statically decode:
|
||||
let event_details: Vec<EventDetails<AllEvents<Event>>> =
|
||||
events.iter().collect::<Result<_, _>>().unwrap();
|
||||
assert_eq!(
|
||||
event_details,
|
||||
vec![EventDetails {
|
||||
index: 0,
|
||||
phase: Phase::Finalization,
|
||||
event: AllEvents::Test(Event::A(1))
|
||||
}]
|
||||
);
|
||||
|
||||
// Dynamically decode:
|
||||
let mut event_details = events.iter_raw();
|
||||
let mut event_details = events.iter();
|
||||
assert_raw_events_match(
|
||||
&metadata,
|
||||
event_details.next().unwrap().unwrap(),
|
||||
@@ -919,7 +716,7 @@ mod tests {
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
fields: vec![Value::uint(1u8)],
|
||||
fields: vec![Value::u128(1)],
|
||||
},
|
||||
);
|
||||
assert!(event_details.next().is_none());
|
||||
@@ -948,20 +745,8 @@ mod tests {
|
||||
)],
|
||||
);
|
||||
|
||||
// Statically decode:
|
||||
let event_details: Vec<EventDetails<AllEvents<Event>>> =
|
||||
events.iter().collect::<Result<_, _>>().unwrap();
|
||||
assert_eq!(
|
||||
event_details,
|
||||
vec![EventDetails {
|
||||
index: 0,
|
||||
phase: Phase::Finalization,
|
||||
event: AllEvents::Test(Event::A(CompactWrapper(1)))
|
||||
}]
|
||||
);
|
||||
|
||||
// Dynamically decode:
|
||||
let mut event_details = events.iter_raw();
|
||||
let mut event_details = events.iter();
|
||||
assert_raw_events_match(
|
||||
&metadata,
|
||||
event_details.next().unwrap().unwrap(),
|
||||
@@ -972,7 +757,7 @@ mod tests {
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
fields: vec![Value::unnamed_composite(vec![Value::uint(1u8)])],
|
||||
fields: vec![Value::unnamed_composite(vec![Value::u128(1)])],
|
||||
},
|
||||
);
|
||||
assert!(event_details.next().is_none());
|
||||
@@ -1002,20 +787,8 @@ mod tests {
|
||||
vec![event_record(Phase::Finalization, Event::A(MyType::B))],
|
||||
);
|
||||
|
||||
// Statically decode:
|
||||
let event_details: Vec<EventDetails<AllEvents<Event>>> =
|
||||
events.iter().collect::<Result<_, _>>().unwrap();
|
||||
assert_eq!(
|
||||
event_details,
|
||||
vec![EventDetails {
|
||||
index: 0,
|
||||
phase: Phase::Finalization,
|
||||
event: AllEvents::Test(Event::A(MyType::B))
|
||||
}]
|
||||
);
|
||||
|
||||
// Dynamically decode:
|
||||
let mut event_details = events.iter_raw();
|
||||
let mut event_details = events.iter();
|
||||
assert_raw_events_match(
|
||||
&metadata,
|
||||
event_details.next().unwrap().unwrap(),
|
||||
|
||||
@@ -4,14 +4,15 @@
|
||||
|
||||
//! Filtering individual events from subscriptions.
|
||||
|
||||
use super::Events;
|
||||
use crate::{
|
||||
BasicError,
|
||||
Config,
|
||||
Event,
|
||||
use super::{
|
||||
Events,
|
||||
Phase,
|
||||
StaticEvent,
|
||||
};
|
||||
use crate::{
|
||||
Config,
|
||||
Error,
|
||||
};
|
||||
use codec::Decode;
|
||||
use futures::{
|
||||
Stream,
|
||||
StreamExt,
|
||||
@@ -28,7 +29,7 @@ use std::{
|
||||
/// 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>, BasicError>> + Unpin + 'a`.
|
||||
// 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.
|
||||
@@ -37,7 +38,7 @@ pub struct FilterEvents<'a, Sub: 'a, T: Config, Filter: EventFilter> {
|
||||
dyn Iterator<
|
||||
Item = Result<
|
||||
FilteredEventDetails<T::Hash, Filter::ReturnType>,
|
||||
BasicError,
|
||||
Error,
|
||||
>,
|
||||
> + Send
|
||||
+ 'a,
|
||||
@@ -56,14 +57,13 @@ impl<'a, Sub: 'a, T: Config, Filter: EventFilter> FilterEvents<'a, Sub, T, Filte
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Sub, T, Evs, Filter> Stream for FilterEvents<'a, Sub, T, Filter>
|
||||
impl<'a, Sub, T, Filter> Stream for FilterEvents<'a, Sub, T, Filter>
|
||||
where
|
||||
Sub: Stream<Item = Result<Events<T, Evs>, BasicError>> + Unpin + 'a,
|
||||
Sub: Stream<Item = Result<Events<T>, Error>> + Unpin + 'a,
|
||||
T: Config,
|
||||
Evs: Decode + 'static,
|
||||
Filter: EventFilter,
|
||||
{
|
||||
type Item = Result<FilteredEventDetails<T::Hash, Filter::ReturnType>, BasicError>;
|
||||
type Item = Result<FilteredEventDetails<T::Hash, Filter::ReturnType>, Error>;
|
||||
fn poll_next(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
@@ -112,14 +112,11 @@ 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<'a, T: Config, Evs: Decode + 'static>(
|
||||
events: Events<T, Evs>,
|
||||
fn filter<'a, T: Config>(
|
||||
events: Events<T>,
|
||||
) -> Box<
|
||||
dyn Iterator<
|
||||
Item = Result<
|
||||
FilteredEventDetails<T::Hash, Self::ReturnType>,
|
||||
BasicError,
|
||||
>,
|
||||
Item = Result<FilteredEventDetails<T::Hash, Self::ReturnType>, Error>,
|
||||
> + Send
|
||||
+ 'a,
|
||||
>;
|
||||
@@ -134,18 +131,16 @@ pub(crate) mod private {
|
||||
|
||||
// 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: Event> private::Sealed for (Ev,) {}
|
||||
impl<Ev: Event> EventFilter for (Ev,) {
|
||||
impl<Ev: StaticEvent> private::Sealed for (Ev,) {}
|
||||
impl<Ev: StaticEvent> EventFilter for (Ev,) {
|
||||
type ReturnType = Ev;
|
||||
fn filter<'a, T: Config, Evs: Decode + 'static>(
|
||||
events: Events<T, Evs>,
|
||||
fn filter<'a, T: Config>(
|
||||
events: Events<T>,
|
||||
) -> Box<
|
||||
dyn Iterator<Item = Result<FilteredEventDetails<T::Hash, Ev>, BasicError>>
|
||||
+ Send
|
||||
+ 'a,
|
||||
dyn Iterator<Item = Result<FilteredEventDetails<T::Hash, Ev>, Error>> + Send + 'a,
|
||||
> {
|
||||
let block_hash = events.block_hash();
|
||||
let mut iter = events.into_iter_raw();
|
||||
let mut iter = events.iter();
|
||||
Box::new(std::iter::from_fn(move || {
|
||||
for ev in iter.by_ref() {
|
||||
// Forward any error immediately:
|
||||
@@ -158,7 +153,7 @@ impl<Ev: Event> EventFilter for (Ev,) {
|
||||
if let Ok(Some(event)) = ev {
|
||||
// We found a match; return our tuple.
|
||||
return Some(Ok(FilteredEventDetails {
|
||||
phase: raw_event.phase,
|
||||
phase: raw_event.phase(),
|
||||
block_hash,
|
||||
event,
|
||||
}))
|
||||
@@ -176,14 +171,14 @@ impl<Ev: Event> EventFilter for (Ev,) {
|
||||
// A generalised impl for tuples of sizes greater than 1:
|
||||
macro_rules! impl_event_filter {
|
||||
($($ty:ident $idx:tt),+) => {
|
||||
impl <$($ty: Event),+> private::Sealed for ( $($ty,)+ ) {}
|
||||
impl <$($ty: Event),+> EventFilter for ( $($ty,)+ ) {
|
||||
impl <$($ty: StaticEvent),+> private::Sealed for ( $($ty,)+ ) {}
|
||||
impl <$($ty: StaticEvent),+> EventFilter for ( $($ty,)+ ) {
|
||||
type ReturnType = ( $(Option<$ty>,)+ );
|
||||
fn filter<'a, T: Config, Evs: Decode + 'static>(
|
||||
events: Events<T, Evs>
|
||||
) -> Box<dyn Iterator<Item=Result<FilteredEventDetails<T::Hash,Self::ReturnType>, BasicError>> + Send + 'a> {
|
||||
fn filter<'a, T: Config>(
|
||||
events: Events<T>
|
||||
) -> Box<dyn Iterator<Item=Result<FilteredEventDetails<T::Hash,Self::ReturnType>, Error>> + Send + 'a> {
|
||||
let block_hash = events.block_hash();
|
||||
let mut iter = events.into_iter_raw();
|
||||
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() {
|
||||
@@ -199,7 +194,7 @@ macro_rules! impl_event_filter {
|
||||
// We found a match; return our tuple.
|
||||
out.$idx = Some(ev);
|
||||
return Some(Ok(FilteredEventDetails {
|
||||
phase: raw_event.phase,
|
||||
phase: raw_event.phase(),
|
||||
block_hash,
|
||||
event: out
|
||||
}))
|
||||
@@ -232,24 +227,24 @@ mod test {
|
||||
event_record,
|
||||
events,
|
||||
metadata,
|
||||
AllEvents,
|
||||
},
|
||||
*,
|
||||
};
|
||||
use crate::{
|
||||
Config,
|
||||
DefaultConfig,
|
||||
Metadata,
|
||||
SubstrateConfig,
|
||||
};
|
||||
use codec::{
|
||||
Decode,
|
||||
Encode,
|
||||
};
|
||||
use codec::Encode;
|
||||
use futures::{
|
||||
stream,
|
||||
Stream,
|
||||
StreamExt,
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use scale_info::TypeInfo;
|
||||
use std::sync::Arc;
|
||||
|
||||
// Some pretend events in a pallet
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
@@ -262,7 +257,7 @@ mod test {
|
||||
// An event in our pallet that we can filter on.
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
struct EventA(u8);
|
||||
impl crate::Event for EventA {
|
||||
impl StaticEvent for EventA {
|
||||
const PALLET: &'static str = "Test";
|
||||
const EVENT: &'static str = "A";
|
||||
}
|
||||
@@ -270,7 +265,7 @@ mod test {
|
||||
// An event in our pallet that we can filter on.
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
struct EventB(bool);
|
||||
impl crate::Event for EventB {
|
||||
impl StaticEvent for EventB {
|
||||
const PALLET: &'static str = "Test";
|
||||
const EVENT: &'static str = "B";
|
||||
}
|
||||
@@ -278,16 +273,15 @@ mod test {
|
||||
// An event in our pallet that we can filter on.
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
struct EventC(u8, bool);
|
||||
impl crate::Event for EventC {
|
||||
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: Arc<RwLock<Metadata>>,
|
||||
) -> impl Stream<Item = Result<Events<DefaultConfig, AllEvents<PalletEvents>>, BasicError>>
|
||||
{
|
||||
metadata: Metadata,
|
||||
) -> impl Stream<Item = Result<Events<SubstrateConfig>, Error>> {
|
||||
stream::iter(vec![
|
||||
events::<PalletEvents>(
|
||||
metadata.clone(),
|
||||
@@ -312,16 +306,16 @@ mod test {
|
||||
],
|
||||
),
|
||||
])
|
||||
.map(Ok::<_, BasicError>)
|
||||
.map(Ok::<_, Error>)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn filter_one_event_from_stream() {
|
||||
let metadata = Arc::new(RwLock::new(metadata::<PalletEvents>()));
|
||||
let metadata = metadata::<PalletEvents>();
|
||||
|
||||
// Filter out fake event stream to select events matching `EventA` only.
|
||||
let actual: Vec<_> =
|
||||
FilterEvents::<_, DefaultConfig, (EventA,)>::new(events_stream(metadata))
|
||||
FilterEvents::<_, SubstrateConfig, (EventA,)>::new(events_stream(metadata))
|
||||
.map(|e| e.unwrap())
|
||||
.collect()
|
||||
.await;
|
||||
@@ -329,17 +323,17 @@ mod test {
|
||||
let expected = vec![
|
||||
FilteredEventDetails {
|
||||
phase: Phase::Initialization,
|
||||
block_hash: <DefaultConfig as Config>::Hash::default(),
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event: EventA(1),
|
||||
},
|
||||
FilteredEventDetails {
|
||||
phase: Phase::Finalization,
|
||||
block_hash: <DefaultConfig as Config>::Hash::default(),
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event: EventA(2),
|
||||
},
|
||||
FilteredEventDetails {
|
||||
phase: Phase::ApplyExtrinsic(3),
|
||||
block_hash: <DefaultConfig as Config>::Hash::default(),
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event: EventA(3),
|
||||
},
|
||||
];
|
||||
@@ -349,10 +343,10 @@ mod test {
|
||||
|
||||
#[tokio::test]
|
||||
async fn filter_some_events_from_stream() {
|
||||
let metadata = Arc::new(RwLock::new(metadata::<PalletEvents>()));
|
||||
let metadata = metadata::<PalletEvents>();
|
||||
|
||||
// Filter out fake event stream to select events matching `EventA` or `EventB`.
|
||||
let actual: Vec<_> = FilterEvents::<_, DefaultConfig, (EventA, EventB)>::new(
|
||||
let actual: Vec<_> = FilterEvents::<_, SubstrateConfig, (EventA, EventB)>::new(
|
||||
events_stream(metadata),
|
||||
)
|
||||
.map(|e| e.unwrap())
|
||||
@@ -362,32 +356,32 @@ mod test {
|
||||
let expected = vec![
|
||||
FilteredEventDetails {
|
||||
phase: Phase::Initialization,
|
||||
block_hash: <DefaultConfig as Config>::Hash::default(),
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event: (Some(EventA(1)), None),
|
||||
},
|
||||
FilteredEventDetails {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
block_hash: <DefaultConfig as Config>::Hash::default(),
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event: (None, Some(EventB(true))),
|
||||
},
|
||||
FilteredEventDetails {
|
||||
phase: Phase::Finalization,
|
||||
block_hash: <DefaultConfig as Config>::Hash::default(),
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event: (Some(EventA(2)), None),
|
||||
},
|
||||
FilteredEventDetails {
|
||||
phase: Phase::ApplyExtrinsic(1),
|
||||
block_hash: <DefaultConfig as Config>::Hash::default(),
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event: (None, Some(EventB(false))),
|
||||
},
|
||||
FilteredEventDetails {
|
||||
phase: Phase::ApplyExtrinsic(2),
|
||||
block_hash: <DefaultConfig as Config>::Hash::default(),
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event: (None, Some(EventB(true))),
|
||||
},
|
||||
FilteredEventDetails {
|
||||
phase: Phase::ApplyExtrinsic(3),
|
||||
block_hash: <DefaultConfig as Config>::Hash::default(),
|
||||
block_hash: <SubstrateConfig as Config>::Hash::default(),
|
||||
event: (Some(EventA(3)), None),
|
||||
},
|
||||
];
|
||||
@@ -397,11 +391,11 @@ mod test {
|
||||
|
||||
#[tokio::test]
|
||||
async fn filter_no_events_from_stream() {
|
||||
let metadata = Arc::new(RwLock::new(metadata::<PalletEvents>()));
|
||||
let metadata = metadata::<PalletEvents>();
|
||||
|
||||
// Filter out fake event stream to select events matching `EventC` (none exist).
|
||||
let actual: Vec<_> =
|
||||
FilterEvents::<_, DefaultConfig, (EventC,)>::new(events_stream(metadata))
|
||||
FilterEvents::<_, SubstrateConfig, (EventC,)>::new(events_stream(metadata))
|
||||
.map(|e| e.unwrap())
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
+43
-86
@@ -2,108 +2,65 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! This module exposes the ability to work with events generated by a given block.
|
||||
//! Subxt can either attempt to statically decode events into known types, or it
|
||||
//! can hand back details of the raw event without knowing what shape its contents
|
||||
//! are (this may be useful if we don't know what exactly we're looking for).
|
||||
//!
|
||||
//! This module is wrapped by the generated API in `RuntimeAPI::EventsApi`.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ## Subscribe to all events
|
||||
//!
|
||||
//! Users can subscribe to all emitted events from blocks using `subscribe()`.
|
||||
//!
|
||||
//! To subscribe to all events from just the finalized blocks use `subscribe_finalized()`.
|
||||
//!
|
||||
//! To obtain the events from a given block use `at()`.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use futures::StreamExt;
|
||||
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
|
||||
//! # #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
//! # pub mod polkadot {}
|
||||
//! # #[tokio::main]
|
||||
//! # async fn main() {
|
||||
//! # let api = ClientBuilder::new()
|
||||
//! # .build()
|
||||
//! # .await
|
||||
//! # .unwrap()
|
||||
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
|
||||
//! let mut events = api.events().subscribe().await.unwrap();
|
||||
//!
|
||||
//! while let Some(ev) = events.next().await {
|
||||
//! // Obtain all events from this block.
|
||||
//! let ev: subxt::Events<_, _> = 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);
|
||||
//! }
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Filter events
|
||||
//!
|
||||
//! The subxt exposes the ability to filter events via the `filter_events()` function.
|
||||
//!
|
||||
//! The function filters events from the provided tuple. If 1-tuple is provided, the events are
|
||||
//! returned directly. Otherwise, we'll be given a corresponding tuple of `Option`'s, with exactly
|
||||
//! one variant populated each time.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use futures::StreamExt;
|
||||
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
|
||||
//!
|
||||
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
//! pub mod polkadot {}
|
||||
//!
|
||||
//! # #[tokio::main]
|
||||
//! # async fn main() {
|
||||
//! # let api = ClientBuilder::new()
|
||||
//! # .build()
|
||||
//! # .await
|
||||
//! # .unwrap()
|
||||
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
|
||||
//!
|
||||
//! let mut transfer_events = api
|
||||
//! .events()
|
||||
//! .subscribe()
|
||||
//! .await
|
||||
//! .unwrap()
|
||||
//! .filter_events::<(polkadot::balances::events::Transfer,)>();
|
||||
//!
|
||||
//! while let Some(transfer_event) = transfer_events.next().await {
|
||||
//! println!("Balance transfer event: {transfer_event:?}");
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
//! This module exposes the types and such necessary for working with events.
|
||||
//! 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::{
|
||||
subscribe,
|
||||
subscribe_finalized,
|
||||
subscribe_to_block_headers_filling_in_gaps,
|
||||
EventSub,
|
||||
EventSubscription,
|
||||
FinalizedEventSub,
|
||||
};
|
||||
pub use events_client::{
|
||||
// Exposed only for testing:
|
||||
subscribe_to_block_headers_filling_in_gaps,
|
||||
EventsClient,
|
||||
};
|
||||
pub use events_type::{
|
||||
at,
|
||||
DecodedValue,
|
||||
EventDetails,
|
||||
Events,
|
||||
RawEventDetails,
|
||||
};
|
||||
pub use filter_events::{
|
||||
EventFilter,
|
||||
FilterEvents,
|
||||
FilteredEventDetails,
|
||||
};
|
||||
|
||||
use codec::{
|
||||
Decode,
|
||||
Encode,
|
||||
};
|
||||
|
||||
/// Trait to uniquely identify the events's identity from the runtime metadata.
|
||||
///
|
||||
/// Generated API structures that represent an event implement this trait.
|
||||
///
|
||||
/// The trait is utilized to decode emitted events from a block, via obtaining the
|
||||
/// form of the `Event` from the metadata.
|
||||
pub trait StaticEvent: Decode {
|
||||
/// Pallet name.
|
||||
const PALLET: &'static str;
|
||||
/// Event name.
|
||||
const EVENT: &'static str;
|
||||
|
||||
/// Returns true if the given pallet and event names match this event.
|
||||
fn is_event(pallet: &str, event: &str) -> bool {
|
||||
Self::PALLET == pallet && Self::EVENT == event
|
||||
}
|
||||
}
|
||||
|
||||
/// A phase of a block's execution.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Decode, Encode)]
|
||||
pub enum Phase {
|
||||
/// Applying an extrinsic.
|
||||
ApplyExtrinsic(u32),
|
||||
/// Finalizing the block.
|
||||
Finalization,
|
||||
/// Initializing the block.
|
||||
Initialization,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user