// Copyright 2019-2024 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. //! This module contains implementations for common transaction extensions, each //! of which implements [`TransactionExtension`], and can be used in conjunction with //! [`AnyOf`] to configure the set of transaction extensions which are known about //! when interacting with a chain. use super::extrinsic_params::ExtrinsicParams; use crate::client::ClientState; use crate::config::ExtrinsicParamsEncoder; use crate::config::{Config, HashFor}; use crate::error::ExtrinsicParamsError; use crate::utils::{Era, Static}; use alloc::borrow::ToOwned; use alloc::boxed::Box; use alloc::vec::Vec; use codec::{Compact, Encode}; use core::any::Any; use core::fmt::Debug; use derive_where::derive_where; use hashbrown::HashMap; use scale_decode::DecodeAsType; use scale_info::PortableRegistry; // Re-export this here; it's a bit generically named to be re-exported from ::config. pub use super::extrinsic_params::Params; /// A single [`TransactionExtension`] has a unique name, but is otherwise the /// same as [`ExtrinsicParams`] in describing how to encode the extra and /// additional data. pub trait TransactionExtension: ExtrinsicParams { /// The type representing the `extra` / value bytes of a transaction extension. /// Decoding from this type should be symmetrical to the respective /// `ExtrinsicParamsEncoder::encode_value_to()` implementation of this transaction extension. type Decoded: DecodeAsType; /// This should return true if the transaction 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 [`VerifySignature`] extension. For V5 General transactions, this is how a signature /// is provided. The signature is constructed by signing a payload which contains the /// transaction call data as well as the encoded "additional" bytes for any extensions _after_ /// this one in the list. pub struct VerifySignature(VerifySignatureDetails); impl ExtrinsicParams for VerifySignature { type Params = (); fn new(_client: &ClientState, _params: Self::Params) -> Result { Ok(VerifySignature(VerifySignatureDetails::Disabled)) } } impl ExtrinsicParamsEncoder for VerifySignature { fn encode_value_to(&self, v: &mut Vec) { self.0.encode_to(v); } fn encode_signer_payload_value_to(&self, v: &mut Vec) { // This extension is never encoded to the signer payload, and extensions // prior to this are ignored when creating said payload, so clear anything // we've seen so far. v.clear(); } fn encode_implicit_to(&self, v: &mut Vec) { // We only use the "implicit" data for extensions _after_ this one // in the pipeline to form the signer payload. Thus, clear anything // we've seen so far. v.clear(); } fn inject_signature(&mut self, account: &dyn Any, signature: &dyn Any) { // Downcast refs back to concrete types (we use `&dyn Any`` so that the trait remains object safe) let account = account .downcast_ref::() .expect("A T::AccountId should have been provided") .clone(); let signature = signature .downcast_ref::() .expect("A T::Signature should have been provided") .clone(); // The signature is not set through params, only here, once given by a user: self.0 = VerifySignatureDetails::Signed { signature, account } } } impl TransactionExtension for VerifySignature { type Decoded = Static>; fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { identifier == "VerifySignature" } } /// This allows a signature to be provided to the [`VerifySignature`] transaction extension. // Dev note: this must encode identically to https://github.com/paritytech/polkadot-sdk/blob/fd72d58313c297a10600037ce1bb88ec958d722e/substrate/frame/verify-signature/src/extension.rs#L43 #[derive(codec::Encode, codec::Decode)] pub enum VerifySignatureDetails { /// A signature has been provided. Signed { /// The signature. signature: T::Signature, /// The account that generated the signature. account: T::AccountId, }, /// No signature was provided. Disabled, } /// The [`CheckMetadataHash`] transaction extension. pub struct CheckMetadataHash { // Eventually we might provide or calculate the metadata hash here, // but for now we never provide a hash and so this is empty. } impl ExtrinsicParams for CheckMetadataHash { type Params = (); fn new(_client: &ClientState, _params: Self::Params) -> Result { Ok(CheckMetadataHash {}) } } impl ExtrinsicParamsEncoder for CheckMetadataHash { fn encode_value_to(&self, v: &mut Vec) { // A single 0 byte in the TX payload indicates that the chain should // _not_ expect any metadata hash to exist in the signer payload. 0u8.encode_to(v); } fn encode_implicit_to(&self, v: &mut Vec) { // We provide no metadata hash in the signer payload to align with the above. None::<()>.encode_to(v); } } impl TransactionExtension for CheckMetadataHash { type Decoded = CheckMetadataHashMode; fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { identifier == "CheckMetadataHash" } } /// Is metadata checking enabled or disabled? // Dev note: The "Disabled" and "Enabled" variant names match those that the // transaction extension will be encoded with, in order that DecodeAsType will work // properly. #[derive(Copy, Clone, Debug, DecodeAsType)] pub enum CheckMetadataHashMode { /// No hash was provided in the signer payload. Disabled, /// A hash was provided in the signer payload. Enabled, } impl CheckMetadataHashMode { /// Is metadata checking enabled or disabled for this transaction? pub fn is_enabled(&self) -> bool { match self { CheckMetadataHashMode::Disabled => false, CheckMetadataHashMode::Enabled => true, } } } /// The [`CheckSpecVersion`] transaction extension. pub struct CheckSpecVersion(u32); impl ExtrinsicParams for CheckSpecVersion { type Params = (); fn new(client: &ClientState, _params: Self::Params) -> Result { Ok(CheckSpecVersion(client.runtime_version.spec_version)) } } impl ExtrinsicParamsEncoder for CheckSpecVersion { fn encode_implicit_to(&self, v: &mut Vec) { self.0.encode_to(v); } } impl TransactionExtension for CheckSpecVersion { type Decoded = (); fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { identifier == "CheckSpecVersion" } } /// The [`CheckNonce`] transaction extension. pub struct CheckNonce(u64); impl ExtrinsicParams for CheckNonce { type Params = CheckNonceParams; fn new(_client: &ClientState, params: Self::Params) -> Result { Ok(CheckNonce(params.0.unwrap_or(0))) } } impl ExtrinsicParamsEncoder for CheckNonce { fn encode_value_to(&self, v: &mut Vec) { Compact(self.0).encode_to(v); } } impl TransactionExtension for CheckNonce { type Decoded = u64; fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { identifier == "CheckNonce" } } /// Configure the nonce used. #[derive(Debug, Clone, Default)] pub struct CheckNonceParams(Option); impl CheckNonceParams { /// Retrieve the nonce from the chain and use that. pub fn from_chain() -> Self { Self(None) } /// Manually set an account nonce to use. pub fn with_nonce(nonce: u64) -> Self { Self(Some(nonce)) } } impl Params for CheckNonceParams { fn inject_account_nonce(&mut self, nonce: u64) { if self.0.is_none() { self.0 = Some(nonce) } } } /// The [`CheckTxVersion`] transaction extension. pub struct CheckTxVersion(u32); impl ExtrinsicParams for CheckTxVersion { type Params = (); fn new(client: &ClientState, _params: Self::Params) -> Result { Ok(CheckTxVersion(client.runtime_version.transaction_version)) } } impl ExtrinsicParamsEncoder for CheckTxVersion { fn encode_implicit_to(&self, v: &mut Vec) { self.0.encode_to(v); } } impl TransactionExtension for CheckTxVersion { type Decoded = (); fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { identifier == "CheckTxVersion" } } /// The [`CheckGenesis`] transaction extension. pub struct CheckGenesis(HashFor); impl ExtrinsicParams for CheckGenesis { type Params = (); fn new(client: &ClientState, _params: Self::Params) -> Result { Ok(CheckGenesis(client.genesis_hash)) } } impl ExtrinsicParamsEncoder for CheckGenesis { fn encode_implicit_to(&self, v: &mut Vec) { self.0.encode_to(v); } } impl TransactionExtension for CheckGenesis { type Decoded = (); fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { identifier == "CheckGenesis" } } /// The [`CheckMortality`] transaction extension. pub struct CheckMortality { params: CheckMortalityParamsInner, genesis_hash: HashFor, } impl ExtrinsicParams for CheckMortality { type Params = CheckMortalityParams; fn new(client: &ClientState, params: Self::Params) -> Result { // If a user has explicitly configured the transaction to be mortal for n blocks, but we get // to this stage and no injected information was able to turn this into MortalFromBlock{..}, // then we hit an error as we are unable to construct a mortal transaction here. if matches!(¶ms.0, CheckMortalityParamsInner::MortalForBlocks(_)) { return Err(ExtrinsicParamsError::custom( "CheckMortality: We cannot construct an offline extrinsic with only the number of blocks it is mortal for. Use mortal_from_unchecked instead.", )); } Ok(CheckMortality { // if nothing has been explicitly configured, we will have a mortal transaction // valid for 32 blocks if block info is available. params: params.0, genesis_hash: client.genesis_hash, }) } } impl ExtrinsicParamsEncoder for CheckMortality { fn encode_value_to(&self, v: &mut Vec) { match &self.params { CheckMortalityParamsInner::MortalFromBlock { for_n_blocks, from_block_n, .. } => { Era::mortal(*for_n_blocks, *from_block_n).encode_to(v); } _ => { // Note: if we see `CheckMortalityInner::MortalForBlocks`, then it means the user has // configured a block to be mortal for N blocks, but the current block was never injected, // so we don't know where to start from and default back to building an immortal tx. Era::Immortal.encode_to(v); } } } fn encode_implicit_to(&self, v: &mut Vec) { match &self.params { CheckMortalityParamsInner::MortalFromBlock { from_block_hash, .. } => { from_block_hash.encode_to(v); } _ => { self.genesis_hash.encode_to(v); } } } } impl TransactionExtension for CheckMortality { type Decoded = Era; fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { identifier == "CheckMortality" } } /// Parameters to configure the [`CheckMortality`] transaction extension. pub struct CheckMortalityParams(CheckMortalityParamsInner); enum CheckMortalityParamsInner { /// The transaction will be immortal. Immortal, /// The transaction is mortal for N blocks. This must be "upgraded" into /// [`CheckMortalityParamsInner::MortalFromBlock`] to ultimately work. MortalForBlocks(u64), /// The transaction is mortal for N blocks, but if it cannot be "upgraded", /// then it will be set to immortal instead. This is the default if unset. MortalForBlocksOrImmortalIfNotPossible(u64), /// The transaction is mortal and all of the relevant information is provided. MortalFromBlock { for_n_blocks: u64, from_block_n: u64, from_block_hash: HashFor, }, } impl Default for CheckMortalityParams { fn default() -> Self { // default to being mortal for 32 blocks if possible, else immortal: CheckMortalityParams(CheckMortalityParamsInner::MortalForBlocksOrImmortalIfNotPossible(32)) } } impl CheckMortalityParams { /// Configure a transaction that will be mortal for the number of blocks given. pub fn mortal(for_n_blocks: u64) -> Self { Self(CheckMortalityParamsInner::MortalForBlocks(for_n_blocks)) } /// Configure a transaction that will be mortal for the number of blocks given, /// and from the block details provided. Prefer to use [`CheckMortalityParams::mortal()`] /// where possible, which prevents the block number and hash from being misaligned. pub fn mortal_from_unchecked( for_n_blocks: u64, from_block_n: u64, from_block_hash: HashFor, ) -> Self { Self(CheckMortalityParamsInner::MortalFromBlock { for_n_blocks, from_block_n, from_block_hash, }) } /// An immortal transaction. pub fn immortal() -> Self { Self(CheckMortalityParamsInner::Immortal) } } impl Params for CheckMortalityParams { fn inject_block(&mut self, from_block_n: u64, from_block_hash: HashFor) { match &self.0 { CheckMortalityParamsInner::MortalForBlocks(n) | CheckMortalityParamsInner::MortalForBlocksOrImmortalIfNotPossible(n) => { self.0 = CheckMortalityParamsInner::MortalFromBlock { for_n_blocks: *n, from_block_n, from_block_hash, } } _ => { // Don't change anything if explicit Immortal or explicit block set. } } } } /// The [`ChargeAssetTxPayment`] transaction extension. #[derive(DecodeAsType)] #[derive_where(Clone, Debug; T::AssetId)] #[decode_as_type(trait_bounds = "T::AssetId: DecodeAsType")] pub struct ChargeAssetTxPayment { tip: Compact, asset_id: Option, } impl ChargeAssetTxPayment { /// Tip to the extrinsic author in the native chain token. pub fn tip(&self) -> u128 { self.tip.0 } /// Tip to the extrinsic author using the asset ID given. pub fn asset_id(&self) -> Option<&T::AssetId> { self.asset_id.as_ref() } } impl ExtrinsicParams for ChargeAssetTxPayment { type Params = ChargeAssetTxPaymentParams; fn new(_client: &ClientState, params: Self::Params) -> Result { Ok(ChargeAssetTxPayment { tip: Compact(params.tip), asset_id: params.asset_id, }) } } impl ExtrinsicParamsEncoder for ChargeAssetTxPayment { fn encode_value_to(&self, v: &mut Vec) { (self.tip, &self.asset_id).encode_to(v); } } impl TransactionExtension for ChargeAssetTxPayment { type Decoded = Self; fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { identifier == "ChargeAssetTxPayment" } } /// Parameters to configure the [`ChargeAssetTxPayment`] transaction extension. pub struct ChargeAssetTxPaymentParams { tip: u128, asset_id: Option, } impl Default for ChargeAssetTxPaymentParams { fn default() -> Self { ChargeAssetTxPaymentParams { tip: Default::default(), asset_id: Default::default(), } } } impl ChargeAssetTxPaymentParams { /// Don't provide a tip to the extrinsic author. pub fn no_tip() -> Self { ChargeAssetTxPaymentParams { tip: 0, asset_id: None, } } /// Tip the extrinsic author in the native chain token. pub fn tip(tip: u128) -> Self { ChargeAssetTxPaymentParams { tip, asset_id: None, } } /// Tip the extrinsic author using the asset ID given. pub fn tip_of(tip: u128, asset_id: T::AssetId) -> Self { ChargeAssetTxPaymentParams { tip, asset_id: Some(asset_id), } } } impl Params for ChargeAssetTxPaymentParams {} /// The [`ChargeTransactionPayment`] transaction extension. #[derive(Clone, Debug, DecodeAsType)] pub struct ChargeTransactionPayment { tip: Compact, } impl ChargeTransactionPayment { /// Tip to the extrinsic author in the native chain token. pub fn tip(&self) -> u128 { self.tip.0 } } impl ExtrinsicParams for ChargeTransactionPayment { type Params = ChargeTransactionPaymentParams; fn new(_client: &ClientState, params: Self::Params) -> Result { Ok(ChargeTransactionPayment { tip: Compact(params.tip), }) } } impl ExtrinsicParamsEncoder for ChargeTransactionPayment { fn encode_value_to(&self, v: &mut Vec) { self.tip.encode_to(v); } } impl TransactionExtension for ChargeTransactionPayment { type Decoded = Self; fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { identifier == "ChargeTransactionPayment" } } /// Parameters to configure the [`ChargeTransactionPayment`] transaction extension. #[derive(Default)] pub struct ChargeTransactionPaymentParams { tip: u128, } impl ChargeTransactionPaymentParams { /// Don't provide a tip to the extrinsic author. pub fn no_tip() -> Self { ChargeTransactionPaymentParams { tip: 0 } } /// Tip the extrinsic author in the native chain token. pub fn tip(tip: u128) -> Self { ChargeTransactionPaymentParams { tip } } } impl Params for ChargeTransactionPaymentParams {} /// This accepts a tuple of [`TransactionExtension`]s, and will dynamically make use of whichever /// ones are actually required for the chain in the correct order, ignoring the rest. This /// is a sensible default, and allows for a single configuration to work across multiple chains. pub struct AnyOf { params: Vec>, _marker: core::marker::PhantomData<(T, Params)>, } macro_rules! impl_tuples { ($($ident:ident $index:tt),+) => { // We do some magic when the tuple is wrapped in AnyOf. We // look at the metadata, and use this to select and make use of only the extensions // that we actually need for the chain we're dealing with. impl ExtrinsicParams for AnyOf where T: Config, $($ident: TransactionExtension,)+ { type Params = ($($ident::Params,)+); fn new( client: &ClientState, params: Self::Params, ) -> Result { let metadata = &client.metadata; let types = metadata.types(); // For each transaction 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().transaction_extensions_to_use_for_encoding().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(client, params.$index)?; let boxed_ext: Box = Box::new(ext); exts_by_index.insert(idx, boxed_ext); break } } })+ // 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().transaction_extensions_to_use_for_encoding().enumerate() { let Some(ext) = exts_by_index.remove(&idx) else { if is_type_empty(e.extra_ty(), types) { continue } else { return Err(ExtrinsicParamsError::UnknownTransactionExtension(e.identifier().to_owned())); } }; params.push(ext); } Ok(AnyOf { params, _marker: core::marker::PhantomData }) } } impl ExtrinsicParamsEncoder for AnyOf where T: Config, $($ident: TransactionExtension,)+ { fn encode_value_to(&self, v: &mut Vec) { for ext in &self.params { ext.encode_value_to(v); } } fn encode_signer_payload_value_to(&self, v: &mut Vec) { for ext in &self.params { ext.encode_signer_payload_value_to(v); } } fn encode_implicit_to(&self, v: &mut Vec) { for ext in &self.params { ext.encode_implicit_to(v); } } fn inject_signature(&mut self, account_id: &dyn Any, signature: &dyn Any) { for ext in &mut self.params { ext.inject_signature(account_id, signature); } } } } } #[rustfmt::skip] const _: () = { impl_tuples!(A 0); impl_tuples!(A 0, B 1); impl_tuples!(A 0, B 1, C 2); impl_tuples!(A 0, B 1, C 2, D 3); impl_tuples!(A 0, B 1, C 2, D 3, E 4); impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5); impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6); impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7); impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8); impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9); impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10); impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11); impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12); impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13); impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14); impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15); impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16); impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17); impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18); impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, U 19); impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, U 19, V 20); }; /// Checks to see whether the type being given is empty, ie would require /// 0 bytes to encode. fn is_type_empty(type_id: u32, types: &scale_info::PortableRegistry) -> bool { let Some(ty) = types.resolve(type_id) else { // Can't resolve; type may not be empty. Not expected to hit this. return false; }; use scale_info::TypeDef; match &ty.type_def { TypeDef::Composite(c) => c.fields.iter().all(|f| is_type_empty(f.ty.id, types)), TypeDef::Array(a) => a.len == 0 || is_type_empty(a.type_param.id, types), TypeDef::Tuple(t) => t.fields.iter().all(|f| is_type_empty(f.id, types)), // Explicitly list these in case any additions are made in the future. TypeDef::BitSequence(_) | TypeDef::Variant(_) | TypeDef::Sequence(_) | TypeDef::Compact(_) | TypeDef::Primitive(_) => false, } }