mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-09 19:57:59 +00:00
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:
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user