mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 07:41:08 +00:00
Finish first draft of transactions, extrinsics (blocks) and events
This commit is contained in:
+1
-1
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>())
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user