Finish first draft of transactions, extrinsics (blocks) and events

This commit is contained in:
James Wilson
2025-12-04 17:27:10 +00:00
parent 60d8ce78df
commit 535a132dea
12 changed files with 1338 additions and 22 deletions
+1 -1
View File
@@ -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 }
+14 -1
View File
@@ -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<T>,
{
/// Construct transactions.
/// Construct and submit transactions.
pub fn tx(&self) -> Transactions<T, Client> {
Transactions::new(self.client.clone())
}
@@ -52,6 +55,16 @@ where
T: Config,
Client: OnlineClientAtBlockT<T>,
{
/// Obtain the extrinsics in this block.
pub async fn extrinsics(&self) -> Result<Extrinsics<T, Client>, ExtrinsicError> {
Extrinsics::fetch(self.client.clone()).await
}
/// Obtain the extrinsic events at this block.
pub async fn events(&self) -> Result<Events<T>, EventsError> {
Events::fetch(self.client.clone()).await
}
/// The current block hash.
pub fn block_hash(&self) -> HashFor<T> {
self.client.block_hash()
+19 -6
View File
@@ -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<T: Config> OnlineClient<T> {
};
let online_client_at_block = OnlineClientAtBlock {
client: self.clone(),
hasher: <T::Hasher as 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<T: Config> OnlineClient<T> {
/// This represents an online client at a specific block.
#[doc(hidden)]
pub trait OnlineClientAtBlockT<T: Config>: OfflineClientAtBlockT<T> {
type AtBlockError: std::error::Error;
/// Return the RPC methods we'll use to interact with the node.
fn backend(&self) -> &dyn Backend<T>;
/// Return the block hash for the current block.
fn block_hash(&self) -> HashFor<T>;
/// Point at a new block.
fn at_block(
&self,
number_or_hash: BlockNumberOrRef<T>,
) -> impl Future<Output = Result<ClientAtBlock<T, Self>, Self::AtBlockError>>;
}
/// The inner type providing the necessary data to work online at a specific block.
#[derive(Clone)]
pub struct OnlineClientAtBlock<T: Config> {
client: OnlineClient<T>,
metadata: Arc<Metadata>,
backend: Arc<dyn Backend<T>>,
hasher: T::Hasher,
block_ref: BlockRef<HashFor<T>>,
block_number: u64,
spec_version: u32,
genesis_hash: HashFor<T>,
transaction_version: u32,
}
impl<T: Config> OnlineClientAtBlockT<T> for OnlineClientAtBlock<T> {
type AtBlockError = OnlineClientAtBlockError;
fn backend(&self) -> &dyn Backend<T> {
&*self.backend
&*self.client.inner.backend
}
fn block_hash(&self) -> HashFor<T> {
self.block_ref.hash()
}
async fn at_block(
&self,
number_or_hash: BlockNumberOrRef<T>,
) -> Result<ClientAtBlock<T, Self>, Self::AtBlockError> {
self.client.at_block(number_or_hash).await
}
}
impl<T: Config> OfflineClientAtBlockT<T> for OnlineClientAtBlock<T> {
@@ -487,7 +500,7 @@ impl<T: Config> OfflineClientAtBlockT<T> for OnlineClientAtBlock<T> {
self.block_number
}
fn genesis_hash(&self) -> Option<HashFor<T>> {
Some(self.genesis_hash)
Some(self.client.inner.genesis_hash)
}
fn spec_version(&self) -> u32 {
self.spec_version
+3 -2
View File
@@ -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<Metadata>,
/// 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<Cow<'a, [u8]>>,
metadata: Metadata,
metadata: Arc<Metadata>,
) -> Result<Self, DispatchErrorDecodeError> {
let bytes = bytes.into();
let dispatch_error_ty_id = metadata
+400
View File
@@ -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<T> {
metadata: Arc<Metadata>,
// Note; raw event bytes are prefixed with a Compact<u32> 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<u8>,
start_idx: usize,
num_events: u32,
marker: core::marker::PhantomData<T>,
}
impl<T: Config> Events<T> {
pub(crate) async fn fetch(client: impl OnlineClientAtBlockT<T>) -> Result<Self, EventsError> {
// 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 = <Compact<u32>>::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<Item = Result<Event<'_, T>, 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<E: DecodeAsEvent>(&self) -> impl Iterator<Item = Result<E, EventsError>> {
self.iter()
.filter_map(|e| e.ok())
.filter_map(|e| e.decode_fields_as::<E>())
}
/// 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<E: DecodeAsEvent>(&self) -> bool {
self.iter().filter_map(|e| e.ok()).any(|e| e.is::<E>())
}
}
/// 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<HashFor<T>>,
}
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<Event<'events, T>, 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("<unknown>".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::<HashFor<T>>::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<T>] {
&self.topics
}
/// Return true if this [`Event`] matches the provided type.
pub fn is<E: DecodeAsEvent>(&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<E: DecodeAsType>(&self) -> Result<E, EventsError> {
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<E: DecodeAsEvent>(&self) -> Option<Result<E, EventsError>> {
if self.is::<E>() {
Some(self.decode_fields_unchecked_as::<E>())
} 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<E: DecodeAsFields>(&self) -> Result<E, EventsError> {
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<scale_info::form::PortableForm>,
}
+16
View File
@@ -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
}
}
+385
View File
@@ -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<T, C> {
client: C,
extrinsics: Vec<Vec<u8>>,
marker: PhantomData<T>,
}
impl<T: Config, C: OnlineClientAtBlockT<T>> Extrinsics<T, C> {
pub(crate) async fn fetch(client: C) -> Result<Self, ExtrinsicError> {
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<T: Config, C: OfflineClientAtBlockT<T>> Extrinsics<T, C> {
/// 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<Item = Result<Extrinsic<'_, T, C>, 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<E: DecodeAsExtrinsic>(&self) -> impl Iterator<Item = Result<E, ExtrinsicError>> {
self.iter()
.filter_map(|e| e.ok())
.filter_map(|e| e.decode_call_data_fields_as::<E>())
}
/// 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<E: DecodeAsExtrinsic>(&self) -> bool {
self.iter().filter_map(|e| e.ok()).any(|e| e.is::<E>())
}
}
/// 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<T>,
{
/// Calculate and return the hash of the extrinsic, based on the configured hasher.
pub fn hash(&self) -> HashFor<T> {
// 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<ExtrinsicTransactionExtensions<'extrinsics, '_, T>> {
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<E: DecodeAsExtrinsic>(&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<E: DecodeAsType>(&self) -> Result<E, ExtrinsicError> {
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<E: DecodeAsExtrinsic>(
&self,
) -> Option<Result<E, ExtrinsicError>> {
if self.is::<E>() {
Some(self.decode_call_data_fields_unchecked_as::<E>())
} 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<E: DecodeAsFields>(
&self,
) -> Result<E, ExtrinsicError> {
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<T>,
{
/// The events associated with the extrinsic.
pub async fn events(&self) -> Result<ExtrinsicEvents<T>, EventsError> {
ExtrinsicEvents::fetch(self.client.clone(), self.hash(), self.index()).await
}
}
/// The events associated with a given extrinsic.
#[derive(Debug)]
pub struct ExtrinsicEvents<T: Config> {
// 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<T>,
// The index of the extrinsic:
extrinsic_index: usize,
// All of the events in the block:
events: crate::events::Events<T>,
}
impl<T: Config> ExtrinsicEvents<T> {
pub(crate) async fn fetch(
client: impl OnlineClientAtBlockT<T>,
extrinsic_hash: HashFor<T>,
extrinsic_index: usize,
) -> Result<Self, EventsError> {
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<T> {
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<T> {
&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<Item = Result<events::Event<'_, T>, 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<E: DecodeAsEvent>(&self) -> impl Iterator<Item = Result<E, EventsError>> {
self.iter()
.filter_map(|e| e.ok())
.filter_map(|e| e.decode_fields_as::<E>())
}
/// 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<E: DecodeAsEvent>(&self) -> bool {
self.iter().filter_map(|e| e.ok()).any(|e| e.is::<E>())
}
}
+16
View File
@@ -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
}
}
@@ -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<T>,
}
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<Item = ExtrinsicTransactionExtension<'extrinsics, 'extrinsic, T>> {
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<S: TransactionExtension<T>>(&self) -> Option<Result<S::Decoded, ExtrinsicError>> {
for ext in self.iter() {
if let Some(e) = ext.decode_as::<S>() {
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<u128> {
// Note: the overhead of iterating multiple time should be negligible.
if let Some(tip) = self.find::<ChargeTransactionPayment>() {
return Some(tip.ok()?.tip());
}
if let Some(tip) = self.find::<ChargeAssetTxPayment<T>>() {
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<u64> {
self.find::<CheckNonce>()?.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<T>,
}
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<S: TransactionExtension<T>>(
&self,
) -> Option<Result<S::Decoded, ExtrinsicError>> {
if !S::matches(self.identifier, self.ty_id, self.metadata.types()) {
return None;
}
Some(self.decode_unchecked_as::<S::Decoded>())
}
/// 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<E: DecodeAsType>(&self) -> Result<E, ExtrinsicError> {
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)
}
}
+2 -1
View File
@@ -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;
+14 -11
View File
@@ -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<T: Config, Client: OnlineClientAtBlockT<T>> SubmittableTransaction<T, Clien
// If we get a bad status or error back straight away then error, else return the hash.
match sub.next().await {
Some(Ok(status)) => 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),
))
@@ -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<T: Config, C> {
sub: Option<StreamOfResults<BackendTransactionStatus<HashFor<T>>>>,
ext_hash: HashFor<T>,
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<T: Config, C> Unpin for TransactionProgress<T, C> {}
impl<T: Config, C> TransactionProgress<T, C> {
/// Instantiate a new [`TransactionProgress`] from a custom subscription.
pub fn new(
sub: StreamOfResults<BackendTransactionStatus<HashFor<T>>>,
client: C,
ext_hash: HashFor<T>,
) -> Self {
Self {
sub: Some(sub),
client,
ext_hash,
}
}
/// Return the hash of the extrinsic.
pub fn extrinsic_hash(&self) -> HashFor<T> {
self.ext_hash
}
}
impl<T, C> TransactionProgress<T, C>
where
T: Config,
C: OnlineClientAtBlockT<T>,
{
/// 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<Result<TransactionStatus<T, C>, 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<TransactionInBlock<T, C>, 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<ExtrinsicEvents<T>, TransactionFinalizedSuccessError> {
let evs = self.wait_for_finalized().await?.wait_for_success().await?;
Ok(evs)
}
}
// TransactionProgress is a stream of transaction events
impl<T: Config, C: Clone> Stream for TransactionProgress<T, C> {
type Item = Result<TransactionStatus<T, C>, TransactionProgressError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
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<T: Config, C> {
/// 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<T, C>),
/// Transaction has been finalized by a finality-gadget, e.g GRANDPA
InFinalizedBlock(TransactionInBlock<T, C>),
/// 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<T: Config, C> TransactionStatus<T, C> {
/// 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<T, C>> {
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<T, C>> {
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<T: Config, C> {
block_ref: BlockRef<HashFor<T>>,
ext_hash: HashFor<T>,
client: C,
}
impl<T: Config, C> TransactionInBlock<T, C> {
pub(crate) fn new(block_ref: BlockRef<HashFor<T>>, ext_hash: HashFor<T>, 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<T> {
self.block_ref.hash()
}
/// Return the hash of the extrinsic that was submitted.
pub fn extrinsic_hash(&self) -> HashFor<T> {
self.ext_hash
}
}
impl<T: Config, C: OnlineClientAtBlockT<T>> TransactionInBlock<T, C> {
/// 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<ExtrinsicEvents<T>, 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<ExtrinsicEvents<T>, 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)
}
}