From 49c66a0fd520221f89027ec4221b8f8b05cbe06c Mon Sep 17 00:00:00 2001 From: James Wilson Date: Thu, 6 Mar 2025 16:30:47 +0000 Subject: [PATCH] Wrap the subxt::events::Events type to avoid exposing subxt_core errors and types unnecessarily (#1948) * Wrap the subxt::events::Events type to avoid exposing subxt_core errors and types unnecessarily (#1947) * Actually import module and fix issues * Remove a couple of unnecessary conversions now * Test --- core/src/events.rs | 4 +- subxt/src/blocks/extrinsic_types.rs | 19 ++-- subxt/src/events/events_type.rs | 159 ++++++++++++++++++++++++++++ subxt/src/events/mod.rs | 7 +- 4 files changed, 173 insertions(+), 16 deletions(-) create mode 100644 subxt/src/events/events_type.rs diff --git a/core/src/events.rs b/core/src/events.rs index 23f599951a..8ca7913558 100644 --- a/core/src/events.rs +++ b/core/src/events.rs @@ -75,7 +75,7 @@ pub trait StaticEvent: DecodeAsFields { /// A collection of events obtained from a block, bundled with the necessary /// information needed to decode and iterate over them. #[derive_where(Clone)] -pub struct Events { +pub struct Events { metadata: Metadata, // Note; raw event bytes are prefixed with a Compact containing // the number of events to be decoded. The start_idx reflects that, so @@ -87,7 +87,7 @@ pub struct Events { } // Ignore the Metadata when debug-logging events; it's big and distracting. -impl core::fmt::Debug for Events { +impl core::fmt::Debug for Events { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Events") .field("event_bytes", &self.event_bytes) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 66515d4ce6..a7e28da556 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -308,14 +308,11 @@ impl ExtrinsicEvents { /// This works in the same way that [`events::Events::iter()`] does, with the /// exception that it filters out events not related to the submitted extrinsic. pub fn iter(&self) -> impl Iterator, Error>> + '_ { - self.events - .iter() - .filter(|ev| { - ev.as_ref() - .map(|ev| ev.phase() == events::Phase::ApplyExtrinsic(self.idx)) - .unwrap_or(true) // Keep any errors. - }) - .map(|e| e.map_err(Error::from)) + self.events.iter().filter(|ev| { + ev.as_ref() + .map(|ev| ev.phase() == events::Phase::ApplyExtrinsic(self.idx)) + .unwrap_or(true) // Keep any errors. + }) } /// Find all of the transaction events matching the event type provided as a generic parameter. @@ -323,10 +320,8 @@ impl ExtrinsicEvents { /// This works in the same way that [`events::Events::find()`] does, with the /// exception that it filters out events not related to the submitted extrinsic. pub fn find(&self) -> impl Iterator> + '_ { - self.iter().filter_map(|ev| { - ev.and_then(|ev| ev.as_event::().map_err(Into::into)) - .transpose() - }) + self.iter() + .filter_map(|ev| ev.and_then(|ev| ev.as_event::()).transpose()) } /// Iterate through the transaction events using metadata to dynamically decode and skip diff --git a/subxt/src/events/events_type.rs b/subxt/src/events/events_type.rs new file mode 100644 index 0000000000..b4a650d603 --- /dev/null +++ b/subxt/src/events/events_type.rs @@ -0,0 +1,159 @@ +use crate::{Config, Error, Metadata}; +use derive_where::derive_where; +use scale_decode::DecodeAsType; +use subxt_core::events::{EventDetails as CoreEventDetails, Events as CoreEvents}; + +pub use subxt_core::events::{EventMetadataDetails, Phase, StaticEvent}; + +/// A collection of events obtained from a block, bundled with the necessary +/// information needed to decode and iterate over them. +// Dev note: we are just wrapping the subxt_core types here to avoid leaking them +// in Subxt and map any errors into Subxt errors so that we don't have this part of the +// API returning a different error type (ie the subxt_core::Error). +#[derive_where(Clone, Debug)] +pub struct Events { + inner: CoreEvents, +} + +impl Events { + /// Create a new [`Events`] instance from the given bytes. + pub fn decode_from(event_bytes: Vec, metadata: Metadata) -> Self { + Self { + inner: CoreEvents::decode_from(event_bytes, metadata), + } + } + + /// The number of events. + pub fn len(&self) -> u32 { + self.inner.len() + } + + /// Are there no events in this block? + // Note: mainly here to satisfy clippy.. + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + /// Return the bytes representing all of the events. + pub fn bytes(&self) -> &[u8] { + self.inner.bytes() + } + + /// 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, Error>> + Send + Sync + 'static { + self.inner + .iter() + .map(|item| item.map(|e| EventDetails { inner: e }).map_err(Into::into)) + } + + /// 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`. + pub fn find(&self) -> impl Iterator> + '_ { + self.inner.find::().map(|item| item.map_err(Into::into)) + } + + /// 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. + pub fn find_first(&self) -> Result, Error> { + self.inner.find_first::().map_err(Into::into) + } + + /// Iterate through the events using metadata to dynamically decode and skip + /// them, and return the last event found which decodes to the provided `Ev` type. + pub fn find_last(&self) -> Result, Error> { + self.inner.find_last::().map_err(Into::into) + } + + /// Find an event that decodes to the type provided. Returns true if it was found. + pub fn has(&self) -> Result { + self.inner.has::().map_err(Into::into) + } +} + +/// The event details. +#[derive(Debug, Clone)] +pub struct EventDetails { + inner: CoreEventDetails, +} + +impl EventDetails { + /// When was the event produced? + pub fn phase(&self) -> Phase { + self.inner.phase() + } + + /// What index is this event in the stored events for this block. + pub fn index(&self) -> u32 { + self.inner.index() + } + + /// The index of the pallet that the event originated from. + pub fn pallet_index(&self) -> u8 { + self.inner.pallet_index() + } + + /// The index of the event variant that the event originated from. + pub fn variant_index(&self) -> u8 { + self.inner.variant_index() + } + + /// The name of the pallet from whence the Event originated. + pub fn pallet_name(&self) -> &str { + self.inner.pallet_name() + } + + /// The name of the event (ie the name of the variant that it corresponds to). + pub fn variant_name(&self) -> &str { + self.inner.variant_name() + } + + /// Fetch details from the metadata for this event. + pub fn event_metadata(&self) -> EventMetadataDetails { + self.inner.event_metadata() + } + + /// Return _all_ of the bytes representing this event, which include, in order: + /// - The phase. + /// - Pallet and event index. + /// - Event fields. + /// - Event Topics. + pub fn bytes(&self) -> &[u8] { + self.inner.bytes() + } + + /// Return the bytes representing the fields stored in this event. + pub fn field_bytes(&self) -> &[u8] { + self.inner.field_bytes() + } + + /// Decode and provide the event fields back in the form of a [`scale_value::Composite`] + /// type which represents the named or unnamed fields that were present in the event. + pub fn field_values(&self) -> Result, Error> { + self.inner.field_values().map_err(Into::into) + } + + /// Attempt to decode these [`EventDetails`] into a type representing the event fields. + /// Such types are exposed in the codegen as `pallet_name::events::EventName` types. + pub fn as_event(&self) -> Result, Error> { + self.inner.as_event::().map_err(Into::into) + } + + /// 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(&self) -> Result { + self.inner.as_root_event::().map_err(Into::into) + } + + /// Return the topics associated with this event. + pub fn topics(&self) -> &[T::Hash] { + self.inner.topics() + } +} diff --git a/subxt/src/events/mod.rs b/subxt/src/events/mod.rs index 599e99da26..ab376efea4 100644 --- a/subxt/src/events/mod.rs +++ b/subxt/src/events/mod.rs @@ -5,13 +5,16 @@ //! 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 events_client; +mod events_type; + use crate::client::OnlineClientT; use crate::Error; use subxt_core::{Config, Metadata}; -mod events_client; pub use events_client::EventsClient; -pub use subxt_core::events::{EventDetails, Events, Phase, StaticEvent}; +pub use events_type::{EventDetails, EventMetadataDetails, Events, Phase, StaticEvent}; /// Creates a new [`Events`] instance by fetching the corresponding bytes at `block_hash` from the client. pub async fn new_events_from_client(