diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c4b5ffb92b..9d0bf3886e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,6 +6,7 @@ updates: schedule: interval: weekly - package-ecosystem: github-actions - directory: "**/*" + directories: + - "**/*" schedule: interval: weekly diff --git a/Cargo.lock b/Cargo.lock index 99cfcb9578..75761f9f1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1748,6 +1748,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "frame-decode" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed90459016b06a2855321469cb01fbc74208c80c06b085d1ed13162cf8bd7e1b" +dependencies = [ + "frame-metadata 16.0.0", + "hex", + "parity-scale-codec", + "scale-decode", + "scale-info", + "scale-type-resolver", + "sp-crypto-hashing", +] + [[package]] name = "frame-metadata" version = "15.1.0" @@ -5313,6 +5328,7 @@ dependencies = [ "bitvec", "blake2", "derive-where", + "frame-decode", "frame-metadata 16.0.0", "hashbrown 0.14.5", "hex", @@ -5387,6 +5403,7 @@ dependencies = [ "assert_matches", "bitvec", "criterion", + "frame-decode", "frame-metadata 16.0.0", "hashbrown 0.14.5", "parity-scale-codec", diff --git a/Cargo.toml b/Cargo.toml index 196606148a..672b799d4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,7 @@ darling = "0.20.10" derive-where = "1.2.7" either = { version = "1.13.0", default-features = false } finito = { version = "0.1.0", default-features = false } +frame-decode = { version = "0.3.0", default-features = false } frame-metadata = { version = "16.0.0", default-features = false } futures = { version = "0.3.30", default-features = false, features = ["std"] } getrandom = { version = "0.2", default-features = false } diff --git a/core/Cargo.toml b/core/Cargo.toml index 638fa570c4..78ca200f75 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -36,6 +36,7 @@ substrate-compat = ["sp-core", "sp-runtime"] [dependencies] codec = { package = "parity-scale-codec", workspace = true, default-features = false, features = ["derive"] } +frame-decode = { workspace = true } scale-info = { workspace = true, default-features = false, features = ["bit-vec"] } scale-value = { workspace = true, default-features = false } scale-bits = { workspace = true, default-features = false } diff --git a/core/src/blocks/extrinsic_signed_extensions.rs b/core/src/blocks/extrinsic_signed_extensions.rs index fbda944cff..4731cba6b0 100644 --- a/core/src/blocks/extrinsic_signed_extensions.rs +++ b/core/src/blocks/extrinsic_signed_extensions.rs @@ -8,6 +8,7 @@ use crate::config::signed_extensions::{ use crate::config::SignedExtension; use crate::dynamic::Value; use crate::{config::Config, error::Error, Metadata}; +use frame_decode::extrinsics::ExtrinsicExtensions; use scale_decode::DecodeAsType; /// The signed extensions of an extrinsic. @@ -15,58 +16,32 @@ use scale_decode::DecodeAsType; pub struct ExtrinsicSignedExtensions<'a, T: Config> { bytes: &'a [u8], metadata: &'a Metadata, + decoded_info: &'a ExtrinsicExtensions<'static, u32>, _marker: core::marker::PhantomData, } impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> { - pub(crate) fn new(bytes: &'a [u8], metadata: &'a Metadata) -> Self { + pub(crate) fn new( + bytes: &'a [u8], + metadata: &'a Metadata, + decoded_info: &'a ExtrinsicExtensions<'static, u32>, + ) -> Self { Self { bytes, metadata, + decoded_info, _marker: core::marker::PhantomData, } } /// Returns an iterator over each of the signed extension details of the extrinsic. - /// If the decoding of any signed extension fails, an error item is yielded and the iterator stops. - pub fn iter(&self) -> impl Iterator, Error>> { - let signed_extension_types = self.metadata.extrinsic().signed_extensions(); - let num_signed_extensions = signed_extension_types.len(); - let bytes = self.bytes; - let mut index = 0; - let mut byte_start_idx = 0; - let metadata = &self.metadata; - - core::iter::from_fn(move || { - if index == num_signed_extensions { - return None; - } - - let extension = &signed_extension_types[index]; - let ty_id = extension.extra_ty(); - let cursor = &mut &bytes[byte_start_idx..]; - if let Err(err) = scale_decode::visitor::decode_with_visitor( - cursor, - ty_id, - metadata.types(), - scale_decode::visitor::IgnoreVisitor::new(), - ) - .map_err(|e| Error::Decode(e.into())) - { - index = num_signed_extensions; // (such that None is returned in next iteration) - return Some(Err(err)); - } - let byte_end_idx = bytes.len() - cursor.len(); - let bytes = &bytes[byte_start_idx..byte_end_idx]; - byte_start_idx = byte_end_idx; - index += 1; - Some(Ok(ExtrinsicSignedExtension { - bytes, - ty_id, - identifier: extension.identifier(), - metadata, - _marker: core::marker::PhantomData, - })) + pub fn iter(&self) -> impl Iterator> { + self.decoded_info.iter().map(|s| ExtrinsicSignedExtension { + bytes: &self.bytes[s.range()], + ty_id: *s.ty(), + identifier: s.name(), + metadata: self.metadata, + _marker: core::marker::PhantomData, }) } @@ -75,9 +50,6 @@ impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> { /// If the Signed Extension is found but decoding failed `Err(_)` is returned. pub fn find>(&self) -> Result, Error> { for ext in self.iter() { - // If we encounter an error while iterating, we won't get any more results - // back, so just return that error as we won't find the signed ext anyway. - let ext = ext?; match ext.as_signed_extension::() { // We found a match; return it: Ok(Some(e)) => return Ok(Some(e)), diff --git a/core/src/blocks/extrinsics.rs b/core/src/blocks/extrinsics.rs index b8afeca47d..2b82d2eb1b 100644 --- a/core/src/blocks/extrinsics.rs +++ b/core/src/blocks/extrinsics.rs @@ -2,15 +2,17 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. +use super::BlockError; use crate::blocks::extrinsic_signed_extensions::ExtrinsicSignedExtensions; use crate::{ config::{Config, Hasher}, - error::{BlockError, Error, MetadataError}, + error::{Error, MetadataError}, Metadata, }; use alloc::sync::Arc; use alloc::vec::Vec; -use codec::{Compact, CompactLen, Decode}; +use core::ops::Deref; +use frame_decode::extrinsics::Extrinsic; use scale_decode::DecodeAsType; use subxt_metadata::PalletMetadata; @@ -18,9 +20,8 @@ pub use crate::blocks::StaticExtrinsic; /// The body of a block. pub struct Extrinsics { - extrinsics: Vec>, + extrinsics: Vec, Vec)>>, metadata: Metadata, - ids: ExtrinsicPartTypeIds, _marker: core::marker::PhantomData, } @@ -28,13 +29,33 @@ impl Extrinsics { /// Instantiate a new [`Extrinsics`] object, given a vector containing /// each extrinsic hash (in the form of bytes) and some metadata that /// we'll use to decode them. - pub fn decode_from(extrinsics: Vec>, metadata: Metadata) -> Result { - let ids = ExtrinsicPartTypeIds::new(&metadata)?; + pub fn decode_from(extrinsics: Vec>, metadata: Metadata) -> Result { + let extrinsics = extrinsics + .into_iter() + .map(|bytes| { + let cursor = &mut &*bytes; + + // Try to decode the extrinsic. + let decoded_info = frame_decode::extrinsics::decode_extrinsic( + cursor, + metadata.deref(), + metadata.types(), + ) + .map_err(BlockError::ExtrinsicDecodeError)? + .into_owned(); + + // We didn't consume all bytes, so decoding probably failed. + if !cursor.is_empty() { + return Err(BlockError::LeftoverBytes(cursor.len()).into()); + } + + Ok(Arc::new((decoded_info, bytes))) + }) + .collect::>()?; Ok(Self { extrinsics, metadata, - ids, _marker: core::marker::PhantomData, }) } @@ -53,35 +74,13 @@ impl Extrinsics { /// Returns an iterator over the extrinsics in the block body. // Dev note: The returned iterator is 'static + Send so that we can box it up and make // use of it with our `FilterExtrinsic` stuff. - pub fn iter( - &self, - ) -> impl Iterator, Error>> + Send + Sync + 'static { + pub fn iter(&self) -> impl Iterator> + Send + Sync + 'static { let extrinsics = self.extrinsics.clone(); let num_extrinsics = self.extrinsics.len(); let metadata = self.metadata.clone(); - let ids = self.ids; - let mut index = 0; - - core::iter::from_fn(move || { - if index == num_extrinsics { - None - } else { - match ExtrinsicDetails::decode_from( - index as u32, - &extrinsics[index], - metadata.clone(), - ids, - ) { - Ok(extrinsic_details) => { - index += 1; - Some(Ok(extrinsic_details)) - } - Err(e) => { - index = num_extrinsics; - Some(Err(e)) - } - } - } + + (0..num_extrinsics).map(move |index| { + ExtrinsicDetails::new(index as u32, extrinsics[index].clone(), metadata.clone()) }) } @@ -91,15 +90,14 @@ impl Extrinsics { pub fn find( &self, ) -> impl Iterator, Error>> + '_ { - self.iter().filter_map(|res| match res { - Err(err) => Some(Err(err)), - Ok(details) => match details.as_extrinsic::() { + self.iter().filter_map(|details| { + match details.as_extrinsic::() { // Failed to decode extrinsic: Err(err) => Some(Err(err)), // Extrinsic for a different pallet / different call (skip): Ok(None) => None, Ok(Some(value)) => Some(Ok(FoundExtrinsic { details, value })), - }, + } }) } @@ -125,141 +123,41 @@ impl Extrinsics { pub struct ExtrinsicDetails { /// The index of the extrinsic in the block. index: u32, - /// Extrinsic bytes. - bytes: Arc<[u8]>, - /// Some if the extrinsic payload is signed. - signed_details: Option, - /// 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 and decode info. + ext: Arc<(Extrinsic<'static, u32>, Vec)>, /// Subxt metadata to fetch the extrinsic metadata. metadata: Metadata, _marker: core::marker::PhantomData, } -/// Details only available in signed extrinsics. -pub struct SignedExtrinsicDetails { - /// start index of the range in `bytes` of `ExtrinsicDetails` that encodes the address. - address_start_idx: usize, - /// end index of the range in `bytes` of `ExtrinsicDetails` that encodes the address. Equivalent to signature_start_idx. - address_end_idx: usize, - /// end index of the range in `bytes` of `ExtrinsicDetails` that encodes the signature. Equivalent to extra_start_idx. - signature_end_idx: usize, - /// end index of the range in `bytes` of `ExtrinsicDetails` that encodes the signature. - extra_end_idx: usize, -} - impl ExtrinsicDetails where T: Config, { // Attempt to dynamically decode a single extrinsic from the given input. #[doc(hidden)] - pub fn decode_from( + pub fn new( index: u32, - extrinsic_bytes: &[u8], + ext: Arc<(Extrinsic<'static, u32>, Vec)>, metadata: Metadata, - ids: ExtrinsicPartTypeIds, - ) -> Result, Error> { - const SIGNATURE_MASK: u8 = 0b1000_0000; - const VERSION_MASK: u8 = 0b0111_1111; - const LATEST_EXTRINSIC_VERSION: u8 = 4; - - // Wrap all of the bytes in Arc for easy sharing. - let bytes: Arc<[u8]> = Arc::from(extrinsic_bytes); - - // The compact encoded length prefix. - let prefix = >::decode(&mut &*extrinsic_bytes)?; - let prefix_len = >::compact_len(&prefix.0); - - // Extrinsic are encoded in memory in the following way: - // - first byte: abbbbbbb (a = 0 for unsigned, 1 for signed, b = version) - // - signature: [unknown TBD with metadata]. - // - extrinsic data - let version_byte: u8 = Decode::decode(&mut &bytes[prefix_len..])?; - - let version = version_byte & VERSION_MASK; - if version != LATEST_EXTRINSIC_VERSION { - return Err(BlockError::UnsupportedVersion(version).into()); - } - - let is_signed = version_byte & SIGNATURE_MASK != 0; - - // Skip over the prefix and first byte which denotes the version and signing. - let cursor = &mut &bytes[prefix_len + 1..]; - - let signed_details = is_signed - .then(|| -> Result { - let address_start_idx = bytes.len() - cursor.len(); - // Skip over the address, signature and extra fields. - scale_decode::visitor::decode_with_visitor( - cursor, - ids.address, - metadata.types(), - scale_decode::visitor::IgnoreVisitor::new(), - ) - .map_err(scale_decode::Error::from)?; - let address_end_idx = bytes.len() - cursor.len(); - - scale_decode::visitor::decode_with_visitor( - cursor, - ids.signature, - metadata.types(), - scale_decode::visitor::IgnoreVisitor::new(), - ) - .map_err(scale_decode::Error::from)?; - let signature_end_idx = bytes.len() - cursor.len(); - - scale_decode::visitor::decode_with_visitor( - cursor, - ids.extra, - metadata.types(), - scale_decode::visitor::IgnoreVisitor::new(), - ) - .map_err(scale_decode::Error::from)?; - let extra_end_idx = bytes.len() - cursor.len(); - - Ok(SignedExtrinsicDetails { - address_start_idx, - address_end_idx, - signature_end_idx, - extra_end_idx, - }) - }) - .transpose()?; - - let call_start_idx = bytes.len() - cursor.len(); - - // Decode the pallet index, then the call variant. - let cursor = &mut &bytes[call_start_idx..]; - - let pallet_index: u8 = Decode::decode(cursor)?; - let variant_index: u8 = Decode::decode(cursor)?; - - Ok(ExtrinsicDetails { + ) -> ExtrinsicDetails { + ExtrinsicDetails { index, - bytes, - signed_details, - call_start_idx, - pallet_index, - variant_index, + ext, metadata, _marker: core::marker::PhantomData, - }) + } } /// Calculate and return the hash of the extrinsic, based on the configured hasher. pub fn hash(&self) -> T::Hash { // Use hash(), not hash_of(), because we don't want to double encode the bytes. - T::Hasher::hash(&self.bytes) + T::Hasher::hash(self.bytes()) } /// Is the extrinsic signed? pub fn is_signed(&self) -> bool { - self.signed_details.is_some() + self.decoded_info().is_signed() } /// The index of the extrinsic in the block. @@ -275,7 +173,7 @@ where /// - Extra fields /// - Extrinsic call bytes pub fn bytes(&self) -> &[u8] { - &self.bytes + &self.ext.1 } /// Return only the bytes representing this extrinsic call: @@ -287,7 +185,7 @@ where /// /// Please use [`Self::bytes`] if you want to get all extrinsic bytes. pub fn call_bytes(&self) -> &[u8] { - &self.bytes[self.call_start_idx..] + &self.bytes()[self.decoded_info().call_data_range()] } /// Return the bytes representing the fields stored in this extrinsic. @@ -299,7 +197,7 @@ where 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..] + &self.bytes()[self.decoded_info().call_data_args_range()] } /// Return only the bytes of the address that signed this extrinsic. @@ -308,16 +206,16 @@ where /// /// Returns `None` if the extrinsic is not signed. pub fn address_bytes(&self) -> Option<&[u8]> { - self.signed_details - .as_ref() - .map(|e| &self.bytes[e.address_start_idx..e.address_end_idx]) + self.decoded_info() + .signature_payload() + .map(|s| &self.bytes()[s.address_range()]) } /// Returns Some(signature_bytes) if the extrinsic was signed otherwise None is returned. pub fn signature_bytes(&self) -> Option<&[u8]> { - self.signed_details - .as_ref() - .map(|e| &self.bytes[e.address_end_idx..e.signature_end_idx]) + self.decoded_info() + .signature_payload() + .map(|s| &self.bytes()[s.signature_range()]) } /// Returns the signed extension `extra` bytes of the extrinsic. @@ -327,26 +225,26 @@ where /// /// Note: Returns `None` if the extrinsic is not signed. pub fn signed_extensions_bytes(&self) -> Option<&[u8]> { - self.signed_details - .as_ref() - .map(|e| &self.bytes[e.signature_end_idx..e.extra_end_idx]) + self.decoded_info() + .transaction_extension_payload() + .map(|t| &self.bytes()[t.range()]) } /// Returns `None` if the extrinsic is not signed. pub fn signed_extensions(&self) -> Option> { - let signed = self.signed_details.as_ref()?; - let extra_bytes = &self.bytes[signed.signature_end_idx..signed.extra_end_idx]; - Some(ExtrinsicSignedExtensions::new(extra_bytes, &self.metadata)) + self.decoded_info() + .transaction_extension_payload() + .map(|t| ExtrinsicSignedExtensions::new(self.bytes(), &self.metadata, t)) } /// The index of the pallet that the extrinsic originated from. pub fn pallet_index(&self) -> u8 { - self.pallet_index + self.decoded_info().pallet_index() } /// The index of the extrinsic variant that the extrinsic originated from. pub fn variant_index(&self) -> u8 { - self.variant_index + self.decoded_info().call_index() } /// The name of the pallet from whence the extrinsic originated. @@ -418,6 +316,10 @@ where Ok(decoded) } + + fn decoded_info(&self) -> &Extrinsic<'static, u32> { + &self.ext.0 + } } /// A Static Extrinsic found in a block coupled with it's details. @@ -436,36 +338,6 @@ pub struct ExtrinsicMetadataDetails<'a> { pub variant: &'a scale_info::Variant, } -/// The type IDs extracted from the metadata that represent the -/// generic type parameters passed to the `UncheckedExtrinsic` from -/// the substrate-based chain. -#[doc(hidden)] -#[derive(Debug, Copy, Clone)] -pub struct ExtrinsicPartTypeIds { - /// The address (source) of the extrinsic. - address: u32, - /// The extrinsic call type. - // Note: the call type can be used to skip over the extrinsic bytes to check - // they are in line with our metadata. This operation is currently postponed. - _call: u32, - /// The signature of the extrinsic. - signature: u32, - /// The extra parameters of the extrinsic. - extra: u32, -} - -impl ExtrinsicPartTypeIds { - /// Extract the generic type parameters IDs from the extrinsic type. - fn new(metadata: &Metadata) -> Result { - Ok(ExtrinsicPartTypeIds { - address: metadata.extrinsic().address_ty(), - _call: metadata.extrinsic().call_ty(), - signature: metadata.extrinsic().signature_ty(), - extra: metadata.extrinsic().extra_ty(), - }) - } -} - #[cfg(test)] mod tests { use super::*; @@ -615,26 +487,32 @@ mod tests { #[test] fn insufficient_extrinsic_bytes() { let metadata = metadata(); - let ids = ExtrinsicPartTypeIds::new(&metadata).unwrap(); // Decode with empty bytes. - let result = ExtrinsicDetails::::decode_from(0, &[], metadata, ids); - assert_matches!(result.err(), Some(crate::Error::Codec(_))); + let result = Extrinsics::::decode_from(vec![vec![]], metadata); + assert_matches!( + result.err(), + Some(crate::Error::Block( + crate::error::BlockError::ExtrinsicDecodeError(_) + )) + ); } #[test] fn unsupported_version_extrinsic() { + use frame_decode::extrinsics::ExtrinsicDecodeError; + let metadata = metadata(); - let ids = ExtrinsicPartTypeIds::new(&metadata).unwrap(); // Decode with invalid version. - let result = - ExtrinsicDetails::::decode_from(0, &vec![3u8].encode(), metadata, ids); + let result = Extrinsics::::decode_from(vec![vec![3u8].encode()], metadata); assert_matches!( result.err(), Some(crate::Error::Block( - crate::error::BlockError::UnsupportedVersion(3) + crate::error::BlockError::ExtrinsicDecodeError( + ExtrinsicDecodeError::VersionNotSupported(3) + ) )) ); } @@ -642,7 +520,6 @@ mod tests { #[test] fn tx_hashes_line_up() { let metadata = metadata(); - let ids = ExtrinsicPartTypeIds::new(&metadata).unwrap(); let tx = crate::dynamic::tx( "Test", @@ -659,14 +536,14 @@ mod tests { .expect("Valid dynamic parameters are provided"); // Extrinsic details ready to decode. - let extrinsic = ExtrinsicDetails::::decode_from( - 1, - tx_encoded.encoded(), + let extrinsics = Extrinsics::::decode_from( + vec![tx_encoded.encoded().to_owned()], metadata, - ids, ) .expect("Valid extrinsic"); + let extrinsic = extrinsics.iter().next().unwrap(); + // Both of these types should produce the same bytes. assert_eq!(tx_encoded.encoded(), extrinsic.bytes(), "bytes should eq"); // Both of these types should produce the same hash. @@ -676,7 +553,6 @@ mod tests { #[test] fn statically_decode_extrinsic() { let metadata = metadata(); - let ids = ExtrinsicPartTypeIds::new(&metadata).unwrap(); let tx = crate::dynamic::tx( "Test", @@ -691,18 +567,18 @@ mod tests { .expect("Valid dynamic parameters are provided"); // Note: `create_unsigned` produces the extrinsic bytes by prefixing the extrinsic length. - // The length is handled by deserializing `ChainBlockExtrinsic`, therefore the first byte is not needed. - let extrinsic = ExtrinsicDetails::::decode_from( - 1, - tx_encoded.encoded(), + // The length is handled deserializing `ChainBlockExtrinsic`, therefore the first byte is not needed. + let extrinsics = Extrinsics::::decode_from( + vec![tx_encoded.encoded().to_owned()], metadata, - ids, ) .expect("Valid extrinsic"); + let extrinsic = extrinsics.iter().next().unwrap(); + assert!(!extrinsic.is_signed()); - assert_eq!(extrinsic.index(), 1); + assert_eq!(extrinsic.index(), 0); assert_eq!(extrinsic.pallet_index(), 0); assert_eq!( diff --git a/core/src/blocks/mod.rs b/core/src/blocks/mod.rs index 8a66966c6a..5bfc90d02d 100644 --- a/core/src/blocks/mod.rs +++ b/core/src/blocks/mod.rs @@ -45,14 +45,12 @@ //! //! // We can iterate over them and decode various details out of them. //! for ext in exts.iter() { -//! let ext = ext.unwrap(); //! println!("Pallet: {}", ext.pallet_name().unwrap()); //! println!("Call: {}", ext.variant_name().unwrap()); //! } //! //! # let ext_details: Vec<_> = exts.iter() //! # .map(|ext| { -//! # let ext = ext.unwrap(); //! # let pallet = ext.pallet_name().unwrap().to_string(); //! # let call = ext.variant_name().unwrap().to_string(); //! # (pallet, call) @@ -71,14 +69,13 @@ mod extrinsics; mod static_extrinsic; use crate::config::Config; -use crate::error::BlockError; +use crate::error::Error; use crate::Metadata; use alloc::vec::Vec; +pub use crate::error::BlockError; pub use extrinsic_signed_extensions::{ExtrinsicSignedExtension, ExtrinsicSignedExtensions}; -pub use extrinsics::{ - ExtrinsicDetails, ExtrinsicMetadataDetails, Extrinsics, FoundExtrinsic, SignedExtrinsicDetails, -}; +pub use extrinsics::{ExtrinsicDetails, ExtrinsicMetadataDetails, Extrinsics, FoundExtrinsic}; pub use static_extrinsic::StaticExtrinsic; /// Instantiate a new [`Extrinsics`] object, given a vector containing each extrinsic hash (in the @@ -88,6 +85,6 @@ pub use static_extrinsic::StaticExtrinsic; pub fn decode_from( extrinsics: Vec>, metadata: Metadata, -) -> Result, BlockError> { +) -> Result, Error> { Extrinsics::decode_from(extrinsics, metadata) } diff --git a/core/src/error.rs b/core/src/error.rs index 9824c49a84..8b9b8a83d5 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -56,29 +56,32 @@ impl_from!(StorageAddressError => Error::StorageAddress); impl_from!(codec::Error => Error::Codec); /// Block error -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Debug)] pub enum BlockError { - /// Extrinsic type ID cannot be resolved with the provided metadata. - MissingType, - /// Unsupported signature. - /// The extrinsic has an unsupported version. - UnsupportedVersion(u8), - /// Decoding error. - DecodingError(codec::Error), + /// Leftover bytes found after decoding the extrinsic. + LeftoverBytes(usize), + /// Something went wrong decoding the extrinsic. + ExtrinsicDecodeError(ExtrinsicDecodeError), } - impl Display for BlockError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - BlockError::MissingType => write!(f, "Extrinsic type ID cannot be resolved with the provided metadata. Make sure this is a valid metadata"), - BlockError::UnsupportedVersion(_) => write!(f, "Unsupported extrinsic version, only version 4 is supported currently"), - BlockError::DecodingError(e) => write!(f, "Cannot decode extrinsic: {e}"), + BlockError::LeftoverBytes(n) => { + write!( + f, + "After decoding, {n} bytes were left, suggesting that decoding may have failed" + ) + } + BlockError::ExtrinsicDecodeError(e) => { + write!(f, "{e}") + } } } } -#[cfg(feature = "std")] -impl std::error::Error for BlockError {} +/// An alias for [`frame_decode::extrinsics::ExtrinsicDecodeError`]. +/// +pub type ExtrinsicDecodeError = frame_decode::extrinsics::ExtrinsicDecodeError; /// Something went wrong trying to access details in the metadata. #[derive(Clone, Debug, PartialEq)] diff --git a/examples/wasm-example/src/services.rs b/examples/wasm-example/src/services.rs index 5b8df203ef..7897b05d95 100644 --- a/examples/wasm-example/src/services.rs +++ b/examples/wasm-example/src/services.rs @@ -49,7 +49,6 @@ pub(crate) async fn subscribe_to_finalized_blocks( writeln!(output, " Extrinsics:").ok(); let extrinsics = block.extrinsics().await?; for ext in extrinsics.iter() { - let ext = ext?; let idx = ext.index(); let events = ext.events().await?; let bytes_hex = format!("0x{}", hex::encode(ext.bytes())); diff --git a/metadata/Cargo.toml b/metadata/Cargo.toml index 0ad5316100..d55660d372 100644 --- a/metadata/Cargo.toml +++ b/metadata/Cargo.toml @@ -19,6 +19,7 @@ std = ["scale-info/std", "frame-metadata/std"] [dependencies] scale-info = { workspace = true, default-features = false } +frame-decode = { workspace = true } frame-metadata = { workspace = true, default-features = false, features = ["current", "decode"] } codec = { package = "parity-scale-codec", workspace = true, default-features = false, features = ["derive"] } sp-crypto-hashing = { workspace = true } diff --git a/metadata/src/lib.rs b/metadata/src/lib.rs index fb86ddcbef..8e0900ea7d 100644 --- a/metadata/src/lib.rs +++ b/metadata/src/lib.rs @@ -22,9 +22,13 @@ extern crate alloc; mod from_into; mod utils; +use alloc::borrow::Cow; use alloc::string::String; use alloc::sync::Arc; use alloc::vec::Vec; +use frame_decode::extrinsics::{ + ExtrinsicInfo, ExtrinsicInfoArg, ExtrinsicInfoError, ExtrinsicSignatureInfo, +}; use hashbrown::HashMap; use scale_info::{form::PortableForm, PortableRegistry, Variant}; use utils::variant_index::VariantIndex; @@ -61,6 +65,63 @@ pub struct Metadata { custom: frame_metadata::v15::CustomMetadata, } +// Since we've abstracted away from frame-metadatas, we impl this on our custom Metadata +// so that it can be used by `frame-decode` to obtain the relevant extrinsic info. +impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata { + type TypeId = u32; + + fn get_extrinsic_info( + &self, + pallet_index: u8, + call_index: u8, + ) -> Result, ExtrinsicInfoError<'_>> { + let pallet = self.pallet_by_index(pallet_index).ok_or({ + ExtrinsicInfoError::PalletNotFound { + index: pallet_index, + } + })?; + + let call = pallet.call_variant_by_index(call_index).ok_or_else(|| { + ExtrinsicInfoError::CallNotFound { + index: call_index, + pallet_index, + pallet_name: Cow::Borrowed(pallet.name()), + } + })?; + + Ok(ExtrinsicInfo { + pallet_name: Cow::Borrowed(pallet.name()), + call_name: Cow::Borrowed(&call.name), + args: call + .fields + .iter() + .map(|f| ExtrinsicInfoArg { + name: Cow::Borrowed(f.name.as_deref().unwrap_or("")), + id: f.ty.id, + }) + .collect(), + }) + } + + fn get_signature_info( + &self, + ) -> Result, ExtrinsicInfoError<'_>> { + Ok(ExtrinsicSignatureInfo { + address_id: self.extrinsic().address_ty(), + signature_id: self.extrinsic().signature_ty(), + transaction_extension_ids: self + .extrinsic() + .signed_extensions() + .iter() + .map(|f| ExtrinsicInfoArg { + name: Cow::Borrowed(f.identifier()), + id: f.extra_ty(), + }) + .collect(), + }) + } +} + impl Metadata { /// Access the underlying type registry. pub fn types(&self) -> &PortableRegistry { diff --git a/subxt/examples/block_decoding_dynamic.rs b/subxt/examples/block_decoding_dynamic.rs index 8bd54d4afb..3826d189ce 100644 --- a/subxt/examples/block_decoding_dynamic.rs +++ b/subxt/examples/block_decoding_dynamic.rs @@ -17,8 +17,6 @@ async fn main() -> Result<(), Box> { // Decode each signed extrinsic in the block dynamically let extrinsics = block.extrinsics().await?; for ext in extrinsics.iter() { - let ext = ext?; - let Some(signed_extensions) = ext.signed_extensions() else { continue; // we do not look at inherents in this example }; @@ -29,7 +27,6 @@ async fn main() -> Result<(), Box> { println!(" {}/{}", meta.pallet.name(), meta.variant.name); println!(" Signed Extensions:"); for signed_ext in signed_extensions.iter() { - let signed_ext = signed_ext?; // We only want to take a look at these 3 signed extensions, because the others all just have unit fields. if ["CheckMortality", "CheckNonce", "ChargeTransactionPayment"] .contains(&signed_ext.name()) diff --git a/subxt/examples/blocks_subscribing.rs b/subxt/examples/blocks_subscribing.rs index 3402a08827..4a6163d80f 100644 --- a/subxt/examples/blocks_subscribing.rs +++ b/subxt/examples/blocks_subscribing.rs @@ -26,7 +26,6 @@ async fn main() -> Result<(), Box> { // Log each of the extrinsic with it's associated events: let extrinsics = block.extrinsics().await?; for ext in extrinsics.iter() { - let ext = ext?; let idx = ext.index(); let events = ext.events().await?; let bytes_hex = format!("0x{}", hex::encode(ext.bytes())); @@ -52,7 +51,6 @@ async fn main() -> Result<(), Box> { println!(" Signed Extensions:"); if let Some(signed_extensions) = ext.signed_extensions() { for signed_extension in signed_extensions.iter() { - let signed_extension = signed_extension?; let name = signed_extension.name(); let value = signed_extension.value()?.to_string(); println!(" {name}: {value}"); diff --git a/subxt/src/blocks/block_types.rs b/subxt/src/blocks/block_types.rs index b93a05ba3e..c80fc8b6a6 100644 --- a/subxt/src/blocks/block_types.rs +++ b/subxt/src/blocks/block_types.rs @@ -84,12 +84,12 @@ where return Err(BlockError::not_found(block_hash).into()); }; - Ok(Extrinsics::new( + Extrinsics::new( self.client.clone(), extrinsics, self.cached_events.clone(), block_hash, - )?) + ) } /// Work with storage. diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index e7c876b679..e03b489257 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -6,7 +6,7 @@ use crate::{ blocks::block_types::{get_events, CachedEvents}, client::{OfflineClientT, OnlineClientT}, config::{Config, Hasher}, - error::{BlockError, Error}, + error::Error, events, }; @@ -37,7 +37,7 @@ where extrinsics: Vec>, cached_events: CachedEvents, hash: T::Hash, - ) -> Result { + ) -> Result { let inner = CoreExtrinsics::decode_from(extrinsics, client.metadata())?; Ok(Self { inner, @@ -65,21 +65,13 @@ 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 iter( - &self, - ) -> impl Iterator, Error>> + Send + Sync + 'static { + pub fn iter(&self) -> impl Iterator> + Send + Sync + 'static { let client = self.client.clone(); let cached_events = self.cached_events.clone(); let block_hash = self.hash; - self.inner.iter().map(move |res| { - let inner = res?; - Ok(ExtrinsicDetails::new( - inner, - client.clone(), - block_hash, - cached_events.clone(), - )) + self.inner.iter().map(move |inner| { + ExtrinsicDetails::new(inner, client.clone(), block_hash, cached_events.clone()) }) } diff --git a/subxt/src/error/mod.rs b/subxt/src/error/mod.rs index 53803450f1..c66dbd6868 100644 --- a/subxt/src/error/mod.rs +++ b/subxt/src/error/mod.rs @@ -165,30 +165,25 @@ impl RpcError { } /// Block error -#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)] +#[derive(Clone, Debug, thiserror::Error)] #[non_exhaustive] 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), - /// 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, - /// Unsupported signature. - #[error("Unsupported extrinsic version, only version 4 is supported currently")] - /// The extrinsic has an unsupported version. - UnsupportedVersion(u8), + /// Leftover bytes found after decoding the extrinsic. + #[error("After decoding, {0} bytes were left, suggesting that decoding may have failed")] + LeftoverBytes(usize), /// Decoding error. #[error("Cannot decode extrinsic: {0}")] - DecodingError(codec::Error), + ExtrinsicDecodeError(subxt_core::error::ExtrinsicDecodeError), } impl From for BlockError { fn from(value: CoreBlockError) -> Self { match value { - CoreBlockError::MissingType => BlockError::MissingType, - CoreBlockError::UnsupportedVersion(n) => BlockError::UnsupportedVersion(n), - CoreBlockError::DecodingError(e) => BlockError::DecodingError(e), + CoreBlockError::LeftoverBytes(n) => BlockError::LeftoverBytes(n), + CoreBlockError::ExtrinsicDecodeError(e) => BlockError::ExtrinsicDecodeError(e), } } } diff --git a/testing/integration-tests/src/full_client/blocks/mod.rs b/testing/integration-tests/src/full_client/blocks/mod.rs index 145cb693ec..55b05ec512 100644 --- a/testing/integration-tests/src/full_client/blocks/mod.rs +++ b/testing/integration-tests/src/full_client/blocks/mod.rs @@ -222,10 +222,7 @@ async fn fetch_block_and_decode_extrinsic_details() { .unwrap() .is_some()); - let block_extrinsics = extrinsics - .iter() - .map(|res| res.unwrap()) - .collect::>(); + let block_extrinsics = extrinsics.iter().collect::>(); let mut balance = None; let mut timestamp = None; @@ -297,10 +294,7 @@ async fn decode_signed_extensions_from_blocks() { let block_hash = in_block.block_hash(); let block = api.blocks().at(block_hash).await.unwrap(); let extrinsics = block.extrinsics().await.unwrap(); - let extrinsic_details = extrinsics - .iter() - .find_map(|e| e.ok().filter(|e| e.is_signed())) - .unwrap(); + let extrinsic_details = extrinsics.iter().find(|e| e.is_signed()).unwrap(); extrinsic_details }}; } @@ -351,12 +345,12 @@ async fn decode_signed_extensions_from_blocks() { assert_eq!(extensions1.iter().count(), expected_signed_extensions.len()); for (e, expected_name) in extensions1.iter().zip(expected_signed_extensions.iter()) { - assert_eq!(e.unwrap().name(), *expected_name); + assert_eq!(e.name(), *expected_name); } assert_eq!(extensions2.iter().count(), expected_signed_extensions.len()); for (e, expected_name) in extensions2.iter().zip(expected_signed_extensions.iter()) { - assert_eq!(e.unwrap().name(), *expected_name); + assert_eq!(e.name(), *expected_name); } // check that era decodes: diff --git a/testing/integration-tests/src/full_client/client/unstable_rpcs.rs b/testing/integration-tests/src/full_client/client/unstable_rpcs.rs index 0e8ceb74ba..3df3696e94 100644 --- a/testing/integration-tests/src/full_client/client/unstable_rpcs.rs +++ b/testing/integration-tests/src/full_client/client/unstable_rpcs.rs @@ -367,10 +367,7 @@ async fn transaction_v1_broadcast() { } let extrinsics = finalized.extrinsics().await.unwrap(); - let block_extrinsics = extrinsics - .iter() - .map(|res| res.unwrap()) - .collect::>(); + let block_extrinsics = extrinsics.iter().collect::>(); let Some(ext) = block_extrinsics .iter()