Files
pezkuwi-subxt/new/src/extrinsics.rs
T
2025-12-09 16:59:04 +00:00

422 lines
15 KiB
Rust

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 std::sync::Arc;
use subxt_metadata::Metadata;
pub use decode_as_extrinsic::DecodeAsExtrinsic;
pub use extrinsic_transaction_extensions::{
ExtrinsicTransactionExtension, ExtrinsicTransactionExtensions,
};
/// A client for working with extrinsics.
pub struct ExtrinsicsClient<'atblock, T, Client> {
client: &'atblock Client,
marker: PhantomData<T>,
}
impl<'atblock, T, Client> ExtrinsicsClient<'atblock, T, Client> {
pub(crate) fn new(client: &'atblock Client) -> Self {
ExtrinsicsClient {
client,
marker: PhantomData,
}
}
}
impl<'atblock, T: Config, Client: OfflineClientAtBlockT<T>> ExtrinsicsClient<'atblock, T, Client> {
/// Work with the block body bytes given.
///
/// No attempt to validate the provided bytes is made here; if invalid bytes are
/// provided then attempting to iterate and decode them will fail.
pub async fn from_bytes(&self, extrinsics: Vec<Vec<u8>>) -> Extrinsics<'atblock, T, Client> {
Extrinsics {
client: self.client,
extrinsics: Arc::new(extrinsics),
marker: PhantomData,
}
}
}
impl<'atblock, T: Config, Client: OnlineClientAtBlockT<T>> ExtrinsicsClient<'atblock, T, Client> {
/// Fetch the extrinsics at this block.
pub async fn fetch(&self) -> Result<Extrinsics<'atblock, T, Client>, ExtrinsicError> {
let client = self.client;
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: Arc::new(extrinsics),
marker: PhantomData,
})
}
}
/// The extrinsics in a block.
#[derive(Debug, Clone)]
pub struct Extrinsics<'atblock, T, C> {
client: &'atblock C,
extrinsics: Arc<Vec<Vec<u8>>>,
marker: PhantomData<T>,
}
impl<'atblock, T: Config, C: OfflineClientAtBlockT<T>> Extrinsics<'atblock, 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;
let all_extrinsic_bytes = self.extrinsics.clone();
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: Arc::new(info),
extrinsics: Arc::clone(&all_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<'atblock, T: Config, C> {
client: &'atblock C,
/// The index of the extrinsic in the block.
index: usize,
/// Information about the extrinsic
info: Arc<ExtrinsicInfo<'atblock, u32>>,
/// All extrinsic bytes. use the index to select the correct bytes.
extrinsics: Arc<Vec<Vec<u8>>>,
/// Hash the extrinsic if we want.
hasher: &'atblock T::Hasher,
/// Subxt metadata to fetch the extrinsic metadata.
metadata: &'atblock Metadata,
}
impl<'atblock, T, C> Extrinsic<'atblock, 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> {
self.hasher.hash(&self.extrinsics[self.index])
}
/// 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) -> &[u8] {
&self.extrinsics[self.index]
}
/// 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) -> &[u8] {
&self.bytes()[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) -> &[u8] {
&self.bytes()[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<&[u8]> {
self.info
.signature_payload()
.map(|s| &self.bytes()[s.address_range()])
}
/// Returns Some(signature_bytes) if the extrinsic was signed otherwise None is returned.
pub fn signature_bytes(&self) -> Option<&[u8]> {
self.info
.signature_payload()
.map(|s| &self.bytes()[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.bytes()[t.range()])
}
/// Returns `None` if the extrinsic is not signed.
pub fn transaction_extensions(
&self,
) -> Option<ExtrinsicTransactionExtensions<'atblock, '_, T>> {
let bytes = self.bytes();
let metadata = self.metadata;
self.info
.transaction_extension_payload()
.map(move |t| ExtrinsicTransactionExtensions::new(bytes, 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<'atblock, T, C> Extrinsic<'atblock, T, C>
where
T: Config,
C: OnlineClientAtBlockT<T>,
{
/// The events associated with the extrinsic.
pub async fn events(&self) -> Result<ExtrinsicEvents<'atblock, T>, EventsError> {
ExtrinsicEvents::fetch(self.client, self.hash(), self.index()).await
}
}
/// The events associated with a given extrinsic.
#[derive(Debug)]
pub struct ExtrinsicEvents<'atblock, 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<'atblock, T>,
}
impl<'atblock, T: Config> ExtrinsicEvents<'atblock, T> {
pub(crate) async fn fetch(
client: &'atblock impl OnlineClientAtBlockT<T>,
extrinsic_hash: HashFor<T>,
extrinsic_index: usize,
) -> Result<Self, EventsError> {
let events = crate::events::EventsClient::new(client).fetch().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<'atblock, 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>())
}
}