// Copyright 2019-2024 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::extrinsic_transaction_extensions::ExtrinsicTransactionExtensions; use crate::{ Metadata, config::{Config, HashFor, Hasher}, error::{ExtrinsicDecodeErrorAt, ExtrinsicDecodeErrorAtReason, ExtrinsicError}, }; use alloc::sync::Arc; use alloc::vec::Vec; use frame_decode::extrinsics::Extrinsic; use scale_decode::{DecodeAsFields, DecodeAsType}; pub use crate::blocks::StaticExtrinsic; /// The body of a block. pub struct Extrinsics { extrinsics: Vec, Vec)>>, metadata: Metadata, hasher: T::Hasher, _marker: core::marker::PhantomData, } 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 hasher = T::Hasher::new(&metadata); let extrinsics = extrinsics .into_iter() .enumerate() .map(|(extrinsic_index, bytes)| { let cursor = &mut &*bytes; // Try to decode the extrinsic. let decoded_info = frame_decode::extrinsics::decode_extrinsic(cursor, &metadata, metadata.types()) .map_err(|error| ExtrinsicDecodeErrorAt { extrinsic_index, error: ExtrinsicDecodeErrorAtReason::DecodeError(error), })? .into_owned(); // We didn't consume all bytes, so decoding probably failed. if !cursor.is_empty() { return Err(ExtrinsicDecodeErrorAt { extrinsic_index, error: ExtrinsicDecodeErrorAtReason::LeftoverBytes(cursor.to_vec()), }); } Ok(Arc::new((decoded_info, bytes))) }) .collect::>()?; Ok(Self { extrinsics, hasher, metadata, _marker: core::marker::PhantomData, }) } /// The number of extrinsics. pub fn len(&self) -> usize { self.extrinsics.len() } /// Are there no extrinsics in this block? // Note: mainly here to satisfy clippy. pub fn is_empty(&self) -> bool { self.extrinsics.is_empty() } /// Returns an iterator over the extrinsics in the block body. // 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> + Send + Sync + 'static { let extrinsics = self.extrinsics.clone(); let num_extrinsics = self.extrinsics.len(); let hasher = self.hasher; let metadata = self.metadata.clone(); (0..num_extrinsics).map(move |index| { ExtrinsicDetails::new( index as u32, extrinsics[index].clone(), hasher, metadata.clone(), ) }) } /// 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( &self, ) -> impl Iterator, ExtrinsicError>> { 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 })), } }) } /// 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( &self, ) -> Result>, ExtrinsicError> { 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>, ExtrinsicError> { self.find::().last().transpose() } /// Find an extrinsics that decodes to the type provided. Returns true if it was found. pub fn has(&self) -> Result { Ok(self.find::().next().transpose()?.is_some()) } } /// A single extrinsic in a block. pub struct ExtrinsicDetails { /// The index of the extrinsic in the block. index: u32, /// Extrinsic bytes and decode info. ext: Arc<(Extrinsic<'static, u32>, Vec)>, /// Hash the extrinsic if we want. hasher: T::Hasher, /// Subxt metadata to fetch the extrinsic metadata. metadata: Metadata, _marker: core::marker::PhantomData, } impl ExtrinsicDetails where T: Config, { // Attempt to dynamically decode a single extrinsic from the given input. #[doc(hidden)] pub fn new( index: u32, ext: Arc<(Extrinsic<'static, u32>, Vec)>, hasher: T::Hasher, metadata: Metadata, ) -> ExtrinsicDetails { ExtrinsicDetails { index, ext, hasher, metadata, _marker: core::marker::PhantomData, } } /// Calculate and return the hash of the extrinsic, based on the configured hasher. pub fn hash(&self) -> HashFor { // Use hash(), not hash_of(), because we don't want to double encode the bytes. self.hasher.hash(self.bytes()) } /// Is the extrinsic signed? pub fn is_signed(&self) -> bool { self.decoded_info().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.ext.1 } /// 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.decoded_info().call_data_range()] } /// Return the bytes representing the fields stored in this extrinsic. /// /// # Note /// /// This is a subset of [`Self::call_bytes`] that does not include the /// first two bytes that denote the pallet index and the variant index. pub fn field_bytes(&self) -> &[u8] { // Note: this cannot panic because we checked the extrinsic bytes // to contain at least two bytes. &self.bytes()[self.decoded_info().call_data_args_range()] } /// Return only the bytes of the address that signed this extrinsic. /// /// # Note /// /// Returns `None` if the extrinsic is not signed. pub fn address_bytes(&self) -> Option<&[u8]> { self.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.decoded_info() .signature_payload() .map(|s| &self.bytes()[s.signature_range()]) } /// Returns the signed extension `extra` bytes of the extrinsic. /// Each signed extension has an `extra` type (May be zero-sized). /// These bytes are the scale encoded `extra` fields of each signed extension in order of the signed extensions. /// They do *not* include the `additional` signed bytes that are used as part of the payload that is signed. /// /// Note: Returns `None` if the extrinsic is not signed. pub fn transaction_extensions_bytes(&self) -> Option<&[u8]> { self.decoded_info() .transaction_extension_payload() .map(|t| &self.bytes()[t.range()]) } /// Returns `None` if the extrinsic is not signed. pub fn transaction_extensions(&self) -> Option> { self.decoded_info() .transaction_extension_payload() .map(|t| ExtrinsicTransactionExtensions::new(self.bytes(), &self.metadata, t)) } /// The index of the pallet that the extrinsic originated from. pub fn pallet_index(&self) -> u8 { self.decoded_info().pallet_index() } /// The index of the extrinsic variant that the extrinsic originated from. pub fn call_index(&self) -> u8 { self.decoded_info().call_index() } /// The name of the pallet from whence the extrinsic originated. pub fn pallet_name(&self) -> &str { self.decoded_info().pallet_name() } /// The name of the call (ie the name of the variant that it corresponds to). pub fn call_name(&self) -> &str { self.decoded_info().call_name() } /// 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 decode_as_fields(&self) -> Result { let bytes = &mut self.field_bytes(); let mut fields = self.decoded_info().call_data().map(|d| { let name = if d.name().is_empty() { None } else { Some(d.name()) }; scale_decode::Field::new(*d.ty(), name) }); let decoded = E::decode_as_fields(bytes, &mut fields, self.metadata.types()).map_err(|e| { ExtrinsicError::CannotDecodeFields { extrinsic_index: self.index as usize, error: e, } })?; Ok(decoded) } /// Attempt to decode these [`ExtrinsicDetails`] into a type representing the extrinsic fields. /// Such types are exposed in the codegen as `pallet_name::calls::types::CallName` types. pub fn as_extrinsic(&self) -> Result, ExtrinsicError> { if self.decoded_info().pallet_name() == E::PALLET && self.decoded_info().call_name() == E::CALL { let mut fields = self.decoded_info().call_data().map(|d| { let name = if d.name().is_empty() { None } else { Some(d.name()) }; scale_decode::Field::new(*d.ty(), name) }); let decoded = E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types()) .map_err(|e| ExtrinsicError::CannotDecodeFields { extrinsic_index: self.index as usize, error: e, })?; Ok(Some(decoded)) } else { Ok(None) } } /// Attempt to decode these [`ExtrinsicDetails`] into an outer call enum 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 decoded = E::decode_as_type( &mut &self.call_bytes()[..], self.metadata.outer_enums().call_enum_ty(), self.metadata.types(), ) .map_err(|e| ExtrinsicError::CannotDecodeIntoRootExtrinsic { extrinsic_index: self.index as usize, error: e, })?; Ok(decoded) } fn decoded_info(&self) -> &Extrinsic<'static, u32> { &self.ext.0 } } /// A Static Extrinsic found in a block coupled with it's details. pub struct FoundExtrinsic { /// Details for the extrinsic. pub details: ExtrinsicDetails, /// The decoded extrinsic value. pub value: E, } #[cfg(test)] mod tests { use super::*; use crate::config::SubstrateConfig; use assert_matches::assert_matches; use codec::{Decode, Encode}; use frame_metadata::v15::{CustomMetadata, OuterEnums}; use frame_metadata::{ RuntimeMetadataPrefixed, v15::{ExtrinsicMetadata, PalletCallMetadata, PalletMetadata, RuntimeMetadataV15}, }; use scale_info::{TypeInfo, meta_type}; 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), } // The calls of the pallet. #[allow(unused)] #[derive( Encode, Decode, TypeInfo, Clone, Debug, PartialEq, Eq, scale_encode::EncodeAsType, scale_decode::DecodeAsType, )] enum Pallet { #[allow(unused)] #[codec(index = 2)] TestCall { value: u128, signed: bool, name: String, }, } #[allow(unused)] #[derive( Encode, Decode, TypeInfo, Clone, Debug, PartialEq, Eq, scale_encode::EncodeAsType, scale_decode::DecodeAsType, )] struct TestCallExtrinsic { value: u128, signed: bool, name: String, } impl StaticExtrinsic for TestCallExtrinsic { const PALLET: &'static str = "Test"; const CALL: &'static str = "TestCall"; } /// Build fake metadata consisting the types needed to represent an extrinsic. fn metadata() -> Metadata { let pallets = vec![PalletMetadata { name: "Test", storage: None, calls: Some(PalletCallMetadata { ty: meta_type::(), }), event: None, constants: vec![], error: None, index: 0, docs: vec![], }]; let extrinsic = ExtrinsicMetadata { version: 4, signed_extensions: vec![], address_ty: meta_type::<()>(), call_ty: meta_type::(), signature_ty: meta_type::<()>(), extra_ty: meta_type::<()>(), }; let meta = RuntimeMetadataV15::new( pallets, extrinsic, meta_type::<()>(), vec![], OuterEnums { call_enum_ty: meta_type::(), event_enum_ty: meta_type::<()>(), error_enum_ty: meta_type::<()>(), }, CustomMetadata { map: Default::default(), }, ); let runtime_metadata: RuntimeMetadataPrefixed = meta.into(); let metadata: subxt_metadata::Metadata = runtime_metadata.try_into().unwrap(); metadata } #[test] fn extrinsic_metadata_consistency() { let metadata = metadata(); // Except our metadata to contain the registered types. let pallet = metadata.pallet_by_call_index(0).expect("pallet exists"); let extrinsic = pallet .call_variant_by_index(2) .expect("metadata contains the RuntimeCall enum with this pallet"); assert_eq!(pallet.name(), "Test"); assert_eq!(&extrinsic.name, "TestCall"); } #[test] fn insufficient_extrinsic_bytes() { let metadata = metadata(); // Decode with empty bytes. let result = Extrinsics::::decode_from(vec![vec![]], metadata); assert_matches!( result.err(), Some(crate::error::ExtrinsicDecodeErrorAt { extrinsic_index: 0, error: _ }) ); } #[test] fn unsupported_version_extrinsic() { use frame_decode::extrinsics::ExtrinsicDecodeError; let metadata = metadata(); // Decode with invalid version. let result = Extrinsics::::decode_from(vec![vec![3u8].encode()], metadata); assert_matches!( result.err(), Some(crate::error::ExtrinsicDecodeErrorAt { extrinsic_index: 0, error: ExtrinsicDecodeErrorAtReason::DecodeError( ExtrinsicDecodeError::VersionNotSupported(3) ), }) ); } #[test] fn tx_hashes_line_up() { let metadata = metadata(); let hasher = ::Hasher::new(&metadata); let tx = crate::dynamic::tx( "Test", "TestCall", vec![ Value::u128(10), Value::bool(true), Value::string("SomeValue"), ], ); // Encoded TX ready to submit. let tx_encoded = crate::tx::create_v4_unsigned::(&tx, &metadata) .expect("Valid dynamic parameters are provided"); // Extrinsic details ready to decode. let extrinsics = Extrinsics::::decode_from( vec![tx_encoded.encoded().to_owned()], metadata, ) .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. assert_eq!( tx_encoded.hash_with(hasher), extrinsic.hash(), "hashes should eq" ); } #[test] fn statically_decode_extrinsic() { let metadata = metadata(); let tx = crate::dynamic::tx( "Test", "TestCall", vec![ Value::u128(10), Value::bool(true), Value::string("SomeValue"), ], ); let tx_encoded = crate::tx::create_v4_unsigned::(&tx, &metadata) .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 extrinsics = Extrinsics::::decode_from( vec![tx_encoded.encoded().to_owned()], metadata, ) .expect("Valid extrinsic"); let extrinsic = extrinsics.iter().next().unwrap(); assert!(!extrinsic.is_signed()); assert_eq!(extrinsic.index(), 0); assert_eq!(extrinsic.pallet_index(), 0); assert_eq!(extrinsic.pallet_name(), "Test"); assert_eq!(extrinsic.call_index(), 2); assert_eq!(extrinsic.call_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 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(), } ); } }