Improve SignedExtension matching logic and remove SkipCheckIfFeeless bits (#1283)

* First pass making matching on signed exts more general and handlng SkipCheckifFeeless

* remove unneeded derives (only exts we can decode into are handled by the user)

* No SkipCheckIfFeeless in integration tests either

* Cargo fmt

* Remove SkipCheckIfFeeless specific logic

* clippy

* matches to just return bool, not result

* remove now-invalid comment

* return error from find if .iter() errors
This commit is contained in:
James Wilson
2023-11-23 15:44:35 +00:00
committed by GitHub
parent 6855b1ffd2
commit 2c528854da
8 changed files with 138 additions and 383 deletions
+2 -3
View File
@@ -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<T: Config> ExtrinsicParams<T> for CustomExtrinsicParams<T> {
type OtherParams = CustomExtrinsicParamsBuilder;
type Error = std::convert::Infallible;
// Gather together all of the params we will need to encode:
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
client: Client,
other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
) -> Result<Self, ExtrinsicParamsError> {
Ok(Self {
genesis_hash: client.genesis_hash(),
tip: other_params.tip,
@@ -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<Self>,
signed_extensions::ChargeAssetTxPayment<Self>,
signed_extensions::ChargeTransactionPayment,
signed_extensions::SkipCheckIfFeeless<
Self,
signed_extensions::ChargeAssetTxPayment<Self>,
>,
// 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<T: Config> signed_extensions::SignedExtension<T> 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<T: Config> ExtrinsicParams<T> for CustomSignedExtension {
type OtherParams = ();
type Error = std::convert::Infallible;
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
_client: Client,
_other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
) -> Result<Self, ExtrinsicParamsError> {
Ok(CustomSignedExtension)
}
}
@@ -87,8 +86,8 @@ impl ExtrinsicParamsEncoder for CustomSignedExtension {
pub fn custom(
params: DefaultExtrinsicParamsBuilder<CustomConfig>,
) -> <<CustomConfig as Config>::ExtrinsicParams as ExtrinsicParams<CustomConfig>>::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]
+26 -33
View File
@@ -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<ExtrinsicSignedExtension<'_, T>> {
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<S: SignedExtension<T>>(&self) -> Result<Option<S::Decoded>, Error> {
self.find_by_name(S::NAME)
.map(|s| {
s.as_signed_extra::<S>().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::<S>() {
// 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::<SkipCheckIfFeeless<T, ChargeAssetTxPayment<T>>>()
.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<u64> {
let nonce = self.find::<CheckNonce>().ok()??.0;
Some(nonce)
self.find::<CheckNonce>().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<E: DecodeAsType>(&self) -> Result<E, Error> {
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<S: SignedExtension<T>>(&self) -> Result<Option<S::Decoded>, 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<S: SignedExtension<T>>(&self) -> Result<Option<S::Decoded>, Error> {
if !S::matches(self.identifier, self.ty_id, self.metadata.types()) {
return Ok(None);
}
self.as_type::<S::Decoded>().map(Some)
}
fn as_type<E: DecodeAsType>(&self) -> Result<E, Error> {
let value = E::decode_as_type(&mut &self.bytes[..], self.ty_id, self.metadata.types())?;
Ok(value)
}
}
#[cfg(test)]
@@ -17,7 +17,6 @@ pub type DefaultExtrinsicParams<T> = signed_extensions::AnyOf<
signed_extensions::CheckMortality<T>,
signed_extensions::ChargeAssetTxPayment<T>,
signed_extensions::ChargeTransactionPayment,
signed_extensions::SkipCheckIfFeeless<T, signed_extensions::ChargeAssetTxPayment<T>>,
),
>;
@@ -132,9 +131,6 @@ impl<T: Config> DefaultExtrinsicParamsBuilder<T> {
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<T: Config> DefaultExtrinsicParamsBuilder<T> {
check_mortality_params,
charge_asset_tx_params,
charge_transaction_params,
skip_check_params,
)
}
}
+23 -24
View File
@@ -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<dyn std::error::Error + Send + Sync + 'static>;
pub type CustomExtrinsicParamsError = Box<dyn std::error::Error + Send + Sync + 'static>;
impl From<std::convert::Infallible> for ExtrinsicParamsError {
fn from(value: std::convert::Infallible) -> Self {
match value {}
}
}
impl From<CustomExtrinsicParamsError> 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<T: Config>: ExtrinsicParamsEncoder + Sized + 'static {
/// help construct your [`ExtrinsicParams`] object.
type OtherParams;
/// The type of error returned from [`ExtrinsicParams::new()`].
type Error: Into<ExtrinsicParamsError>;
/// Construct a new instance of our [`ExtrinsicParams`]
/// Construct a new instance of our [`ExtrinsicParams`].
fn new<Client: OfflineClientT<T>>(
nonce: u64,
client: Client,
other_params: Self::OtherParams,
) -> Result<Self, Self::Error>;
) -> Result<Self, ExtrinsicParamsError>;
}
/// This trait is expected to be implemented for any [`ExtrinsicParams`], and
+72 -297
View File
@@ -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<T: Config>: ExtrinsicParams<T> {
/// 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<T: Config> ExtrinsicParams<T> for CheckSpecVersion {
type OtherParams = ();
type Error = std::convert::Infallible;
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
client: Client,
_other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
) -> Result<Self, ExtrinsicParamsError> {
Ok(CheckSpecVersion(client.runtime_version().spec_version))
}
}
@@ -56,23 +55,23 @@ impl ExtrinsicParamsEncoder for CheckSpecVersion {
}
impl<T: Config> SignedExtension<T> 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<u64>);
impl<T: Config> ExtrinsicParams<T> for CheckNonce {
type OtherParams = ();
type Error = std::convert::Infallible;
fn new<Client: OfflineClientT<T>>(
nonce: u64,
_client: Client,
_other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
) -> Result<Self, ExtrinsicParamsError> {
Ok(CheckNonce(Compact(nonce)))
}
}
@@ -84,23 +83,23 @@ impl ExtrinsicParamsEncoder for CheckNonce {
}
impl<T: Config> SignedExtension<T> for CheckNonce {
const NAME: &'static str = "CheckNonce";
type Decoded = Compact<u64>;
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<T: Config> ExtrinsicParams<T> for CheckTxVersion {
type OtherParams = ();
type Error = std::convert::Infallible;
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
client: Client,
_other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
) -> Result<Self, ExtrinsicParamsError> {
Ok(CheckTxVersion(client.runtime_version().transaction_version))
}
}
@@ -112,31 +111,23 @@ impl ExtrinsicParamsEncoder for CheckTxVersion {
}
impl<T: Config> SignedExtension<T> 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: Config>(T::Hash);
impl<T: Config> std::fmt::Debug for CheckGenesis<T> {
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: Config>(T::Hash);
impl<T: Config> ExtrinsicParams<T> for CheckGenesis<T> {
type OtherParams = ();
type Error = std::convert::Infallible;
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
client: Client,
_other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
) -> Result<Self, ExtrinsicParamsError> {
Ok(CheckGenesis(client.genesis_hash()))
}
}
@@ -148,21 +139,19 @@ impl<T: Config> ExtrinsicParamsEncoder for CheckGenesis<T> {
}
impl<T: Config> SignedExtension<T> for CheckGenesis<T> {
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<T: Config> {
era: Era,
checkpoint: T::Hash,
}
/// Parameters to configure the [`CheckMortality`] signed extension.
#[derive(Clone, Debug)]
pub struct CheckMortalityParams<T: Config> {
era: Era,
checkpoint: Option<T::Hash>,
@@ -197,24 +186,14 @@ impl<T: Config> CheckMortalityParams<T> {
}
}
impl<T: Config> std::fmt::Debug for CheckMortality<T> {
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<T: Config> ExtrinsicParams<T> for CheckMortality<T> {
type OtherParams = CheckMortalityParams<T>;
type Error = std::convert::Infallible;
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
client: Client,
other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
) -> Result<Self, ExtrinsicParamsError> {
Ok(CheckMortality {
era: other_params.era,
checkpoint: other_params.checkpoint.unwrap_or(client.genesis_hash()),
@@ -227,19 +206,21 @@ impl<T: Config> ExtrinsicParamsEncoder for CheckMortality<T> {
self.era.encode_to(v);
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
self.checkpoint.encode_to(v)
self.checkpoint.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckMortality<T> {
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<T: Config> {
tip: Compact<u128>,
asset_id: Option<T::AssetId>,
@@ -258,22 +239,11 @@ impl<T: Config> ChargeAssetTxPayment<T> {
}
/// Parameters to configure the [`ChargeAssetTxPayment`] signed extension.
#[derive(Debug)]
pub struct ChargeAssetTxPaymentParams<T: Config> {
tip: u128,
asset_id: Option<T::AssetId>,
}
// Dev note: `#[derive(Clone)]` implies `T: Clone` instead of `T::AssetId: Clone`.
impl<T: Config> Clone for ChargeAssetTxPaymentParams<T> {
fn clone(&self) -> Self {
Self {
tip: self.tip,
asset_id: self.asset_id.clone(),
}
}
}
impl<T: Config> Default for ChargeAssetTxPaymentParams<T> {
fn default() -> Self {
ChargeAssetTxPaymentParams {
@@ -309,13 +279,12 @@ impl<T: Config> ChargeAssetTxPaymentParams<T> {
impl<T: Config> ExtrinsicParams<T> for ChargeAssetTxPayment<T> {
type OtherParams = ChargeAssetTxPaymentParams<T>;
type Error = std::convert::Infallible;
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
_client: Client,
other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
) -> Result<Self, ExtrinsicParamsError> {
Ok(ChargeAssetTxPayment {
tip: Compact(other_params.tip),
asset_id: other_params.asset_id,
@@ -330,12 +299,14 @@ impl<T: Config> ExtrinsicParamsEncoder for ChargeAssetTxPayment<T> {
}
impl<T: Config> SignedExtension<T> for ChargeAssetTxPayment<T> {
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<u128>,
}
@@ -366,13 +337,12 @@ impl ChargeTransactionPaymentParams {
impl<T: Config> ExtrinsicParams<T> for ChargeTransactionPayment {
type OtherParams = ChargeTransactionPaymentParams;
type Error = std::convert::Infallible;
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
_client: Client,
other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
) -> Result<Self, ExtrinsicParamsError> {
Ok(ChargeTransactionPayment {
tip: Compact(other_params.tip),
})
@@ -386,212 +356,9 @@ impl ExtrinsicParamsEncoder for ChargeTransactionPayment {
}
impl<T: Config> SignedExtension<T> 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<Self, ExtrinsicParamsError> {
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<T, S>
where
T: Config,
S: SignedExtension<T> + 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<SkipCheckIfFeelessEncodingData>,
#[decode_as_type(skip)]
#[encode_as_type(skip)]
_phantom: PhantomData<T>,
}
impl<T, S> SkipCheckIfFeeless<T, S>
where
T: Config,
S: SignedExtension<T> + DecodeAsType + EncodeAsType,
{
/// The inner signed extension.
pub fn inner_signed_extension(&self) -> &S {
&self.inner
}
}
impl<T, S> ExtrinsicParams<T> for SkipCheckIfFeeless<T, S>
where
T: Config,
S: SignedExtension<T> + DecodeAsType + EncodeAsType,
<S as ExtrinsicParams<T>>::OtherParams: Default,
{
type OtherParams = SkipCheckIfFeelessParams<T, S>;
type Error = ExtrinsicParamsError;
fn new<Client: OfflineClientT<T>>(
nonce: u64,
client: Client,
other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
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<T, S> ExtrinsicParamsEncoder for SkipCheckIfFeeless<T, S>
where
T: Config,
S: SignedExtension<T> + DecodeAsType + EncodeAsType,
{
fn encode_extra_to(&self, v: &mut Vec<u8>) {
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<T, S> SignedExtension<T> for SkipCheckIfFeeless<T, S>
where
T: Config,
S: SignedExtension<T> + DecodeAsType + EncodeAsType,
<S as ExtrinsicParams<T>>::OtherParams: Default,
{
const NAME: &'static str = "SkipCheckIfFeeless";
type Decoded = Self;
}
/// Parameters to configure the [`SkipCheckIfFeeless`] signed extension.
pub struct SkipCheckIfFeelessParams<T, S>(Option<<S as ExtrinsicParams<T>>::OtherParams>)
where
T: Config,
S: SignedExtension<T>;
impl<T, S> std::fmt::Debug for SkipCheckIfFeelessParams<T, S>
where
T: Config,
S: SignedExtension<T>,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("SkipCheckIfFeelessParams").finish()
}
}
impl<T: Config, S: SignedExtension<T>> Default for SkipCheckIfFeelessParams<T, S>
where
T: Config,
S: SignedExtension<T>,
{
fn default() -> Self {
SkipCheckIfFeelessParams(None)
}
}
impl<T, S> SkipCheckIfFeelessParams<T, S>
where
T: Config,
S: SignedExtension<T>,
{
/// Skip the check if the transaction is feeless.
pub fn from(extrinsic_params: <S as ExtrinsicParams<T>>::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<T>,)+
{
type OtherParams = ($($ident::OtherParams,)+);
type Error = ExtrinsicParamsError;
fn new<Client: OfflineClientT<T>>(
nonce: u64,
client: Client,
other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
// First, push encoders to map as we are given them:
let mut map = HashMap::new();
$({
let e: Box<dyn ExtrinsicParamsEncoder>
= 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<Self, ExtrinsicParamsError> {
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<dyn ExtrinsicParamsEncoder> = 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 {
+1 -2
View File
@@ -124,8 +124,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
account_nonce,
self.client.clone(),
other_params,
)
.map_err(Into::into)?;
)?;
// Return these details, ready to construct a signed extrinsic from.
Ok(PartialExtrinsic {
@@ -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::<CheckNonce>().unwrap().unwrap().0;
let nonce1_static = extensions1.find::<CheckNonce>().unwrap().unwrap();
let tip1 = extensions1.tip().unwrap();
let tip1_static: u128 = extensions1
.find::<SkipCheckIfFeeless<SubstrateConfig, ChargeAssetTxPayment<SubstrateConfig>>>()
.find::<ChargeAssetTxPayment<SubstrateConfig>>()
.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::<CheckNonce>().unwrap().unwrap().0;
let nonce2_static = extensions2.find::<CheckNonce>().unwrap().unwrap();
let tip2 = extensions2.tip().unwrap();
let tip2_static: u128 = extensions2
.find::<SkipCheckIfFeeless<SubstrateConfig, ChargeAssetTxPayment<SubstrateConfig>>>()
.find::<ChargeAssetTxPayment<SubstrateConfig>>()
.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());