From 0be58436273d65bbeeee20b0c588287fb9d5bd07 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Mon, 24 Apr 2023 19:17:05 +0300 Subject: [PATCH 01/38] Update polkadot.scale Signed-off-by: Alexandru Vasile --- artifacts/polkadot_metadata.scale | Bin 356565 -> 383934 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/artifacts/polkadot_metadata.scale b/artifacts/polkadot_metadata.scale index b0d3e7de879b563b10e38c65ea021b0e15d015bc..0b480c52435ef1f0947f5eb060aa17e2d2b84b7c 100644 GIT binary patch delta 75774 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 delta 56548 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 From 5d8c70c0331b3d62a20ec4bf761ed7a211da58ab Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Mon, 24 Apr 2023 19:17:32 +0300 Subject: [PATCH 02/38] extrinsics: Add extrinsics client Signed-off-by: Alexandru Vasile --- subxt/src/extrinsics/extrinsics_client.rs | 84 +++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 subxt/src/extrinsics/extrinsics_client.rs diff --git a/subxt/src/extrinsics/extrinsics_client.rs b/subxt/src/extrinsics/extrinsics_client.rs new file mode 100644 index 0000000000..b2c6edb9f7 --- /dev/null +++ b/subxt/src/extrinsics/extrinsics_client.rs @@ -0,0 +1,84 @@ +// 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::{ + client::OnlineClientT, + error::{BlockError, Error}, + extrinsics::Extrinsics, + Config, +}; +use derivative::Derivative; +use std::future::Future; + +/// A client for working with extrinsics. +#[derive(Derivative)] +#[derivative(Clone(bound = "Client: Clone"))] +pub struct ExtrinsicsClient { + client: Client, + _marker: std::marker::PhantomData, +} + +impl ExtrinsicsClient { + /// Create a new [`ExtrinsicsClient`]. + pub fn new(client: Client) -> Self { + Self { + client, + _marker: std::marker::PhantomData, + } + } +} + +impl ExtrinsicsClient +where + T: Config, + Client: OnlineClientT, +{ + /// Obtain extrinsics at some block hash. + /// + /// # Warning + /// + /// This call only supports blocks produced since the most recent + /// runtime upgrade. You can attempt to retrieve extrinsics from older blocks, + /// but may run into errors attempting to work with them. + pub fn at( + &self, + block_hash: T::Hash, + ) -> impl Future, Error>> + Send + 'static { + self.at_or_latest(Some(block_hash)) + } + + /// Obtain extrinsics at the latest block hash. + pub fn at_latest(&self) -> impl Future, Error>> + Send + 'static { + self.at_or_latest(None) + } + + /// Obtain extrinsics at some block hash. + fn at_or_latest( + &self, + block_hash: Option, + ) -> impl Future, Error>> + Send + 'static { + // Clone and pass the client in like this so that we can explicitly + // return a Future that's Send + 'static, rather than tied to &self. + let client = self.client.clone(); + async move { + // If block hash is not provided, get the hash + // for the latest block and use that to create an explicit error. + let block_hash = match block_hash { + Some(hash) => hash, + None => client + .rpc() + .block_hash(None) + .await? + .expect("didn't pass a block number; qed"), + }; + + let result = client.rpc().block(Some(block_hash)).await?; + let Some(block_details) = result else { + return Err(BlockError::not_found(block_hash).into()); + }; + + Extrinsics::new(client.metadata(), block_details.block) + } + } +} From 664b2a699de9df6821e958f84d9969404577dec3 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Mon, 24 Apr 2023 19:20:01 +0300 Subject: [PATCH 03/38] extrinsics: Decode extrinsics Signed-off-by: Alexandru Vasile --- subxt/src/extrinsics/extrinsics_type.rs | 324 ++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 subxt/src/extrinsics/extrinsics_type.rs diff --git a/subxt/src/extrinsics/extrinsics_type.rs b/subxt/src/extrinsics/extrinsics_type.rs new file mode 100644 index 0000000000..f02d486e6f --- /dev/null +++ b/subxt/src/extrinsics/extrinsics_type.rs @@ -0,0 +1,324 @@ +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +use crate::{ + config::{Config, Header}, + error::{Error, ExtrinsicError}, + rpc::types::ChainBlock, + Metadata, +}; +use codec::Decode; +use derivative::Derivative; +use frame_metadata::v15::RuntimeMetadataV15; +use std::{collections::HashMap, sync::Arc}; + +/// A collection of extrinsics obtained from a block, bundled with the necessary +/// information needed to decode and iterate over them. +#[derive(Derivative)] +#[derivative(Debug(bound = ""), Clone(bound = ""))] +pub struct Extrinsics { + metadata: Metadata, + /// The block hash. + hash: T::Hash, + /// The accompanying extrinsics. + extrinsics: Vec>, + /// Generic extrinsic parameter ids from the metadata. + ids: ExtrinsicIds, +} + +impl Extrinsics { + pub(crate) fn new(metadata: Metadata, block: ChainBlock) -> Result { + let ids = ExtrinsicIds::new(metadata.runtime_metadata())?; + + let extrinsics: Vec<_> = block + .extrinsics + .into_iter() + .map(|ext| ext.0.into()) + .collect(); + + Ok(Self { + metadata, + hash: block.header.hash(), + extrinsics, + ids, + }) + } + + /// The number of extrinsics. + pub fn len(&self) -> usize { + self.extrinsics.len() + } + + /// Are there no extrinsics in this block? + pub fn is_empty(&self) -> bool { + self.extrinsics.len() == 0 + } + + /// Return the block hash that these extrinsics are from. + pub fn block_hash(&self) -> T::Hash { + self.hash + } + + /// 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> + Send + Sync + 'static { + let metadata = self.metadata.clone(); + let extrinsics = self.extrinsics.clone(); + let ids = self.ids.clone(); + let num_extr = self.extrinsics.len(); + let mut index = 0; + std::iter::from_fn(move || { + if index == num_extr { + None + } else { + match ExtrinsicDetails::decode_from::( + metadata.clone(), + extrinsics[index].clone(), + ids, + index, + ) { + Ok(event_details) => { + // 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: + index = num_extr; + Some(Err(e)) + } + } + } + }) + } +} + +/// 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)] +struct ExtrinsicIds { + /// The address (source) of the extrinsic. + address: u32, + /// The extrinsic call type. + call: u32, + /// The signature of the extrinsic. + signature: u32, + /// The extra parameters of the extrinsic. + extra: u32, +} + +impl ExtrinsicIds { + /// Extract the generic type parameters IDs from the extrinsic type. + fn new(metadata: &RuntimeMetadataV15) -> Result { + 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(ExtrinsicError::MissingType); + }; + + let params: HashMap<_, _> = ty + .type_params + .iter() + .map(|ty_param| { + let Some(ty) = ty_param.ty else { + return Err(ExtrinsicError::MissingType); + }; + + Ok((ty_param.name.as_str(), ty.id)) + }) + .collect::>()?; + + let Some(address) = params.get(ADDRESS) else { + return Err(ExtrinsicError::MissingType); + }; + let Some(call) = params.get(CALL) else { + return Err(ExtrinsicError::MissingType); + }; + let Some(signature) = params.get(SIGNATURE) else { + return Err(ExtrinsicError::MissingType); + }; + let Some(extra) = params.get(EXTRA) else { + return Err(ExtrinsicError::MissingType); + }; + + Ok(ExtrinsicIds { + address: *address, + call: *call, + signature: *signature, + extra: *extra, + }) + } +} + +/// The extrinsic details. +#[derive(Debug, Clone)] +pub struct ExtrinsicDetails { + /// The index of the extrinsic in the block. + index: usize, + /// 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, + /// Extrinsic bytes. + bytes: Arc<[u8]>, +} + +impl ExtrinsicDetails { + // Attempt to dynamically decode a single event from our events input. + fn decode_from( + metadata: Metadata, + extrinsic_bytes: Arc<[u8]>, + ids: ExtrinsicIds, + index: usize, + ) -> Result { + const SIGNATURE_MASK: u8 = 0b1000_0000; + const VERSION_MASK: u8 = 0b0111_1111; + const LATEST_EXTRINSIC_VERSION: u8 = 4; + + // 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 + if extrinsic_bytes.is_empty() { + return Err(ExtrinsicError::InsufficientData.into()); + } + + let version = extrinsic_bytes[0] & VERSION_MASK; + if version != LATEST_EXTRINSIC_VERSION { + return Err(ExtrinsicError::UnsupportedVersion(version).into()); + } + + let is_signed = extrinsic_bytes[0] & 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(); + + // Postpone decoding the extrinsic function call. + scale_decode::visitor::decode_with_visitor( + cursor, + ids.call, + &metadata.runtime_metadata().types, + scale_decode::visitor::IgnoreVisitor, + ) + .map_err(scale_decode::Error::from)?; + + Ok(ExtrinsicDetails { + index, + is_signed, + address_start_idx, + address_end_idx, + call_start_idx, + bytes: extrinsic_bytes, + }) + } + + /// The index of the extrinsic in the given block. + /// What index is this event in the stored events for this block. + pub fn index(&self) -> usize { + 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. + /// + /// # 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 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]) + } + + /// Attempt to statically decode the address bytes into the provided type. + /// + /// # Note + /// + /// Returns `None` if the extrinsic is not signed. + pub fn as_address(&self) -> Option> { + self.address_bytes() + .and_then(|bytes| Some(T::decode(&mut &bytes[..]).map_err(Error::Codec))) + } + + /// Attempt to statically decode the extrinsic call bytes into the provided type. + pub fn as_call(&self) -> Result { + let bytes = &mut &self.call_bytes()[..]; + T::decode(bytes).map_err(Error::Codec) + } + + /// Is the extrinsic signed? + pub fn is_signed(&self) -> bool { + self.is_signed + } +} From 7f85f4bda8e387b343cbc5fbf583b86c0d7488f2 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Mon, 24 Apr 2023 19:20:16 +0300 Subject: [PATCH 04/38] subxt: Add extrinsic error Signed-off-by: Alexandru Vasile --- subxt/src/error/mod.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/subxt/src/error/mod.rs b/subxt/src/error/mod.rs index 16f3bbfabc..1e16c52e79 100644 --- a/subxt/src/error/mod.rs +++ b/subxt/src/error/mod.rs @@ -57,6 +57,9 @@ pub enum Error { /// Block related error. #[error("Block error: {0}")] Block(#[from] BlockError), + /// Extrinsic related error. + #[error("Extrinsic error: {0}")] + Extrinsic(#[from] ExtrinsicError), /// An error encoding a storage address. #[error("Error encoding storage address: {0}")] StorageAddress(#[from] StorageAddressError), @@ -112,6 +115,25 @@ impl BlockError { } } +/// Extrinsic error. +#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)] +#[non_exhaustive] +pub enum ExtrinsicError { + /// Extrinsic type ID cannot be resolved with the provided metadata. + #[error("Extrinsic type ID cannot be resolved with the provided metadata. Make sure this is a valid metadata")] + MissingType, + /// Expected more extrinsic bytes. + #[error("Expected more extrinsic bytes")] + InsufficientData, + /// Unsupported signature. + #[error("Unsupported extrinsic version, only version 4 is supported currently")] + /// The extrinsic has an unsupported version. + UnsupportedVersion(u8), + /// Decoding error. + #[error("Cannot decode extrinsic: {0}")] + DecodingError(codec::Error), +} + /// Transaction error. #[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)] #[non_exhaustive] From dd16475bc10f403d4eabc5361f9d1e0d085bc5e6 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Mon, 24 Apr 2023 19:21:37 +0300 Subject: [PATCH 05/38] blocks: Expose extrinsics Signed-off-by: Alexandru Vasile --- subxt/src/blocks/block_types.rs | 23 +++++++++++++++++------ subxt/src/extrinsics/mod.rs | 13 +++++++++++++ subxt/src/lib.rs | 1 + 3 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 subxt/src/extrinsics/mod.rs diff --git a/subxt/src/blocks/block_types.rs b/subxt/src/blocks/block_types.rs index 3c98e40d39..4978e2becd 100644 --- a/subxt/src/blocks/block_types.rs +++ b/subxt/src/blocks/block_types.rs @@ -6,7 +6,7 @@ use crate::{ client::{OfflineClientT, OnlineClientT}, config::{Config, Hasher, Header}, error::{BlockError, Error}, - events, + events, extrinsics, rpc::types::ChainBlockResponse, runtime_api::RuntimeApi, storage::Storage, @@ -69,11 +69,7 @@ where /// Fetch and return the block body. pub async fn body(&self) -> Result, Error> { - 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 block_details = self.block_details().await?; Ok(BlockBody::new( self.client.clone(), @@ -82,6 +78,12 @@ where )) } + /// Return the extrinsics associated with the block. + pub async fn extrinsics(&self) -> Result, Error> { + let block_details = self.block_details().await?; + extrinsics::Extrinsics::new(self.client.metadata(), block_details.block) + } + /// Work with storage. pub fn storage(&self) -> Storage { let block_hash = self.hash(); @@ -92,6 +94,15 @@ where pub async fn runtime_api(&self) -> Result, Error> { Ok(RuntimeApi::new(self.client.clone(), self.hash())) } + + /// Fetch the block's body from the chain. + async fn block_details(&self) -> Result, Error> { + let block_hash = self.header.hash(); + match self.client.rpc().block(Some(block_hash)).await? { + Some(block) => Ok(block), + None => Err(BlockError::not_found(block_hash).into()), + } + } } /// The body of a block. diff --git a/subxt/src/extrinsics/mod.rs b/subxt/src/extrinsics/mod.rs new file mode 100644 index 0000000000..435026c469 --- /dev/null +++ b/subxt/src/extrinsics/mod.rs @@ -0,0 +1,13 @@ +// 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. + +//! This module exposes the types and such necessary for working with extrinsics. +//! The two main entry points into events are [`crate::OnlineClient::events()`] +//! and calls like [crate::tx::TxProgress::wait_for_finalized_success()]. + +mod extrinsics_client; +mod extrinsics_type; + +pub use extrinsics_client::ExtrinsicsClient; +pub use extrinsics_type::{ExtrinsicDetails, Extrinsics}; diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index 3b41f95963..6317f1a266 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -155,6 +155,7 @@ pub mod constants; pub mod dynamic; pub mod error; pub mod events; +pub mod extrinsics; pub mod metadata; pub mod rpc; pub mod runtime_api; From f475ddf24098f7f722137996cfdc3729cda5522a Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Mon, 24 Apr 2023 19:22:41 +0300 Subject: [PATCH 06/38] examples: Fetch and decode block extrinsics Signed-off-by: Alexandru Vasile --- examples/examples/block_extrinsics.rs | 85 +++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 examples/examples/block_extrinsics.rs diff --git a/examples/examples/block_extrinsics.rs b/examples/examples/block_extrinsics.rs new file mode 100644 index 0000000000..8550570841 --- /dev/null +++ b/examples/examples/block_extrinsics.rs @@ -0,0 +1,85 @@ +// 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. + +//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da. +//! +//! E.g. +//! ```bash +//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location +//! polkadot --dev --tmp +//! ``` + +use futures::StreamExt; +use sp_keyring::AccountKeyring; +use std::time::Duration; +use subxt::{tx::PairSigner, Config, OnlineClient, PolkadotConfig}; + +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +pub mod polkadot {} + +/// Subscribe to all events, and then manually look through them and +/// pluck out the events that we care about. +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber::fmt::init(); + + // Create a client to use: + let api = OnlineClient::::new().await?; + + // Subscribe to (in this case, finalized) blocks. + let mut block_sub = api.blocks().subscribe_finalized().await?; + + // While this subscription is active, balance transfers are made somewhere: + tokio::task::spawn({ + let api = api.clone(); + async move { + let signer = PairSigner::new(AccountKeyring::Alice.pair()); + let mut transfer_amount = 1_000_000_000; + + // Make small balance transfers from Alice to Bob in a loop: + loop { + let transfer_tx = polkadot::tx() + .balances() + .transfer(AccountKeyring::Bob.to_account_id().into(), transfer_amount); + api.tx() + .sign_and_submit_default(&transfer_tx, &signer) + .await + .unwrap(); + + tokio::time::sleep(Duration::from_secs(10)).await; + transfer_amount += 100_000_000; + } + } + }); + + // Get each finalized block as it arrives. + while let Some(block) = block_sub.next().await { + let block = block?; + + let block_hash = block.hash(); + println!(" Block {:?}", block_hash); + + // Ask for the extrinsics for this block. + let extrinsics = block.extrinsics().await?; + + // Ask for the extrinsics for this block. + for extrinsic in extrinsics.iter() { + let extrinsic = extrinsic?; + println!(" Extrinsic index {:?}", extrinsic.index()); + + let call: polkadot::runtime_types::polkadot_runtime::RuntimeCall = + extrinsic.as_call()?; + println!(" Extrinsic call: {:?}", call); + + if extrinsic.is_signed() { + let address = extrinsic.as_address::<::Address>(); + println!(" Extrinsic address: {:?}", address); + } + } + + println!("\n"); + } + + Ok(()) +} From 00abf923ed45d3c564c021b83e2cf8d72a622160 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Mon, 24 Apr 2023 19:44:31 +0300 Subject: [PATCH 07/38] Fix clippy Signed-off-by: Alexandru Vasile --- subxt/src/extrinsics/extrinsics_type.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/subxt/src/extrinsics/extrinsics_type.rs b/subxt/src/extrinsics/extrinsics_type.rs index f02d486e6f..49471e26bd 100644 --- a/subxt/src/extrinsics/extrinsics_type.rs +++ b/subxt/src/extrinsics/extrinsics_type.rs @@ -69,14 +69,14 @@ impl Extrinsics { ) -> impl Iterator> + Send + Sync + 'static { let metadata = self.metadata.clone(); let extrinsics = self.extrinsics.clone(); - let ids = self.ids.clone(); + let ids = self.ids; let num_extr = self.extrinsics.len(); let mut index = 0; std::iter::from_fn(move || { if index == num_extr { None } else { - match ExtrinsicDetails::decode_from::( + match ExtrinsicDetails::decode_from( metadata.clone(), extrinsics[index].clone(), ids, @@ -183,7 +183,7 @@ pub struct ExtrinsicDetails { impl ExtrinsicDetails { // Attempt to dynamically decode a single event from our events input. - fn decode_from( + fn decode_from( metadata: Metadata, extrinsic_bytes: Arc<[u8]>, ids: ExtrinsicIds, @@ -308,7 +308,7 @@ impl ExtrinsicDetails { /// Returns `None` if the extrinsic is not signed. pub fn as_address(&self) -> Option> { self.address_bytes() - .and_then(|bytes| Some(T::decode(&mut &bytes[..]).map_err(Error::Codec))) + .map(|bytes| T::decode(&mut &bytes[..]).map_err(Error::Codec)) } /// Attempt to statically decode the extrinsic call bytes into the provided type. From 42be28f668c0ab9442f685695d856d7cf48a4164 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Mon, 24 Apr 2023 20:00:31 +0300 Subject: [PATCH 08/38] extrinsics: Fetch pallet and variant index Signed-off-by: Alexandru Vasile --- examples/examples/block_extrinsics.rs | 7 ++++++- subxt/src/extrinsics/extrinsics_type.rs | 24 ++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/examples/examples/block_extrinsics.rs b/examples/examples/block_extrinsics.rs index 8550570841..3d98feee8f 100644 --- a/examples/examples/block_extrinsics.rs +++ b/examples/examples/block_extrinsics.rs @@ -66,7 +66,12 @@ async fn main() -> Result<(), Box> { // Ask for the extrinsics for this block. for extrinsic in extrinsics.iter() { let extrinsic = extrinsic?; - println!(" Extrinsic index {:?}", extrinsic.index()); + println!( + " Extrinsic block index {:?}, pallet index {:?}, variant index {:?}", + extrinsic.index(), + extrinsic.pallet_index(), + extrinsic.variant_index() + ); let call: polkadot::runtime_types::polkadot_runtime::RuntimeCall = extrinsic.as_call()?; diff --git a/subxt/src/extrinsics/extrinsics_type.rs b/subxt/src/extrinsics/extrinsics_type.rs index 49471e26bd..1d625a07dc 100644 --- a/subxt/src/extrinsics/extrinsics_type.rs +++ b/subxt/src/extrinsics/extrinsics_type.rs @@ -177,6 +177,10 @@ pub struct ExtrinsicDetails { 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, /// Extrinsic bytes. bytes: Arc<[u8]>, } @@ -246,7 +250,7 @@ impl ExtrinsicDetails { let call_start_idx = extrinsic_bytes.len() - cursor.len(); - // Postpone decoding the extrinsic function call. + // Ensure the provided bytes are sound. scale_decode::visitor::decode_with_visitor( cursor, ids.call, @@ -255,22 +259,38 @@ impl ExtrinsicDetails { ) .map_err(scale_decode::Error::from)?; + // Decode the pallet index, then the call variant. + let cursor = &mut &extrinsic_bytes[call_start_idx..]; + let pallet_index: u8 = Decode::decode(cursor).map_err(Error::Codec)?; + let variant_index: u8 = Decode::decode(cursor).map_err(Error::Codec)?; + Ok(ExtrinsicDetails { index, is_signed, address_start_idx, address_end_idx, call_start_idx, + pallet_index, + variant_index, bytes: extrinsic_bytes, }) } /// The index of the extrinsic in the given block. - /// What index is this event in the stored events for this 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.pallet_index + } + + /// The index of the event variant that the event originated from. + pub fn variant_index(&self) -> u8 { + self.variant_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) From 5d8f931f023ccf03b706b69c2b36bc88c0f59ec6 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Wed, 26 Apr 2023 15:30:46 +0300 Subject: [PATCH 09/38] subxt: Move extrinsics on the subxt::blocks Signed-off-by: Alexandru Vasile --- subxt/src/blocks/block_types.rs | 319 ++++++++++++++++++-- subxt/src/blocks/mod.rs | 2 +- subxt/src/config/mod.rs | 2 +- subxt/src/extrinsics/extrinsics_client.rs | 84 ------ subxt/src/extrinsics/extrinsics_type.rs | 344 ---------------------- subxt/src/extrinsics/mod.rs | 13 - subxt/src/lib.rs | 1 - 7 files changed, 292 insertions(+), 473 deletions(-) delete mode 100644 subxt/src/extrinsics/extrinsics_client.rs delete mode 100644 subxt/src/extrinsics/extrinsics_type.rs delete mode 100644 subxt/src/extrinsics/mod.rs diff --git a/subxt/src/blocks/block_types.rs b/subxt/src/blocks/block_types.rs index 4978e2becd..b5526d2fd3 100644 --- a/subxt/src/blocks/block_types.rs +++ b/subxt/src/blocks/block_types.rs @@ -5,15 +5,17 @@ use crate::{ client::{OfflineClientT, OnlineClientT}, config::{Config, Hasher, Header}, - error::{BlockError, Error}, - events, extrinsics, + error::{BlockError, Error, ExtrinsicError}, + events, rpc::types::ChainBlockResponse, runtime_api::RuntimeApi, storage::Storage, }; +use codec::Decode; use derivative::Derivative; +use frame_metadata::v15::RuntimeMetadataV15; use futures::lock::Mutex as AsyncMutex; -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; /// A representation of a block. pub struct Block { @@ -69,21 +71,17 @@ where /// Fetch and return the block body. pub async fn body(&self) -> Result, Error> { + let ids = ExtrinsicIds::new(self.client.metadata().runtime_metadata())?; let block_details = self.block_details().await?; Ok(BlockBody::new( self.client.clone(), block_details, self.cached_events.clone(), + ids, )) } - /// Return the extrinsics associated with the block. - pub async fn extrinsics(&self) -> Result, Error> { - let block_details = self.block_details().await?; - extrinsics::Extrinsics::new(self.client.metadata(), block_details.block) - } - /// Work with storage. pub fn storage(&self) -> Storage { let block_hash = self.hash(); @@ -110,6 +108,7 @@ pub struct BlockBody { details: ChainBlockResponse, client: C, cached_events: CachedEvents, + ids: ExtrinsicIds, } impl BlockBody @@ -121,59 +120,321 @@ where client: C, details: ChainBlockResponse, cached_events: CachedEvents, + ids: ExtrinsicIds, ) -> Self { Self { details, client, cached_events, + ids, } } /// Returns an iterator over the extrinsics in the block body. - pub fn extrinsics(&self) -> impl Iterator> { - self.details - .block - .extrinsics + // 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, + ) -> impl Iterator, Error>> + Send + Sync + 'static { + let extrinsics = self.details.block.extrinsics.clone(); + let num_extrinsics = self.details.block.extrinsics.len(); + let client = self.client.clone(); + let hash = self.details.block.header.hash(); + let cached_events = self.cached_events.clone(); + let ids = self.ids.clone(); + 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)) + } + } + } + }) + } +} + +/// 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 ExtrinsicIds { + /// The address (source) of the extrinsic. + address: u32, + /// The extrinsic call type. + call: u32, + /// The signature of the extrinsic. + signature: u32, + /// The extra parameters of the extrinsic. + extra: u32, +} + +impl ExtrinsicIds { + /// Extract the generic type parameters IDs from the extrinsic type. + fn new(metadata: &RuntimeMetadataV15) -> Result { + 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(ExtrinsicError::MissingType); + }; + + let params: HashMap<_, _> = ty + .type_params .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, + .map(|ty_param| { + let Some(ty) = ty_param.ty else { + return Err(ExtrinsicError::MissingType); + }; + + Ok((ty_param.name.as_str(), ty.id)) }) + .collect::>()?; + + let Some(address) = params.get(ADDRESS) else { + return Err(ExtrinsicError::MissingType); + }; + let Some(call) = params.get(CALL) else { + return Err(ExtrinsicError::MissingType); + }; + let Some(signature) = params.get(SIGNATURE) else { + return Err(ExtrinsicError::MissingType); + }; + let Some(extra) = params.get(EXTRA) else { + return Err(ExtrinsicError::MissingType); + }; + + Ok(ExtrinsicIds { + address: *address, + call: *call, + signature: *signature, + extra: *extra, + }) } } /// A single extrinsic in a block. -pub struct Extrinsic<'a, T: Config, C> { +pub struct ExtrinsicDetails { + /// The index of the extrinsic in the block. index: u32, - bytes: &'a [u8], - client: C, + /// 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, _marker: std::marker::PhantomData, } -impl<'a, T, C> Extrinsic<'a, T, C> +impl ExtrinsicDetails where T: Config, C: OfflineClientT, { + // Attempt to dynamically decode a single extrinsic from the given input. + fn decode_from( + index: u32, + extrinsic_bytes: Arc<[u8]>, + client: C, + block_hash: T::Hash, + cached_events: CachedEvents, + ids: ExtrinsicIds, + ) -> Result, 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 + if extrinsic_bytes.is_empty() { + return Err(ExtrinsicError::InsufficientData.into()); + } + + let version = extrinsic_bytes[0] & VERSION_MASK; + if version != LATEST_EXTRINSIC_VERSION { + return Err(ExtrinsicError::UnsupportedVersion(version).into()); + } + + let is_signed = extrinsic_bytes[0] & 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(); + + // Ensure the provided bytes are sound. + scale_decode::visitor::decode_with_visitor( + &mut *cursor, + ids.call, + &metadata.runtime_metadata().types, + scale_decode::visitor::IgnoreVisitor, + ) + .map_err(scale_decode::Error::from)?; + + // Decode the pallet index, then the call variant. + let cursor = &mut &extrinsic_bytes[call_start_idx..]; + + if cursor.len() < 2 { + return Err(ExtrinsicError::InsufficientData.into()); + } + let pallet_index = cursor[0]; + let variant_index = cursor[1]; + + 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, + _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 } - /// The bytes of the extrinsic. - pub fn bytes(&self) -> &'a [u8] { - self.bytes + /// 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. + /// + /// # 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 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]) + } + + /// Attempt to statically decode the address bytes into the provided type. + /// + /// # Note + /// + /// Returns `None` if the extrinsic is not signed. + pub fn as_address(&self) -> Option> { + self.address_bytes() + .map(|bytes| Address::decode(&mut &bytes[..]).map_err(Error::Codec)) + } + + /// Attempt to statically decode the extrinsic call bytes into the provided type. + pub fn as_call(&self) -> Result { + let bytes = &mut &self.call_bytes()[..]; + Call::decode(bytes).map_err(Error::Codec) + } + + /// The index of the pallet that the extrinsic originated from. + pub fn pallet_index(&self) -> u8 { + self.pallet_index + } + + /// The index of the event variant that the event originated from. + pub fn variant_index(&self) -> u8 { + self.variant_index } } -impl<'a, T, C> Extrinsic<'a, T, C> +impl ExtrinsicDetails where T: Config, C: OnlineClientT, diff --git a/subxt/src/blocks/mod.rs b/subxt/src/blocks/mod.rs index d60df23d99..f4f390f622 100644 --- a/subxt/src/blocks/mod.rs +++ b/subxt/src/blocks/mod.rs @@ -7,5 +7,5 @@ mod block_types; mod blocks_client; -pub use block_types::{Block, Extrinsic, ExtrinsicEvents}; +pub use block_types::{Block, ExtrinsicDetails, ExtrinsicEvents}; pub use blocks_client::{subscribe_to_block_headers_filling_in_gaps, BlocksClient}; diff --git a/subxt/src/config/mod.rs b/subxt/src/config/mod.rs index 9652cca48c..8501f28ba0 100644 --- a/subxt/src/config/mod.rs +++ b/subxt/src/config/mod.rs @@ -54,7 +54,7 @@ pub trait Config: 'static { type Hasher: Debug + Hasher; /// The block header. - type Header: Debug + Header + Send + DeserializeOwned; + type Header: Debug + Header + Sync + Send + DeserializeOwned; /// This type defines the extrinsic extra and additional parameters. type ExtrinsicParams: extrinsic_params::ExtrinsicParams; diff --git a/subxt/src/extrinsics/extrinsics_client.rs b/subxt/src/extrinsics/extrinsics_client.rs deleted file mode 100644 index b2c6edb9f7..0000000000 --- a/subxt/src/extrinsics/extrinsics_client.rs +++ /dev/null @@ -1,84 +0,0 @@ -// 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::{ - client::OnlineClientT, - error::{BlockError, Error}, - extrinsics::Extrinsics, - Config, -}; -use derivative::Derivative; -use std::future::Future; - -/// A client for working with extrinsics. -#[derive(Derivative)] -#[derivative(Clone(bound = "Client: Clone"))] -pub struct ExtrinsicsClient { - client: Client, - _marker: std::marker::PhantomData, -} - -impl ExtrinsicsClient { - /// Create a new [`ExtrinsicsClient`]. - pub fn new(client: Client) -> Self { - Self { - client, - _marker: std::marker::PhantomData, - } - } -} - -impl ExtrinsicsClient -where - T: Config, - Client: OnlineClientT, -{ - /// Obtain extrinsics at some block hash. - /// - /// # Warning - /// - /// This call only supports blocks produced since the most recent - /// runtime upgrade. You can attempt to retrieve extrinsics from older blocks, - /// but may run into errors attempting to work with them. - pub fn at( - &self, - block_hash: T::Hash, - ) -> impl Future, Error>> + Send + 'static { - self.at_or_latest(Some(block_hash)) - } - - /// Obtain extrinsics at the latest block hash. - pub fn at_latest(&self) -> impl Future, Error>> + Send + 'static { - self.at_or_latest(None) - } - - /// Obtain extrinsics at some block hash. - fn at_or_latest( - &self, - block_hash: Option, - ) -> impl Future, Error>> + Send + 'static { - // Clone and pass the client in like this so that we can explicitly - // return a Future that's Send + 'static, rather than tied to &self. - let client = self.client.clone(); - async move { - // If block hash is not provided, get the hash - // for the latest block and use that to create an explicit error. - let block_hash = match block_hash { - Some(hash) => hash, - None => client - .rpc() - .block_hash(None) - .await? - .expect("didn't pass a block number; qed"), - }; - - let result = client.rpc().block(Some(block_hash)).await?; - let Some(block_details) = result else { - return Err(BlockError::not_found(block_hash).into()); - }; - - Extrinsics::new(client.metadata(), block_details.block) - } - } -} diff --git a/subxt/src/extrinsics/extrinsics_type.rs b/subxt/src/extrinsics/extrinsics_type.rs deleted file mode 100644 index 1d625a07dc..0000000000 --- a/subxt/src/extrinsics/extrinsics_type.rs +++ /dev/null @@ -1,344 +0,0 @@ -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -use crate::{ - config::{Config, Header}, - error::{Error, ExtrinsicError}, - rpc::types::ChainBlock, - Metadata, -}; -use codec::Decode; -use derivative::Derivative; -use frame_metadata::v15::RuntimeMetadataV15; -use std::{collections::HashMap, sync::Arc}; - -/// A collection of extrinsics obtained from a block, bundled with the necessary -/// information needed to decode and iterate over them. -#[derive(Derivative)] -#[derivative(Debug(bound = ""), Clone(bound = ""))] -pub struct Extrinsics { - metadata: Metadata, - /// The block hash. - hash: T::Hash, - /// The accompanying extrinsics. - extrinsics: Vec>, - /// Generic extrinsic parameter ids from the metadata. - ids: ExtrinsicIds, -} - -impl Extrinsics { - pub(crate) fn new(metadata: Metadata, block: ChainBlock) -> Result { - let ids = ExtrinsicIds::new(metadata.runtime_metadata())?; - - let extrinsics: Vec<_> = block - .extrinsics - .into_iter() - .map(|ext| ext.0.into()) - .collect(); - - Ok(Self { - metadata, - hash: block.header.hash(), - extrinsics, - ids, - }) - } - - /// The number of extrinsics. - pub fn len(&self) -> usize { - self.extrinsics.len() - } - - /// Are there no extrinsics in this block? - pub fn is_empty(&self) -> bool { - self.extrinsics.len() == 0 - } - - /// Return the block hash that these extrinsics are from. - pub fn block_hash(&self) -> T::Hash { - self.hash - } - - /// 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> + Send + Sync + 'static { - let metadata = self.metadata.clone(); - let extrinsics = self.extrinsics.clone(); - let ids = self.ids; - let num_extr = self.extrinsics.len(); - let mut index = 0; - std::iter::from_fn(move || { - if index == num_extr { - None - } else { - match ExtrinsicDetails::decode_from( - metadata.clone(), - extrinsics[index].clone(), - ids, - index, - ) { - Ok(event_details) => { - // 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: - index = num_extr; - Some(Err(e)) - } - } - } - }) - } -} - -/// 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)] -struct ExtrinsicIds { - /// The address (source) of the extrinsic. - address: u32, - /// The extrinsic call type. - call: u32, - /// The signature of the extrinsic. - signature: u32, - /// The extra parameters of the extrinsic. - extra: u32, -} - -impl ExtrinsicIds { - /// Extract the generic type parameters IDs from the extrinsic type. - fn new(metadata: &RuntimeMetadataV15) -> Result { - 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(ExtrinsicError::MissingType); - }; - - let params: HashMap<_, _> = ty - .type_params - .iter() - .map(|ty_param| { - let Some(ty) = ty_param.ty else { - return Err(ExtrinsicError::MissingType); - }; - - Ok((ty_param.name.as_str(), ty.id)) - }) - .collect::>()?; - - let Some(address) = params.get(ADDRESS) else { - return Err(ExtrinsicError::MissingType); - }; - let Some(call) = params.get(CALL) else { - return Err(ExtrinsicError::MissingType); - }; - let Some(signature) = params.get(SIGNATURE) else { - return Err(ExtrinsicError::MissingType); - }; - let Some(extra) = params.get(EXTRA) else { - return Err(ExtrinsicError::MissingType); - }; - - Ok(ExtrinsicIds { - address: *address, - call: *call, - signature: *signature, - extra: *extra, - }) - } -} - -/// The extrinsic details. -#[derive(Debug, Clone)] -pub struct ExtrinsicDetails { - /// The index of the extrinsic in the block. - index: usize, - /// 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, - /// Extrinsic bytes. - bytes: Arc<[u8]>, -} - -impl ExtrinsicDetails { - // Attempt to dynamically decode a single event from our events input. - fn decode_from( - metadata: Metadata, - extrinsic_bytes: Arc<[u8]>, - ids: ExtrinsicIds, - index: usize, - ) -> Result { - const SIGNATURE_MASK: u8 = 0b1000_0000; - const VERSION_MASK: u8 = 0b0111_1111; - const LATEST_EXTRINSIC_VERSION: u8 = 4; - - // 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 - if extrinsic_bytes.is_empty() { - return Err(ExtrinsicError::InsufficientData.into()); - } - - let version = extrinsic_bytes[0] & VERSION_MASK; - if version != LATEST_EXTRINSIC_VERSION { - return Err(ExtrinsicError::UnsupportedVersion(version).into()); - } - - let is_signed = extrinsic_bytes[0] & 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(); - - // Ensure the provided bytes are sound. - scale_decode::visitor::decode_with_visitor( - cursor, - ids.call, - &metadata.runtime_metadata().types, - scale_decode::visitor::IgnoreVisitor, - ) - .map_err(scale_decode::Error::from)?; - - // Decode the pallet index, then the call variant. - let cursor = &mut &extrinsic_bytes[call_start_idx..]; - let pallet_index: u8 = Decode::decode(cursor).map_err(Error::Codec)?; - let variant_index: u8 = Decode::decode(cursor).map_err(Error::Codec)?; - - Ok(ExtrinsicDetails { - index, - is_signed, - address_start_idx, - address_end_idx, - call_start_idx, - pallet_index, - variant_index, - bytes: extrinsic_bytes, - }) - } - - /// The index of the extrinsic in the given 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.pallet_index - } - - /// The index of the event variant that the event originated from. - pub fn variant_index(&self) -> u8 { - self.variant_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. - /// - /// # 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 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]) - } - - /// Attempt to statically decode the address bytes into the provided type. - /// - /// # Note - /// - /// Returns `None` if the extrinsic is not signed. - pub fn as_address(&self) -> Option> { - self.address_bytes() - .map(|bytes| T::decode(&mut &bytes[..]).map_err(Error::Codec)) - } - - /// Attempt to statically decode the extrinsic call bytes into the provided type. - pub fn as_call(&self) -> Result { - let bytes = &mut &self.call_bytes()[..]; - T::decode(bytes).map_err(Error::Codec) - } - - /// Is the extrinsic signed? - pub fn is_signed(&self) -> bool { - self.is_signed - } -} diff --git a/subxt/src/extrinsics/mod.rs b/subxt/src/extrinsics/mod.rs deleted file mode 100644 index 435026c469..0000000000 --- a/subxt/src/extrinsics/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -// 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. - -//! This module exposes the types and such necessary for working with extrinsics. -//! The two main entry points into events are [`crate::OnlineClient::events()`] -//! and calls like [crate::tx::TxProgress::wait_for_finalized_success()]. - -mod extrinsics_client; -mod extrinsics_type; - -pub use extrinsics_client::ExtrinsicsClient; -pub use extrinsics_type::{ExtrinsicDetails, Extrinsics}; diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index 6317f1a266..3b41f95963 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -155,7 +155,6 @@ pub mod constants; pub mod dynamic; pub mod error; pub mod events; -pub mod extrinsics; pub mod metadata; pub mod rpc; pub mod runtime_api; From fa70294a339c4b50790eb4b7266b4c8b01840d3a Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Wed, 26 Apr 2023 15:43:55 +0300 Subject: [PATCH 10/38] example: Adjust example Signed-off-by: Alexandru Vasile --- examples/examples/block_extrinsics.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/examples/block_extrinsics.rs b/examples/examples/block_extrinsics.rs index 3d98feee8f..a489d00525 100644 --- a/examples/examples/block_extrinsics.rs +++ b/examples/examples/block_extrinsics.rs @@ -61,10 +61,10 @@ async fn main() -> Result<(), Box> { println!(" Block {:?}", block_hash); // Ask for the extrinsics for this block. - let extrinsics = block.extrinsics().await?; + let body = block.body().await?; // Ask for the extrinsics for this block. - for extrinsic in extrinsics.iter() { + for extrinsic in body.extrinsics() { let extrinsic = extrinsic?; println!( " Extrinsic block index {:?}, pallet index {:?}, variant index {:?}", From 0a890a19fc86c5ed659bf777a8b28630310ed608 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Wed, 26 Apr 2023 18:05:09 +0300 Subject: [PATCH 11/38] metadata: Collect ExtrinsicMetadata Signed-off-by: Alexandru Vasile --- subxt/src/metadata/metadata_type.rs | 102 ++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/subxt/src/metadata/metadata_type.rs b/subxt/src/metadata/metadata_type.rs index 7a8966d3ea..59badc51a1 100644 --- a/subxt/src/metadata/metadata_type.rs +++ b/subxt/src/metadata/metadata_type.rs @@ -27,6 +27,9 @@ pub enum MetadataError { /// Event is not in metadata. #[error("Pallet {0}, Event {0} not found")] EventNotFound(u8, u8), + /// Extrinsic is not in metadata. + #[error("Pallet {0}, Extrinsic {0} not found")] + ExtrinsicNotFound(u8, u8), /// Event is not in metadata. #[error("Pallet {0}, Error {0} not found")] ErrorNotFound(u8, u8), @@ -69,6 +72,8 @@ struct MetadataInner { // Events are hashed by pallet an error index (decode oriented) events: HashMap<(u8, u8), EventMetadata>, + // Extrinsics are hashed by pallet an error index (decode oriented) + extrinsics: HashMap<(u8, u8), ExtrinsicMetadata>, // Errors are hashed by pallet and error index (decode oriented) errors: HashMap<(u8, u8), ErrorMetadata>, @@ -117,6 +122,20 @@ impl Metadata { Ok(event) } + /// Returns the metadata for the extrinsic at the given pallet and call indices. + pub fn extrinsic( + &self, + pallet_index: u8, + call_index: u8, + ) -> Result<&ExtrinsicMetadata, MetadataError> { + let event = self + .inner + .extrinsics + .get(&(pallet_index, call_index)) + .ok_or(MetadataError::ExtrinsicNotFound(pallet_index, call_index))?; + Ok(event) + } + /// Returns the metadata for the error at the given pallet and error indices. pub fn error( &self, @@ -318,6 +337,39 @@ impl EventMetadata { } } +/// Metadata for specific extrinsics. +#[derive(Clone, Debug)] +pub struct ExtrinsicMetadata { + // The pallet name is shared across every extrinsic, so put it + // behind an Arc to avoid lots of needless clones of it existing. + pallet: Arc, + call: String, + fields: Vec>, + docs: Vec, +} + +impl ExtrinsicMetadata { + /// Get the name of the pallet from which the extrinsic was emitted. + pub fn pallet(&self) -> &str { + &self.pallet + } + + /// Get the name of the extrinsic call. + pub fn call(&self) -> &str { + &self.call + } + + /// The names, type names & types of each field in the extrinsic. + pub fn fields(&self) -> &[scale_info::Field] { + &self.fields + } + + /// Documentation for this extrinsic. + pub fn docs(&self) -> &[String] { + &self.docs + } +} + /// Details about a specific runtime error. #[derive(Clone, Debug)] pub struct ErrorMetadata { @@ -358,6 +410,12 @@ pub enum InvalidMetadataError { /// Type missing from type registry #[error("Type {0} missing from type registry")] MissingType(u32), + /// Type missing extrinsic "Call" type + #[error("Missing extrinsic Call type")] + MissingCallType, + /// The extrinsic variant expected to contain a single field. + #[error("Extrinsic variant at index {0} expected to contain a single field")] + InvalidExtrinsicVariant(u8), /// Type was not a variant/enum type #[error("Type {0} was not a variant/enum type")] TypeDefNotVariant(u32), @@ -485,11 +543,55 @@ impl TryFrom for Metadata { .find(|ty| ty.ty.path.segments == ["sp_runtime", "DispatchError"]) .map(|ty| ty.id); + let extrinsic_ty = metadata + .types + .resolve(metadata.extrinsic.ty.id) + .ok_or(InvalidMetadataError::MissingType(metadata.extrinsic.ty.id))?; + + let Some(call_id) = extrinsic_ty.type_params + .iter() + .find(|ty| ty.name == "Call") + .and_then(|ty| ty.ty) + .map(|ty| ty.id) else { + return Err(InvalidMetadataError::MissingCallType); + }; + + let type_def_variant = get_type_def_variant(call_id)?; + + let mut extrinsics = HashMap::<(u8, u8), ExtrinsicMetadata>::new(); + for variant in &type_def_variant.variants { + let pallet_name: Arc = variant.name.to_string().into(); + let pallet_index = variant.index; + + // Pallet variants must contain one single call variant. + if variant.fields.len() != 1 { + return Err(InvalidMetadataError::InvalidExtrinsicVariant(pallet_index)); + } + let Some(ty) = variant.fields.first() else { + return Err(InvalidMetadataError::InvalidExtrinsicVariant(pallet_index)); + }; + + // Get the call variant. + let type_def_variant = get_type_def_variant(ty.ty.id)?; + for variant in &type_def_variant.variants { + extrinsics.insert( + (pallet_index, variant.index), + ExtrinsicMetadata { + pallet: pallet_name.clone(), + call: variant.name.to_string(), + fields: variant.fields.clone(), + docs: variant.docs.clone(), + }, + ); + } + } + Ok(Metadata { inner: Arc::new(MetadataInner { metadata, pallets, events, + extrinsics, errors, dispatch_error_ty, cached_metadata_hash: Default::default(), From cb917662a6563cade5103db83025b4e2b9388d48 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Wed, 26 Apr 2023 18:21:49 +0300 Subject: [PATCH 12/38] subxt: Implement StaticExtrinsic for the calls Signed-off-by: Alexandru Vasile --- codegen/src/api/calls.rs | 5 +++++ subxt/src/blocks/block_types.rs | 19 +++++++++++++++++++ subxt/src/blocks/mod.rs | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/codegen/src/api/calls.rs b/codegen/src/api/calls.rs index 37fe1e0e66..f8a7c2e221 100644 --- a/codegen/src/api/calls.rs +++ b/codegen/src/api/calls.rs @@ -83,6 +83,11 @@ pub fn generate_calls( // The call structure's documentation was stripped above. let call_struct = quote! { #struct_def + + impl #crate_path::blocks::StaticExtrinsic for #struct_name { + const PALLET: &'static str = #pallet_name; + const CALL: &'static str = #call_name; + } }; let client_fn = quote! { diff --git a/subxt/src/blocks/block_types.rs b/subxt/src/blocks/block_types.rs index b5526d2fd3..6f47ff1c52 100644 --- a/subxt/src/blocks/block_types.rs +++ b/subxt/src/blocks/block_types.rs @@ -15,6 +15,7 @@ use codec::Decode; use derivative::Derivative; use frame_metadata::v15::RuntimeMetadataV15; use futures::lock::Mutex as AsyncMutex; +use scale_decode::DecodeAsFields; use std::{collections::HashMap, sync::Arc}; /// A representation of a block. @@ -260,6 +261,24 @@ pub struct ExtrinsicDetails { _marker: std::marker::PhantomData, } +/// 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 + } +} + impl ExtrinsicDetails where T: Config, diff --git a/subxt/src/blocks/mod.rs b/subxt/src/blocks/mod.rs index f4f390f622..948c53f34a 100644 --- a/subxt/src/blocks/mod.rs +++ b/subxt/src/blocks/mod.rs @@ -7,5 +7,5 @@ mod block_types; mod blocks_client; -pub use block_types::{Block, ExtrinsicDetails, ExtrinsicEvents}; +pub use block_types::{Block, ExtrinsicDetails, ExtrinsicEvents, StaticExtrinsic}; pub use blocks_client::{subscribe_to_block_headers_filling_in_gaps, BlocksClient}; From a4ef5fcc935e2535eec440eeae9ccbee924bda6a Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Thu, 27 Apr 2023 14:49:20 +0300 Subject: [PATCH 13/38] Adjust examples Signed-off-by: Alexandru Vasile --- examples/examples/block_extrinsics.rs | 23 +++++++++++++++-------- examples/examples/subscribe_blocks.rs | 1 + 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/examples/examples/block_extrinsics.rs b/examples/examples/block_extrinsics.rs index a489d00525..68a32f28a2 100644 --- a/examples/examples/block_extrinsics.rs +++ b/examples/examples/block_extrinsics.rs @@ -13,7 +13,7 @@ use futures::StreamExt; use sp_keyring::AccountKeyring; use std::time::Duration; -use subxt::{tx::PairSigner, Config, OnlineClient, PolkadotConfig}; +use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig}; #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] pub mod polkadot {} @@ -73,14 +73,21 @@ async fn main() -> Result<(), Box> { extrinsic.variant_index() ); - let call: polkadot::runtime_types::polkadot_runtime::RuntimeCall = - extrinsic.as_call()?; - println!(" Extrinsic call: {:?}", call); + let root_extrinsic = extrinsic.as_root_extrinsic::(); + println!(" As root extrinsic {:?}\n", root_extrinsic); - if extrinsic.is_signed() { - let address = extrinsic.as_address::<::Address>(); - println!(" Extrinsic address: {:?}", address); - } + let pallet_extrinsic = extrinsic + .as_pallet_extrinsic::(); + println!( + " Extrinsic as Balances Pallet call: {:?}\n", + pallet_extrinsic + ); + + let call = extrinsic.as_extrinsic::()?; + println!( + " Extrinsic as polkadot::balances::calls::Transfer: {:?}\n\n", + call + ); } println!("\n"); diff --git a/examples/examples/subscribe_blocks.rs b/examples/examples/subscribe_blocks.rs index 81a88f09b5..2a7055ab25 100644 --- a/examples/examples/subscribe_blocks.rs +++ b/examples/examples/subscribe_blocks.rs @@ -38,6 +38,7 @@ async fn main() -> Result<(), Box> { let body = block.body().await?; for ext in body.extrinsics() { + let ext = ext?; let idx = ext.index(); let events = ext.events().await?; let bytes_hex = format!("0x{}", hex::encode(ext.bytes())); From 1b6a60c4008fc3f6dca49955ebee39b89b26cf29 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Thu, 27 Apr 2023 14:49:33 +0300 Subject: [PATCH 14/38] codegen: Add root level Call enum Signed-off-by: Alexandru Vasile --- codegen/src/api/calls.rs | 2 ++ codegen/src/api/mod.rs | 48 ++++++++++++++++++++++++++++++++++++++++ codegen/src/error.rs | 5 +++++ 3 files changed, 55 insertions(+) diff --git a/codegen/src/api/calls.rs b/codegen/src/api/calls.rs index f8a7c2e221..7403f5ee5f 100644 --- a/codegen/src/api/calls.rs +++ b/codegen/src/api/calls.rs @@ -111,6 +111,7 @@ pub fn generate_calls( .into_iter() .unzip(); + let call_type = type_gen.resolve_type_path(call.ty.id); let call_ty = type_gen.resolve_type(call.ty.id); let docs = &call_ty.docs; let docs = should_gen_docs @@ -119,6 +120,7 @@ pub fn generate_calls( Ok(quote! { #docs + pub type Call = #call_type; pub mod calls { use super::root_mod; use super::#types_mod_ident; diff --git a/codegen/src/api/mod.rs b/codegen/src/api/mod.rs index ae52fb3f2a..d63fe7e7d4 100644 --- a/codegen/src/api/mod.rs +++ b/codegen/src/api/mod.rs @@ -353,6 +353,26 @@ impl RuntimeGenerator { } }; + let outer_extrinsic_variants = self.metadata.pallets.iter().filter_map(|p| { + let variant_name = format_ident!("{}", p.name); + let mod_name = format_ident!("{}", p.name.to_string().to_snake_case()); + let index = proc_macro2::Literal::u8_unsuffixed(p.index); + + p.calls.as_ref().map(|_| { + quote! { + #[codec(index = #index)] + #variant_name(#mod_name::Call), + } + }) + }); + + let outer_extrinsic = quote! { + #default_derives + pub enum Call { + #( #outer_extrinsic_variants )* + } + }; + let root_event_if_arms = self.metadata.pallets.iter().filter_map(|p| { let variant_name_str = &p.name; let variant_name = format_ident!("{}", variant_name_str); @@ -371,6 +391,24 @@ impl RuntimeGenerator { }) }); + let root_extrinsic_if_arms = self.metadata.pallets.iter().filter_map(|p| { + let variant_name_str = &p.name; + let variant_name = format_ident!("{}", variant_name_str); + let mod_name = format_ident!("{}", variant_name_str.to_string().to_snake_case()); + p.calls.as_ref().map(|_| { + // An 'if' arm for the RootExtrinsic impl to match this variant name: + quote! { + if pallet_name == #variant_name_str { + return Ok(Call::#variant_name(#mod_name::Call::decode_with_metadata( + &mut &*pallet_bytes, + pallet_ty, + metadata + )?)); + } + } + }) + }); + let mod_ident = &item_mod_ir.ident; let pallets_with_constants: Vec<_> = pallets_with_mod_names .iter() @@ -424,6 +462,16 @@ impl RuntimeGenerator { } } + #outer_extrinsic + + impl #crate_path::blocks::RootExtrinsic for Call { + fn root_extrinsic(pallet_bytes: &[u8], pallet_name: &str, pallet_ty: u32, metadata: &#crate_path::Metadata) -> Result { + use #crate_path::metadata::DecodeWithMetadata; + #( #root_extrinsic_if_arms )* + Err(#crate_path::ext::scale_decode::Error::custom(format!("Pallet name '{}' not found in root Call enum", pallet_name)).into()) + } + } + pub fn constants() -> ConstantsApi { ConstantsApi } diff --git a/codegen/src/error.rs b/codegen/src/error.rs index 411225e34a..7e79406e7a 100644 --- a/codegen/src/error.rs +++ b/codegen/src/error.rs @@ -50,6 +50,11 @@ pub enum CodegenError { "{0} type should be an variant/enum type. Make sure you are providing a valid substrate-based metadata" )] InvalidType(String), + /// Extrinsic call type could not be found. + #[error( + "Extrinsic call type could not be found. Make sure you are providing a valid substrate-based metadata" + )] + MissingCallType, } impl CodegenError { From 70884602765a6b75f3d414c68594db0985e36d9d Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Thu, 27 Apr 2023 14:50:00 +0300 Subject: [PATCH 15/38] Adjust testing Signed-off-by: Alexandru Vasile --- subxt/src/metadata/metadata_type.rs | 31 ++++++++++++++++++- .../src/metadata/validation.rs | 26 +++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/subxt/src/metadata/metadata_type.rs b/subxt/src/metadata/metadata_type.rs index 59badc51a1..1707a4a564 100644 --- a/subxt/src/metadata/metadata_type.rs +++ b/subxt/src/metadata/metadata_type.rs @@ -564,6 +564,11 @@ impl TryFrom for Metadata { let pallet_index = variant.index; // Pallet variants must contain one single call variant. + // In the following form: + // + // enum RuntimeCall { + // Pallet(pallet_call) + // } if variant.fields.len() != 1 { return Err(InvalidMetadataError::InvalidExtrinsicVariant(pallet_index)); } @@ -613,6 +618,30 @@ mod tests { use scale_info::{meta_type, TypeInfo}; fn load_metadata() -> Metadata { + // 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. + // In reality, the extrinsic type has "Call", "Address", "Extra", "Signature" generic types. + #[allow(unused)] + #[derive(TypeInfo)] + struct ExtrinsicType { + call: 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(TypeInfo)] + enum RuntimeCall { + PalletName(Pallet), + } + // The calls of the pallet. + #[allow(unused)] + #[derive(TypeInfo)] + enum Pallet { + #[allow(unused)] + SomeCall, + } + #[allow(dead_code)] #[allow(non_camel_case_types)] #[derive(TypeInfo)] @@ -651,7 +680,7 @@ mod tests { let metadata = RuntimeMetadataV15::new( vec![pallet], ExtrinsicMetadata { - ty: meta_type::<()>(), + ty: meta_type::>(), version: 0, signed_extensions: vec![], }, diff --git a/testing/integration-tests/src/metadata/validation.rs b/testing/integration-tests/src/metadata/validation.rs index c8d30d6418..d5a8642bc9 100644 --- a/testing/integration-tests/src/metadata/validation.rs +++ b/testing/integration-tests/src/metadata/validation.rs @@ -96,10 +96,34 @@ fn default_pallet() -> PalletMetadata { } fn pallets_to_metadata(pallets: Vec) -> RuntimeMetadataV15 { + // 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. + // In reality, the extrinsic type has "Call", "Address", "Extra", "Signature" generic types. + #[allow(unused)] + #[derive(TypeInfo)] + struct ExtrinsicType { + call: 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(TypeInfo)] + enum RuntimeCall { + PalletName(Pallet), + } + // The calls of the pallet. + #[allow(unused)] + #[derive(TypeInfo)] + enum Pallet { + #[allow(unused)] + SomeCall, + } + RuntimeMetadataV15::new( pallets, ExtrinsicMetadata { - ty: meta_type::<()>(), + ty: meta_type::>(), version: 0, signed_extensions: vec![], }, From 98f912ded178d0182aa5a2fd0a1727d8d8b56121 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Thu, 27 Apr 2023 14:50:11 +0300 Subject: [PATCH 16/38] subxt: Add new decode interface Signed-off-by: Alexandru Vasile --- subxt/src/blocks/block_types.rs | 217 ++++++++++++++++++++++++++------ subxt/src/blocks/mod.rs | 2 +- subxt/src/events/events_type.rs | 26 +++- subxt/src/metadata/mod.rs | 3 +- 4 files changed, 207 insertions(+), 41 deletions(-) diff --git a/subxt/src/blocks/block_types.rs b/subxt/src/blocks/block_types.rs index 6f47ff1c52..a69da449fc 100644 --- a/subxt/src/blocks/block_types.rs +++ b/subxt/src/blocks/block_types.rs @@ -7,11 +7,12 @@ use crate::{ config::{Config, Hasher, Header}, error::{BlockError, Error, ExtrinsicError}, events, + metadata::{DecodeWithMetadata, ExtrinsicMetadata}, rpc::types::ChainBlockResponse, runtime_api::RuntimeApi, storage::Storage, + Metadata, }; -use codec::Decode; use derivative::Derivative; use frame_metadata::v15::RuntimeMetadataV15; use futures::lock::Mutex as AsyncMutex; @@ -142,7 +143,7 @@ where let client = self.client.clone(); let hash = self.details.block.header.hash(); let cached_events = self.cached_events.clone(); - let ids = self.ids.clone(); + let ids = self.ids; let mut index = 0; std::iter::from_fn(move || { @@ -169,6 +170,35 @@ where } }) } + + /// 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_extrinsic( + &self, + ) -> impl Iterator> + '_ { + self.extrinsics().filter_map(|e| { + e.and_then(|e| e.as_extrinsic::().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_extrinsic(&self) -> Result, Error> { + self.find_extrinsic::().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(&self) -> Result, Error> { + self.find_extrinsic::().last().transpose() + } + + /// Find an extrinsics that decodes to the type provided. Returns true if it was found. + pub fn has_extrinsic(&self) -> Result { + Ok(self.find_extrinsic::().next().transpose()?.is_some()) + } } /// The type IDs extracted from the metadata that represent the @@ -234,6 +264,24 @@ impl ExtrinsicIds { } } +/// 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 + } +} + /// A single extrinsic in a block. pub struct ExtrinsicDetails { /// The index of the extrinsic in the block. @@ -258,27 +306,11 @@ pub struct ExtrinsicDetails { client: C, /// Cached events. cached_events: CachedEvents, + /// Subxt metadata to fetch the extrinsic metadata. + metadata: Metadata, _marker: std::marker::PhantomData, } -/// 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 - } -} - impl ExtrinsicDetails where T: Config, @@ -382,6 +414,7 @@ where block_hash, client, cached_events, + metadata, _marker: std::marker::PhantomData, }) } @@ -407,39 +440,38 @@ where &self.bytes } - /// Return only the bytes representing this extrinsic call. + /// 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. + /// 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 only the bytes of the address that signed this extrinsic. + /// Return the bytes representing the fields stored in 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]) + /// 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..] } - /// Attempt to statically decode the address bytes into the provided type. + /// Return only the bytes of the address that signed this extrinsic. /// /// # Note /// /// Returns `None` if the extrinsic is not signed. - pub fn as_address(&self) -> Option> { - self.address_bytes() - .map(|bytes| Address::decode(&mut &bytes[..]).map_err(Error::Codec)) - } - - /// Attempt to statically decode the extrinsic call bytes into the provided type. - pub fn as_call(&self) -> Result { - let bytes = &mut &self.call_bytes()[..]; - Call::decode(bytes).map_err(Error::Codec) + 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. @@ -447,10 +479,119 @@ where self.pallet_index } - /// The index of the event variant that the event originated from. + /// 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) -> &str { + self.extrinsic_metadata().pallet() + } + + /// The name of the call (ie the name of the variant that it corresponds to). + pub fn variant_name(&self) -> &str { + self.extrinsic_metadata().call() + } + + /// Fetch the metadata for this extrinsic. + pub fn extrinsic_metadata(&self) -> &ExtrinsicMetadata { + self.metadata + .extrinsic(self.pallet_index(), self.variant_index()) + .expect("this must exist in order to have produced the ExtrinsicDetails") + } + + /// 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, Error> { + let bytes = &mut self.field_bytes(); + let extrinsic_metadata = self.extrinsic_metadata(); + + let decoded = >::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(&self) -> Result, 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(&self) -> Result { + 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(&self) -> Result { + 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, + ) + } +} + +/// 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; } impl ExtrinsicDetails diff --git a/subxt/src/blocks/mod.rs b/subxt/src/blocks/mod.rs index 948c53f34a..b7b01ed7e3 100644 --- a/subxt/src/blocks/mod.rs +++ b/subxt/src/blocks/mod.rs @@ -7,5 +7,5 @@ mod block_types; mod blocks_client; -pub use block_types::{Block, ExtrinsicDetails, ExtrinsicEvents, StaticExtrinsic}; +pub use block_types::{Block, ExtrinsicDetails, ExtrinsicEvents, RootExtrinsic, StaticExtrinsic}; pub use blocks_client::{subscribe_to_block_headers_filling_in_gaps, BlocksClient}; diff --git a/subxt/src/events/events_type.rs b/subxt/src/events/events_type.rs index 4fcec55cb7..72a9f23d75 100644 --- a/subxt/src/events/events_type.rs +++ b/subxt/src/events/events_type.rs @@ -493,6 +493,30 @@ pub(crate) mod test_utils { /// Build fake metadata consisting of a single pallet that knows /// about the event type provided. pub fn metadata() -> Metadata { + // 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. + // In reality, the extrinsic type has "Call", "Address", "Extra", "Signature" generic types. + #[allow(unused)] + #[derive(TypeInfo)] + struct ExtrinsicType { + call: 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(TypeInfo)] + enum RuntimeCall { + PalletName(Pallet), + } + // The calls of the pallet. + #[allow(unused)] + #[derive(TypeInfo)] + enum Pallet { + #[allow(unused)] + SomeCall, + } + let pallets = vec![PalletMetadata { name: "Test", storage: None, @@ -507,7 +531,7 @@ pub(crate) mod test_utils { }]; let extrinsic = ExtrinsicMetadata { - ty: meta_type::<()>(), + ty: meta_type::>(), version: 0, signed_extensions: vec![], }; diff --git a/subxt/src/metadata/mod.rs b/subxt/src/metadata/mod.rs index 2e80ac9c15..3f903e0c1d 100644 --- a/subxt/src/metadata/mod.rs +++ b/subxt/src/metadata/mod.rs @@ -12,7 +12,8 @@ mod metadata_type; pub use metadata_location::MetadataLocation; pub use metadata_type::{ - ErrorMetadata, EventMetadata, InvalidMetadataError, Metadata, MetadataError, PalletMetadata, + ErrorMetadata, EventMetadata, ExtrinsicMetadata, InvalidMetadataError, Metadata, MetadataError, + PalletMetadata, }; pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata}; From 3750c76ddb677e32412ab4dbc882e436570864f4 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Thu, 27 Apr 2023 14:56:44 +0300 Subject: [PATCH 17/38] subxt: Merge ExtrinsicError with BlockError Signed-off-by: Alexandru Vasile --- subxt/src/blocks/block_types.rs | 22 +++++++++++----------- subxt/src/error/mod.rs | 25 ++++++++----------------- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/subxt/src/blocks/block_types.rs b/subxt/src/blocks/block_types.rs index a69da449fc..49f8a67d40 100644 --- a/subxt/src/blocks/block_types.rs +++ b/subxt/src/blocks/block_types.rs @@ -5,7 +5,7 @@ use crate::{ client::{OfflineClientT, OnlineClientT}, config::{Config, Hasher, Header}, - error::{BlockError, Error, ExtrinsicError}, + error::{BlockError, Error}, events, metadata::{DecodeWithMetadata, ExtrinsicMetadata}, rpc::types::ChainBlockResponse, @@ -218,7 +218,7 @@ pub(crate) struct ExtrinsicIds { impl ExtrinsicIds { /// Extract the generic type parameters IDs from the extrinsic type. - fn new(metadata: &RuntimeMetadataV15) -> Result { + fn new(metadata: &RuntimeMetadataV15) -> Result { const ADDRESS: &str = "Address"; const CALL: &str = "Call"; const SIGNATURE: &str = "Signature"; @@ -227,7 +227,7 @@ impl ExtrinsicIds { let id = metadata.extrinsic.ty.id; let Some(ty) = metadata.types.resolve(id) else { - return Err(ExtrinsicError::MissingType); + return Err(BlockError::MissingType); }; let params: HashMap<_, _> = ty @@ -235,7 +235,7 @@ impl ExtrinsicIds { .iter() .map(|ty_param| { let Some(ty) = ty_param.ty else { - return Err(ExtrinsicError::MissingType); + return Err(BlockError::MissingType); }; Ok((ty_param.name.as_str(), ty.id)) @@ -243,16 +243,16 @@ impl ExtrinsicIds { .collect::>()?; let Some(address) = params.get(ADDRESS) else { - return Err(ExtrinsicError::MissingType); + return Err(BlockError::MissingType); }; let Some(call) = params.get(CALL) else { - return Err(ExtrinsicError::MissingType); + return Err(BlockError::MissingType); }; let Some(signature) = params.get(SIGNATURE) else { - return Err(ExtrinsicError::MissingType); + return Err(BlockError::MissingType); }; let Some(extra) = params.get(EXTRA) else { - return Err(ExtrinsicError::MissingType); + return Err(BlockError::MissingType); }; Ok(ExtrinsicIds { @@ -336,12 +336,12 @@ where // - signature: [unknown TBD with metadata]. // - extrinsic data if extrinsic_bytes.is_empty() { - return Err(ExtrinsicError::InsufficientData.into()); + return Err(BlockError::InsufficientData.into()); } let version = extrinsic_bytes[0] & VERSION_MASK; if version != LATEST_EXTRINSIC_VERSION { - return Err(ExtrinsicError::UnsupportedVersion(version).into()); + return Err(BlockError::UnsupportedVersion(version).into()); } let is_signed = extrinsic_bytes[0] & SIGNATURE_MASK != 0; @@ -397,7 +397,7 @@ where let cursor = &mut &extrinsic_bytes[call_start_idx..]; if cursor.len() < 2 { - return Err(ExtrinsicError::InsufficientData.into()); + return Err(BlockError::InsufficientData.into()); } let pallet_index = cursor[0]; let variant_index = cursor[1]; diff --git a/subxt/src/error/mod.rs b/subxt/src/error/mod.rs index 1e16c52e79..9df7c018b6 100644 --- a/subxt/src/error/mod.rs +++ b/subxt/src/error/mod.rs @@ -57,9 +57,6 @@ pub enum Error { /// Block related error. #[error("Block error: {0}")] Block(#[from] BlockError), - /// Extrinsic related error. - #[error("Extrinsic error: {0}")] - Extrinsic(#[from] ExtrinsicError), /// An error encoding a storage address. #[error("Error encoding storage address: {0}")] StorageAddress(#[from] StorageAddressError), @@ -105,20 +102,6 @@ pub enum BlockError { /// An error containing the hash of the block that was not found. #[error("Could not find a block with hash {0} (perhaps it was on a non-finalized fork?)")] NotFound(String), -} - -impl BlockError { - /// Produce an error that a block with the given hash cannot be found. - pub fn not_found(hash: impl AsRef<[u8]>) -> BlockError { - let hash = format!("0x{}", hex::encode(hash)); - BlockError::NotFound(hash) - } -} - -/// Extrinsic error. -#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)] -#[non_exhaustive] -pub enum ExtrinsicError { /// Extrinsic type ID cannot be resolved with the provided metadata. #[error("Extrinsic type ID cannot be resolved with the provided metadata. Make sure this is a valid metadata")] MissingType, @@ -134,6 +117,14 @@ pub enum ExtrinsicError { DecodingError(codec::Error), } +impl BlockError { + /// Produce an error that a block with the given hash cannot be found. + pub fn not_found(hash: impl AsRef<[u8]>) -> BlockError { + let hash = format!("0x{}", hex::encode(hash)); + BlockError::NotFound(hash) + } +} + /// Transaction error. #[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)] #[non_exhaustive] From 5890032cf8e7fe54693ed501869d4a4ed6bcd3d9 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Thu, 27 Apr 2023 14:56:53 +0300 Subject: [PATCH 18/38] examples: Find first extrinsic Signed-off-by: Alexandru Vasile --- examples/examples/block_extrinsics.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/examples/block_extrinsics.rs b/examples/examples/block_extrinsics.rs index 68a32f28a2..99f0f00b0c 100644 --- a/examples/examples/block_extrinsics.rs +++ b/examples/examples/block_extrinsics.rs @@ -63,6 +63,9 @@ async fn main() -> Result<(), Box> { // Ask for the extrinsics for this block. let body = block.body().await?; + let transfer_tx = body.find_first_extrinsic::(); + println!(" Transfer tx: {:?}", transfer_tx); + // Ask for the extrinsics for this block. for extrinsic in body.extrinsics() { let extrinsic = extrinsic?; From 6f66f5ceff80d7cde04cfe0c40c3c0323ece2dec Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Thu, 27 Apr 2023 16:01:02 +0300 Subject: [PATCH 19/38] Move code to extrinsic_types Signed-off-by: Alexandru Vasile --- subxt/src/blocks/block_types.rs | 515 +---------------------- subxt/src/blocks/extrinsic_types.rs | 618 ++++++++++++++++++++++++++++ subxt/src/blocks/mod.rs | 4 +- 3 files changed, 627 insertions(+), 510 deletions(-) create mode 100644 subxt/src/blocks/extrinsic_types.rs diff --git a/subxt/src/blocks/block_types.rs b/subxt/src/blocks/block_types.rs index 49f8a67d40..d4e3a6b0d3 100644 --- a/subxt/src/blocks/block_types.rs +++ b/subxt/src/blocks/block_types.rs @@ -3,21 +3,18 @@ // see LICENSE for license details. use crate::{ + blocks::{extrinsic_types::ExtrinsicIds, ExtrinsicDetails, StaticExtrinsic}, client::{OfflineClientT, OnlineClientT}, - config::{Config, Hasher, Header}, + config::{Config, Header}, error::{BlockError, Error}, events, - metadata::{DecodeWithMetadata, ExtrinsicMetadata}, rpc::types::ChainBlockResponse, runtime_api::RuntimeApi, storage::Storage, - Metadata, }; -use derivative::Derivative; -use frame_metadata::v15::RuntimeMetadataV15; + use futures::lock::Mutex as AsyncMutex; -use scale_decode::DecodeAsFields; -use std::{collections::HashMap, sync::Arc}; +use std::sync::Arc; /// A representation of a block. pub struct Block { @@ -30,7 +27,7 @@ pub struct Block { // A cache for our events so we don't fetch them more than once when // iterating over events for extrinsics. -type CachedEvents = Arc>>>; +pub(crate) type CachedEvents = Arc>>>; impl Block where @@ -201,508 +198,8 @@ where } } -/// 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 ExtrinsicIds { - /// The address (source) of the extrinsic. - address: u32, - /// The extrinsic call type. - call: u32, - /// The signature of the extrinsic. - signature: u32, - /// The extra parameters of the extrinsic. - extra: u32, -} - -impl ExtrinsicIds { - /// Extract the generic type parameters IDs from the extrinsic type. - fn new(metadata: &RuntimeMetadataV15) -> Result { - 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::>()?; - - 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(ExtrinsicIds { - address: *address, - call: *call, - signature: *signature, - extra: *extra, - }) - } -} - -/// 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 - } -} - -/// A single extrinsic in a block. -pub struct ExtrinsicDetails { - /// 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, - /// Subxt metadata to fetch the extrinsic metadata. - metadata: Metadata, - _marker: std::marker::PhantomData, -} - -impl ExtrinsicDetails -where - T: Config, - C: OfflineClientT, -{ - // Attempt to dynamically decode a single extrinsic from the given input. - fn decode_from( - index: u32, - extrinsic_bytes: Arc<[u8]>, - client: C, - block_hash: T::Hash, - cached_events: CachedEvents, - ids: ExtrinsicIds, - ) -> Result, 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 - if extrinsic_bytes.is_empty() { - return Err(BlockError::InsufficientData.into()); - } - - let version = extrinsic_bytes[0] & VERSION_MASK; - if version != LATEST_EXTRINSIC_VERSION { - return Err(BlockError::UnsupportedVersion(version).into()); - } - - let is_signed = extrinsic_bytes[0] & 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(); - - // Ensure the provided bytes are sound. - scale_decode::visitor::decode_with_visitor( - &mut *cursor, - ids.call, - &metadata.runtime_metadata().types, - scale_decode::visitor::IgnoreVisitor, - ) - .map_err(scale_decode::Error::from)?; - - // Decode the pallet index, then the call variant. - let cursor = &mut &extrinsic_bytes[call_start_idx..]; - - if cursor.len() < 2 { - return Err(BlockError::InsufficientData.into()); - } - let pallet_index = cursor[0]; - let variant_index = cursor[1]; - - 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) -> &str { - self.extrinsic_metadata().pallet() - } - - /// The name of the call (ie the name of the variant that it corresponds to). - pub fn variant_name(&self) -> &str { - self.extrinsic_metadata().call() - } - - /// Fetch the metadata for this extrinsic. - pub fn extrinsic_metadata(&self) -> &ExtrinsicMetadata { - self.metadata - .extrinsic(self.pallet_index(), self.variant_index()) - .expect("this must exist in order to have produced the ExtrinsicDetails") - } - - /// 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, Error> { - let bytes = &mut self.field_bytes(); - let extrinsic_metadata = self.extrinsic_metadata(); - - let decoded = >::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(&self) -> Result, 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(&self) -> Result { - 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(&self) -> Result { - 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, - ) - } -} - -/// 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; -} - -impl ExtrinsicDetails -where - T: Config, - C: OnlineClientT, -{ - /// The events associated with the extrinsic. - pub async fn events(&self) -> Result, 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 { - // 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, -} - -impl ExtrinsicEvents { - pub(crate) fn new(ext_hash: T::Hash, idx: u32, events: events::Events) -> 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 { - &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> + '_ { - 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(&self) -> impl Iterator> + '_ { - self.iter().filter_map(|ev| { - ev.and_then(|ev| ev.as_event::().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(&self) -> Result, Error> { - self.find::().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(&self) -> Result, Error> { - self.find::().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(&self) -> Result { - Ok(self.find::().next().transpose()?.is_some()) - } -} - // Return Events from the cache, or fetch from the node if needed. -async fn get_events( +pub(crate) async fn get_events( client: &C, block_hash: T::Hash, cached_events: &AsyncMutex>>, diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs new file mode 100644 index 0000000000..d592c49b24 --- /dev/null +++ b/subxt/src/blocks/extrinsic_types.rs @@ -0,0 +1,618 @@ +// 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}, + Metadata, +}; +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; +} + +/// The body of a block. +pub struct Extrinsics { + client: C, + extrinsics: Vec, + cached_events: CachedEvents, + ids: ExtrinsicIds, +} + +impl Extrinsics +where + T: Config, + C: OfflineClientT, +{ + pub(crate) fn new( + client: C, + extrinsics: Vec, + cached_events: CachedEvents, + ids: ExtrinsicIds, + ) -> Self { + Self { + client, + extrinsics, + cached_events, + ids, + } + } + + /// 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 extrinsics( + &self, + ) -> impl Iterator, Error>> + Send + Sync + 'static { + let extrinsics = self.details.block.extrinsics.clone(); + let num_extrinsics = self.details.block.extrinsics.len(); + let client = self.client.clone(); + let hash = self.details.block.header.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_extrinsic( + &self, + ) -> impl Iterator> + '_ { + self.extrinsics().filter_map(|e| { + e.and_then(|e| e.as_extrinsic::().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_extrinsic(&self) -> Result, Error> { + self.find_extrinsic::().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(&self) -> Result, Error> { + self.find_extrinsic::().last().transpose() + } + + /// Find an extrinsics that decodes to the type provided. Returns true if it was found. + pub fn has_extrinsic(&self) -> Result { + Ok(self.find_extrinsic::().next().transpose()?.is_some()) + } +} + + +pub struct { + +} + +/// A single extrinsic in a block. +pub struct ExtrinsicDetails { + /// 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, + /// Subxt metadata to fetch the extrinsic metadata. + metadata: Metadata, + _marker: std::marker::PhantomData, +} + +impl ExtrinsicDetails +where + T: Config, + C: OfflineClientT, +{ + // 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, + ids: ExtrinsicIds, + ) -> Result, 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 + if extrinsic_bytes.is_empty() { + return Err(BlockError::InsufficientData.into()); + } + + let version = extrinsic_bytes[0] & VERSION_MASK; + if version != LATEST_EXTRINSIC_VERSION { + return Err(BlockError::UnsupportedVersion(version).into()); + } + + let is_signed = extrinsic_bytes[0] & 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(); + + // Ensure the provided bytes are sound. + scale_decode::visitor::decode_with_visitor( + &mut *cursor, + ids.call, + &metadata.runtime_metadata().types, + scale_decode::visitor::IgnoreVisitor, + ) + .map_err(scale_decode::Error::from)?; + + // Decode the pallet index, then the call variant. + let cursor = &mut &extrinsic_bytes[call_start_idx..]; + + if cursor.len() < 2 { + return Err(BlockError::InsufficientData.into()); + } + let pallet_index = cursor[0]; + let variant_index = cursor[1]; + + 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) -> &str { + self.extrinsic_metadata().pallet() + } + + /// The name of the call (ie the name of the variant that it corresponds to). + pub fn variant_name(&self) -> &str { + self.extrinsic_metadata().call() + } + + /// Fetch the metadata for this extrinsic. + pub fn extrinsic_metadata(&self) -> &ExtrinsicMetadata { + self.metadata + .extrinsic(self.pallet_index(), self.variant_index()) + .expect("this must exist in order to have produced the ExtrinsicDetails") + } + + /// 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, Error> { + let bytes = &mut self.field_bytes(); + let extrinsic_metadata = self.extrinsic_metadata(); + + let decoded = >::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(&self) -> Result, 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(&self) -> Result { + 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(&self) -> Result { + 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 ExtrinsicDetails +where + T: Config, + C: OnlineClientT, +{ + /// The events associated with the extrinsic. + pub async fn events(&self) -> Result, 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 ExtrinsicIds { + /// The address (source) of the extrinsic. + address: u32, + /// The extrinsic call type. + call: u32, + /// The signature of the extrinsic. + signature: u32, + /// The extra parameters of the extrinsic. + extra: u32, +} + +impl ExtrinsicIds { + /// Extract the generic type parameters IDs from the extrinsic type. + pub(crate) fn new(metadata: &RuntimeMetadataV15) -> Result { + 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::>()?; + + 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(ExtrinsicIds { + address: *address, + call: *call, + signature: *signature, + extra: *extra, + }) + } +} + +/// The events associated with a given extrinsic. +#[derive(Derivative)] +#[derivative(Debug(bound = ""))] +pub struct ExtrinsicEvents { + // The hash of the extrinsic (handy to expose here because + // this type is returned from TxProgress things in the most + // basic flows, so it's the only place people can access it + // without complicating things for themselves). + ext_hash: T::Hash, + // The index of the extrinsic: + idx: u32, + // All of the events in the block: + events: events::Events, +} + +impl ExtrinsicEvents { + pub(crate) fn new(ext_hash: T::Hash, idx: u32, events: events::Events) -> 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 { + &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> + '_ { + 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(&self) -> impl Iterator> + '_ { + self.iter().filter_map(|ev| { + ev.and_then(|ev| ev.as_event::().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(&self) -> Result, Error> { + self.find::().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(&self) -> Result, Error> { + self.find::().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(&self) -> Result { + Ok(self.find::().next().transpose()?.is_some()) + } +} diff --git a/subxt/src/blocks/mod.rs b/subxt/src/blocks/mod.rs index b7b01ed7e3..9a685649df 100644 --- a/subxt/src/blocks/mod.rs +++ b/subxt/src/blocks/mod.rs @@ -6,6 +6,8 @@ mod block_types; mod blocks_client; +mod extrinsic_types; -pub use block_types::{Block, ExtrinsicDetails, ExtrinsicEvents, RootExtrinsic, StaticExtrinsic}; +pub use block_types::Block; pub use blocks_client::{subscribe_to_block_headers_filling_in_gaps, BlocksClient}; +pub use extrinsic_types::{ExtrinsicDetails, ExtrinsicEvents, RootExtrinsic, StaticExtrinsic}; From 1a5110ce5a10831dc01279eced51c92fef0003d5 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Thu, 27 Apr 2023 16:11:30 +0300 Subject: [PATCH 20/38] Add Extrinsic struct Signed-off-by: Alexandru Vasile --- subxt/src/blocks/block_types.rs | 73 ++++------------------------- subxt/src/blocks/extrinsic_types.rs | 34 +++++++------- subxt/src/blocks/mod.rs | 4 +- 3 files changed, 28 insertions(+), 83 deletions(-) diff --git a/subxt/src/blocks/block_types.rs b/subxt/src/blocks/block_types.rs index d4e3a6b0d3..f62f88a377 100644 --- a/subxt/src/blocks/block_types.rs +++ b/subxt/src/blocks/block_types.rs @@ -3,7 +3,7 @@ // see LICENSE for license details. use crate::{ - blocks::{extrinsic_types::ExtrinsicIds, ExtrinsicDetails, StaticExtrinsic}, + blocks::{extrinsic_types::ExtrinsicIds, Extrinsics}, client::{OfflineClientT, OnlineClientT}, config::{Config, Header}, error::{BlockError, Error}, @@ -132,69 +132,14 @@ where /// 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 extrinsics( - &self, - ) -> impl Iterator, Error>> + Send + Sync + 'static { - let extrinsics = self.details.block.extrinsics.clone(); - let num_extrinsics = self.details.block.extrinsics.len(); - let client = self.client.clone(); - let hash = self.details.block.header.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_extrinsic( - &self, - ) -> impl Iterator> + '_ { - self.extrinsics().filter_map(|e| { - e.and_then(|e| e.as_extrinsic::().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_extrinsic(&self) -> Result, Error> { - self.find_extrinsic::().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(&self) -> Result, Error> { - self.find_extrinsic::().last().transpose() - } - - /// Find an extrinsics that decodes to the type provided. Returns true if it was found. - pub fn has_extrinsic(&self) -> Result { - Ok(self.find_extrinsic::().next().transpose()?.is_some()) + pub fn extrinsics(&self) -> Extrinsics { + Extrinsics::new( + self.client.clone(), + self.details.block.extrinsics.clone(), + self.cached_events.clone(), + self.ids, + self.details.block.header.hash(), + ) } } diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index d592c49b24..3c9c35ba9c 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -9,8 +9,10 @@ use crate::{ error::{BlockError, Error}, events, metadata::{DecodeWithMetadata, ExtrinsicMetadata}, + rpc::types::ChainBlockExtrinsic, Metadata, }; + use derivative::Derivative; use frame_metadata::v15::RuntimeMetadataV15; use scale_decode::DecodeAsFields; @@ -57,6 +59,7 @@ pub struct Extrinsics { extrinsics: Vec, cached_events: CachedEvents, ids: ExtrinsicIds, + hash: T::Hash, } impl Extrinsics @@ -69,25 +72,27 @@ where extrinsics: Vec, cached_events: CachedEvents, ids: ExtrinsicIds, + hash: T::Hash, ) -> Self { Self { client, extrinsics, cached_events, ids, + 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 extrinsics( + pub fn iter( &self, ) -> impl Iterator, Error>> + Send + Sync + 'static { - let extrinsics = self.details.block.extrinsics.clone(); - let num_extrinsics = self.details.block.extrinsics.len(); + let extrinsics = self.extrinsics.clone(); + let num_extrinsics = self.extrinsics.len(); let client = self.client.clone(); - let hash = self.details.block.header.hash(); + let hash = self.hash.clone(); let cached_events = self.cached_events.clone(); let ids = self.ids; let mut index = 0; @@ -120,10 +125,8 @@ where /// 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_extrinsic( - &self, - ) -> impl Iterator> + '_ { - self.extrinsics().filter_map(|e| { + pub fn find(&self) -> impl Iterator> + '_ { + self.iter().filter_map(|e| { e.and_then(|e| e.as_extrinsic::().map_err(Into::into)) .transpose() }) @@ -131,27 +134,22 @@ where /// 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_extrinsic(&self) -> Result, Error> { - self.find_extrinsic::().next().transpose() + pub fn find_first(&self) -> Result, Error> { + self.find::().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(&self) -> Result, Error> { - self.find_extrinsic::().last().transpose() + self.find::().last().transpose() } /// Find an extrinsics that decodes to the type provided. Returns true if it was found. - pub fn has_extrinsic(&self) -> Result { - Ok(self.find_extrinsic::().next().transpose()?.is_some()) + pub fn has(&self) -> Result { + Ok(self.find::().next().transpose()?.is_some()) } } - -pub struct { - -} - /// A single extrinsic in a block. pub struct ExtrinsicDetails { /// The index of the extrinsic in the block. diff --git a/subxt/src/blocks/mod.rs b/subxt/src/blocks/mod.rs index 9a685649df..2ba76f0c13 100644 --- a/subxt/src/blocks/mod.rs +++ b/subxt/src/blocks/mod.rs @@ -10,4 +10,6 @@ mod extrinsic_types; pub use block_types::Block; pub use blocks_client::{subscribe_to_block_headers_filling_in_gaps, BlocksClient}; -pub use extrinsic_types::{ExtrinsicDetails, ExtrinsicEvents, RootExtrinsic, StaticExtrinsic}; +pub use extrinsic_types::{ + ExtrinsicDetails, ExtrinsicEvents, Extrinsics, RootExtrinsic, StaticExtrinsic, +}; From 59ca2e7e062ce497fbb333c3d0eafeb5b576f028 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Thu, 27 Apr 2023 16:44:02 +0300 Subject: [PATCH 21/38] Adjust examples Signed-off-by: Alexandru Vasile --- examples/examples/block_extrinsics.rs | 7 ++++--- examples/examples/subscribe_blocks.rs | 2 +- subxt/src/blocks/extrinsic_types.rs | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/examples/block_extrinsics.rs b/examples/examples/block_extrinsics.rs index 99f0f00b0c..c8b78266bb 100644 --- a/examples/examples/block_extrinsics.rs +++ b/examples/examples/block_extrinsics.rs @@ -61,14 +61,15 @@ async fn main() -> Result<(), Box> { println!(" Block {:?}", block_hash); // Ask for the extrinsics for this block. - let body = block.body().await?; + let extrinsics = block.body().await?.extrinsics(); - let transfer_tx = body.find_first_extrinsic::(); + let transfer_tx = extrinsics.find_first::(); println!(" Transfer tx: {:?}", transfer_tx); // Ask for the extrinsics for this block. - for extrinsic in body.extrinsics() { + for extrinsic in extrinsics.iter() { let extrinsic = extrinsic?; + println!( " Extrinsic block index {:?}, pallet index {:?}, variant index {:?}", extrinsic.index(), diff --git a/examples/examples/subscribe_blocks.rs b/examples/examples/subscribe_blocks.rs index 2a7055ab25..72637c06fe 100644 --- a/examples/examples/subscribe_blocks.rs +++ b/examples/examples/subscribe_blocks.rs @@ -37,7 +37,7 @@ async fn main() -> Result<(), Box> { println!(" Extrinsics:"); let body = block.body().await?; - for ext in body.extrinsics() { + for ext in body.extrinsics().iter() { let ext = ext?; let idx = ext.index(); let events = ext.events().await?; diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 3c9c35ba9c..e5ee7e1b7c 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -92,7 +92,7 @@ where let extrinsics = self.extrinsics.clone(); let num_extrinsics = self.extrinsics.len(); let client = self.client.clone(); - let hash = self.hash.clone(); + let hash = self.hash; let cached_events = self.cached_events.clone(); let ids = self.ids; let mut index = 0; From 77ebb8415561b00f6247d417e836a7a1e9493c44 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Fri, 28 Apr 2023 15:18:34 +0300 Subject: [PATCH 22/38] test: Decode extinsics Signed-off-by: Alexandru Vasile --- subxt/src/blocks/extrinsic_types.rs | 16 +++++ testing/integration-tests/src/blocks/mod.rs | 72 ++++++++++++++++++++- 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index e5ee7e1b7c..08c69ddd2d 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -83,6 +83,22 @@ where } } + /// 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. diff --git a/testing/integration-tests/src/blocks/mod.rs b/testing/integration-tests/src/blocks/mod.rs index 57d02972b4..2573102aaa 100644 --- a/testing/integration-tests/src/blocks/mod.rs +++ b/testing/integration-tests/src/blocks/mod.rs @@ -2,10 +2,12 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use crate::test_context; +use crate::{pair_signer, test_context, utils::node_runtime}; use codec::Compact; use frame_metadata::RuntimeMetadataPrefixed; use futures::StreamExt; +use sp_keyring::AccountKeyring; +use subxt::blocks::BlocksClient; // Check that we can subscribe to non-finalized blocks. #[tokio::test] @@ -118,3 +120,71 @@ async fn runtime_api_call() -> Result<(), subxt::Error> { assert_eq!(&metadata_call, metadata); Ok(()) } + +#[tokio::test] +async fn decode_extrinsics() { + let ctx = test_context().await; + let api = ctx.client(); + + let alice = pair_signer(AccountKeyring::Alice.pair()); + let bob = pair_signer(AccountKeyring::Bob.pair()); + + // Generate a block that has unsigned and signed transactions. + let tx = node_runtime::tx() + .balances() + .transfer(bob.account_id().clone().into(), 10_000); + + let signed_extrinsic = api + .tx() + .create_signed(&tx, &alice, Default::default()) + .await + .unwrap(); + + let in_block = signed_extrinsic + .submit_and_watch() + .await + .unwrap() + .wait_for_in_block() + .await + .unwrap(); + + let block_hash = in_block.block_hash(); + + let block = BlocksClient::new(api).at(block_hash).await.unwrap(); + let extrinsics = block.body().await.unwrap().extrinsics(); + assert_eq!(extrinsics.len(), 2); + assert_eq!(extrinsics.block_hash(), block_hash); + + assert!(extrinsics + .has::() + .unwrap()); + + assert!(extrinsics + .find_first::() + .unwrap() + .is_some()); + + let block_extrinsics = extrinsics + .iter() + .map(|res| res.unwrap()) + .collect::>(); + + assert_eq!(block_extrinsics.len(), 2); + let timestamp = block_extrinsics.get(0).unwrap(); + timestamp.as_root_extrinsic::().unwrap(); + timestamp + .as_pallet_extrinsic::() + .unwrap(); + timestamp + .as_extrinsic::() + .unwrap(); + assert!(!timestamp.is_signed()); + + let tx = block_extrinsics.get(1).unwrap(); + tx.as_root_extrinsic::().unwrap(); + tx.as_pallet_extrinsic::() + .unwrap(); + tx.as_extrinsic::() + .unwrap(); + assert!(tx.is_signed()); +} From 4f9b706f70aa5f7f3feda3ad447ed6111d85c689 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Tue, 2 May 2023 17:44:01 +0300 Subject: [PATCH 23/38] extrinsics/test: Add fake metadata for static decoding Signed-off-by: Alexandru Vasile --- Cargo.lock | 1 + subxt/Cargo.toml | 1 + subxt/src/blocks/extrinsic_types.rs | 138 ++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index f653eb2d3d..a60e67c27d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3502,6 +3502,7 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" name = "subxt" version = "0.28.0" dependencies = [ + "assert_matches", "base58", "bitvec", "blake2", diff --git a/subxt/Cargo.toml b/subxt/Cargo.toml index c9dc8061ed..99cfb4372e 100644 --- a/subxt/Cargo.toml +++ b/subxt/Cargo.toml @@ -84,3 +84,4 @@ sp-core = { workspace = true } sp-runtime = { workspace = true } sp-keyring = { workspace = true } sp-version = { workspace = true } +assert_matches = { workspace = true } diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 08c69ddd2d..3fd43b8810 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -630,3 +630,141 @@ impl ExtrinsicEvents { Ok(self.find::().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 { + 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 { + 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, + }, + } + + /// 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::(), + }), + event: None, + constants: vec![], + error: None, + index: 0, + docs: vec![], + }]; + + let extrinsic = ExtrinsicMetadata { + ty: meta_type::>(), + 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 { + // 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"); + } +} From 0fcb395d005db2e990fc6a7d3577249244632ee6 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Tue, 2 May 2023 17:44:35 +0300 Subject: [PATCH 24/38] extrinsics/test: Decode from insufficient bytes Signed-off-by: Alexandru Vasile --- subxt/src/blocks/extrinsic_types.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 3fd43b8810..30aef5ec18 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -767,4 +767,27 @@ mod tests { 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 = ExtrinsicIds::new(metadata.runtime_metadata()).unwrap(); + + // Decode with empty bytes. + let result = ExtrinsicDetails::decode_from( + 1, + vec![].into(), + client.clone(), + H256::random(), + Default::default(), + ids, + ); + assert_matches!( + result.err(), + Some(crate::Error::Block( + crate::error::BlockError::InsufficientData + )) + ); + } } From 1f79f39360c67d7caada9de324621e1e91aba193 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Tue, 2 May 2023 17:44:58 +0300 Subject: [PATCH 25/38] extrinsics/test: Check unsupported versions Signed-off-by: Alexandru Vasile --- subxt/src/blocks/extrinsic_types.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 30aef5ec18..de59507cc5 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -790,4 +790,28 @@ mod tests { )) ); } + + #[test] + fn unsupported_version_extrinsic() { + let metadata = metadata(); + let client = client(metadata.clone()); + let ids = ExtrinsicIds::new(metadata.runtime_metadata()).unwrap(); + + // Decode with invalid version. + let result = ExtrinsicDetails::decode_from( + 1, + 3u8.encode().into(), + client.clone(), + H256::random(), + Default::default(), + ids, + ); + + assert_matches!( + result.err(), + Some(crate::Error::Block( + crate::error::BlockError::UnsupportedVersion(3) + )) + ); + } } From f6cccd76458ff9db3734bb5213c5d854eb0341dc Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Tue, 2 May 2023 17:45:23 +0300 Subject: [PATCH 26/38] extrinsics/test: Statically decode to root and pallet enums Signed-off-by: Alexandru Vasile --- subxt/src/blocks/extrinsic_types.rs | 71 +++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index de59507cc5..41860bed9c 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -814,4 +814,75 @@ mod tests { )) ); } + + #[test] + fn statically_decode_extrinsic() { + let metadata = metadata(); + let client = client(metadata.clone()); + let ids = ExtrinsicIds::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(), "Test"); + + assert_eq!(extrinsic.variant_index(), 2); + assert_eq!(extrinsic.variant_name(), "TestCall"); + + // Decode the extrinsic to the root enum. + let decoded_extrinsic = extrinsic + .as_root_extrinsic::() + .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::() + .expect("can decode extrinsic to pallet enum"); + + assert_eq!( + decoded_extrinsic, + Pallet::TestCall { + value: 10, + signed: true, + name: "SomeValue".into(), + } + ); + } } From 6f3421f592c4b9470fcdb0d3fd3f7e7f0bae5c8c Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Tue, 2 May 2023 17:51:09 +0300 Subject: [PATCH 27/38] extrinsics/tests: Remove clones Signed-off-by: Alexandru Vasile --- subxt/src/blocks/extrinsic_types.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 41860bed9c..640219f5fd 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -778,7 +778,7 @@ mod tests { let result = ExtrinsicDetails::decode_from( 1, vec![].into(), - client.clone(), + client, H256::random(), Default::default(), ids, @@ -801,7 +801,7 @@ mod tests { let result = ExtrinsicDetails::decode_from( 1, 3u8.encode().into(), - client.clone(), + client, H256::random(), Default::default(), ids, From db5749cafcdacc3324131a7a8e74d668b2f48bb5 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Wed, 3 May 2023 00:06:25 +0300 Subject: [PATCH 28/38] blocks: Fetch block body inline Signed-off-by: Alexandru Vasile --- subxt/src/blocks/block_types.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/subxt/src/blocks/block_types.rs b/subxt/src/blocks/block_types.rs index f62f88a377..5f068a6ec7 100644 --- a/subxt/src/blocks/block_types.rs +++ b/subxt/src/blocks/block_types.rs @@ -71,7 +71,10 @@ where /// Fetch and return the block body. pub async fn body(&self) -> Result, Error> { let ids = ExtrinsicIds::new(self.client.metadata().runtime_metadata())?; - let block_details = self.block_details().await?; + let block_hash = self.header.hash(); + 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(), @@ -91,15 +94,6 @@ where pub async fn runtime_api(&self) -> Result, Error> { Ok(RuntimeApi::new(self.client.clone(), self.hash())) } - - /// Fetch the block's body from the chain. - async fn block_details(&self) -> Result, Error> { - let block_hash = self.header.hash(); - match self.client.rpc().block(Some(block_hash)).await? { - Some(block) => Ok(block), - None => Err(BlockError::not_found(block_hash).into()), - } - } } /// The body of a block. From 3edce152d46d8956d5dcbefcc653bf11aaf6387b Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Wed, 3 May 2023 00:07:17 +0300 Subject: [PATCH 29/38] blocks: Rename ExtrinsicIds to ExtrinsicPartTypeIds Signed-off-by: Alexandru Vasile --- subxt/src/blocks/block_types.rs | 8 ++++---- subxt/src/blocks/extrinsic_types.rs | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/subxt/src/blocks/block_types.rs b/subxt/src/blocks/block_types.rs index 5f068a6ec7..37830973ee 100644 --- a/subxt/src/blocks/block_types.rs +++ b/subxt/src/blocks/block_types.rs @@ -3,7 +3,7 @@ // see LICENSE for license details. use crate::{ - blocks::{extrinsic_types::ExtrinsicIds, Extrinsics}, + blocks::{extrinsic_types::ExtrinsicPartTypeIds, Extrinsics}, client::{OfflineClientT, OnlineClientT}, config::{Config, Header}, error::{BlockError, Error}, @@ -70,7 +70,7 @@ where /// Fetch and return the block body. pub async fn body(&self) -> Result, Error> { - let ids = ExtrinsicIds::new(self.client.metadata().runtime_metadata())?; + let ids = ExtrinsicPartTypeIds::new(self.client.metadata().runtime_metadata())?; let block_hash = self.header.hash(); let Some(block_details) = self.client.rpc().block(Some(block_hash)).await? else { return Err(BlockError::not_found(block_hash).into()); @@ -101,7 +101,7 @@ pub struct BlockBody { details: ChainBlockResponse, client: C, cached_events: CachedEvents, - ids: ExtrinsicIds, + ids: ExtrinsicPartTypeIds, } impl BlockBody @@ -113,7 +113,7 @@ where client: C, details: ChainBlockResponse, cached_events: CachedEvents, - ids: ExtrinsicIds, + ids: ExtrinsicPartTypeIds, ) -> Self { Self { details, diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 640219f5fd..82e389ecc8 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -58,7 +58,7 @@ pub struct Extrinsics { client: C, extrinsics: Vec, cached_events: CachedEvents, - ids: ExtrinsicIds, + ids: ExtrinsicPartTypeIds, hash: T::Hash, } @@ -71,7 +71,7 @@ where client: C, extrinsics: Vec, cached_events: CachedEvents, - ids: ExtrinsicIds, + ids: ExtrinsicPartTypeIds, hash: T::Hash, ) -> Self { Self { @@ -207,7 +207,7 @@ where client: C, block_hash: T::Hash, cached_events: CachedEvents, - ids: ExtrinsicIds, + ids: ExtrinsicPartTypeIds, ) -> Result, Error> { const SIGNATURE_MASK: u8 = 0b1000_0000; const VERSION_MASK: u8 = 0b0111_1111; @@ -478,7 +478,7 @@ where /// generic type parameters passed to the `UncheckedExtrinsic` from /// the substrate-based chain. #[derive(Debug, Copy, Clone)] -pub(crate) struct ExtrinsicIds { +pub(crate) struct ExtrinsicPartTypeIds { /// The address (source) of the extrinsic. address: u32, /// The extrinsic call type. @@ -489,7 +489,7 @@ pub(crate) struct ExtrinsicIds { extra: u32, } -impl ExtrinsicIds { +impl ExtrinsicPartTypeIds { /// Extract the generic type parameters IDs from the extrinsic type. pub(crate) fn new(metadata: &RuntimeMetadataV15) -> Result { const ADDRESS: &str = "Address"; @@ -528,7 +528,7 @@ impl ExtrinsicIds { return Err(BlockError::MissingType); }; - Ok(ExtrinsicIds { + Ok(ExtrinsicPartTypeIds { address: *address, call: *call, signature: *signature, @@ -772,7 +772,7 @@ mod tests { fn insufficient_extrinsic_bytes() { let metadata = metadata(); let client = client(metadata.clone()); - let ids = ExtrinsicIds::new(metadata.runtime_metadata()).unwrap(); + let ids = ExtrinsicPartTypeIds::new(metadata.runtime_metadata()).unwrap(); // Decode with empty bytes. let result = ExtrinsicDetails::decode_from( @@ -795,7 +795,7 @@ mod tests { fn unsupported_version_extrinsic() { let metadata = metadata(); let client = client(metadata.clone()); - let ids = ExtrinsicIds::new(metadata.runtime_metadata()).unwrap(); + let ids = ExtrinsicPartTypeIds::new(metadata.runtime_metadata()).unwrap(); // Decode with invalid version. let result = ExtrinsicDetails::decode_from( @@ -819,7 +819,7 @@ mod tests { fn statically_decode_extrinsic() { let metadata = metadata(); let client = client(metadata.clone()); - let ids = ExtrinsicIds::new(metadata.runtime_metadata()).unwrap(); + let ids = ExtrinsicPartTypeIds::new(metadata.runtime_metadata()).unwrap(); let tx = crate::tx::dynamic( "Test", From e371a24cb693ff92163ac3facf85aeaff316f770 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Wed, 3 May 2023 00:12:20 +0300 Subject: [PATCH 30/38] extrinsics/test: Check decode as_extrinsic Signed-off-by: Alexandru Vasile --- subxt/src/blocks/extrinsic_types.rs | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 82e389ecc8..80b17882d7 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -717,6 +717,28 @@ mod tests { }, } + #[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 { @@ -884,5 +906,20 @@ mod tests { name: "SomeValue".into(), } ); + + // Decode the extrinsic to the extrinsic variant. + let decoded_extrinsic = extrinsic + .as_extrinsic::() + .expect("can decode extrinsic to extrinsic variant") + .expect("value cannot be None"); + + assert_eq!( + decoded_extrinsic, + TestCallExtrinsic { + value: 10, + signed: true, + name: "SomeValue".into(), + } + ); } } From 73fac1382711265892a4d4facac78765c68434e2 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Wed, 3 May 2023 12:17:18 +0300 Subject: [PATCH 31/38] blocks: Remove InsufficientData error Signed-off-by: Alexandru Vasile --- subxt/src/blocks/extrinsic_types.rs | 23 +++++++---------------- subxt/src/error/mod.rs | 3 --- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 80b17882d7..3f714fe3ed 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -13,6 +13,7 @@ use crate::{ Metadata, }; +use codec::Decode; use derivative::Derivative; use frame_metadata::v15::RuntimeMetadataV15; use scale_decode::DecodeAsFields; @@ -219,16 +220,14 @@ where // - first byte: abbbbbbb (a = 0 for unsigned, 1 for signed, b = version) // - signature: [unknown TBD with metadata]. // - extrinsic data - if extrinsic_bytes.is_empty() { - return Err(BlockError::InsufficientData.into()); - } + let first_byte: u8 = Decode::decode(&mut &extrinsic_bytes[..])?; - let version = extrinsic_bytes[0] & VERSION_MASK; + let version = first_byte & VERSION_MASK; if version != LATEST_EXTRINSIC_VERSION { return Err(BlockError::UnsupportedVersion(version).into()); } - let is_signed = extrinsic_bytes[0] & SIGNATURE_MASK != 0; + 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..]; @@ -280,11 +279,8 @@ where // Decode the pallet index, then the call variant. let cursor = &mut &extrinsic_bytes[call_start_idx..]; - if cursor.len() < 2 { - return Err(BlockError::InsufficientData.into()); - } - let pallet_index = cursor[0]; - let variant_index = cursor[1]; + let pallet_index: u8 = Decode::decode(cursor)?; + let variant_index: u8 = Decode::decode(cursor)?; Ok(ExtrinsicDetails { index, @@ -805,12 +801,7 @@ mod tests { Default::default(), ids, ); - assert_matches!( - result.err(), - Some(crate::Error::Block( - crate::error::BlockError::InsufficientData - )) - ); + assert_matches!(result.err(), Some(crate::Error::Codec(_))); } #[test] diff --git a/subxt/src/error/mod.rs b/subxt/src/error/mod.rs index 9df7c018b6..70ae1679a5 100644 --- a/subxt/src/error/mod.rs +++ b/subxt/src/error/mod.rs @@ -105,9 +105,6 @@ pub enum BlockError { /// Extrinsic type ID cannot be resolved with the provided metadata. #[error("Extrinsic type ID cannot be resolved with the provided metadata. Make sure this is a valid metadata")] MissingType, - /// Expected more extrinsic bytes. - #[error("Expected more extrinsic bytes")] - InsufficientData, /// Unsupported signature. #[error("Unsupported extrinsic version, only version 4 is supported currently")] /// The extrinsic has an unsupported version. From 6fada8fcdca9a77c7bc048190f801c36adfd24cb Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Wed, 3 May 2023 12:32:53 +0300 Subject: [PATCH 32/38] blocks: Return error from extrinsic_metadata Signed-off-by: Alexandru Vasile --- subxt/src/blocks/extrinsic_types.rs | 40 ++++++++++++++++++----------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 3f714fe3ed..c19c1b3de5 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -365,20 +365,20 @@ where } /// The name of the pallet from whence the extrinsic originated. - pub fn pallet_name(&self) -> &str { - self.extrinsic_metadata().pallet() + 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) -> &str { - self.extrinsic_metadata().call() + pub fn variant_name(&self) -> Result<&str, Error> { + Ok(self.extrinsic_metadata()?.call()) } /// Fetch the metadata for this extrinsic. - pub fn extrinsic_metadata(&self) -> &ExtrinsicMetadata { - self.metadata - .extrinsic(self.pallet_index(), self.variant_index()) - .expect("this must exist in order to have produced the ExtrinsicDetails") + 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`] @@ -388,7 +388,7 @@ where &self, ) -> Result, Error> { let bytes = &mut self.field_bytes(); - let extrinsic_metadata = self.extrinsic_metadata(); + let extrinsic_metadata = self.extrinsic_metadata()?; let decoded = >::decode_as_fields( bytes, @@ -404,7 +404,7 @@ where /// 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(&self) -> Result, Error> { - let extrinsic_metadata = self.extrinsic_metadata(); + 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(), @@ -421,7 +421,7 @@ where /// 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(&self) -> Result { - let pallet = self.metadata.pallet(self.pallet_name())?; + 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(), @@ -439,7 +439,7 @@ where /// 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(&self) -> Result { - let pallet = self.metadata.pallet(self.pallet_name())?; + 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(), @@ -450,7 +450,7 @@ where // Ignore root enum index. E::root_extrinsic( &self.call_bytes()[1..], - self.pallet_name(), + self.pallet_name()?, pallet_extrinsic_ty, &self.metadata, ) @@ -865,10 +865,20 @@ mod tests { assert_eq!(extrinsic.index(), 1); assert_eq!(extrinsic.pallet_index(), 0); - assert_eq!(extrinsic.pallet_name(), "Test"); + assert_eq!( + extrinsic + .pallet_name() + .expect("Valid metadata contains pallet name"), + "Test" + ); assert_eq!(extrinsic.variant_index(), 2); - assert_eq!(extrinsic.variant_name(), "TestCall"); + assert_eq!( + extrinsic + .variant_name() + .expect("Valid metadata contains variant name"), + "TestCall" + ); // Decode the extrinsic to the root enum. let decoded_extrinsic = extrinsic From 96b403ecf3ad328fa3c779c540678d694aeced83 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Wed, 3 May 2023 12:33:42 +0300 Subject: [PATCH 33/38] extrinsics: Postpone decoding of call bytes Signed-off-by: Alexandru Vasile --- subxt/src/blocks/extrinsic_types.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index c19c1b3de5..4a51d65dd2 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -267,15 +267,6 @@ where let call_start_idx = extrinsic_bytes.len() - cursor.len(); - // Ensure the provided bytes are sound. - scale_decode::visitor::decode_with_visitor( - &mut *cursor, - ids.call, - &metadata.runtime_metadata().types, - scale_decode::visitor::IgnoreVisitor, - ) - .map_err(scale_decode::Error::from)?; - // Decode the pallet index, then the call variant. let cursor = &mut &extrinsic_bytes[call_start_idx..]; From e7510ab6578f72d753e80de7d323766287d62892 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Wed, 3 May 2023 18:01:10 +0300 Subject: [PATCH 34/38] metadata_type: Rename variables Signed-off-by: Alexandru Vasile --- subxt/src/metadata/metadata_type.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/subxt/src/metadata/metadata_type.rs b/subxt/src/metadata/metadata_type.rs index b090d2f8ee..c24065c1b6 100644 --- a/subxt/src/metadata/metadata_type.rs +++ b/subxt/src/metadata/metadata_type.rs @@ -667,10 +667,10 @@ impl TryFrom for Metadata { return Err(InvalidMetadataError::MissingCallType); }; - let type_def_variant = get_type_def_variant(call_id)?; + let call_type_variants = get_type_def_variant(call_id)?; let mut extrinsics = HashMap::<(u8, u8), ExtrinsicMetadata>::new(); - for variant in &type_def_variant.variants { + for variant in &call_type_variants.variants { let pallet_name: Arc = variant.name.to_string().into(); let pallet_index = variant.index; @@ -688,8 +688,8 @@ impl TryFrom for Metadata { }; // Get the call variant. - let type_def_variant = get_type_def_variant(ty.ty.id)?; - for variant in &type_def_variant.variants { + let call_type_variant = get_type_def_variant(ty.ty.id)?; + for variant in &call_type_variant.variants { extrinsics.insert( (pallet_index, variant.index), ExtrinsicMetadata { From 331116e5f54a683b3af785f2b1366d2adaccdc4f Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Wed, 3 May 2023 18:54:06 +0300 Subject: [PATCH 35/38] Adjust calls path for example and tests Signed-off-by: Alexandru Vasile --- examples/examples/block_extrinsics.rs | 4 ++-- testing/integration-tests/src/blocks/mod.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/examples/block_extrinsics.rs b/examples/examples/block_extrinsics.rs index c8b78266bb..5ff1b7f465 100644 --- a/examples/examples/block_extrinsics.rs +++ b/examples/examples/block_extrinsics.rs @@ -63,7 +63,7 @@ async fn main() -> Result<(), Box> { // Ask for the extrinsics for this block. let extrinsics = block.body().await?.extrinsics(); - let transfer_tx = extrinsics.find_first::(); + let transfer_tx = extrinsics.find_first::(); println!(" Transfer tx: {:?}", transfer_tx); // Ask for the extrinsics for this block. @@ -87,7 +87,7 @@ async fn main() -> Result<(), Box> { pallet_extrinsic ); - let call = extrinsic.as_extrinsic::()?; + let call = extrinsic.as_extrinsic::()?; println!( " Extrinsic as polkadot::balances::calls::Transfer: {:?}\n\n", call diff --git a/testing/integration-tests/src/blocks/mod.rs b/testing/integration-tests/src/blocks/mod.rs index b509105100..bf9e707997 100644 --- a/testing/integration-tests/src/blocks/mod.rs +++ b/testing/integration-tests/src/blocks/mod.rs @@ -156,11 +156,11 @@ async fn decode_extrinsics() { assert_eq!(extrinsics.block_hash(), block_hash); assert!(extrinsics - .has::() + .has::() .unwrap()); assert!(extrinsics - .find_first::() + .find_first::() .unwrap() .is_some()); @@ -176,7 +176,7 @@ async fn decode_extrinsics() { .as_pallet_extrinsic::() .unwrap(); timestamp - .as_extrinsic::() + .as_extrinsic::() .unwrap(); assert!(!timestamp.is_signed()); @@ -184,7 +184,7 @@ async fn decode_extrinsics() { tx.as_root_extrinsic::().unwrap(); tx.as_pallet_extrinsic::() .unwrap(); - tx.as_extrinsic::() + tx.as_extrinsic::() .unwrap(); assert!(tx.is_signed()); } From 04f31749c4e75606f30e9da349dc32dfdbd1efbe Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Tue, 9 May 2023 18:54:41 +0300 Subject: [PATCH 36/38] examples: Remove traces Signed-off-by: Alexandru Vasile --- examples/examples/block_extrinsics.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/examples/block_extrinsics.rs b/examples/examples/block_extrinsics.rs index 5ff1b7f465..e71182c421 100644 --- a/examples/examples/block_extrinsics.rs +++ b/examples/examples/block_extrinsics.rs @@ -22,8 +22,6 @@ pub mod polkadot {} /// pluck out the events that we care about. #[tokio::main] async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - // Create a client to use: let api = OnlineClient::::new().await?; From 1a56328e7cbda2132c51973c78d8123352ba2004 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Tue, 9 May 2023 19:30:40 +0300 Subject: [PATCH 37/38] book: Add extrinsics documentation Signed-off-by: Alexandru Vasile --- subxt/src/book/usage/extrinsics.rs | 51 ++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/subxt/src/book/usage/extrinsics.rs b/subxt/src/book/usage/extrinsics.rs index 3d409f5772..30b99e64af 100644 --- a/subxt/src/book/usage/extrinsics.rs +++ b/subxt/src/book/usage/extrinsics.rs @@ -19,6 +19,8 @@ //! > can construct unsigned extrinsics, but overwhelmingly you'll need to sign them, and so the //! > documentation tends to use the terms _extrinsic_ and _transaction_ interchangeably. //! +//! Furthermore, Subxt is capable of decoding extrinsics included in blocks. +//! //! ## Constructing an extrinsic payload //! //! We can use the statically generated interface to build extrinsic payloads: @@ -173,3 +175,52 @@ //! Take a look at the API docs for [`crate::tx::TxProgress`], [`crate::tx::TxStatus`] and //! [`crate::tx::TxInBlock`] for more options. //! +//! ## Decoding Extrinsics +//! +//! Subxt uses a block-centric API to allow users to decode extrinsics. This approach requires users to retrieve +//! a block's body from the chain before being able to decode the extrinsics. By considering that the block's body +//! consists of the extrinsics and an additional justification, this API aligns well with the operational nature of the chain +//! and with how subscriptions work. +//! +//! The process of decoding extrinsics generally involves the following steps: +//! +//! 1. Retrieve a block from the chain: This can be done directly by providing a specific hash using [crate::blocks::BlocksClient::at()] +//! and [crate::blocks::BlocksClient::at_latest()], or indirectly by subscribing to the latest produced blocks of the chain using +//! [crate::blocks::BlocksClient::subscribe_finalized()]. +//! +//! 2. Fetch the block's body using [crate::blocks::Block::body()]. +//! +//! 3. Obtain the extrinsics from the block's body using [crate::blocks::BlockBody::extrinsics()]. +//! +//! ```rust,no_run +//! # #[tokio::main] +//! # async fn main() -> Result<(), Box> { +//! use subxt::client::OnlineClient; +//! use subxt::config::PolkadotConfig; +//! +//! // Create client: +//! let client = OnlineClient::::new().await?; +//! +//! // Get the lastest block (or use .at() to specify a block hash): +//! let block = client.blocks().at_latest().await?; +//! +//! // Get the block's body which contains the extrinsics. +//! let body = block.body().await?; +//! +//! // Fetch the extrinsics of the block's body. +//! let extrinsics = block.body().await?.extrinsics(); +//! # Ok(()) +//! # } +//! ``` +//! +//! Once the extrinsics are loaded, similar to events, you can iterate through the extrinsics or search for specific extrinsics using methods +//! such as [crate::blocks::Extrinsics::iter()] and [crate::blocks::Extrinsics::find()]. For more information, refer to [crate::blocks::ExtrinsicDetails]. +//! +//! ### Example +//! +//! Here's an example that demonstrates the usage of these concepts: +//! +//! ```rust,ignore +#![doc = include_str!("../../../../examples/examples/block_extrinsics.rs")] +//! ``` +//! From e2bab4c3b1bdc6ee9cc104918e45b7ca61d54500 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Wed, 10 May 2023 12:56:15 +0300 Subject: [PATCH 38/38] book: Improve extrinsics docs Signed-off-by: Alexandru Vasile --- subxt/src/book/usage/extrinsics.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/subxt/src/book/usage/extrinsics.rs b/subxt/src/book/usage/extrinsics.rs index 30b99e64af..314ee252dc 100644 --- a/subxt/src/book/usage/extrinsics.rs +++ b/subxt/src/book/usage/extrinsics.rs @@ -177,10 +177,8 @@ //! //! ## Decoding Extrinsics //! -//! Subxt uses a block-centric API to allow users to decode extrinsics. This approach requires users to retrieve -//! a block's body from the chain before being able to decode the extrinsics. By considering that the block's body -//! consists of the extrinsics and an additional justification, this API aligns well with the operational nature of the chain -//! and with how subscriptions work. +//! The block body is made up of extrinsics representing the generalization of the concept of transactions. +//! Extrinsics can contain any external data the underlying chain wishes to validate and track. //! //! The process of decoding extrinsics generally involves the following steps: //!