diff --git a/new/Cargo.toml b/new/Cargo.toml index 5db50f5499..97c558f665 100644 --- a/new/Cargo.toml +++ b/new/Cargo.toml @@ -115,7 +115,7 @@ jsonrpsee = { workspace = true, optional = true, features = ["jsonrpsee-types"] # Other subxt crates we depend on. subxt-macro = { workspace = true } -subxt-metadata = { workspace = true, features = ["std"] } +subxt-metadata = { workspace = true, features = ["std", "legacy"] } subxt-lightclient = { workspace = true, optional = true, default-features = false } subxt-rpcs = { workspace = true } diff --git a/new/src/client.rs b/new/src/client.rs index 2f57afbc8b..9699b29b35 100644 --- a/new/src/client.rs +++ b/new/src/client.rs @@ -2,6 +2,9 @@ mod offline_client; mod online_client; use crate::config::{Config, HashFor}; +use crate::error::{EventsError, ExtrinsicError}; +use crate::events::Events; +use crate::extrinsics::Extrinsics; use crate::transactions::Transactions; use core::marker::PhantomData; use subxt_metadata::Metadata; @@ -31,7 +34,7 @@ where T: Config, Client: OfflineClientAtBlockT, { - /// Construct transactions. + /// Construct and submit transactions. pub fn tx(&self) -> Transactions { Transactions::new(self.client.clone()) } @@ -52,6 +55,16 @@ where T: Config, Client: OnlineClientAtBlockT, { + /// Obtain the extrinsics in this block. + pub async fn extrinsics(&self) -> Result, ExtrinsicError> { + Extrinsics::fetch(self.client.clone()).await + } + + /// Obtain the extrinsic events at this block. + pub async fn events(&self) -> Result, EventsError> { + Events::fetch(self.client.clone()).await + } + /// The current block hash. pub fn block_hash(&self) -> HashFor { self.client.block_hash() diff --git a/new/src/client/online_client.rs b/new/src/client/online_client.rs index 12e7f12a22..287f0257f7 100644 --- a/new/src/client/online_client.rs +++ b/new/src/client/online_client.rs @@ -12,6 +12,7 @@ use core::marker::PhantomData; use frame_decode::helpers::ToTypeRegistry; use frame_metadata::{RuntimeMetadata, RuntimeMetadataPrefixed}; use scale_info_legacy::TypeRegistrySet; +use std::future::Future; use std::sync::Arc; use subxt_metadata::Metadata; use subxt_rpcs::RpcClient; @@ -428,13 +429,12 @@ impl OnlineClient { }; let online_client_at_block = OnlineClientAtBlock { + client: self.clone(), hasher: ::new(&metadata), metadata, - backend: self.inner.backend.clone(), block_ref, block_number, spec_version, - genesis_hash: self.inner.genesis_hash, transaction_version, }; @@ -448,32 +448,45 @@ impl OnlineClient { /// This represents an online client at a specific block. #[doc(hidden)] pub trait OnlineClientAtBlockT: OfflineClientAtBlockT { + type AtBlockError: std::error::Error; /// Return the RPC methods we'll use to interact with the node. fn backend(&self) -> &dyn Backend; /// Return the block hash for the current block. fn block_hash(&self) -> HashFor; + /// Point at a new block. + fn at_block( + &self, + number_or_hash: BlockNumberOrRef, + ) -> impl Future, Self::AtBlockError>>; } /// The inner type providing the necessary data to work online at a specific block. #[derive(Clone)] pub struct OnlineClientAtBlock { + client: OnlineClient, metadata: Arc, - backend: Arc>, hasher: T::Hasher, block_ref: BlockRef>, block_number: u64, spec_version: u32, - genesis_hash: HashFor, transaction_version: u32, } impl OnlineClientAtBlockT for OnlineClientAtBlock { + type AtBlockError = OnlineClientAtBlockError; + fn backend(&self) -> &dyn Backend { - &*self.backend + &*self.client.inner.backend } fn block_hash(&self) -> HashFor { self.block_ref.hash() } + async fn at_block( + &self, + number_or_hash: BlockNumberOrRef, + ) -> Result, Self::AtBlockError> { + self.client.at_block(number_or_hash).await + } } impl OfflineClientAtBlockT for OnlineClientAtBlock { @@ -487,7 +500,7 @@ impl OfflineClientAtBlockT for OnlineClientAtBlock { self.block_number } fn genesis_hash(&self) -> Option> { - Some(self.genesis_hash) + Some(self.client.inner.genesis_hash) } fn spec_version(&self) -> u32 { self.spec_version diff --git a/new/src/error/dispatch_error.rs b/new/src/error/dispatch_error.rs index f29dcae575..94df6b430b 100644 --- a/new/src/error/dispatch_error.rs +++ b/new/src/error/dispatch_error.rs @@ -8,6 +8,7 @@ use super::{DispatchErrorDecodeError, ModuleErrorDecodeError, ModuleErrorDetailsError}; use core::fmt::Debug; use scale_decode::{DecodeAsType, TypeResolver, visitor::DecodeAsTypeResult}; +use std::sync::Arc; use std::{borrow::Cow, marker::PhantomData}; use subxt_metadata::Metadata; @@ -133,7 +134,7 @@ pub enum TransactionalError { #[derive(Clone, thiserror::Error)] #[non_exhaustive] pub struct ModuleError { - metadata: Metadata, + metadata: Arc, /// Bytes representation: /// - `bytes[0]`: pallet index /// - `bytes[1]`: error index @@ -242,7 +243,7 @@ impl DispatchError { #[doc(hidden)] pub fn decode_from<'a>( bytes: impl Into>, - metadata: Metadata, + metadata: Arc, ) -> Result { let bytes = bytes.into(); let dispatch_error_ty_id = metadata diff --git a/new/src/events.rs b/new/src/events.rs new file mode 100644 index 0000000000..fbced5aeb5 --- /dev/null +++ b/new/src/events.rs @@ -0,0 +1,400 @@ +mod decode_as_event; + +use crate::config::{Config, HashFor}; +use crate::error::EventsError; +use crate::{backend::BackendExt, client::OnlineClientAtBlockT}; +use codec::{Compact, Decode, Encode}; +use scale_decode::{DecodeAsFields, DecodeAsType}; +use std::marker::PhantomData; +use std::sync::Arc; +use subxt_metadata::Metadata; + +pub use decode_as_event::DecodeAsEvent; + +/// The events at some block. +#[derive(Debug)] +pub struct Events { + metadata: Arc, + // Note; raw event bytes are prefixed with a Compact containing + // the number of events to be decoded. The start_idx reflects that, so + // that we can skip over those bytes when decoding them + event_bytes: Vec, + start_idx: usize, + num_events: u32, + marker: core::marker::PhantomData, +} + +impl Events { + pub(crate) async fn fetch(client: impl OnlineClientAtBlockT) -> Result { + // Fetch the bytes. Ensure things work if we get 0 bytes back. + let block_hash = client.block_hash(); + let event_bytes = client + .backend() + .storage_fetch_value(system_events_key().to_vec(), block_hash) + .await + .map_err(EventsError::CannotFetchEventBytes)? + .unwrap_or_default(); + + // 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 = >::decode(cursor).unwrap_or(Compact(0)).0; + + // Start decoding after the compact encoded bytes. + let start_idx = event_bytes.len() - cursor.len(); + + Ok(Self { + metadata: client.metadata(), + event_bytes, + start_idx, + num_events, + marker: PhantomData, + }) + } + + /// The number of events. + pub fn len(&self) -> u32 { + self.num_events + } + + /// Are there no events in this block? + // Note: mainly here to satisfy clippy. + pub fn is_empty(&self) -> bool { + self.num_events == 0 + } + + /// Return the bytes representing all of the events. + pub fn bytes(&self) -> &[u8] { + &self.event_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, EventsError>> + Send + Sync { + // The event bytes ignoring the compact encoded length on the front: + let event_bytes = &*self.event_bytes; + let metadata = &*self.metadata; + let num_events = self.num_events; + + let mut pos = self.start_idx; + let mut index = 0; + core::iter::from_fn(move || { + if event_bytes.len() <= pos || num_events == index { + None + } else { + match Event::decode_from(metadata, event_bytes, pos, index) { + Ok(event_details) => { + // Skip over decoded bytes in next iteration: + pos += event_details.bytes().len(); + // Increment the index: + index += 1; + // Return the event details: + Some(Ok(event_details)) + } + 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 through the events, Decoding and returning any that match the given type. + /// + /// This is a convenience function for calling [`Self::iter`] and then [`Event::decode_call_data_fields_as`] + /// on each event that we iterate over, filtering those that don't match. + pub fn find(&self) -> impl Iterator> { + self.iter() + .filter_map(|e| e.ok()) + .filter_map(|e| e.decode_fields_as::()) + } + + /// Find an event matching the given type, returning true if it exists. This function does _not_ + /// try to actually decode the event bytes into the given type. + pub fn has(&self) -> bool { + self.iter().filter_map(|e| e.ok()).any(|e| e.is::()) + } +} + +/// 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, +} + +/// The event details. +#[derive(Debug, Clone)] +pub struct Event<'events, T: Config> { + pallet_name: &'events str, + event_name: &'events str, + metadata: &'events Metadata, + // all of the event bytes (not just this one). + all_bytes: &'events [u8], + // event phase. + phase: Phase, + /// The index of the event in the list of events in a given block. + index: u32, + // start of the bytes (phase, pallet/variant index and then fields and then topic to follow). + start_idx: usize, + // start of the event (ie pallet/variant index and then the fields and topic after). + event_start_idx: usize, + // start of the fields (ie after phase and pallet/variant index). + event_fields_start_idx: usize, + // end of the fields. + event_fields_end_idx: usize, + // end of everything (fields + topics) + end_idx: usize, + // event topics. + topics: Vec>, +} + +impl<'events, T: Config> Event<'events, T> { + /// Attempt to dynamically decode a single event from our events input. + fn decode_from( + metadata: &'events Metadata, + all_bytes: &'events [u8], + start_idx: usize, + index: u32, + ) -> Result, EventsError> { + let input = &mut &all_bytes[start_idx..]; + + let phase = Phase::decode(input).map_err(EventsError::CannotDecodePhase)?; + + let event_start_idx = all_bytes.len() - input.len(); + + let pallet_index = u8::decode(input).map_err(EventsError::CannotDecodePalletIndex)?; + let variant_index = u8::decode(input).map_err(EventsError::CannotDecodeVariantIndex)?; + + let event_fields_start_idx = all_bytes.len() - input.len(); + + // Get metadata for the event: + let event_pallet = metadata + .pallet_by_event_index(pallet_index) + .ok_or_else(|| EventsError::CannotFindPalletWithIndex(pallet_index))?; + let event_variant = event_pallet + .event_variant_by_index(variant_index) + .ok_or_else(|| EventsError::CannotFindVariantWithIndex { + pallet_name: event_pallet.name().to_string(), + variant_index, + })?; + + tracing::debug!( + "Decoding Event '{}::{}'", + event_pallet.name(), + &event_variant.name + ); + + // Skip over the bytes belonging to this event. + for field_metadata in &event_variant.fields { + // Skip over the bytes for this field: + scale_decode::visitor::decode_with_visitor( + input, + field_metadata.ty.id, + metadata.types(), + scale_decode::visitor::IgnoreVisitor::new(), + ) + .map_err(|e| EventsError::CannotDecodeFieldInEvent { + pallet_name: event_pallet.name().to_string(), + event_name: event_variant.name.clone(), + field_name: field_metadata + .name + .clone() + .unwrap_or("".to_string()), + reason: e, + })?; + } + + // the end of the field bytes. + let event_fields_end_idx = all_bytes.len() - input.len(); + + // topics come after the event data in EventRecord. + let topics = + Vec::>::decode(input).map_err(EventsError::CannotDecodeEventTopics)?; + + // what bytes did we skip over in total, including topics. + let end_idx = all_bytes.len() - input.len(); + + Ok(Event { + pallet_name: event_pallet.name(), + event_name: &event_variant.name, + phase, + index, + start_idx, + event_start_idx, + event_fields_start_idx, + event_fields_end_idx, + end_idx, + all_bytes, + metadata, + topics, + }) + } + + /// 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; we expect these bytes to exist + // in order that the EventDetails could be created. + self.all_bytes[self.event_fields_start_idx - 2] + } + + /// The index of the event variant that the event originated from. + pub fn event_index(&self) -> u8 { + // Note: never panics; we expect these bytes to exist + // in order that the EventDetails could be created. + self.all_bytes[self.event_fields_start_idx - 1] + } + + /// The name of the pallet from whence the Event originated. + pub fn pallet_name(&self) -> &str { + self.pallet_name + } + + /// The name of the event (ie the name of the variant that it corresponds to). + pub fn event_name(&self) -> &str { + self.event_name + } + + /// 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.all_bytes[self.start_idx..self.end_idx] + } + + /// Return the bytes representing the fields stored in this event. + pub fn field_bytes(&self) -> &[u8] { + &self.all_bytes[self.event_fields_start_idx..self.event_fields_end_idx] + } + + /// Return the topics associated with this event. + pub fn topics(&self) -> &[HashFor] { + &self.topics + } + + /// Return true if this [`Event`] matches the provided type. + pub fn is(&self) -> bool { + E::is_event(self.pallet_name(), self.event_name()) + } + + /// Attempt to decode this [`Event`] into an outer event enum type (which includes + /// the pallet and event enum variants as well as the event fields). One compatible + /// type for this is exposed via static codegen as a root level `Event` type. + pub fn decode_as(&self) -> Result { + let bytes = &self.all_bytes[self.event_start_idx..self.event_fields_end_idx]; + + let decoded = E::decode_as_type( + &mut &bytes[..], + self.metadata.outer_enums().event_enum_ty(), + self.metadata.types(), + ) + .map_err(|e| { + let md = self.event_metadata(); + EventsError::CannotDecodeEventEnum { + pallet_name: md.pallet.name().to_string(), + event_name: md.variant.name.clone(), + reason: e, + } + })?; + + Ok(decoded) + } + + /// Decode the event call data fields into some type which implements [`DecodeAsEvent`]. + /// + /// Event types generated via the [`crate::subxt!`] macro implement this. + pub fn decode_fields_as(&self) -> Option> { + if self.is::() { + Some(self.decode_fields_unchecked_as::()) + } else { + None + } + } + + /// Decode the event call data fields into some type which implements [`DecodeAsFields`]. + /// + /// This ignores the pallet and event name information, so you should check those via [`Self::pallet_name()`] + /// and [`Self::event_name()`] to confirm that this event is the one you are intending to decode. + /// + /// Prefer to use [`Self::decode_call_data_fields_as`] where possible. + pub fn decode_fields_unchecked_as(&self) -> Result { + let bytes = &mut self.field_bytes(); + let event_metadata = self.event_metadata(); + + let mut fields = event_metadata + .variant + .fields + .iter() + .map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref())); + + let decoded = + E::decode_as_fields(bytes, &mut fields, self.metadata.types()).map_err(|e| { + EventsError::CannotDecodeEventFields { + pallet_name: event_metadata.pallet.name().to_string(), + event_name: event_metadata.variant.name.clone(), + reason: e, + } + })?; + + Ok(decoded) + } + + /// Fetch details from the metadata for this event. This is used for decoding but + /// we try to avoid using it elsewhere. + fn event_metadata(&self) -> EventMetadataDetails<'_> { + let pallet = self + .metadata + .pallet_by_event_index(self.pallet_index()) + .expect("event pallet to be found; we did this already during decoding"); + let variant = pallet + .event_variant_by_index(self.event_index()) + .expect("event variant to be found; we did this already during decoding"); + + EventMetadataDetails { pallet, variant } + } +} + +// The storage key needed to access events. +fn system_events_key() -> [u8; 32] { + let a = sp_crypto_hashing::twox_128(b"System"); + let b = sp_crypto_hashing::twox_128(b"Events"); + let mut res = [0; 32]; + res[0..16].clone_from_slice(&a); + res[16..32].clone_from_slice(&b); + res +} + +/// Details for the given event plucked from the metadata. +struct EventMetadataDetails<'a> { + /// Metadata for the pallet that the event belongs to. + pub pallet: subxt_metadata::PalletMetadata<'a>, + /// Metadata for the variant which describes the pallet events. + pub variant: &'a scale_info::Variant, +} diff --git a/new/src/events/decode_as_event.rs b/new/src/events/decode_as_event.rs new file mode 100644 index 0000000000..e8cac10296 --- /dev/null +++ b/new/src/events/decode_as_event.rs @@ -0,0 +1,16 @@ +use scale_decode::DecodeAsFields; + +/// This trait can be implemented for any type which implements [`DecodeAsFields`]. +/// This adds information to the type about which event it is, which enforces that +/// only the correct event can be decoded into it. +pub trait DecodeAsEvent: DecodeAsFields { + /// Pallet name. + const PALLET_NAME: &'static str; + /// Event name. + const EVENT_NAME: &'static str; + + /// Returns true if the given pallet and event names match this event. + fn is_event(pallet: &str, event: &str) -> bool { + Self::PALLET_NAME == pallet && Self::EVENT_NAME == event + } +} diff --git a/new/src/extrinsics.rs b/new/src/extrinsics.rs new file mode 100644 index 0000000000..913a0a926c --- /dev/null +++ b/new/src/extrinsics.rs @@ -0,0 +1,385 @@ +mod decode_as_extrinsic; +mod extrinsic_transaction_extensions; + +use crate::client::{OfflineClientAtBlockT, OnlineClientAtBlockT}; +use crate::config::{Config, HashFor, Hasher}; +use crate::error::{ + EventsError, ExtrinsicDecodeErrorAt, ExtrinsicDecodeErrorAtReason, ExtrinsicError, +}; +use crate::events::{self, DecodeAsEvent}; +use frame_decode::extrinsics::Extrinsic as ExtrinsicInfo; +use scale_decode::{DecodeAsFields, DecodeAsType}; +use std::marker::PhantomData; +use subxt_metadata::Metadata; + +pub use decode_as_extrinsic::DecodeAsExtrinsic; +pub use extrinsic_transaction_extensions::{ + ExtrinsicTransactionExtension, ExtrinsicTransactionExtensions, +}; + +/// The extrinsics in a block. +pub struct Extrinsics { + client: C, + extrinsics: Vec>, + marker: PhantomData, +} + +impl> Extrinsics { + pub(crate) async fn fetch(client: C) -> Result { + let block_hash = client.block_hash(); + let extrinsics = client + .backend() + .block_body(block_hash) + .await + .map_err(ExtrinsicError::CannotGetBlockBody)? + .ok_or_else(|| ExtrinsicError::BlockNotFound(block_hash.into()))?; + + Ok(Extrinsics { + client, + extrinsics, + marker: PhantomData, + }) + } +} + +impl> Extrinsics { + /// The number of extrinsics. + pub fn len(&self) -> usize { + self.extrinsics.len() + } + + /// Are there no extrinsics in this block? + // Note: mainly here to satisfy clippy. + pub fn is_empty(&self) -> bool { + self.extrinsics.is_empty() + } + + /// Returns an iterator over the extrinsics in the block body. We decode the extrinsics on + /// demand as we iterate, and so if any fail to decode an error will be returned. + pub fn iter( + &self, + ) -> impl Iterator, ExtrinsicDecodeErrorAt>> { + let hasher = self.client.hasher(); + let metadata = self.client.metadata_ref(); + let client = &self.client; + + self.extrinsics + .iter() + .enumerate() + .map(move |(extrinsic_index, extrinsic_bytes)| { + let cursor = &mut &**extrinsic_bytes; + + // Try to decode the extrinsic. + let info = + frame_decode::extrinsics::decode_extrinsic(cursor, metadata, metadata.types()) + .map_err(|error| ExtrinsicDecodeErrorAt { + extrinsic_index, + error: ExtrinsicDecodeErrorAtReason::DecodeError(error), + })? + .into_owned(); + + // We didn't consume all bytes, so decoding probably failed. + if !cursor.is_empty() { + return Err(ExtrinsicDecodeErrorAt { + extrinsic_index, + error: ExtrinsicDecodeErrorAtReason::LeftoverBytes(cursor.to_vec()), + }); + } + + Ok(Extrinsic { + client: client, + index: extrinsic_index, + info, + extrinsic: &**extrinsic_bytes, + hasher, + metadata, + }) + }) + } + + /// Iterate through the extrinsics, Decoding and returning any that match the given type. + /// + /// This is a convenience function for calling [`Self::iter`] and then [`Extrinsic::decode_call_data_fields_as`] + /// on each extrinsic that we iterate over, filtering those that don't match. + pub fn find(&self) -> impl Iterator> { + self.iter() + .filter_map(|e| e.ok()) + .filter_map(|e| e.decode_call_data_fields_as::()) + } + + /// Find an extrinsic matching the given type, returning true if it exists. This function does _not_ + /// try to actually decode the extrinsic bytes into the given type. + pub fn has(&self) -> bool { + self.iter().filter_map(|e| e.ok()).any(|e| e.is::()) + } +} + +/// A single extrinsic in a block. +pub struct Extrinsic<'extrinsics, T: Config, C> { + client: &'extrinsics C, + /// The index of the extrinsic in the block. + index: usize, + /// Information about the extrinsic + info: ExtrinsicInfo<'extrinsics, u32>, + /// Extrinsic bytes and decode info. + extrinsic: &'extrinsics [u8], + /// Hash the extrinsic if we want. + hasher: &'extrinsics T::Hasher, + /// Subxt metadata to fetch the extrinsic metadata. + metadata: &'extrinsics Metadata, +} + +impl<'extrinsics, T, C> Extrinsic<'extrinsics, T, C> +where + T: Config, + C: OfflineClientAtBlockT, +{ + /// Calculate and return the hash of the extrinsic, based on the configured hasher. + pub fn hash(&self) -> HashFor { + // Use hash(), not hash_of(), because we don't want to double encode the bytes. + self.hasher.hash(self.extrinsic) + } + + /// Is the extrinsic signed? + pub fn is_signed(&self) -> bool { + self.info.is_signed() + } + + /// The index of the extrinsic in the block. + pub fn index(&self) -> usize { + self.index + } + + /// The index of the pallet that the extrinsic originated from. + pub fn pallet_index(&self) -> u8 { + self.info.pallet_index() + } + + /// The index of the extrinsic variant that the extrinsic originated from. + pub fn call_index(&self) -> u8 { + self.info.call_index() + } + + /// The name of the pallet from whence the extrinsic originated. + pub fn pallet_name(&self) -> &str { + self.info.pallet_name() + } + + /// The name of the call (ie the name of the variant that it corresponds to). + pub fn call_name(&self) -> &str { + self.info.call_name() + } + + /// Return the extrinsic bytes. + pub fn bytes(&self) -> &'extrinsics [u8] { + self.extrinsic + } + + /// Return only the bytes representing this extrinsic call: + /// - First byte is the pallet index + /// - Second byte is the variant (call) index + /// - Followed by field bytes. + /// + /// # Note + /// + /// Please use [`Self::bytes`] if you want to get all extrinsic bytes. + pub fn call_data_bytes(&self) -> &'extrinsics [u8] { + &self.extrinsic[self.info.call_data_range()] + } + + /// Return the bytes representing the fields stored in this extrinsic. + /// + /// # Note + /// + /// This is a subset of [`Self::call_bytes`] that does not include the + /// first two bytes that denote the pallet index and the variant index. + pub fn call_data_field_bytes(&self) -> &'extrinsics [u8] { + &self.extrinsic[self.info.call_data_args_range()] + } + + /// Return only the bytes of the address that signed this extrinsic. + /// + /// # Note + /// + /// Returns `None` if the extrinsic is not signed. + pub fn address_bytes(&self) -> Option<&'extrinsics [u8]> { + self.info + .signature_payload() + .map(|s| &self.extrinsic[s.address_range()]) + } + + /// Returns Some(signature_bytes) if the extrinsic was signed otherwise None is returned. + pub fn signature_bytes(&self) -> Option<&'extrinsics [u8]> { + self.info + .signature_payload() + .map(|s| &self.extrinsic[s.signature_range()]) + } + + /// Returns the signed extension `extra` bytes of the extrinsic. + /// Each signed extension has an `extra` type (May be zero-sized). + /// These bytes are the scale encoded `extra` fields of each signed extension in order of the signed extensions. + /// They do *not* include the `additional` signed bytes that are used as part of the payload that is signed. + /// + /// Note: Returns `None` if the extrinsic is not signed. + pub fn transaction_extensions_bytes(&self) -> Option<&[u8]> { + self.info + .transaction_extension_payload() + .map(|t| &self.extrinsic[t.range()]) + } + + /// Returns `None` if the extrinsic is not signed. + pub fn transaction_extensions( + &self, + ) -> Option> { + self.info + .transaction_extension_payload() + .map(|t| ExtrinsicTransactionExtensions::new(self.extrinsic, self.metadata, t)) + } + + /// Return true if this [`Extrinsic`] matches the provided type. + pub fn is(&self) -> bool { + E::is_extrinsic(self.pallet_name(), self.call_name()) + } + + /// Attempt to decode this [`Extrinsic`] into an outer call enum type (which includes + /// the pallet and extrinsic enum variants as well as the extrinsic fields). One compatible + /// type for this is exposed via static codegen as a root level `Call` type. + pub fn decode_call_data_as(&self) -> Result { + let decoded = E::decode_as_type( + &mut &self.call_data_bytes()[..], + self.metadata.outer_enums().call_enum_ty(), + self.metadata.types(), + ) + .map_err(|e| ExtrinsicError::CannotDecodeIntoRootExtrinsic { + extrinsic_index: self.index as usize, + error: e, + })?; + + Ok(decoded) + } + + /// Decode the extrinsic call data fields into some type which implements [`DecodeAsExtrinsic`]. + /// + /// Extrinsic types generated via the [`crate::subxt!`] macro implement this. + pub fn decode_call_data_fields_as( + &self, + ) -> Option> { + if self.is::() { + Some(self.decode_call_data_fields_unchecked_as::()) + } else { + None + } + } + + /// Decode the extrinsic call data fields into some type which implements [`DecodeAsFields`]. + /// + /// This ignores the pallet and call name information, so you should check those via [`Self::pallet_name()`] + /// and [`Self::call_name()`] to confirm that this extrinsic is the one you are intending to decode. + /// + /// Prefer to use [`Self::decode_call_data_fields_as`] where possible. + pub fn decode_call_data_fields_unchecked_as( + &self, + ) -> Result { + let bytes = &mut self.call_data_field_bytes(); + let mut fields = self.info.call_data().map(|d| { + let name = if d.name().is_empty() { + None + } else { + Some(d.name()) + }; + scale_decode::Field::new(*d.ty(), name) + }); + let decoded = + E::decode_as_fields(bytes, &mut fields, self.metadata.types()).map_err(|e| { + ExtrinsicError::CannotDecodeFields { + extrinsic_index: self.index as usize, + error: e, + } + })?; + + Ok(decoded) + } +} + +impl<'extrinsics, T, C> Extrinsic<'extrinsics, T, C> +where + T: Config, + C: OnlineClientAtBlockT, +{ + /// The events associated with the extrinsic. + pub async fn events(&self) -> Result, EventsError> { + ExtrinsicEvents::fetch(self.client.clone(), self.hash(), self.index()).await + } +} + +/// The events associated with a given extrinsic. +#[derive(Debug)] +pub struct ExtrinsicEvents { + // The hash of the extrinsic (handy to expose here because + // this type is returned from TxProgress things in the most + // basic flows, so it's the only place people can access it + // without complicating things for themselves). + extrinsic_hash: HashFor, + // The index of the extrinsic: + extrinsic_index: usize, + // All of the events in the block: + events: crate::events::Events, +} + +impl ExtrinsicEvents { + pub(crate) async fn fetch( + client: impl OnlineClientAtBlockT, + extrinsic_hash: HashFor, + extrinsic_index: usize, + ) -> Result { + let events = crate::client::ClientAtBlock::new(client).events().await?; + Ok(ExtrinsicEvents { + extrinsic_hash, + extrinsic_index, + events, + }) + } + + /// The index of the extrinsic that these events are produced from. + pub fn extrinsic_index(&self) -> usize { + self.extrinsic_index + } + + /// Return the hash of the extrinsic. + pub fn extrinsic_hash(&self) -> HashFor { + self.extrinsic_hash + } + + /// Return all of the events in the block that the extrinsic is in. + pub fn all_events_in_block(&self) -> &events::Events { + &self.events + } + + /// Iterate over all of the raw events associated with this extrinsic. + /// + /// This works in the same way that [`events::Events::iter()`] does, with the + /// exception that it filters out events not related to the current extrinsic. + pub fn iter(&'_ self) -> impl Iterator, EventsError>> { + self.events.iter().filter(|ev| { + ev.as_ref() + .map(|ev| ev.phase() == events::Phase::ApplyExtrinsic(self.extrinsic_index as u32)) + .unwrap_or(true) // Keep any errors. + }) + } + + /// Iterate through the extrinsic's events, Decoding and returning any that match the given type. + /// + /// This is a convenience function for calling [`Self::iter`] and then [`events::Event::decode_fields_as`] + /// on each event that we iterate over, filtering those that don't match. + pub fn find(&self) -> impl Iterator> { + self.iter() + .filter_map(|e| e.ok()) + .filter_map(|e| e.decode_fields_as::()) + } + + /// Find an event matching the given type, returning true if it exists. This function does _not_ + /// try to actually decode the event bytes into the given type. + pub fn has(&self) -> bool { + self.iter().filter_map(|e| e.ok()).any(|e| e.is::()) + } +} diff --git a/new/src/extrinsics/decode_as_extrinsic.rs b/new/src/extrinsics/decode_as_extrinsic.rs new file mode 100644 index 0000000000..ce2134d1c3 --- /dev/null +++ b/new/src/extrinsics/decode_as_extrinsic.rs @@ -0,0 +1,16 @@ +use scale_decode::DecodeAsFields; + +/// This trait can be implemented for any type which implements [`DecodeAsFields`]. +/// This adds information to the type about which extrinsic it is, which enforces that +/// only the correct extrinsic can be decoded into it. +pub trait DecodeAsExtrinsic: DecodeAsFields { + /// Pallet name. + const PALLET_NAME: &'static str; + /// Call name. + const CALL_NAME: &'static str; + + /// Returns true if the given pallet and call names match this extrinsic. + fn is_extrinsic(pallet: &str, call: &str) -> bool { + Self::PALLET_NAME == pallet && Self::CALL_NAME == call + } +} diff --git a/new/src/extrinsics/extrinsic_transaction_extensions.rs b/new/src/extrinsics/extrinsic_transaction_extensions.rs new file mode 100644 index 0000000000..e1dc37d0b5 --- /dev/null +++ b/new/src/extrinsics/extrinsic_transaction_extensions.rs @@ -0,0 +1,136 @@ +use crate::config::Config; +use crate::config::TransactionExtension; +use crate::config::transaction_extensions::{ + ChargeAssetTxPayment, ChargeTransactionPayment, CheckNonce, +}; +use crate::error::ExtrinsicError; +use frame_decode::extrinsics::ExtrinsicExtensions as ExtrinsicExtensionsInfo; +use scale_decode::DecodeAsType; +use subxt_metadata::Metadata; + +/// The signed extensions of an extrinsic. +#[derive(Debug, Clone)] +pub struct ExtrinsicTransactionExtensions<'extrinsics, 'extrinsic, T: Config> { + bytes: &'extrinsics [u8], + metadata: &'extrinsics Metadata, + decoded_info: &'extrinsic ExtrinsicExtensionsInfo<'extrinsics, u32>, + marker: core::marker::PhantomData, +} + +impl<'extrinsics, 'extrinsic, T: Config> + ExtrinsicTransactionExtensions<'extrinsics, 'extrinsic, T> +{ + pub(crate) fn new( + bytes: &'extrinsics [u8], + metadata: &'extrinsics Metadata, + decoded_info: &'extrinsic ExtrinsicExtensionsInfo<'extrinsics, u32>, + ) -> Self { + Self { + bytes, + metadata, + decoded_info, + marker: core::marker::PhantomData, + } + } + + /// Returns an iterator over each of the signed extension details of the extrinsic. + pub fn iter( + &self, + ) -> impl Iterator> { + self.decoded_info + .iter() + .map(|s| ExtrinsicTransactionExtension { + bytes: &self.bytes[s.range()], + ty_id: *s.ty(), + identifier: s.name(), + metadata: self.metadata, + _marker: core::marker::PhantomData, + }) + } + + /// Searches through all signed extensions to find a specific one. + /// If the Signed Extension is not found `Ok(None)` is returned. + /// If the Signed Extension is found but decoding failed `Err(_)` is returned. + pub fn find>(&self) -> Option> { + for ext in self.iter() { + if let Some(e) = ext.decode_as::() { + return Some(e); + } + } + None + } + + /// The tip of an extrinsic, extracted from the ChargeTransactionPayment or ChargeAssetTxPayment + /// signed extension, depending on which is present. + /// + /// Returns `None` if `tip` was not found or decoding failed. + pub fn tip(&self) -> Option { + // Note: the overhead of iterating multiple time should be negligible. + if let Some(tip) = self.find::() { + return Some(tip.ok()?.tip()); + } + if let Some(tip) = self.find::>() { + return Some(tip.ok()?.tip()); + } + None + } + + /// The nonce of the account that submitted the extrinsic, extracted from the CheckNonce signed extension. + /// + /// Returns `None` if `nonce` was not found or decoding failed. + pub fn nonce(&self) -> Option { + self.find::()?.ok() + } +} + +/// A single signed extension +#[derive(Debug, Clone)] +pub struct ExtrinsicTransactionExtension<'extrinsics, 'extrinsic, T: Config> { + bytes: &'extrinsics [u8], + ty_id: u32, + identifier: &'extrinsic str, + metadata: &'extrinsics Metadata, + _marker: core::marker::PhantomData, +} + +impl<'extrinsics, 'extrinsic, T: Config> ExtrinsicTransactionExtension<'extrinsics, 'extrinsic, T> { + /// The bytes representing this signed extension. + pub fn bytes(&self) -> &'extrinsics [u8] { + self.bytes + } + + /// The name of the signed extension. + pub fn name(&self) -> &'extrinsic str { + self.identifier + } + + /// The type id of the signed extension. + pub fn type_id(&self) -> u32 { + self.ty_id + } + + /// Decodes this signed extension based on the provided [`TransactionExtension`] type. + pub fn decode_as>( + &self, + ) -> Option> { + if !S::matches(self.identifier, self.ty_id, self.metadata.types()) { + return None; + } + Some(self.decode_unchecked_as::()) + } + + /// Decode the extension into some type which implements [`DecodeAsType`]. + /// + /// This ignores the extension name, so you should first check that this is what you expect + /// via [`Self::name()`]. + /// + /// Prefer to use [`Self::decode_as`] where possible. + pub fn decode_unchecked_as(&self) -> Result { + let value = E::decode_as_type(&mut &self.bytes[..], self.ty_id, self.metadata.types()) + .map_err(|e| ExtrinsicError::CouldNotDecodeTransactionExtension { + name: self.identifier.to_owned(), + error: e, + })?; + Ok(value) + } +} diff --git a/new/src/lib.rs b/new/src/lib.rs index fcba35dc36..b8e3965367 100644 --- a/new/src/lib.rs +++ b/new/src/lib.rs @@ -36,13 +36,14 @@ pub mod backend; pub mod client; pub mod config; pub mod error; +pub mod events; +pub mod extrinsics; pub mod transactions; pub mod utils; // pub mod book; // pub mod blocks; // pub mod constants; // pub mod custom_values; -// pub mod events; // pub mod runtime_api; // pub mod storage; // pub mod tx; diff --git a/new/src/transactions.rs b/new/src/transactions.rs index 53850a8a11..59e36710b0 100644 --- a/new/src/transactions.rs +++ b/new/src/transactions.rs @@ -1,16 +1,18 @@ mod account_nonce; mod default_params; -mod payload; mod signer; +mod transaction_progress; mod validation_result; -use crate::backend::BackendExt; +pub mod payload; + +use crate::backend::{BackendExt, TransactionStatus as BackendTransactionStatus}; use crate::client::{OfflineClientAtBlockT, OnlineClientAtBlockT}; use crate::config::extrinsic_params::Params; use crate::config::{ ClientState, Config, ExtrinsicParams, ExtrinsicParamsEncoder, HashFor, Hasher, Header, }; -use crate::error::ExtrinsicError; +use crate::error::{ExtrinsicError, TransactionStatusError}; use codec::{Compact, Encode}; use core::marker::PhantomData; use futures::{TryFutureExt, future::try_join}; @@ -20,6 +22,7 @@ use std::borrow::Cow; pub use default_params::DefaultParams; pub use payload::Payload; pub use signer::Signer; +pub use transaction_progress::{TransactionProgress, TransactionStatus}; pub use validation_result::{ TransactionInvalid, TransactionUnknown, TransactionValid, ValidationResult, }; @@ -700,20 +703,20 @@ impl> SubmittableTransaction match status { - TransactionStatus::Validated - | TransactionStatus::Broadcasted - | TransactionStatus::InBestBlock { .. } - | TransactionStatus::NoLongerInBestBlock - | TransactionStatus::InFinalizedBlock { .. } => Ok(ext_hash), - TransactionStatus::Error { message } => Err( + BackendTransactionStatus::Validated + | BackendTransactionStatus::Broadcasted + | BackendTransactionStatus::InBestBlock { .. } + | BackendTransactionStatus::NoLongerInBestBlock + | BackendTransactionStatus::InFinalizedBlock { .. } => Ok(ext_hash), + BackendTransactionStatus::Error { message } => Err( ExtrinsicError::TransactionStatusError(TransactionStatusError::Error(message)), ), - TransactionStatus::Invalid { message } => { + BackendTransactionStatus::Invalid { message } => { Err(ExtrinsicError::TransactionStatusError( TransactionStatusError::Invalid(message), )) } - TransactionStatus::Dropped { message } => { + BackendTransactionStatus::Dropped { message } => { Err(ExtrinsicError::TransactionStatusError( TransactionStatusError::Dropped(message), )) diff --git a/new/src/transactions/transaction_progress.rs b/new/src/transactions/transaction_progress.rs new file mode 100644 index 0000000000..0a89506c1c --- /dev/null +++ b/new/src/transactions/transaction_progress.rs @@ -0,0 +1,332 @@ +use crate::backend::BlockRef; +use crate::backend::{StreamOfResults, TransactionStatus as BackendTransactionStatus}; +use crate::client::OnlineClientAtBlockT; +use crate::config::{Config, HashFor}; +use crate::error::{ + DispatchError, TransactionEventsError, TransactionFinalizedSuccessError, + TransactionProgressError, TransactionStatusError, +}; +use crate::extrinsics::ExtrinsicEvents; +use futures::{Stream, StreamExt}; +use std::pin::Pin; +use std::task::{Context, Poll}; + +#[derive(Debug)] +pub struct TransactionProgress { + sub: Option>>>, + ext_hash: HashFor, + client: C, +} + +// The above type is not `Unpin` by default unless the generic param `T` is, +// so we manually make it clear that Unpin is actually fine regardless of `T` +// (we don't care if this moves around in memory while it's "pinned"). +impl Unpin for TransactionProgress {} + +impl TransactionProgress { + /// Instantiate a new [`TransactionProgress`] from a custom subscription. + pub fn new( + sub: StreamOfResults>>, + client: C, + ext_hash: HashFor, + ) -> Self { + Self { + sub: Some(sub), + client, + ext_hash, + } + } + + /// Return the hash of the extrinsic. + pub fn extrinsic_hash(&self) -> HashFor { + self.ext_hash + } +} + +impl TransactionProgress +where + T: Config, + C: OnlineClientAtBlockT, +{ + /// Return the next transaction status when it's emitted. This just delegates to the + /// [`futures::Stream`] implementation for [`TransactionProgress`], but allows you to + /// avoid importing that trait if you don't otherwise need it. + pub async fn next( + &mut self, + ) -> Option, TransactionProgressError>> { + StreamExt::next(self).await + } + + /// Wait for the transaction to be finalized, and return a [`TransactionInBlock`] + /// instance when it is, or an error if there was a problem waiting for finalization. + /// + /// **Note:** consumes `self`. If you'd like to perform multiple actions as the state of the + /// transaction progresses, use [`TxProgress::next()`] instead. + /// + /// **Note:** transaction statuses like `Invalid`/`Usurped`/`Dropped` indicate with some + /// probability that the transaction will not make it into a block but there is no guarantee + /// that this is true. In those cases the stream is closed however, so you currently have no way to find + /// out if they finally made it into a block or not. + pub async fn wait_for_finalized( + mut self, + ) -> Result, TransactionProgressError> { + while let Some(status) = self.next().await { + match status? { + // Finalized! Return. + TransactionStatus::InFinalizedBlock(s) => return Ok(s), + // Error scenarios; return the error. + TransactionStatus::Error { message } => { + return Err(TransactionStatusError::Error(message).into()); + } + TransactionStatus::Invalid { message } => { + return Err(TransactionStatusError::Invalid(message).into()); + } + TransactionStatus::Dropped { message } => { + return Err(TransactionStatusError::Dropped(message).into()); + } + // Ignore and wait for next status event: + _ => continue, + } + } + Err(TransactionProgressError::UnexpectedEndOfTransactionStatusStream) + } + + /// Wait for the transaction to be finalized, and for the transaction events to indicate + /// that the transaction was successful. Returns the events associated with the transaction, + /// as well as a couple of other details (block hash and extrinsic hash). + /// + /// **Note:** consumes self. If you'd like to perform multiple actions as progress is made, + /// use [`TxProgress::next()`] instead. + /// + /// **Note:** transaction statuses like `Invalid`/`Usurped`/`Dropped` indicate with some + /// probability that the transaction will not make it into a block but there is no guarantee + /// that this is true. In those cases the stream is closed however, so you currently have no way to find + /// out if they finally made it into a block or not. + pub async fn wait_for_finalized_success( + self, + ) -> Result, TransactionFinalizedSuccessError> { + let evs = self.wait_for_finalized().await?.wait_for_success().await?; + Ok(evs) + } +} + +// TransactionProgress is a stream of transaction events +impl Stream for TransactionProgress { + type Item = Result, TransactionProgressError>; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let sub = match self.sub.as_mut() { + Some(sub) => sub, + None => return Poll::Ready(None), + }; + + sub.poll_next_unpin(cx) + .map_err(TransactionProgressError::CannotGetNextProgressUpdate) + .map_ok(|status| { + match status { + BackendTransactionStatus::Validated => TransactionStatus::Validated, + BackendTransactionStatus::Broadcasted => TransactionStatus::Broadcasted, + BackendTransactionStatus::NoLongerInBestBlock => { + TransactionStatus::NoLongerInBestBlock + } + BackendTransactionStatus::InBestBlock { hash } => { + TransactionStatus::InBestBlock(TransactionInBlock::new( + hash, + self.ext_hash, + self.client.clone(), + )) + } + // These stream events mean that nothing further will be sent: + BackendTransactionStatus::InFinalizedBlock { hash } => { + self.sub = None; + TransactionStatus::InFinalizedBlock(TransactionInBlock::new( + hash, + self.ext_hash, + self.client.clone(), + )) + } + BackendTransactionStatus::Error { message } => { + self.sub = None; + TransactionStatus::Error { message } + } + BackendTransactionStatus::Invalid { message } => { + self.sub = None; + TransactionStatus::Invalid { message } + } + BackendTransactionStatus::Dropped { message } => { + self.sub = None; + TransactionStatus::Dropped { message } + } + } + }) + } +} + +/// Possible transaction statuses returned from our [`TransactionProgress::next()`] call. +#[derive(Debug)] +pub enum TransactionStatus { + /// Transaction is part of the future queue. + Validated, + /// The transaction has been broadcast to other nodes. + Broadcasted, + /// Transaction is no longer in a best block. + NoLongerInBestBlock, + /// Transaction has been included in block with given hash. + InBestBlock(TransactionInBlock), + /// Transaction has been finalized by a finality-gadget, e.g GRANDPA + InFinalizedBlock(TransactionInBlock), + /// Something went wrong in the node. + Error { + /// Human readable message; what went wrong. + message: String, + }, + /// Transaction is invalid (bad nonce, signature etc). + Invalid { + /// Human readable message; why was it invalid. + message: String, + }, + /// The transaction was dropped. + Dropped { + /// Human readable message; why was it dropped. + message: String, + }, +} + +impl TransactionStatus { + /// A convenience method to return the finalized details. Returns + /// [`None`] if the enum variant is not [`TxStatus::InFinalizedBlock`]. + pub fn as_finalized(&self) -> Option<&TransactionInBlock> { + match self { + Self::InFinalizedBlock(val) => Some(val), + _ => None, + } + } + + /// A convenience method to return the best block details. Returns + /// [`None`] if the enum variant is not [`TxStatus::InBestBlock`]. + pub fn as_in_block(&self) -> Option<&TransactionInBlock> { + match self { + Self::InBestBlock(val) => Some(val), + _ => None, + } + } +} + +/// This struct represents a transaction that has made it into a block. +#[derive(Debug)] +pub struct TransactionInBlock { + block_ref: BlockRef>, + ext_hash: HashFor, + client: C, +} + +impl TransactionInBlock { + pub(crate) fn new(block_ref: BlockRef>, ext_hash: HashFor, client: C) -> Self { + Self { + block_ref, + ext_hash, + client, + } + } + + /// Return the hash of the block that the transaction has made it into. + pub fn block_hash(&self) -> HashFor { + self.block_ref.hash() + } + + /// Return the hash of the extrinsic that was submitted. + pub fn extrinsic_hash(&self) -> HashFor { + self.ext_hash + } +} + +impl> TransactionInBlock { + /// Fetch the events associated with this transaction. If the transaction + /// was successful (ie no `ExtrinsicFailed`) events were found, then we return + /// the events associated with it. If the transaction was not successful, or + /// something else went wrong, we return an error. + /// + /// **Note:** If multiple `ExtrinsicFailed` errors are returned (for instance + /// because a pallet chooses to emit one as an event, which is considered + /// abnormal behaviour), it is not specified which of the errors is returned here. + /// You can use [`TxInBlock::fetch_events`] instead if you'd like to + /// work with multiple "error" events. + /// + /// **Note:** This has to download block details from the node and decode events + /// from them. + pub async fn wait_for_success(&self) -> Result, TransactionEventsError> { + let events = self.fetch_events().await?; + + // Try to find any errors; return the first one we encounter. + for (ev_idx, ev) in events.iter().enumerate() { + let ev = ev.map_err(|e| TransactionEventsError::CannotDecodeEventInBlock { + event_index: ev_idx, + block_hash: self.block_hash().into(), + error: e, + })?; + + if ev.pallet_name() == "System" && ev.event_name() == "ExtrinsicFailed" { + let dispatch_error = + DispatchError::decode_from(ev.field_bytes(), self.client.metadata()).map_err( + |e| TransactionEventsError::CannotDecodeDispatchError { + error: e, + bytes: ev.field_bytes().to_vec(), + }, + )?; + return Err(dispatch_error.into()); + } + } + + Ok(events) + } + + /// Fetch all of the events associated with this transaction. This succeeds whether + /// the transaction was a success or not; it's up to you to handle the error and + /// success events however you prefer. + /// + /// **Note:** This has to download block details from the node and decode events + /// from them. + pub async fn fetch_events(&self) -> Result, TransactionEventsError> { + let hasher = self.client.hasher(); + + let block_body = self + .client + .backend() + .block_body(self.block_ref.hash()) + .await + .map_err(|e| TransactionEventsError::CannotFetchBlockBody { + block_hash: self.block_hash().into(), + error: e, + })? + .ok_or_else(|| TransactionEventsError::BlockNotFound { + block_hash: self.block_hash().into(), + })?; + + let extrinsic_index = block_body + .iter() + .position(|ext| { + use crate::config::Hasher; + let hash = hasher.hash(&ext); + hash == self.ext_hash + }) + // If we successfully obtain the block hash we think contains our + // extrinsic, the extrinsic should be in there somewhere.. + .ok_or_else(|| TransactionEventsError::CannotFindTransactionInBlock { + block_hash: self.block_hash().into(), + transaction_hash: self.ext_hash.into(), + })?; + + let events = + ExtrinsicEvents::fetch(self.client.clone(), self.extrinsic_hash(), extrinsic_index) + .await + .map_err( + |e| TransactionEventsError::CannotFetchEventsForTransaction { + block_hash: self.block_hash().into(), + transaction_hash: self.ext_hash.into(), + error: e, + }, + )?; + + Ok(events) + } +}