diff --git a/subxt/examples/setup_config_custom.rs b/subxt/examples/setup_config_custom.rs index 6768787600..b1da57913c 100644 --- a/subxt/examples/setup_config_custom.rs +++ b/subxt/examples/setup_config_custom.rs @@ -1,6 +1,6 @@ use codec::Encode; use subxt::client::OfflineClientT; -use subxt::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder}; +use subxt::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError}; use subxt_signer::sr25519::dev; #[subxt::subxt( @@ -66,14 +66,13 @@ impl CustomExtrinsicParamsBuilder { // Describe how to fetch and then encode the params: impl ExtrinsicParams for CustomExtrinsicParams { type OtherParams = CustomExtrinsicParamsBuilder; - type Error = std::convert::Infallible; // Gather together all of the params we will need to encode: fn new>( _nonce: u64, client: Client, other_params: Self::OtherParams, - ) -> Result { + ) -> Result { Ok(Self { genesis_hash: client.genesis_hash(), tip: other_params.tip, diff --git a/subxt/examples/setup_config_signed_extension.rs b/subxt/examples/setup_config_signed_extension.rs index ab985a8838..94836c4485 100644 --- a/subxt/examples/setup_config_signed_extension.rs +++ b/subxt/examples/setup_config_signed_extension.rs @@ -1,9 +1,11 @@ use codec::Encode; use scale_encode::EncodeAsType; +use scale_info::PortableRegistry; use subxt::client::OfflineClientT; use subxt::config::signed_extensions; use subxt::config::{ Config, DefaultExtrinsicParamsBuilder, ExtrinsicParams, ExtrinsicParamsEncoder, + ExtrinsicParamsError, }; use subxt_signer::sr25519::dev; @@ -34,10 +36,6 @@ impl Config for CustomConfig { signed_extensions::CheckMortality, signed_extensions::ChargeAssetTxPayment, signed_extensions::ChargeTransactionPayment, - signed_extensions::SkipCheckIfFeeless< - Self, - signed_extensions::ChargeAssetTxPayment, - >, // And add a new one of our own: CustomSignedExtension, ), @@ -51,20 +49,21 @@ pub struct CustomSignedExtension; // Give the extension a name; this allows `AnyOf` to look it // up in the chain metadata in order to know when and if to use it. impl signed_extensions::SignedExtension for CustomSignedExtension { - const NAME: &'static str = "CustomSignedExtension"; type Decoded = (); + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "CustomSignedExtension" + } } // Gather together any params we need for our signed extension, here none. impl ExtrinsicParams for CustomSignedExtension { type OtherParams = (); - type Error = std::convert::Infallible; fn new>( _nonce: u64, _client: Client, _other_params: Self::OtherParams, - ) -> Result { + ) -> Result { Ok(CustomSignedExtension) } } @@ -87,8 +86,8 @@ impl ExtrinsicParamsEncoder for CustomSignedExtension { pub fn custom( params: DefaultExtrinsicParamsBuilder, ) -> <::ExtrinsicParams as ExtrinsicParams>::OtherParams { - let (a, b, c, d, e, f, g, h) = params.build(); - (a, b, c, d, e, f, g, h, ()) + let (a, b, c, d, e, f, g) = params.build(); + (a, b, c, d, e, f, g, ()) } #[tokio::main] diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 070c84e6da..92f70d565c 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -13,7 +13,7 @@ use crate::{ }; use crate::config::signed_extensions::{ - ChargeAssetTxPayment, ChargeTransactionPayment, CheckNonce, SkipCheckIfFeeless, + ChargeAssetTxPayment, ChargeTransactionPayment, CheckNonce, }; use crate::config::SignedExtension; use crate::dynamic::DecodedValue; @@ -660,24 +660,24 @@ impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> { }) } - fn find_by_name(&self, name: &str) -> Option> { - let signed_extension = self - .iter() - .find_map(|e| e.ok().filter(|e| e.name() == name))?; - Some(signed_extension) - } - /// Searches through all signed extensions to find a specific one. /// If the Signed Extension is not found `Ok(None)` is returned. /// If the Signed Extension is found but decoding failed `Err(_)` is returned. pub fn find>(&self) -> Result, Error> { - self.find_by_name(S::NAME) - .map(|s| { - s.as_signed_extra::().map(|e| { - e.expect("signed extra name is correct, because it was found before; qed.") - }) - }) - .transpose() + 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)), + // No error, but no match either; next! + Ok(None) => continue, + // Error? return it + Err(e) => return Err(e), + } + } + Ok(None) } /// The tip of an extrinsic, extracted from the ChargeTransactionPayment or ChargeAssetTxPayment @@ -696,20 +696,13 @@ impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> { .flatten() .map(|e| e.tip()) }) - .or_else(|| { - self.find::>>() - .ok() - .flatten() - .map(|skip_check| skip_check.inner_signed_extension().tip()) - }) } /// The nonce of the account that submitted the extrinsic, extracted from the CheckNonce signed extension. /// /// Returns `None` if `nonce` was not found or decoding failed. pub fn nonce(&self) -> Option { - let nonce = self.find::().ok()??.0; - Some(nonce) + self.find::().ok()? } } @@ -744,20 +737,20 @@ impl<'a, T: Config> ExtrinsicSignedExtension<'a, T> { self.as_type() } - /// Decodes the `extra` bytes of this Signed Extension into a static type. - fn as_type(&self) -> Result { - let value = E::decode_as_type(&mut &self.bytes[..], self.ty_id, self.metadata.types())?; - Ok(value) - } - - /// Decodes the `extra` bytes of this Signed Extension into its associated `Decoded` type. - /// Returns `Ok(None)` if the identitfier of this Signed Extension object does not line up with the `NAME` constant of the provided Signed Extension type. - pub fn as_signed_extra>(&self) -> Result, Error> { - if self.identifier != S::NAME { + /// Decodes the bytes of this Signed Extension into its associated `Decoded` type. + /// Returns `Ok(None)` if the data we have doesn't match the Signed Extension we're asking to + /// decode with. + pub fn as_signed_extension>(&self) -> Result, Error> { + if !S::matches(self.identifier, self.ty_id, self.metadata.types()) { return Ok(None); } self.as_type::().map(Some) } + + fn as_type(&self) -> Result { + let value = E::decode_as_type(&mut &self.bytes[..], self.ty_id, self.metadata.types())?; + Ok(value) + } } #[cfg(test)] diff --git a/subxt/src/config/default_extrinsic_params.rs b/subxt/src/config/default_extrinsic_params.rs index df78ffa34c..880591e7f0 100644 --- a/subxt/src/config/default_extrinsic_params.rs +++ b/subxt/src/config/default_extrinsic_params.rs @@ -17,7 +17,6 @@ pub type DefaultExtrinsicParams = signed_extensions::AnyOf< signed_extensions::CheckMortality, signed_extensions::ChargeAssetTxPayment, signed_extensions::ChargeTransactionPayment, - signed_extensions::SkipCheckIfFeeless>, ), >; @@ -132,9 +131,6 @@ impl DefaultExtrinsicParamsBuilder { let charge_transaction_params = signed_extensions::ChargeTransactionPaymentParams::tip(self.tip); - let skip_check_params = - signed_extensions::SkipCheckIfFeelessParams::from(charge_asset_tx_params.clone()); - ( (), (), @@ -143,7 +139,6 @@ impl DefaultExtrinsicParamsBuilder { check_mortality_params, charge_asset_tx_params, charge_transaction_params, - skip_check_params, ) } } diff --git a/subxt/src/config/extrinsic_params.rs b/subxt/src/config/extrinsic_params.rs index f3f0cc332e..42f3b620af 100644 --- a/subxt/src/config/extrinsic_params.rs +++ b/subxt/src/config/extrinsic_params.rs @@ -10,39 +10,41 @@ use crate::{client::OfflineClientT, Config}; use core::fmt::Debug; -/// An error that can be emitted when trying to construct -/// an instance of [`ExtrinsicParams`]. +/// An error that can be emitted when trying to construct an instance of [`ExtrinsicParams`], +/// encode data from the instance, or match on signed extensions. #[derive(thiserror::Error, Debug)] #[non_exhaustive] pub enum ExtrinsicParamsError { - /// A signed extension was encountered that we don't know about. - #[error("Error constructing extrinsic parameters: Unknown signed extension '{0}'")] + /// Cannot find a type id in the metadata. The context provides some additional + /// information about the source of the error (eg the signed extension name). + #[error("Cannot find type id '{type_id} in the metadata (context: {context})")] + MissingTypeId { + /// Type ID. + type_id: u32, + /// Some arbitrary context to help narrow the source of the error. + context: &'static str, + }, + /// A signed extension in use on some chain was not provided. + #[error("The chain expects a signed extension with the name {0}, but we did not provide one")] UnknownSignedExtension(String), - /// Cannot find the type id of a signed extension in the metadata. - #[error("Cannot find extension's '{0}' type id '{1} in the metadata")] - MissingTypeId(String, u32), - /// User provided a different signed extension than the one expected. - #[error("Provided a different signed extension for '{0}', the metadata expect '{1}'")] - ExpectedAnotherExtension(String, String), - /// The inner type of a signed extension is not present in the metadata. - #[error("The inner type of the signed extension '{0}' is not present in the metadata")] - MissingInnerSignedExtension(String), - /// The inner type of the signed extension is not named. - #[error("The signed extension's '{0}' type id '{1}' does not have a name in the metadata")] - ExpectedNamedTypeId(String, u32), - /// Some custom error.s + /// Some custom error. #[error("Error constructing extrinsic parameters: {0}")] - Custom(CustomError), + Custom(CustomExtrinsicParamsError), } /// A custom error. -type CustomError = Box; +pub type CustomExtrinsicParamsError = Box; impl From for ExtrinsicParamsError { fn from(value: std::convert::Infallible) -> Self { match value {} } } +impl From for ExtrinsicParamsError { + fn from(value: CustomExtrinsicParamsError) -> Self { + ExtrinsicParamsError::Custom(value) + } +} /// This trait allows you to configure the "signed extra" and /// "additional" parameters that are a part of the transaction payload @@ -53,15 +55,12 @@ pub trait ExtrinsicParams: ExtrinsicParamsEncoder + Sized + 'static { /// help construct your [`ExtrinsicParams`] object. type OtherParams; - /// The type of error returned from [`ExtrinsicParams::new()`]. - type Error: Into; - - /// Construct a new instance of our [`ExtrinsicParams`] + /// Construct a new instance of our [`ExtrinsicParams`]. fn new>( nonce: u64, client: Client, other_params: Self::OtherParams, - ) -> Result; + ) -> Result; } /// This trait is expected to be implemented for any [`ExtrinsicParams`], and diff --git a/subxt/src/config/signed_extensions.rs b/subxt/src/config/signed_extensions.rs index 24f536bd72..f0864cee61 100644 --- a/subxt/src/config/signed_extensions.rs +++ b/subxt/src/config/signed_extensions.rs @@ -9,12 +9,12 @@ use super::extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError}; use crate::utils::Era; -use crate::{client::OfflineClientT, Config, Metadata}; +use crate::{client::OfflineClientT, Config}; use codec::{Compact, Encode}; use core::fmt::Debug; +use derivative::Derivative; use scale_decode::DecodeAsType; -use scale_encode::EncodeAsType; -use std::marker::PhantomData; +use scale_info::PortableRegistry; use std::collections::HashMap; @@ -22,29 +22,28 @@ use std::collections::HashMap; /// same as [`ExtrinsicParams`] in describing how to encode the extra and /// additional data. pub trait SignedExtension: ExtrinsicParams { - /// The name of the signed extension. This is used to associate it - /// with the signed extensions that the node is making use of. - const NAME: &'static str; - /// The type representing the `extra` bytes of a signed extension. /// Decoding from this type should be symmetrical to the respective /// `ExtrinsicParamsEncoder::encode_extra_to()` implementation of this signed extension. type Decoded: DecodeAsType; + + /// This should return true if the signed extension matches the details given. + /// Often, this will involve just checking that the identifier given matches that of the + /// extension in question. + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool; } /// The [`CheckSpecVersion`] signed extension. -#[derive(Clone, Debug, EncodeAsType, DecodeAsType)] pub struct CheckSpecVersion(u32); impl ExtrinsicParams for CheckSpecVersion { type OtherParams = (); - type Error = std::convert::Infallible; fn new>( _nonce: u64, client: Client, _other_params: Self::OtherParams, - ) -> Result { + ) -> Result { Ok(CheckSpecVersion(client.runtime_version().spec_version)) } } @@ -56,23 +55,23 @@ impl ExtrinsicParamsEncoder for CheckSpecVersion { } impl SignedExtension for CheckSpecVersion { - const NAME: &'static str = "CheckSpecVersion"; type Decoded = (); + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "CheckSpecVersion" + } } /// The [`CheckNonce`] signed extension. -#[derive(Clone, Debug, EncodeAsType, DecodeAsType)] pub struct CheckNonce(Compact); impl ExtrinsicParams for CheckNonce { type OtherParams = (); - type Error = std::convert::Infallible; fn new>( nonce: u64, _client: Client, _other_params: Self::OtherParams, - ) -> Result { + ) -> Result { Ok(CheckNonce(Compact(nonce))) } } @@ -84,23 +83,23 @@ impl ExtrinsicParamsEncoder for CheckNonce { } impl SignedExtension for CheckNonce { - const NAME: &'static str = "CheckNonce"; - type Decoded = Compact; + type Decoded = u64; + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "CheckNonce" + } } /// The [`CheckTxVersion`] signed extension. -#[derive(Clone, Debug, EncodeAsType, DecodeAsType)] pub struct CheckTxVersion(u32); impl ExtrinsicParams for CheckTxVersion { type OtherParams = (); - type Error = std::convert::Infallible; fn new>( _nonce: u64, client: Client, _other_params: Self::OtherParams, - ) -> Result { + ) -> Result { Ok(CheckTxVersion(client.runtime_version().transaction_version)) } } @@ -112,31 +111,23 @@ impl ExtrinsicParamsEncoder for CheckTxVersion { } impl SignedExtension for CheckTxVersion { - const NAME: &'static str = "CheckTxVersion"; type Decoded = (); -} - -/// The [`CheckGenesis`] signed extension. -#[derive(Clone, EncodeAsType, DecodeAsType)] -#[decode_as_type(trait_bounds = "T::Hash: DecodeAsType")] -#[encode_as_type(trait_bounds = "T::Hash: EncodeAsType")] -pub struct CheckGenesis(T::Hash); - -impl std::fmt::Debug for CheckGenesis { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("CheckGenesis").field(&self.0).finish() + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "CheckTxVersion" } } +/// The [`CheckGenesis`] signed extension. +pub struct CheckGenesis(T::Hash); + impl ExtrinsicParams for CheckGenesis { type OtherParams = (); - type Error = std::convert::Infallible; fn new>( _nonce: u64, client: Client, _other_params: Self::OtherParams, - ) -> Result { + ) -> Result { Ok(CheckGenesis(client.genesis_hash())) } } @@ -148,21 +139,19 @@ impl ExtrinsicParamsEncoder for CheckGenesis { } impl SignedExtension for CheckGenesis { - const NAME: &'static str = "CheckGenesis"; type Decoded = (); + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "CheckGenesis" + } } /// The [`CheckMortality`] signed extension. -#[derive(Clone, EncodeAsType, DecodeAsType)] -#[decode_as_type(trait_bounds = "T::Hash: DecodeAsType")] -#[encode_as_type(trait_bounds = "T::Hash: EncodeAsType")] pub struct CheckMortality { era: Era, checkpoint: T::Hash, } /// Parameters to configure the [`CheckMortality`] signed extension. -#[derive(Clone, Debug)] pub struct CheckMortalityParams { era: Era, checkpoint: Option, @@ -197,24 +186,14 @@ impl CheckMortalityParams { } } -impl std::fmt::Debug for CheckMortality { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("CheckMortality") - .field("era", &self.era) - .field("checkpoint", &self.checkpoint) - .finish() - } -} - impl ExtrinsicParams for CheckMortality { type OtherParams = CheckMortalityParams; - type Error = std::convert::Infallible; fn new>( _nonce: u64, client: Client, other_params: Self::OtherParams, - ) -> Result { + ) -> Result { Ok(CheckMortality { era: other_params.era, checkpoint: other_params.checkpoint.unwrap_or(client.genesis_hash()), @@ -227,19 +206,21 @@ impl ExtrinsicParamsEncoder for CheckMortality { self.era.encode_to(v); } fn encode_additional_to(&self, v: &mut Vec) { - self.checkpoint.encode_to(v) + self.checkpoint.encode_to(v); } } impl SignedExtension for CheckMortality { - const NAME: &'static str = "CheckMortality"; type Decoded = Era; + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "CheckMortality" + } } /// The [`ChargeAssetTxPayment`] signed extension. -#[derive(Clone, Debug, DecodeAsType, EncodeAsType)] +#[derive(Derivative, DecodeAsType)] +#[derivative(Clone(bound = "T::AssetId: Clone"), Debug(bound = "T::AssetId: Debug"))] #[decode_as_type(trait_bounds = "T::AssetId: DecodeAsType")] -#[encode_as_type(trait_bounds = "T::AssetId: EncodeAsType")] pub struct ChargeAssetTxPayment { tip: Compact, asset_id: Option, @@ -258,22 +239,11 @@ impl ChargeAssetTxPayment { } /// Parameters to configure the [`ChargeAssetTxPayment`] signed extension. -#[derive(Debug)] pub struct ChargeAssetTxPaymentParams { tip: u128, asset_id: Option, } -// Dev note: `#[derive(Clone)]` implies `T: Clone` instead of `T::AssetId: Clone`. -impl Clone for ChargeAssetTxPaymentParams { - fn clone(&self) -> Self { - Self { - tip: self.tip, - asset_id: self.asset_id.clone(), - } - } -} - impl Default for ChargeAssetTxPaymentParams { fn default() -> Self { ChargeAssetTxPaymentParams { @@ -309,13 +279,12 @@ impl ChargeAssetTxPaymentParams { impl ExtrinsicParams for ChargeAssetTxPayment { type OtherParams = ChargeAssetTxPaymentParams; - type Error = std::convert::Infallible; fn new>( _nonce: u64, _client: Client, other_params: Self::OtherParams, - ) -> Result { + ) -> Result { Ok(ChargeAssetTxPayment { tip: Compact(other_params.tip), asset_id: other_params.asset_id, @@ -330,12 +299,14 @@ impl ExtrinsicParamsEncoder for ChargeAssetTxPayment { } impl SignedExtension for ChargeAssetTxPayment { - const NAME: &'static str = "ChargeAssetTxPayment"; type Decoded = Self; + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "ChargeAssetTxPayment" + } } /// The [`ChargeTransactionPayment`] signed extension. -#[derive(Clone, Debug, DecodeAsType, EncodeAsType)] +#[derive(Clone, Debug, DecodeAsType)] pub struct ChargeTransactionPayment { tip: Compact, } @@ -366,13 +337,12 @@ impl ChargeTransactionPaymentParams { impl ExtrinsicParams for ChargeTransactionPayment { type OtherParams = ChargeTransactionPaymentParams; - type Error = std::convert::Infallible; fn new>( _nonce: u64, _client: Client, other_params: Self::OtherParams, - ) -> Result { + ) -> Result { Ok(ChargeTransactionPayment { tip: Compact(other_params.tip), }) @@ -386,212 +356,9 @@ impl ExtrinsicParamsEncoder for ChargeTransactionPayment { } impl SignedExtension for ChargeTransactionPayment { - const NAME: &'static str = "ChargeTransactionPayment"; type Decoded = Self; -} - -/// Information needed to encode the [`SkipCheckIfFeeless`] signed extension. -#[derive(Debug)] -struct SkipCheckIfFeelessEncodingData { - metadata: Metadata, - type_id: u32, -} - -impl SkipCheckIfFeelessEncodingData { - /// Construct [`SkipCheckIfFeelessEncodingData`]. - fn new( - metadata: Metadata, - extension: &str, - inner_extension: &str, - ) -> Result { - let skip_check_type_id = metadata - .extrinsic() - .signed_extensions() - .iter() - .find_map(|ext| { - if ext.identifier() == extension { - Some(ext.extra_ty()) - } else { - None - } - }); - let Some(skip_check_type_id) = skip_check_type_id else { - return Err(ExtrinsicParamsError::UnknownSignedExtension( - inner_extension.to_owned(), - )); - }; - - // Ensure that the `SkipCheckIfFeeless` type has the same inner signed extension as provided. - let Some(skip_check_ty) = metadata.types().resolve(skip_check_type_id) else { - return Err(ExtrinsicParamsError::MissingTypeId( - inner_extension.to_owned(), - skip_check_type_id, - )); - }; - - // The substrate's `SkipCheckIfFeeless` contains 2 types: the inner signed extension and a phantom data. - // Phantom data does not have a type associated, so we need to find the inner signed extension. - let Some(inner_type_id) = skip_check_ty - .type_params - .iter() - .find_map(|param| param.ty.map(|ty| ty.id)) - else { - return Err(ExtrinsicParamsError::MissingInnerSignedExtension( - inner_extension.to_owned(), - )); - }; - - // Get the inner type of the `SkipCheckIfFeeless` extension to check if the naming matches the provided parameters. - let Some(inner_extension_ty) = metadata.types().resolve(inner_type_id) else { - return Err(ExtrinsicParamsError::MissingTypeId( - inner_extension.to_owned(), - inner_type_id, - )); - }; - - let Some(inner_extension_name) = inner_extension_ty.path.segments.last() else { - return Err(ExtrinsicParamsError::ExpectedNamedTypeId( - inner_extension.to_owned(), - inner_type_id, - )); - }; - - if inner_extension_name != inner_extension { - return Err(ExtrinsicParamsError::ExpectedAnotherExtension( - inner_extension.to_owned(), - inner_extension_name.to_owned(), - )); - } - - Ok(SkipCheckIfFeelessEncodingData { - metadata, - type_id: inner_type_id, - }) - } -} - -/// The [`SkipCheckIfFeeless`] signed extension. -#[derive(Debug, DecodeAsType, EncodeAsType)] -#[decode_as_type(trait_bounds = "S: DecodeAsType")] -#[encode_as_type(trait_bounds = "S: EncodeAsType")] -pub struct SkipCheckIfFeeless -where - T: Config, - S: SignedExtension + DecodeAsType + EncodeAsType, -{ - inner: S, - // Dev note: This is `Option` because `#[derive(DecodeAsType)]` requires the - // `Default` bound on skipped parameters. - // This field is populated when the [`SkipCheckIfFeeless`] is constructed from - // [`ExtrinsicParams`] (ie, when subxt submits extrinsics). However, it is not - // populated when decoding signed extensions from the node. - #[decode_as_type(skip)] - #[encode_as_type(skip)] - encoding_data: Option, - #[decode_as_type(skip)] - #[encode_as_type(skip)] - _phantom: PhantomData, -} - -impl SkipCheckIfFeeless -where - T: Config, - S: SignedExtension + DecodeAsType + EncodeAsType, -{ - /// The inner signed extension. - pub fn inner_signed_extension(&self) -> &S { - &self.inner - } -} - -impl ExtrinsicParams for SkipCheckIfFeeless -where - T: Config, - S: SignedExtension + DecodeAsType + EncodeAsType, - >::OtherParams: Default, -{ - type OtherParams = SkipCheckIfFeelessParams; - type Error = ExtrinsicParamsError; - - fn new>( - nonce: u64, - client: Client, - other_params: Self::OtherParams, - ) -> Result { - let other_params = other_params.0.unwrap_or_default(); - - let metadata = client.metadata(); - let encoding_data = SkipCheckIfFeelessEncodingData::new(metadata, Self::NAME, S::NAME)?; - let inner_extension = S::new(nonce, client, other_params).map_err(Into::into)?; - - Ok(SkipCheckIfFeeless { - inner: inner_extension, - encoding_data: Some(encoding_data), - _phantom: PhantomData, - }) - } -} - -impl ExtrinsicParamsEncoder for SkipCheckIfFeeless -where - T: Config, - S: SignedExtension + DecodeAsType + EncodeAsType, -{ - fn encode_extra_to(&self, v: &mut Vec) { - if let Some(encoding_data) = &self.encoding_data { - let _ = self.inner.encode_as_type_to( - encoding_data.type_id, - encoding_data.metadata.types(), - v, - ); - } - } -} - -impl SignedExtension for SkipCheckIfFeeless -where - T: Config, - S: SignedExtension + DecodeAsType + EncodeAsType, - >::OtherParams: Default, -{ - const NAME: &'static str = "SkipCheckIfFeeless"; - type Decoded = Self; -} - -/// Parameters to configure the [`SkipCheckIfFeeless`] signed extension. -pub struct SkipCheckIfFeelessParams(Option<>::OtherParams>) -where - T: Config, - S: SignedExtension; - -impl std::fmt::Debug for SkipCheckIfFeelessParams -where - T: Config, - S: SignedExtension, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("SkipCheckIfFeelessParams").finish() - } -} - -impl> Default for SkipCheckIfFeelessParams -where - T: Config, - S: SignedExtension, -{ - fn default() -> Self { - SkipCheckIfFeelessParams(None) - } -} - -impl SkipCheckIfFeelessParams -where - T: Config, - S: SignedExtension, -{ - /// Skip the check if the transaction is feeless. - pub fn from(extrinsic_params: >::OtherParams) -> Self { - SkipCheckIfFeelessParams(Some(extrinsic_params)) + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "ChargeTransactionPayment" } } @@ -614,37 +381,45 @@ macro_rules! impl_tuples { $($ident: SignedExtension,)+ { type OtherParams = ($($ident::OtherParams,)+); - type Error = ExtrinsicParamsError; fn new>( nonce: u64, client: Client, other_params: Self::OtherParams, - ) -> Result { - // First, push encoders to map as we are given them: - let mut map = HashMap::new(); - $({ - let e: Box - = Box::new($ident::new(nonce, client.clone(), other_params.$index).map_err(Into::into)?); - map.insert($ident::NAME, e); - })+ - - // Next, based on metadata, push to vec in the order the node needs: - let mut params = Vec::new(); + ) -> Result { let metadata = client.metadata(); let types = metadata.types(); - for ext in metadata.extrinsic().signed_extensions() { - if let Some(ext) = map.remove(ext.identifier()) { - params.push(ext) - } else { - if is_type_empty(ext.extra_ty(), types) && is_type_empty(ext.additional_ty(), types) { - // If we don't know about the signed extension, _but_ it appears to require zero bytes - // to encode its extra and additional data, then we can safely ignore it as it makes - // no difference either way. - continue; + + // For each signed extension in the tuple, find the matching index in the metadata, if + // there is one, and add it to a map with that index as the key. + let mut exts_by_index = HashMap::new(); + $({ + for (idx, e) in metadata.extrinsic().signed_extensions().iter().enumerate() { + // Skip over any exts that have a match already: + if exts_by_index.contains_key(&idx) { + continue + } + // Break and record as soon as we find a match: + if $ident::matches(e.identifier(), e.extra_ty(), types) { + let ext = $ident::new(nonce, client.clone(), other_params.$index)?; + let boxed_ext: Box = Box::new(ext); + exts_by_index.insert(idx, boxed_ext); + break } - return Err(ExtrinsicParamsError::UnknownSignedExtension(ext.identifier().to_owned())); } + })+ + + // Next, turn these into an ordered vec, erroring if we haven't matched on any exts yet. + let mut params = Vec::new(); + for (idx, e) in metadata.extrinsic().signed_extensions().iter().enumerate() { + let Some(ext) = exts_by_index.remove(&idx) else { + if is_type_empty(e.extra_ty(), types) { + continue + } else { + return Err(ExtrinsicParamsError::UnknownSignedExtension(e.identifier().to_owned())); + } + }; + params.push(ext); } Ok(AnyOf { diff --git a/subxt/src/tx/tx_client.rs b/subxt/src/tx/tx_client.rs index 5b5df91106..6bf6c9ab57 100644 --- a/subxt/src/tx/tx_client.rs +++ b/subxt/src/tx/tx_client.rs @@ -124,8 +124,7 @@ impl> TxClient { account_nonce, self.client.clone(), other_params, - ) - .map_err(Into::into)?; + )?; // Return these details, ready to construct a signed extrinsic from. Ok(PartialExtrinsic { diff --git a/testing/integration-tests/src/full_client/blocks/mod.rs b/testing/integration-tests/src/full_client/blocks/mod.rs index 8485f1f4cd..efba9f0a14 100644 --- a/testing/integration-tests/src/full_client/blocks/mod.rs +++ b/testing/integration-tests/src/full_client/blocks/mod.rs @@ -5,9 +5,7 @@ use crate::{test_context, utils::node_runtime}; use codec::{Compact, Encode}; use futures::StreamExt; -use subxt::config::signed_extensions::{ - ChargeAssetTxPayment, CheckMortality, CheckNonce, SkipCheckIfFeeless, -}; +use subxt::config::signed_extensions::{ChargeAssetTxPayment, CheckMortality, CheckNonce}; use subxt::config::DefaultExtrinsicParamsBuilder; use subxt::config::SubstrateConfig; use subxt::utils::Era; @@ -280,25 +278,23 @@ async fn decode_signed_extensions_from_blocks() { let extensions1 = transaction1.signed_extensions().unwrap(); let nonce1 = extensions1.nonce().unwrap(); - let nonce1_static = extensions1.find::().unwrap().unwrap().0; + let nonce1_static = extensions1.find::().unwrap().unwrap(); let tip1 = extensions1.tip().unwrap(); let tip1_static: u128 = extensions1 - .find::>>() + .find::>() .unwrap() .unwrap() - .inner_signed_extension() .tip(); let transaction2 = submit_transfer_extrinsic_and_get_it_back!(5678); let extensions2 = transaction2.signed_extensions().unwrap(); let nonce2 = extensions2.nonce().unwrap(); - let nonce2_static = extensions2.find::().unwrap().unwrap().0; + let nonce2_static = extensions2.find::().unwrap().unwrap(); let tip2 = extensions2.tip().unwrap(); let tip2_static: u128 = extensions2 - .find::>>() + .find::>() .unwrap() .unwrap() - .inner_signed_extension() .tip(); assert_eq!(nonce1, 0); @@ -318,7 +314,7 @@ async fn decode_signed_extensions_from_blocks() { "CheckMortality", "CheckNonce", "CheckWeight", - "SkipCheckIfFeeless", + "ChargeAssetTxPayment", ]; assert_eq!(extensions1.iter().count(), expected_signed_extensions.len());