mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 09:21:05 +00:00
extrinsics: Decode extrinsics from blocks (#929)
* Update polkadot.scale Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics: Add extrinsics client Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics: Decode extrinsics Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Add extrinsic error Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * blocks: Expose extrinsics Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * examples: Fetch and decode block extrinsics Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Fix clippy Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics: Fetch pallet and variant index Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Move extrinsics on the subxt::blocks Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * example: Adjust example Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Collect ExtrinsicMetadata Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Implement StaticExtrinsic for the calls Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Adjust examples Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Add root level Call enum Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Adjust testing Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Add new decode interface Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Merge ExtrinsicError with BlockError Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * examples: Find first extrinsic Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Move code to extrinsic_types Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Add Extrinsic struct Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Adjust examples Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * test: Decode extinsics Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics/test: Add fake metadata for static decoding Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics/test: Decode from insufficient bytes Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics/test: Check unsupported versions Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics/test: Statically decode to root and pallet enums Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics/tests: Remove clones Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * blocks: Fetch block body inline Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * blocks: Rename ExtrinsicIds to ExtrinsicPartTypeIds Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics/test: Check decode as_extrinsic Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * blocks: Remove InsufficientData error Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * blocks: Return error from extrinsic_metadata Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics: Postpone decoding of call bytes Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata_type: Rename variables Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Adjust calls path for example and tests Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * examples: Remove traces Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * book: Add extrinsics documentation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * book: Improve extrinsics docs Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> --------- Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> Co-authored-by: James Wilson <james@jsdw.me>
This commit is contained in:
+22
-154
@@ -3,15 +3,16 @@
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::{
|
||||
blocks::{extrinsic_types::ExtrinsicPartTypeIds, Extrinsics},
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
config::{Config, Hasher, Header},
|
||||
config::{Config, Header},
|
||||
error::{BlockError, Error},
|
||||
events,
|
||||
rpc::types::ChainBlockResponse,
|
||||
runtime_api::RuntimeApi,
|
||||
storage::Storage,
|
||||
};
|
||||
use derivative::Derivative;
|
||||
|
||||
use futures::lock::Mutex as AsyncMutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -26,7 +27,7 @@ pub struct Block<T: Config, C> {
|
||||
|
||||
// A cache for our events so we don't fetch them more than once when
|
||||
// iterating over events for extrinsics.
|
||||
type CachedEvents<T> = Arc<AsyncMutex<Option<events::Events<T>>>>;
|
||||
pub(crate) type CachedEvents<T> = Arc<AsyncMutex<Option<events::Events<T>>>>;
|
||||
|
||||
impl<T, C> Block<T, C>
|
||||
where
|
||||
@@ -69,16 +70,17 @@ where
|
||||
|
||||
/// Fetch and return the block body.
|
||||
pub async fn body(&self) -> Result<BlockBody<T, C>, Error> {
|
||||
let ids = ExtrinsicPartTypeIds::new(self.client.metadata().runtime_metadata())?;
|
||||
let block_hash = self.header.hash();
|
||||
let block_details = match self.client.rpc().block(Some(block_hash)).await? {
|
||||
Some(block) => block,
|
||||
None => return Err(BlockError::not_found(block_hash).into()),
|
||||
let Some(block_details) = self.client.rpc().block(Some(block_hash)).await? else {
|
||||
return Err(BlockError::not_found(block_hash).into());
|
||||
};
|
||||
|
||||
Ok(BlockBody::new(
|
||||
self.client.clone(),
|
||||
block_details,
|
||||
self.cached_events.clone(),
|
||||
ids,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -99,6 +101,7 @@ pub struct BlockBody<T: Config, C> {
|
||||
details: ChainBlockResponse<T>,
|
||||
client: C,
|
||||
cached_events: CachedEvents<T>,
|
||||
ids: ExtrinsicPartTypeIds,
|
||||
}
|
||||
|
||||
impl<T, C> BlockBody<T, C>
|
||||
@@ -110,167 +113,32 @@ where
|
||||
client: C,
|
||||
details: ChainBlockResponse<T>,
|
||||
cached_events: CachedEvents<T>,
|
||||
ids: ExtrinsicPartTypeIds,
|
||||
) -> Self {
|
||||
Self {
|
||||
details,
|
||||
client,
|
||||
cached_events,
|
||||
ids,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over the extrinsics in the block body.
|
||||
pub fn extrinsics(&self) -> impl Iterator<Item = Extrinsic<'_, T, C>> {
|
||||
self.details
|
||||
.block
|
||||
.extrinsics
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, e)| Extrinsic {
|
||||
index: idx as u32,
|
||||
bytes: &e.0,
|
||||
client: self.client.clone(),
|
||||
block_hash: self.details.block.header.hash(),
|
||||
cached_events: self.cached_events.clone(),
|
||||
_marker: std::marker::PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A single extrinsic in a block.
|
||||
pub struct Extrinsic<'a, T: Config, C> {
|
||||
index: u32,
|
||||
bytes: &'a [u8],
|
||||
client: C,
|
||||
block_hash: T::Hash,
|
||||
cached_events: CachedEvents<T>,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T, C> Extrinsic<'a, T, C>
|
||||
where
|
||||
T: Config,
|
||||
C: OfflineClientT<T>,
|
||||
{
|
||||
/// The index of the extrinsic in the block.
|
||||
pub fn index(&self) -> u32 {
|
||||
self.index
|
||||
}
|
||||
|
||||
/// The bytes of the extrinsic.
|
||||
pub fn bytes(&self) -> &'a [u8] {
|
||||
self.bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, C> Extrinsic<'a, T, C>
|
||||
where
|
||||
T: Config,
|
||||
C: OnlineClientT<T>,
|
||||
{
|
||||
/// The events associated with the extrinsic.
|
||||
pub async fn events(&self) -> Result<ExtrinsicEvents<T>, Error> {
|
||||
let events = get_events(&self.client, self.block_hash, &self.cached_events).await?;
|
||||
let ext_hash = T::Hasher::hash_of(&self.bytes);
|
||||
Ok(ExtrinsicEvents::new(ext_hash, self.index, events))
|
||||
}
|
||||
}
|
||||
|
||||
/// The events associated with a given extrinsic.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug(bound = ""))]
|
||||
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).
|
||||
ext_hash: T::Hash,
|
||||
// The index of the extrinsic:
|
||||
idx: u32,
|
||||
// All of the events in the block:
|
||||
events: events::Events<T>,
|
||||
}
|
||||
|
||||
impl<T: Config> ExtrinsicEvents<T> {
|
||||
pub(crate) fn new(ext_hash: T::Hash, idx: u32, events: events::Events<T>) -> Self {
|
||||
Self {
|
||||
ext_hash,
|
||||
idx,
|
||||
events,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the hash of the block that the extrinsic is in.
|
||||
pub fn block_hash(&self) -> T::Hash {
|
||||
self.events.block_hash()
|
||||
}
|
||||
|
||||
/// The index of the extrinsic that these events are produced from.
|
||||
pub fn extrinsic_index(&self) -> u32 {
|
||||
self.idx
|
||||
}
|
||||
|
||||
/// Return the hash of the extrinsic.
|
||||
pub fn extrinsic_hash(&self) -> T::Hash {
|
||||
self.ext_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 transaction.
|
||||
///
|
||||
/// This works in the same way that [`events::Events::iter()`] does, with the
|
||||
/// exception that it filters out events not related to the submitted extrinsic.
|
||||
pub fn iter(&self) -> impl Iterator<Item = Result<events::EventDetails, Error>> + '_ {
|
||||
self.events.iter().filter(|ev| {
|
||||
ev.as_ref()
|
||||
.map(|ev| ev.phase() == events::Phase::ApplyExtrinsic(self.idx))
|
||||
.unwrap_or(true) // Keep any errors.
|
||||
})
|
||||
}
|
||||
|
||||
/// Find all of the transaction events matching the event type provided as a generic parameter.
|
||||
///
|
||||
/// This works in the same way that [`events::Events::find()`] does, with the
|
||||
/// exception that it filters out events not related to the submitted extrinsic.
|
||||
pub fn find<Ev: events::StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> + '_ {
|
||||
self.iter().filter_map(|ev| {
|
||||
ev.and_then(|ev| ev.as_event::<Ev>().map_err(Into::into))
|
||||
.transpose()
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterate through the transaction events using metadata to dynamically decode and skip
|
||||
/// them, and return the first event found which decodes to the provided `Ev` type.
|
||||
///
|
||||
/// This works in the same way that [`events::Events::find_first()`] does, with the
|
||||
/// exception that it ignores events not related to the submitted extrinsic.
|
||||
pub fn find_first<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, Error> {
|
||||
self.find::<Ev>().next().transpose()
|
||||
}
|
||||
|
||||
/// Iterate through the transaction events using metadata to dynamically decode and skip
|
||||
/// them, and return the last event found which decodes to the provided `Ev` type.
|
||||
///
|
||||
/// This works in the same way that [`events::Events::find_last()`] does, with the
|
||||
/// exception that it ignores events not related to the submitted extrinsic.
|
||||
pub fn find_last<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, Error> {
|
||||
self.find::<Ev>().last().transpose()
|
||||
}
|
||||
|
||||
/// Find an event in those associated with this transaction. Returns true if it was found.
|
||||
///
|
||||
/// This works in the same way that [`events::Events::has()`] does, with the
|
||||
/// exception that it ignores events not related to the submitted extrinsic.
|
||||
pub fn has<Ev: events::StaticEvent>(&self) -> Result<bool, Error> {
|
||||
Ok(self.find::<Ev>().next().transpose()?.is_some())
|
||||
// Dev note: The returned iterator is 'static + Send so that we can box it up and make
|
||||
// use of it with our `FilterExtrinsic` stuff.
|
||||
pub fn extrinsics(&self) -> Extrinsics<T, C> {
|
||||
Extrinsics::new(
|
||||
self.client.clone(),
|
||||
self.details.block.extrinsics.clone(),
|
||||
self.cached_events.clone(),
|
||||
self.ids,
|
||||
self.details.block.header.hash(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Return Events from the cache, or fetch from the node if needed.
|
||||
async fn get_events<C, T>(
|
||||
pub(crate) async fn get_events<C, T>(
|
||||
client: &C,
|
||||
block_hash: T::Hash,
|
||||
cached_events: &AsyncMutex<Option<events::Events<T>>>,
|
||||
|
||||
@@ -0,0 +1,919 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::{
|
||||
blocks::block_types::{get_events, CachedEvents},
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
config::{Config, Hasher},
|
||||
error::{BlockError, Error},
|
||||
events,
|
||||
metadata::{DecodeWithMetadata, ExtrinsicMetadata},
|
||||
rpc::types::ChainBlockExtrinsic,
|
||||
Metadata,
|
||||
};
|
||||
|
||||
use codec::Decode;
|
||||
use derivative::Derivative;
|
||||
use frame_metadata::v15::RuntimeMetadataV15;
|
||||
use scale_decode::DecodeAsFields;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
/// Trait to uniquely identify the extrinsic's identity from the runtime metadata.
|
||||
///
|
||||
/// Generated API structures that represent an extrinsic implement this trait.
|
||||
///
|
||||
/// The trait is utilized to decode emitted extrinsics from a block, via obtaining the
|
||||
/// form of the `Extrinsic` from the metadata.
|
||||
pub trait StaticExtrinsic: DecodeAsFields {
|
||||
/// Pallet name.
|
||||
const PALLET: &'static str;
|
||||
/// Call name.
|
||||
const CALL: &'static str;
|
||||
|
||||
/// Returns true if the given pallet and call names match this extrinsic.
|
||||
fn is_extrinsic(pallet: &str, call: &str) -> bool {
|
||||
Self::PALLET == pallet && Self::CALL == call
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait is implemented on the statically generated root extrinsic type, so that we're able
|
||||
/// to decode it properly via a pallet that impls `DecodeAsMetadata`. This is necessary
|
||||
/// because the "root extrinsic" type is generated using pallet info but doesn't actually exist in the
|
||||
/// metadata types, so we have no easy way to decode things into it via type information and need a
|
||||
/// little help via codegen.
|
||||
#[doc(hidden)]
|
||||
pub trait RootExtrinsic: Sized {
|
||||
/// Given details of the pallet extrinsic we want to decode, and the name of the pallet, try to hand
|
||||
/// back a "root extrinsic".
|
||||
fn root_extrinsic(
|
||||
pallet_bytes: &[u8],
|
||||
pallet_name: &str,
|
||||
pallet_extrinsic_ty: u32,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Self, Error>;
|
||||
}
|
||||
|
||||
/// The body of a block.
|
||||
pub struct Extrinsics<T: Config, C> {
|
||||
client: C,
|
||||
extrinsics: Vec<ChainBlockExtrinsic>,
|
||||
cached_events: CachedEvents<T>,
|
||||
ids: ExtrinsicPartTypeIds,
|
||||
hash: T::Hash,
|
||||
}
|
||||
|
||||
impl<T, C> Extrinsics<T, C>
|
||||
where
|
||||
T: Config,
|
||||
C: OfflineClientT<T>,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
client: C,
|
||||
extrinsics: Vec<ChainBlockExtrinsic>,
|
||||
cached_events: CachedEvents<T>,
|
||||
ids: ExtrinsicPartTypeIds,
|
||||
hash: T::Hash,
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
extrinsics,
|
||||
cached_events,
|
||||
ids,
|
||||
hash,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
||||
/// Return the block hash that these extrinsics are from.
|
||||
pub fn block_hash(&self) -> T::Hash {
|
||||
self.hash
|
||||
}
|
||||
|
||||
/// Returns an iterator over the extrinsics in the block body.
|
||||
// Dev note: The returned iterator is 'static + Send so that we can box it up and make
|
||||
// use of it with our `FilterExtrinsic` stuff.
|
||||
pub fn iter(
|
||||
&self,
|
||||
) -> impl Iterator<Item = Result<ExtrinsicDetails<T, C>, Error>> + Send + Sync + 'static {
|
||||
let extrinsics = self.extrinsics.clone();
|
||||
let num_extrinsics = self.extrinsics.len();
|
||||
let client = self.client.clone();
|
||||
let hash = self.hash;
|
||||
let cached_events = self.cached_events.clone();
|
||||
let ids = self.ids;
|
||||
let mut index = 0;
|
||||
|
||||
std::iter::from_fn(move || {
|
||||
if index == num_extrinsics {
|
||||
None
|
||||
} else {
|
||||
match ExtrinsicDetails::decode_from(
|
||||
index as u32,
|
||||
extrinsics[index].0.clone().into(),
|
||||
client.clone(),
|
||||
hash,
|
||||
cached_events.clone(),
|
||||
ids,
|
||||
) {
|
||||
Ok(extrinsic_details) => {
|
||||
index += 1;
|
||||
Some(Ok(extrinsic_details))
|
||||
}
|
||||
Err(e) => {
|
||||
index = num_extrinsics;
|
||||
Some(Err(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterate through the extrinsics using metadata to dynamically decode and skip
|
||||
/// them, and return only those which should decode to the provided `E` type.
|
||||
/// If an error occurs, all subsequent iterations return `None`.
|
||||
pub fn find<E: StaticExtrinsic>(&self) -> impl Iterator<Item = Result<E, Error>> + '_ {
|
||||
self.iter().filter_map(|e| {
|
||||
e.and_then(|e| e.as_extrinsic::<E>().map_err(Into::into))
|
||||
.transpose()
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterate through the extrinsics using metadata to dynamically decode and skip
|
||||
/// them, and return the first extrinsic found which decodes to the provided `E` type.
|
||||
pub fn find_first<E: StaticExtrinsic>(&self) -> Result<Option<E>, Error> {
|
||||
self.find::<E>().next().transpose()
|
||||
}
|
||||
|
||||
/// Iterate through the extrinsics using metadata to dynamically decode and skip
|
||||
/// them, and return the last extrinsic found which decodes to the provided `Ev` type.
|
||||
pub fn find_last<E: StaticExtrinsic>(&self) -> Result<Option<E>, Error> {
|
||||
self.find::<E>().last().transpose()
|
||||
}
|
||||
|
||||
/// Find an extrinsics that decodes to the type provided. Returns true if it was found.
|
||||
pub fn has<E: StaticExtrinsic>(&self) -> Result<bool, Error> {
|
||||
Ok(self.find::<E>().next().transpose()?.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
/// A single extrinsic in a block.
|
||||
pub struct ExtrinsicDetails<T: Config, C> {
|
||||
/// The index of the extrinsic in the block.
|
||||
index: u32,
|
||||
/// Extrinsic bytes.
|
||||
bytes: Arc<[u8]>,
|
||||
/// True if the extrinsic payload is signed.
|
||||
is_signed: bool,
|
||||
/// The start index in the `bytes` from which the address is encoded.
|
||||
address_start_idx: usize,
|
||||
/// The end index of the address in the encoded `bytes`.
|
||||
address_end_idx: usize,
|
||||
/// The start index in the `bytes` from which the call is encoded.
|
||||
call_start_idx: usize,
|
||||
/// The pallet index.
|
||||
pallet_index: u8,
|
||||
/// The variant index.
|
||||
variant_index: u8,
|
||||
/// The block hash of this extrinsic (needed to fetch events).
|
||||
block_hash: T::Hash,
|
||||
/// Subxt client.
|
||||
client: C,
|
||||
/// Cached events.
|
||||
cached_events: CachedEvents<T>,
|
||||
/// Subxt metadata to fetch the extrinsic metadata.
|
||||
metadata: Metadata,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, C> ExtrinsicDetails<T, C>
|
||||
where
|
||||
T: Config,
|
||||
C: OfflineClientT<T>,
|
||||
{
|
||||
// Attempt to dynamically decode a single extrinsic from the given input.
|
||||
pub(crate) fn decode_from(
|
||||
index: u32,
|
||||
extrinsic_bytes: Arc<[u8]>,
|
||||
client: C,
|
||||
block_hash: T::Hash,
|
||||
cached_events: CachedEvents<T>,
|
||||
ids: ExtrinsicPartTypeIds,
|
||||
) -> Result<ExtrinsicDetails<T, C>, Error> {
|
||||
const SIGNATURE_MASK: u8 = 0b1000_0000;
|
||||
const VERSION_MASK: u8 = 0b0111_1111;
|
||||
const LATEST_EXTRINSIC_VERSION: u8 = 4;
|
||||
|
||||
let metadata = client.metadata();
|
||||
|
||||
// Extrinsic are encoded in memory in the following way:
|
||||
// - first byte: abbbbbbb (a = 0 for unsigned, 1 for signed, b = version)
|
||||
// - signature: [unknown TBD with metadata].
|
||||
// - extrinsic data
|
||||
let first_byte: u8 = Decode::decode(&mut &extrinsic_bytes[..])?;
|
||||
|
||||
let version = first_byte & VERSION_MASK;
|
||||
if version != LATEST_EXTRINSIC_VERSION {
|
||||
return Err(BlockError::UnsupportedVersion(version).into());
|
||||
}
|
||||
|
||||
let is_signed = first_byte & SIGNATURE_MASK != 0;
|
||||
|
||||
// Skip over the first byte which denotes the version and signing.
|
||||
let cursor = &mut &extrinsic_bytes[1..];
|
||||
|
||||
let mut address_start_idx = 0;
|
||||
let mut address_end_idx = 0;
|
||||
|
||||
if is_signed {
|
||||
address_start_idx = extrinsic_bytes.len() - cursor.len();
|
||||
|
||||
// Skip over the address, signature and extra fields.
|
||||
scale_decode::visitor::decode_with_visitor(
|
||||
cursor,
|
||||
ids.address,
|
||||
&metadata.runtime_metadata().types,
|
||||
scale_decode::visitor::IgnoreVisitor,
|
||||
)
|
||||
.map_err(scale_decode::Error::from)?;
|
||||
address_end_idx = extrinsic_bytes.len() - cursor.len();
|
||||
|
||||
scale_decode::visitor::decode_with_visitor(
|
||||
cursor,
|
||||
ids.signature,
|
||||
&metadata.runtime_metadata().types,
|
||||
scale_decode::visitor::IgnoreVisitor,
|
||||
)
|
||||
.map_err(scale_decode::Error::from)?;
|
||||
|
||||
scale_decode::visitor::decode_with_visitor(
|
||||
cursor,
|
||||
ids.extra,
|
||||
&metadata.runtime_metadata().types,
|
||||
scale_decode::visitor::IgnoreVisitor,
|
||||
)
|
||||
.map_err(scale_decode::Error::from)?;
|
||||
}
|
||||
|
||||
let call_start_idx = extrinsic_bytes.len() - cursor.len();
|
||||
|
||||
// Decode the pallet index, then the call variant.
|
||||
let cursor = &mut &extrinsic_bytes[call_start_idx..];
|
||||
|
||||
let pallet_index: u8 = Decode::decode(cursor)?;
|
||||
let variant_index: u8 = Decode::decode(cursor)?;
|
||||
|
||||
Ok(ExtrinsicDetails {
|
||||
index,
|
||||
bytes: extrinsic_bytes,
|
||||
is_signed,
|
||||
address_start_idx,
|
||||
address_end_idx,
|
||||
call_start_idx,
|
||||
pallet_index,
|
||||
variant_index,
|
||||
block_hash,
|
||||
client,
|
||||
cached_events,
|
||||
metadata,
|
||||
_marker: std::marker::PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Is the extrinsic signed?
|
||||
pub fn is_signed(&self) -> bool {
|
||||
self.is_signed
|
||||
}
|
||||
|
||||
/// The index of the extrinsic in the block.
|
||||
pub fn index(&self) -> u32 {
|
||||
self.index
|
||||
}
|
||||
|
||||
/// Return _all_ of the bytes representing this extrinsic, which include, in order:
|
||||
/// - First byte: abbbbbbb (a = 0 for unsigned, 1 for signed, b = version)
|
||||
/// - SignatureType (if the payload is signed)
|
||||
/// - Address
|
||||
/// - Signature
|
||||
/// - Extra fields
|
||||
/// - Extrinsic call bytes
|
||||
pub fn bytes(&self) -> &[u8] {
|
||||
&self.bytes
|
||||
}
|
||||
|
||||
/// 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_bytes(&self) -> &[u8] {
|
||||
&self.bytes[self.call_start_idx..]
|
||||
}
|
||||
|
||||
/// 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 field_bytes(&self) -> &[u8] {
|
||||
// Note: this cannot panic because we checked the extrinsic bytes
|
||||
// to contain at least two bytes.
|
||||
&self.call_bytes()[2..]
|
||||
}
|
||||
|
||||
/// 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.is_signed
|
||||
.then(|| &self.bytes[self.address_start_idx..self.address_end_idx])
|
||||
}
|
||||
|
||||
/// The index of the pallet that the extrinsic originated from.
|
||||
pub fn pallet_index(&self) -> u8 {
|
||||
self.pallet_index
|
||||
}
|
||||
|
||||
/// The index of the extrinsic variant that the extrinsic originated from.
|
||||
pub fn variant_index(&self) -> u8 {
|
||||
self.variant_index
|
||||
}
|
||||
|
||||
/// The name of the pallet from whence the extrinsic originated.
|
||||
pub fn pallet_name(&self) -> Result<&str, Error> {
|
||||
Ok(self.extrinsic_metadata()?.pallet())
|
||||
}
|
||||
|
||||
/// The name of the call (ie the name of the variant that it corresponds to).
|
||||
pub fn variant_name(&self) -> Result<&str, Error> {
|
||||
Ok(self.extrinsic_metadata()?.call())
|
||||
}
|
||||
|
||||
/// Fetch the metadata for this extrinsic.
|
||||
pub fn extrinsic_metadata(&self) -> Result<&ExtrinsicMetadata, Error> {
|
||||
Ok(self
|
||||
.metadata
|
||||
.extrinsic(self.pallet_index(), self.variant_index())?)
|
||||
}
|
||||
|
||||
/// Decode and provide the extrinsic fields back in the form of a [`scale_value::Composite`]
|
||||
/// type which represents the named or unnamed fields that were
|
||||
/// present in the extrinsic.
|
||||
pub fn field_values(
|
||||
&self,
|
||||
) -> Result<scale_value::Composite<scale_value::scale::TypeId>, Error> {
|
||||
let bytes = &mut self.field_bytes();
|
||||
let extrinsic_metadata = self.extrinsic_metadata()?;
|
||||
|
||||
let decoded = <scale_value::Composite<scale_value::scale::TypeId>>::decode_as_fields(
|
||||
bytes,
|
||||
extrinsic_metadata.fields(),
|
||||
&self.metadata.runtime_metadata().types,
|
||||
)?;
|
||||
|
||||
Ok(decoded)
|
||||
}
|
||||
|
||||
/// Attempt to statically decode these [`ExtrinsicDetails`] into a type representing the extrinsic
|
||||
/// fields. This leans directly on [`codec::Decode`]. You can also attempt to decode the entirety
|
||||
/// of the extrinsic using [`Self::as_root_extrinsic()`], which is more lenient because it's able
|
||||
/// to lean on [`scale_decode::DecodeAsType`].
|
||||
pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, Error> {
|
||||
let extrinsic_metadata = self.extrinsic_metadata()?;
|
||||
if extrinsic_metadata.pallet() == E::PALLET && extrinsic_metadata.call() == E::CALL {
|
||||
let decoded = E::decode_as_fields(
|
||||
&mut self.field_bytes(),
|
||||
extrinsic_metadata.fields(),
|
||||
self.metadata.types(),
|
||||
)?;
|
||||
Ok(Some(decoded))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to decode these [`ExtrinsicDetails`] into a pallet extrinsic type (which includes
|
||||
/// the pallet enum variants as well as the extrinsic fields). These extrinsics can be found in
|
||||
/// the static codegen under a path like `pallet_name::Call`.
|
||||
pub fn as_pallet_extrinsic<E: DecodeWithMetadata>(&self) -> Result<E, Error> {
|
||||
let pallet = self.metadata.pallet(self.pallet_name()?)?;
|
||||
let extrinsic_ty = pallet.call_ty_id().ok_or_else(|| {
|
||||
Error::Metadata(crate::metadata::MetadataError::ExtrinsicNotFound(
|
||||
pallet.index(),
|
||||
self.variant_index(),
|
||||
))
|
||||
})?;
|
||||
|
||||
// Ignore the root enum index, so start 1 byte after that:
|
||||
let decoded =
|
||||
E::decode_with_metadata(&mut &self.call_bytes()[1..], extrinsic_ty, &self.metadata)?;
|
||||
Ok(decoded)
|
||||
}
|
||||
|
||||
/// Attempt to decode these [`ExtrinsicDetails`] into a root extrinsic type (which includes
|
||||
/// the pallet and extrinsic enum variants as well as the extrinsic fields). A compatible
|
||||
/// type for this is exposed via static codegen as a root level `Call` type.
|
||||
pub fn as_root_extrinsic<E: RootExtrinsic>(&self) -> Result<E, Error> {
|
||||
let pallet = self.metadata.pallet(self.pallet_name()?)?;
|
||||
let pallet_extrinsic_ty = pallet.call_ty_id().ok_or_else(|| {
|
||||
Error::Metadata(crate::metadata::MetadataError::ExtrinsicNotFound(
|
||||
pallet.index(),
|
||||
self.variant_index(),
|
||||
))
|
||||
})?;
|
||||
|
||||
// Ignore root enum index.
|
||||
E::root_extrinsic(
|
||||
&self.call_bytes()[1..],
|
||||
self.pallet_name()?,
|
||||
pallet_extrinsic_ty,
|
||||
&self.metadata,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C> ExtrinsicDetails<T, C>
|
||||
where
|
||||
T: Config,
|
||||
C: OnlineClientT<T>,
|
||||
{
|
||||
/// The events associated with the extrinsic.
|
||||
pub async fn events(&self) -> Result<ExtrinsicEvents<T>, Error> {
|
||||
let events = get_events(&self.client, self.block_hash, &self.cached_events).await?;
|
||||
let ext_hash = T::Hasher::hash_of(&self.bytes);
|
||||
Ok(ExtrinsicEvents::new(ext_hash, self.index, events))
|
||||
}
|
||||
}
|
||||
|
||||
/// The type IDs extracted from the metadata that represent the
|
||||
/// generic type parameters passed to the `UncheckedExtrinsic` from
|
||||
/// the substrate-based chain.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) struct ExtrinsicPartTypeIds {
|
||||
/// The address (source) of the extrinsic.
|
||||
address: u32,
|
||||
/// The extrinsic call type.
|
||||
// Note: the call type can be used to skip over the extrinsic bytes to check
|
||||
// they are in line with our metadata. This operation is currently postponed.
|
||||
_call: u32,
|
||||
/// The signature of the extrinsic.
|
||||
signature: u32,
|
||||
/// The extra parameters of the extrinsic.
|
||||
extra: u32,
|
||||
}
|
||||
|
||||
impl ExtrinsicPartTypeIds {
|
||||
/// Extract the generic type parameters IDs from the extrinsic type.
|
||||
pub(crate) fn new(metadata: &RuntimeMetadataV15) -> Result<Self, BlockError> {
|
||||
const ADDRESS: &str = "Address";
|
||||
const CALL: &str = "Call";
|
||||
const SIGNATURE: &str = "Signature";
|
||||
const EXTRA: &str = "Extra";
|
||||
|
||||
let id = metadata.extrinsic.ty.id;
|
||||
|
||||
let Some(ty) = metadata.types.resolve(id) else {
|
||||
return Err(BlockError::MissingType);
|
||||
};
|
||||
|
||||
let params: HashMap<_, _> = ty
|
||||
.type_params
|
||||
.iter()
|
||||
.map(|ty_param| {
|
||||
let Some(ty) = ty_param.ty else {
|
||||
return Err(BlockError::MissingType);
|
||||
};
|
||||
|
||||
Ok((ty_param.name.as_str(), ty.id))
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let Some(address) = params.get(ADDRESS) else {
|
||||
return Err(BlockError::MissingType);
|
||||
};
|
||||
let Some(call) = params.get(CALL) else {
|
||||
return Err(BlockError::MissingType);
|
||||
};
|
||||
let Some(signature) = params.get(SIGNATURE) else {
|
||||
return Err(BlockError::MissingType);
|
||||
};
|
||||
let Some(extra) = params.get(EXTRA) else {
|
||||
return Err(BlockError::MissingType);
|
||||
};
|
||||
|
||||
Ok(ExtrinsicPartTypeIds {
|
||||
address: *address,
|
||||
_call: *call,
|
||||
signature: *signature,
|
||||
extra: *extra,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The events associated with a given extrinsic.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug(bound = ""))]
|
||||
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).
|
||||
ext_hash: T::Hash,
|
||||
// The index of the extrinsic:
|
||||
idx: u32,
|
||||
// All of the events in the block:
|
||||
events: events::Events<T>,
|
||||
}
|
||||
|
||||
impl<T: Config> ExtrinsicEvents<T> {
|
||||
pub(crate) fn new(ext_hash: T::Hash, idx: u32, events: events::Events<T>) -> Self {
|
||||
Self {
|
||||
ext_hash,
|
||||
idx,
|
||||
events,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the hash of the block that the extrinsic is in.
|
||||
pub fn block_hash(&self) -> T::Hash {
|
||||
self.events.block_hash()
|
||||
}
|
||||
|
||||
/// The index of the extrinsic that these events are produced from.
|
||||
pub fn extrinsic_index(&self) -> u32 {
|
||||
self.idx
|
||||
}
|
||||
|
||||
/// Return the hash of the extrinsic.
|
||||
pub fn extrinsic_hash(&self) -> T::Hash {
|
||||
self.ext_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 transaction.
|
||||
///
|
||||
/// This works in the same way that [`events::Events::iter()`] does, with the
|
||||
/// exception that it filters out events not related to the submitted extrinsic.
|
||||
pub fn iter(&self) -> impl Iterator<Item = Result<events::EventDetails, Error>> + '_ {
|
||||
self.events.iter().filter(|ev| {
|
||||
ev.as_ref()
|
||||
.map(|ev| ev.phase() == events::Phase::ApplyExtrinsic(self.idx))
|
||||
.unwrap_or(true) // Keep any errors.
|
||||
})
|
||||
}
|
||||
|
||||
/// Find all of the transaction events matching the event type provided as a generic parameter.
|
||||
///
|
||||
/// This works in the same way that [`events::Events::find()`] does, with the
|
||||
/// exception that it filters out events not related to the submitted extrinsic.
|
||||
pub fn find<Ev: events::StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> + '_ {
|
||||
self.iter().filter_map(|ev| {
|
||||
ev.and_then(|ev| ev.as_event::<Ev>().map_err(Into::into))
|
||||
.transpose()
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterate through the transaction events using metadata to dynamically decode and skip
|
||||
/// them, and return the first event found which decodes to the provided `Ev` type.
|
||||
///
|
||||
/// This works in the same way that [`events::Events::find_first()`] does, with the
|
||||
/// exception that it ignores events not related to the submitted extrinsic.
|
||||
pub fn find_first<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, Error> {
|
||||
self.find::<Ev>().next().transpose()
|
||||
}
|
||||
|
||||
/// Iterate through the transaction events using metadata to dynamically decode and skip
|
||||
/// them, and return the last event found which decodes to the provided `Ev` type.
|
||||
///
|
||||
/// This works in the same way that [`events::Events::find_last()`] does, with the
|
||||
/// exception that it ignores events not related to the submitted extrinsic.
|
||||
pub fn find_last<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, Error> {
|
||||
self.find::<Ev>().last().transpose()
|
||||
}
|
||||
|
||||
/// Find an event in those associated with this transaction. Returns true if it was found.
|
||||
///
|
||||
/// This works in the same way that [`events::Events::has()`] does, with the
|
||||
/// exception that it ignores events not related to the submitted extrinsic.
|
||||
pub fn has<Ev: events::StaticEvent>(&self) -> Result<bool, Error> {
|
||||
Ok(self.find::<Ev>().next().transpose()?.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{rpc::types::RuntimeVersion, OfflineClient, PolkadotConfig};
|
||||
use assert_matches::assert_matches;
|
||||
use codec::{Decode, Encode};
|
||||
use frame_metadata::{
|
||||
v15::{ExtrinsicMetadata, PalletCallMetadata, PalletMetadata, RuntimeMetadataV15},
|
||||
RuntimeMetadataPrefixed,
|
||||
};
|
||||
use primitive_types::H256;
|
||||
use scale_info::{meta_type, TypeInfo};
|
||||
use scale_value::Value;
|
||||
|
||||
// Extrinsic needs to contain at least the generic type parameter "Call"
|
||||
// for the metadata to be valid.
|
||||
// The "Call" type from the metadata is used to decode extrinsics.
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct ExtrinsicType<Address, Call, Signature, Extra> {
|
||||
pub signature: Option<(Address, Signature, Extra)>,
|
||||
pub function: Call,
|
||||
}
|
||||
|
||||
// Because this type is used to decode extrinsics, we expect this to be a TypeDefVariant.
|
||||
// Each pallet must contain one single variant.
|
||||
#[allow(unused)]
|
||||
#[derive(
|
||||
Encode,
|
||||
Decode,
|
||||
TypeInfo,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
scale_encode::EncodeAsType,
|
||||
scale_decode::DecodeAsType,
|
||||
)]
|
||||
enum RuntimeCall {
|
||||
Test(Pallet),
|
||||
}
|
||||
|
||||
// We need this in order to be able to decode into a root extrinsic type:
|
||||
impl RootExtrinsic for RuntimeCall {
|
||||
fn root_extrinsic(
|
||||
mut pallet_bytes: &[u8],
|
||||
pallet_name: &str,
|
||||
pallet_extrinsic_ty: u32,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Self, Error> {
|
||||
if pallet_name == "Test" {
|
||||
return Ok(RuntimeCall::Test(Pallet::decode_with_metadata(
|
||||
&mut pallet_bytes,
|
||||
pallet_extrinsic_ty,
|
||||
metadata,
|
||||
)?));
|
||||
}
|
||||
panic!(
|
||||
"Asked for pallet name '{pallet_name}', which isn't in our test RuntimeCall type"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// The calls of the pallet.
|
||||
#[allow(unused)]
|
||||
#[derive(
|
||||
Encode,
|
||||
Decode,
|
||||
TypeInfo,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
scale_encode::EncodeAsType,
|
||||
scale_decode::DecodeAsType,
|
||||
)]
|
||||
enum Pallet {
|
||||
#[allow(unused)]
|
||||
#[codec(index = 2)]
|
||||
TestCall {
|
||||
value: u128,
|
||||
signed: bool,
|
||||
name: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(
|
||||
Encode,
|
||||
Decode,
|
||||
TypeInfo,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
scale_encode::EncodeAsType,
|
||||
scale_decode::DecodeAsType,
|
||||
)]
|
||||
struct TestCallExtrinsic {
|
||||
value: u128,
|
||||
signed: bool,
|
||||
name: String,
|
||||
}
|
||||
impl StaticExtrinsic for TestCallExtrinsic {
|
||||
const PALLET: &'static str = "Test";
|
||||
const CALL: &'static str = "TestCall";
|
||||
}
|
||||
|
||||
/// Build fake metadata consisting the types needed to represent an extrinsic.
|
||||
fn metadata() -> Metadata {
|
||||
let pallets = vec![PalletMetadata {
|
||||
name: "Test",
|
||||
storage: None,
|
||||
calls: Some(PalletCallMetadata {
|
||||
ty: meta_type::<Pallet>(),
|
||||
}),
|
||||
event: None,
|
||||
constants: vec![],
|
||||
error: None,
|
||||
index: 0,
|
||||
docs: vec![],
|
||||
}];
|
||||
|
||||
let extrinsic = ExtrinsicMetadata {
|
||||
ty: meta_type::<ExtrinsicType<(), RuntimeCall, (), ()>>(),
|
||||
version: 4,
|
||||
signed_extensions: vec![],
|
||||
};
|
||||
|
||||
let meta = RuntimeMetadataV15::new(pallets, extrinsic, meta_type::<()>(), vec![]);
|
||||
let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
|
||||
|
||||
Metadata::try_from(runtime_metadata).unwrap()
|
||||
}
|
||||
|
||||
/// Build an offline client to work with the test metadata.
|
||||
fn client(metadata: Metadata) -> OfflineClient<PolkadotConfig> {
|
||||
// Create the encoded extrinsic bytes.
|
||||
let rt_version = RuntimeVersion {
|
||||
spec_version: 1,
|
||||
transaction_version: 4,
|
||||
other: Default::default(),
|
||||
};
|
||||
let block_hash = H256::random();
|
||||
OfflineClient::new(block_hash, rt_version, metadata)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extrinsic_metadata_consistency() {
|
||||
let metadata = metadata();
|
||||
|
||||
// Except our metadata to contain the registered types.
|
||||
let extrinsic = metadata
|
||||
.extrinsic(0, 2)
|
||||
.expect("metadata contains the RuntimeCall enum with this pallet");
|
||||
assert_eq!(extrinsic.pallet(), "Test");
|
||||
assert_eq!(extrinsic.call(), "TestCall");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insufficient_extrinsic_bytes() {
|
||||
let metadata = metadata();
|
||||
let client = client(metadata.clone());
|
||||
let ids = ExtrinsicPartTypeIds::new(metadata.runtime_metadata()).unwrap();
|
||||
|
||||
// Decode with empty bytes.
|
||||
let result = ExtrinsicDetails::decode_from(
|
||||
1,
|
||||
vec![].into(),
|
||||
client,
|
||||
H256::random(),
|
||||
Default::default(),
|
||||
ids,
|
||||
);
|
||||
assert_matches!(result.err(), Some(crate::Error::Codec(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_version_extrinsic() {
|
||||
let metadata = metadata();
|
||||
let client = client(metadata.clone());
|
||||
let ids = ExtrinsicPartTypeIds::new(metadata.runtime_metadata()).unwrap();
|
||||
|
||||
// Decode with invalid version.
|
||||
let result = ExtrinsicDetails::decode_from(
|
||||
1,
|
||||
3u8.encode().into(),
|
||||
client,
|
||||
H256::random(),
|
||||
Default::default(),
|
||||
ids,
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
result.err(),
|
||||
Some(crate::Error::Block(
|
||||
crate::error::BlockError::UnsupportedVersion(3)
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn statically_decode_extrinsic() {
|
||||
let metadata = metadata();
|
||||
let client = client(metadata.clone());
|
||||
let ids = ExtrinsicPartTypeIds::new(metadata.runtime_metadata()).unwrap();
|
||||
|
||||
let tx = crate::tx::dynamic(
|
||||
"Test",
|
||||
"TestCall",
|
||||
vec![
|
||||
Value::u128(10),
|
||||
Value::bool(true),
|
||||
Value::string("SomeValue"),
|
||||
],
|
||||
);
|
||||
let tx_encoded = client
|
||||
.tx()
|
||||
.create_unsigned(&tx)
|
||||
.expect("Valid dynamic parameters are provided");
|
||||
|
||||
// Note: `create_unsigned` produces the extrinsic bytes by prefixing the extrinsic length.
|
||||
// The length is handled deserializing `ChainBlockExtrinsic`, therefore the first byte is not needed.
|
||||
let extrinsic = ExtrinsicDetails::decode_from(
|
||||
1,
|
||||
tx_encoded.encoded()[1..].into(),
|
||||
client,
|
||||
H256::random(),
|
||||
Default::default(),
|
||||
ids,
|
||||
)
|
||||
.expect("Valid extrinsic");
|
||||
|
||||
assert!(!extrinsic.is_signed());
|
||||
|
||||
assert_eq!(extrinsic.index(), 1);
|
||||
|
||||
assert_eq!(extrinsic.pallet_index(), 0);
|
||||
assert_eq!(
|
||||
extrinsic
|
||||
.pallet_name()
|
||||
.expect("Valid metadata contains pallet name"),
|
||||
"Test"
|
||||
);
|
||||
|
||||
assert_eq!(extrinsic.variant_index(), 2);
|
||||
assert_eq!(
|
||||
extrinsic
|
||||
.variant_name()
|
||||
.expect("Valid metadata contains variant name"),
|
||||
"TestCall"
|
||||
);
|
||||
|
||||
// Decode the extrinsic to the root enum.
|
||||
let decoded_extrinsic = extrinsic
|
||||
.as_root_extrinsic::<RuntimeCall>()
|
||||
.expect("can decode extrinsic to root enum");
|
||||
|
||||
assert_eq!(
|
||||
decoded_extrinsic,
|
||||
RuntimeCall::Test(Pallet::TestCall {
|
||||
value: 10,
|
||||
signed: true,
|
||||
name: "SomeValue".into(),
|
||||
})
|
||||
);
|
||||
|
||||
// Decode the extrinsic to the pallet enum.
|
||||
let decoded_extrinsic = extrinsic
|
||||
.as_pallet_extrinsic::<Pallet>()
|
||||
.expect("can decode extrinsic to pallet enum");
|
||||
|
||||
assert_eq!(
|
||||
decoded_extrinsic,
|
||||
Pallet::TestCall {
|
||||
value: 10,
|
||||
signed: true,
|
||||
name: "SomeValue".into(),
|
||||
}
|
||||
);
|
||||
|
||||
// Decode the extrinsic to the extrinsic variant.
|
||||
let decoded_extrinsic = extrinsic
|
||||
.as_extrinsic::<TestCallExtrinsic>()
|
||||
.expect("can decode extrinsic to extrinsic variant")
|
||||
.expect("value cannot be None");
|
||||
|
||||
assert_eq!(
|
||||
decoded_extrinsic,
|
||||
TestCallExtrinsic {
|
||||
value: 10,
|
||||
signed: true,
|
||||
name: "SomeValue".into(),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,10 @@
|
||||
|
||||
mod block_types;
|
||||
mod blocks_client;
|
||||
mod extrinsic_types;
|
||||
|
||||
pub use block_types::{Block, BlockBody, Extrinsic, ExtrinsicEvents};
|
||||
pub use block_types::{Block, BlockBody};
|
||||
pub use blocks_client::{subscribe_to_block_headers_filling_in_gaps, BlocksClient};
|
||||
pub use extrinsic_types::{
|
||||
ExtrinsicDetails, ExtrinsicEvents, Extrinsics, RootExtrinsic, StaticExtrinsic,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user