Support constructing and submitting V5 transactions (#1931)

* TransactionExtensions basic support for V5 VerifySignature and renames

* WIP: subxt-core v5 transaction support

* Subxt to support V5 extrinsics

* WIP tests failing with wsm trap error

* Actually encode mortality to fix tx encode issue

* fmt

* rename to sign_with_account_and_signature

* Add explicit methods for v4 and v5 ext construction

* clippy

* fix wasm example and no mut self where not needed

* fix doc example

* another doc fix

* Add tests for tx encoding and fix v5 encode issue

* add copyright and todo

* refactor APIs to have clear v4/v5 split in core and slightly nicer split in subxt proper

* rename Partial/SubmittableExtrinsic to *Transaction

* Remove SignerT::address since it's not needed

* doc fixes

* fmt

* doc fixes

* Fix comment number

* Clarify panic behaviour of inject_signature

* fmt
This commit is contained in:
James Wilson
2025-03-11 11:14:27 +00:00
committed by GitHub
parent dcb9c27fcc
commit b6b9ac65c7
50 changed files with 1368 additions and 781 deletions
@@ -2,10 +2,10 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::config::signed_extensions::{
use crate::config::transaction_extensions::{
ChargeAssetTxPayment, ChargeTransactionPayment, CheckNonce,
};
use crate::config::SignedExtension;
use crate::config::TransactionExtension;
use crate::dynamic::Value;
use crate::{config::Config, error::Error, Metadata};
use frame_decode::extrinsics::ExtrinsicExtensions;
@@ -13,14 +13,14 @@ use scale_decode::DecodeAsType;
/// The signed extensions of an extrinsic.
#[derive(Debug, Clone)]
pub struct ExtrinsicSignedExtensions<'a, T: Config> {
pub struct ExtrinsicTransactionExtensions<'a, T: Config> {
bytes: &'a [u8],
metadata: &'a Metadata,
decoded_info: &'a ExtrinsicExtensions<'static, u32>,
_marker: core::marker::PhantomData<T>,
}
impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> {
impl<'a, T: Config> ExtrinsicTransactionExtensions<'a, T> {
pub(crate) fn new(
bytes: &'a [u8],
metadata: &'a Metadata,
@@ -35,20 +35,22 @@ impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> {
}
/// Returns an iterator over each of the signed extension details of the extrinsic.
pub fn iter(&self) -> impl Iterator<Item = ExtrinsicSignedExtension<T>> {
self.decoded_info.iter().map(|s| ExtrinsicSignedExtension {
bytes: &self.bytes[s.range()],
ty_id: *s.ty(),
identifier: s.name(),
metadata: self.metadata,
_marker: core::marker::PhantomData,
})
pub fn iter(&self) -> impl Iterator<Item = ExtrinsicTransactionExtension<T>> {
self.decoded_info
.iter()
.map(|s| ExtrinsicTransactionExtension {
bytes: &self.bytes[s.range()],
ty_id: *s.ty(),
identifier: s.name(),
metadata: self.metadata,
_marker: core::marker::PhantomData,
})
}
/// 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> {
pub fn find<S: TransactionExtension<T>>(&self) -> Result<Option<S::Decoded>, Error> {
for ext in self.iter() {
match ext.as_signed_extension::<S>() {
// We found a match; return it:
@@ -90,7 +92,7 @@ impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> {
/// A single signed extension
#[derive(Debug, Clone)]
pub struct ExtrinsicSignedExtension<'a, T: Config> {
pub struct ExtrinsicTransactionExtension<'a, T: Config> {
bytes: &'a [u8],
ty_id: u32,
identifier: &'a str,
@@ -98,7 +100,7 @@ pub struct ExtrinsicSignedExtension<'a, T: Config> {
_marker: core::marker::PhantomData<T>,
}
impl<'a, T: Config> ExtrinsicSignedExtension<'a, T> {
impl<'a, T: Config> ExtrinsicTransactionExtension<'a, T> {
/// The bytes representing this signed extension.
pub fn bytes(&self) -> &'a [u8] {
self.bytes
@@ -127,7 +129,9 @@ impl<'a, T: Config> ExtrinsicSignedExtension<'a, T> {
/// 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> {
pub fn as_signed_extension<S: TransactionExtension<T>>(
&self,
) -> Result<Option<S::Decoded>, Error> {
if !S::matches(self.identifier, self.ty_id, self.metadata.types()) {
return Ok(None);
}
+6 -6
View File
@@ -3,7 +3,7 @@
// see LICENSE for license details.
use super::BlockError;
use crate::blocks::extrinsic_signed_extensions::ExtrinsicSignedExtensions;
use crate::blocks::extrinsic_transaction_extensions::ExtrinsicTransactionExtensions;
use crate::{
config::{Config, Hasher},
error::{Error, MetadataError},
@@ -232,17 +232,17 @@ where
/// They do *not* include the `additional` signed bytes that are used as part of the payload that is signed.
///
/// Note: Returns `None` if the extrinsic is not signed.
pub fn signed_extensions_bytes(&self) -> Option<&[u8]> {
pub fn transaction_extensions_bytes(&self) -> Option<&[u8]> {
self.decoded_info()
.transaction_extension_payload()
.map(|t| &self.bytes()[t.range()])
}
/// Returns `None` if the extrinsic is not signed.
pub fn signed_extensions(&self) -> Option<ExtrinsicSignedExtensions<'_, T>> {
pub fn transaction_extensions(&self) -> Option<ExtrinsicTransactionExtensions<'_, T>> {
self.decoded_info()
.transaction_extension_payload()
.map(|t| ExtrinsicSignedExtensions::new(self.bytes(), &self.metadata, t))
.map(|t| ExtrinsicTransactionExtensions::new(self.bytes(), &self.metadata, t))
}
/// The index of the pallet that the extrinsic originated from.
@@ -544,7 +544,7 @@ mod tests {
);
// Encoded TX ready to submit.
let tx_encoded = crate::tx::create_unsigned::<SubstrateConfig, _>(&tx, &metadata)
let tx_encoded = crate::tx::create_v4_unsigned::<SubstrateConfig, _>(&tx, &metadata)
.expect("Valid dynamic parameters are provided");
// Extrinsic details ready to decode.
@@ -575,7 +575,7 @@ mod tests {
Value::string("SomeValue"),
],
);
let tx_encoded = crate::tx::create_unsigned::<SubstrateConfig, _>(&tx, &metadata)
let tx_encoded = crate::tx::create_v4_unsigned::<SubstrateConfig, _>(&tx, &metadata)
.expect("Valid dynamic parameters are provided");
// Note: `create_unsigned` produces the extrinsic bytes by prefixing the extrinsic length.
+4 -2
View File
@@ -64,7 +64,7 @@
//! # ]);
//! ```
mod extrinsic_signed_extensions;
mod extrinsic_transaction_extensions;
mod extrinsics;
mod static_extrinsic;
@@ -74,7 +74,9 @@ use crate::Metadata;
use alloc::vec::Vec;
pub use crate::error::BlockError;
pub use extrinsic_signed_extensions::{ExtrinsicSignedExtension, ExtrinsicSignedExtensions};
pub use extrinsic_transaction_extensions::{
ExtrinsicTransactionExtension, ExtrinsicTransactionExtensions,
};
pub use extrinsics::{ExtrinsicDetails, ExtrinsicMetadataDetails, Extrinsics, FoundExtrinsic};
pub use static_extrinsic::StaticExtrinsic;
+1 -1
View File
@@ -7,7 +7,7 @@
use crate::{config::Config, metadata::Metadata};
use derive_where::derive_where;
/// This provides access to some relevant client state in signed extensions,
/// This provides access to some relevant client state in transaction extensions,
/// and is just a combination of some of the available properties.
#[derive_where(Clone, Debug)]
pub struct ClientState<C: Config> {
+43 -64
View File
@@ -2,23 +2,23 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::signed_extensions::CheckNonceParams;
use super::{signed_extensions, ExtrinsicParams};
use super::{Config, Header};
use super::Config;
use super::{transaction_extensions, ExtrinsicParams};
/// The default [`super::ExtrinsicParams`] implementation understands common signed extensions
/// and how to apply them to a given chain.
pub type DefaultExtrinsicParams<T> = signed_extensions::AnyOf<
pub type DefaultExtrinsicParams<T> = transaction_extensions::AnyOf<
T,
(
signed_extensions::CheckSpecVersion,
signed_extensions::CheckTxVersion,
signed_extensions::CheckNonce,
signed_extensions::CheckGenesis<T>,
signed_extensions::CheckMortality<T>,
signed_extensions::ChargeAssetTxPayment<T>,
signed_extensions::ChargeTransactionPayment,
signed_extensions::CheckMetadataHash,
transaction_extensions::VerifySignature<T>,
transaction_extensions::CheckSpecVersion,
transaction_extensions::CheckTxVersion,
transaction_extensions::CheckNonce,
transaction_extensions::CheckGenesis<T>,
transaction_extensions::CheckMortality<T>,
transaction_extensions::ChargeAssetTxPayment<T>,
transaction_extensions::ChargeTransactionPayment,
transaction_extensions::CheckMetadataHash,
),
>;
@@ -26,8 +26,8 @@ pub type DefaultExtrinsicParams<T> = signed_extensions::AnyOf<
/// [`DefaultExtrinsicParams`]. This may expose methods that aren't applicable to the current
/// chain; such values will simply be ignored if so.
pub struct DefaultExtrinsicParamsBuilder<T: Config> {
/// `None` means the tx will be immortal.
mortality: Option<Mortality<T::Hash>>,
/// `None` means the tx will be immortal, else it's mortal for N blocks (if possible).
mortality: Option<u64>,
/// `None` means the nonce will be automatically set.
nonce: Option<u64>,
/// `None` means we'll use the native token.
@@ -36,16 +36,6 @@ pub struct DefaultExtrinsicParamsBuilder<T: Config> {
tip_of: u128,
}
struct Mortality<Hash> {
/// Block hash that mortality starts from
checkpoint_hash: Hash,
/// Block number that mortality starts from (must
// point to the same block as the hash above)
checkpoint_number: u64,
/// How many blocks the tx is mortal for
period: u64,
}
impl<T: Config> Default for DefaultExtrinsicParamsBuilder<T> {
fn default() -> Self {
Self {
@@ -65,15 +55,10 @@ impl<T: Config> DefaultExtrinsicParamsBuilder<T> {
Default::default()
}
/// Make the transaction mortal, given a block header that it should be mortal from,
/// and the number of blocks (roughly; it'll be rounded to a power of two) that it will
/// be mortal for.
pub fn mortal(mut self, from_block: &T::Header, for_n_blocks: u64) -> Self {
self.mortality = Some(Mortality {
checkpoint_hash: from_block.hash(),
checkpoint_number: from_block.number().into(),
period: for_n_blocks,
});
/// Make the transaction mortal, given a number of blocks it will be mortal for from
/// the current block at the time of submission.
pub fn mortal(mut self, for_n_blocks: u64) -> Self {
self.mortality = Some(for_n_blocks);
self
}
@@ -83,26 +68,6 @@ impl<T: Config> DefaultExtrinsicParamsBuilder<T> {
self
}
/// Make the transaction mortal, given a block number and block hash (which must both point to
/// the same block) that it should be mortal from, and the number of blocks (roughly; it'll be
/// rounded to a power of two) that it will be mortal for.
///
/// Prefer to use [`DefaultExtrinsicParamsBuilder::mortal()`], which ensures that the block hash
/// and number align.
pub fn mortal_unchecked(
mut self,
from_block_number: u64,
from_block_hash: T::Hash,
for_n_blocks: u64,
) -> Self {
self.mortality = Some(Mortality {
checkpoint_hash: from_block_hash,
checkpoint_number: from_block_number,
period: for_n_blocks,
});
self
}
/// Provide a tip to the block author in the chain's native token.
pub fn tip(mut self, tip: u128) -> Self {
self.tip = tip;
@@ -123,28 +88,29 @@ impl<T: Config> DefaultExtrinsicParamsBuilder<T> {
/// Build the extrinsic parameters.
pub fn build(self) -> <DefaultExtrinsicParams<T> as ExtrinsicParams<T>>::Params {
let check_mortality_params = if let Some(mortality) = self.mortality {
signed_extensions::CheckMortalityParams::mortal(
mortality.period,
mortality.checkpoint_number,
mortality.checkpoint_hash,
)
let check_mortality_params = if let Some(for_n_blocks) = self.mortality {
transaction_extensions::CheckMortalityParams::mortal(for_n_blocks)
} else {
signed_extensions::CheckMortalityParams::immortal()
transaction_extensions::CheckMortalityParams::immortal()
};
let charge_asset_tx_params = if let Some(asset_id) = self.tip_of_asset_id {
signed_extensions::ChargeAssetTxPaymentParams::tip_of(self.tip, asset_id)
transaction_extensions::ChargeAssetTxPaymentParams::tip_of(self.tip, asset_id)
} else {
signed_extensions::ChargeAssetTxPaymentParams::tip(self.tip)
transaction_extensions::ChargeAssetTxPaymentParams::tip(self.tip)
};
let charge_transaction_params =
signed_extensions::ChargeTransactionPaymentParams::tip(self.tip);
transaction_extensions::ChargeTransactionPaymentParams::tip(self.tip);
let check_nonce_params = CheckNonceParams(self.nonce);
let check_nonce_params = if let Some(nonce) = self.nonce {
transaction_extensions::CheckNonceParams::with_nonce(nonce)
} else {
transaction_extensions::CheckNonceParams::from_chain()
};
(
(),
(),
(),
check_nonce_params,
@@ -156,3 +122,16 @@ impl<T: Config> DefaultExtrinsicParamsBuilder<T> {
)
}
}
#[cfg(test)]
mod test {
use super::*;
fn assert_default<T: Default>(_t: T) {}
#[test]
fn params_are_default() {
let params = DefaultExtrinsicParamsBuilder::<crate::config::PolkadotConfig>::new().build();
assert_default(params)
}
}
+88 -11
View File
@@ -7,9 +7,9 @@
//! [`crate::config::DefaultExtrinsicParams`] provides a general-purpose
//! implementation of this that will work in many cases.
use super::refine_params::RefineParams;
use crate::{client::ClientState, error::ExtrinsicParamsError, Config};
use alloc::vec::Vec;
use core::any::Any;
/// This trait allows you to configure the "signed extra" and
/// "additional" parameters that are a part of the transaction payload
@@ -18,7 +18,7 @@ pub trait ExtrinsicParams<T: Config>: ExtrinsicParamsEncoder + Sized + Send + 's
/// These parameters can be provided to the constructor along with
/// some default parameters that `subxt` understands, in order to
/// help construct your [`ExtrinsicParams`] object.
type Params: RefineParams<T>;
type Params: Params<T>;
/// Construct a new instance of our [`ExtrinsicParams`].
fn new(client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError>;
@@ -28,15 +28,92 @@ pub trait ExtrinsicParams<T: Config>: ExtrinsicParamsEncoder + Sized + Send + 's
/// defines how to encode the "additional" and "extra" params. Both functions
/// are optional and will encode nothing by default.
pub trait ExtrinsicParamsEncoder: 'static {
/// This is expected to SCALE encode the "signed extra" parameters
/// to some buffer that has been provided. These are the parameters
/// which are sent along with the transaction, as well as taken into
/// account when signing the transaction.
fn encode_extra_to(&self, _v: &mut Vec<u8>) {}
/// This is expected to SCALE encode the transaction extension data to some
/// buffer that has been provided. This data is attached to the transaction
/// and also (by default) attached to the signer payload which is signed to
/// provide a signature for the transaction.
///
/// If [`ExtrinsicParamsEncoder::encode_signer_payload_value_to`] is implemented,
/// then that will be used instead when generating a signer payload. Useful for
/// eg the `VerifySignature` extension, which is send with the transaction but
/// is not a part of the signer payload.
fn encode_value_to(&self, _v: &mut Vec<u8>) {}
/// This is expected to SCALE encode the "additional" parameters
/// to some buffer that has been provided. These parameters are _not_
/// sent along with the transaction, but are taken into account when
/// See [`ExtrinsicParamsEncoder::encode_value_to`]. This defaults to calling that
/// method, but if implemented will dictate what is encoded to the signer payload.
fn encode_signer_payload_value_to(&self, v: &mut Vec<u8>) {
self.encode_value_to(v);
}
/// This is expected to SCALE encode the "implicit" (formally "additional")
/// parameters to some buffer that has been provided. These parameters are
/// _not_ sent along with the transaction, but are taken into account when
/// signing it, meaning the client and node must agree on their values.
fn encode_additional_to(&self, _v: &mut Vec<u8>) {}
fn encode_implicit_to(&self, _v: &mut Vec<u8>) {}
/// Set the signature. This happens after we have constructed the extrinsic params,
/// and so is defined here rather than on the params, below. We need to use `&dyn Any`
/// to keep this trait object safe, but can downcast in the impls.
///
/// # Panics
///
/// Implementations of this will likely try to downcast the provided `account_id`
/// and `signature` into `T::AccountId` and `T::Signature` (where `T: Config`), and are
/// free to panic if this downcasting does not succeed.
///
/// In typical usage, this is not a problem, since this method is only called internally
/// and provided values which line up with the relevant `Config`. In theory though, this
/// method can be called manually with any types, hence this warning.
fn inject_signature(&mut self, _account_id: &dyn Any, _signature: &dyn Any) {}
}
/// The parameters (ie [`ExtrinsicParams::Params`]) can also have data injected into them,
/// allowing Subxt to retrieve data from the chain and amend the parameters with it when
/// online.
pub trait Params<T: Config> {
/// Set the account nonce.
fn inject_account_nonce(&mut self, _nonce: u64) {}
/// Set the current block.
fn inject_block(&mut self, _number: u64, _hash: T::Hash) {}
}
impl<T: Config> Params<T> for () {}
macro_rules! impl_tuples {
($($ident:ident $index:tt),+) => {
impl <T: Config, $($ident : Params<T>),+> Params<T> for ($($ident,)+){
fn inject_account_nonce(&mut self, nonce: u64) {
$(self.$index.inject_account_nonce(nonce);)+
}
fn inject_block(&mut self, number: u64, hash: T::Hash) {
$(self.$index.inject_block(number, hash);)+
}
}
}
}
#[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);
};
+4 -6
View File
@@ -10,11 +10,10 @@
mod default_extrinsic_params;
mod extrinsic_params;
mod refine_params;
pub mod polkadot;
pub mod signed_extensions;
pub mod substrate;
pub mod transaction_extensions;
use codec::{Decode, Encode};
use core::fmt::Debug;
@@ -25,9 +24,8 @@ use serde::{de::DeserializeOwned, Serialize};
pub use default_extrinsic_params::{DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder};
pub use extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder};
pub use polkadot::{PolkadotConfig, PolkadotExtrinsicParams, PolkadotExtrinsicParamsBuilder};
pub use refine_params::{RefineParams, RefineParamsData};
pub use signed_extensions::SignedExtension;
pub use substrate::{SubstrateConfig, SubstrateExtrinsicParams, SubstrateExtrinsicParamsBuilder};
pub use transaction_extensions::TransactionExtension;
/// Runtime types.
// Note: the `Send + Sync + 'static` bound isn't strictly required, but currently deriving
@@ -39,13 +37,13 @@ pub trait Config: Sized + Send + Sync + 'static {
type Hash: BlockHash;
/// The account ID type.
type AccountId: Debug + Clone + Encode + Serialize;
type AccountId: Debug + Clone + Encode + Decode + Serialize + Send;
/// The address type.
type Address: Debug + Encode + From<Self::AccountId>;
/// The signature type.
type Signature: Debug + Encode;
type Signature: Debug + Clone + Encode + Decode + Send;
/// The hashing system (algorithm) being used in the runtime (e.g. Blake2).
type Hasher: Debug + Hasher<Output = Self::Hash>;
-87
View File
@@ -1,87 +0,0 @@
// 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.
//! Refining params with values fetched from the chain
use crate::Config;
/// Data that can be used to refine the params of signed extensions.
pub struct RefineParamsData<T: Config> {
account_nonce: u64,
block_number: u64,
block_hash: T::Hash,
}
impl<T: Config> RefineParamsData<T> {
#[doc(hidden)]
/// Creates a new [`RefineParamsData`] instance. Called from `subxt` when refining signed extensions.
pub fn new(account_nonce: u64, block_number: u64, block_hash: T::Hash) -> Self {
RefineParamsData {
account_nonce,
block_number,
block_hash,
}
}
/// account nonce for extrinsic author
pub fn account_nonce(&self) -> u64 {
self.account_nonce
}
/// latest finalized block number
pub fn block_number(&self) -> u64 {
self.block_number
}
/// latest finalized block hash
pub fn block_hash(&self) -> T::Hash {
self.block_hash
}
}
/// Types implementing [`RefineParams`] can be modified to reflect live information from the chain.
pub trait RefineParams<T: Config> {
/// Refine params to an extrinsic. There is usually some notion of 'the param is already set/unset' in types implementing this trait.
/// The refinement should most likely not affect cases where a param is in a 'is already set by the user' state.
fn refine(&mut self, _data: &RefineParamsData<T>) {}
}
impl<T: Config> RefineParams<T> for () {}
macro_rules! impl_tuples {
($($ident:ident $index:tt),+) => {
impl <T: Config, $($ident : RefineParams<T>),+> RefineParams<T> for ($($ident,)+){
fn refine(&mut self, data: &RefineParamsData<T>) {
$(self.$index.refine(data);)+
}
}
}
}
#[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);
};
@@ -2,45 +2,116 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! This module contains implementations for common signed extensions, each
//! of which implements [`SignedExtension`], and can be used in conjunction with
//! [`AnyOf`] to configure the set of signed extensions which are known about
//! 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 super::refine_params::RefineParamsData;
use super::RefineParams;
use crate::client::ClientState;
use crate::config::ExtrinsicParamsEncoder;
use crate::config::{ExtrinsicParamsEncoder, Header};
use crate::error::ExtrinsicParamsError;
use crate::utils::Era;
use crate::utils::{Era, Static};
use crate::Config;
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;
/// A single [`SignedExtension`] has a unique name, but is otherwise the
// 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 SignedExtension<T: Config>: ExtrinsicParams<T> {
/// The type representing the `extra` bytes of a signed extension.
pub trait TransactionExtension<T: Config>: ExtrinsicParams<T> {
/// The type representing the `extra` / value bytes of a transaction extension.
/// Decoding from this type should be symmetrical to the respective
/// `ExtrinsicParamsEncoder::encode_extra_to()` implementation of this signed extension.
/// `ExtrinsicParamsEncoder::encode_value_to()` implementation of this transaction extension.
type Decoded: DecodeAsType;
/// This should return true if the signed extension matches the details given.
/// 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 [`CheckMetadataHash`] signed extension.
/// 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<T: Config>(VerifySignatureDetails<T>);
impl<T: Config> ExtrinsicParams<T> for VerifySignature<T> {
type Params = ();
fn new(_client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(VerifySignature(VerifySignatureDetails::Disabled))
}
}
impl<T: Config> ExtrinsicParamsEncoder for VerifySignature<T> {
fn encode_value_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
fn encode_signer_payload_value_to(&self, v: &mut Vec<u8>) {
// 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<u8>) {
// 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::<T::AccountId>()
.expect("A T::AccoundId should have been provided")
.clone();
let signature = signature
.downcast_ref::<T::Signature>()
.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<T: Config> TransactionExtension<T> for VerifySignature<T> {
type Decoded = Static<VerifySignatureDetails<T>>;
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<T: Config> {
/// 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.
@@ -55,18 +126,18 @@ impl<T: Config> ExtrinsicParams<T> for CheckMetadataHash {
}
impl ExtrinsicParamsEncoder for CheckMetadataHash {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
fn encode_value_to(&self, v: &mut Vec<u8>) {
// 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_additional_to(&self, v: &mut Vec<u8>) {
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
// We provide no metadata hash in the signer payload to align with the above.
None::<()>.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckMetadataHash {
impl<T: Config> TransactionExtension<T> for CheckMetadataHash {
type Decoded = CheckMetadataHashMode;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckMetadataHash"
@@ -75,7 +146,7 @@ impl<T: Config> SignedExtension<T> for CheckMetadataHash {
/// Is metadata checking enabled or disabled?
// Dev note: The "Disabled" and "Enabled" variant names match those that the
// signed extension will be encoded with, in order that DecodeAsType will work
// transaction extension will be encoded with, in order that DecodeAsType will work
// properly.
#[derive(Copy, Clone, Debug, DecodeAsType)]
pub enum CheckMetadataHashMode {
@@ -95,7 +166,7 @@ impl CheckMetadataHashMode {
}
}
/// The [`CheckSpecVersion`] signed extension.
/// The [`CheckSpecVersion`] transaction extension.
pub struct CheckSpecVersion(u32);
impl<T: Config> ExtrinsicParams<T> for CheckSpecVersion {
@@ -107,57 +178,66 @@ impl<T: Config> ExtrinsicParams<T> for CheckSpecVersion {
}
impl ExtrinsicParamsEncoder for CheckSpecVersion {
fn encode_additional_to(&self, v: &mut Vec<u8>) {
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckSpecVersion {
impl<T: Config> TransactionExtension<T> for CheckSpecVersion {
type Decoded = ();
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckSpecVersion"
}
}
/// The [`CheckNonce`] signed extension.
pub struct CheckNonce(Compact<u64>);
/// The [`CheckNonce`] transaction extension.
pub struct CheckNonce(u64);
impl<T: Config> ExtrinsicParams<T> for CheckNonce {
type Params = CheckNonceParams;
fn new(_client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
// If no nonce is set (nor by user nor refinement), use a nonce of 0.
let nonce = params.0.unwrap_or(0);
Ok(CheckNonce(Compact(nonce)))
Ok(CheckNonce(params.0.unwrap_or(0)))
}
}
impl ExtrinsicParamsEncoder for CheckNonce {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
fn encode_value_to(&self, v: &mut Vec<u8>) {
Compact(self.0).encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckNonce {
impl<T: Config> TransactionExtension<T> for CheckNonce {
type Decoded = u64;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckNonce"
}
}
/// Params for [`CheckNonce`]
/// Configure the nonce used.
#[derive(Debug, Clone, Default)]
pub struct CheckNonceParams(pub Option<u64>);
pub struct CheckNonceParams(Option<u64>);
impl<T: Config> RefineParams<T> for CheckNonceParams {
fn refine(&mut self, data: &RefineParamsData<T>) {
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<T: Config> Params<T> for CheckNonceParams {
fn inject_account_nonce(&mut self, nonce: u64) {
if self.0.is_none() {
self.0 = Some(data.account_nonce());
self.0 = Some(nonce)
}
}
}
/// The [`CheckTxVersion`] signed extension.
/// The [`CheckTxVersion`] transaction extension.
pub struct CheckTxVersion(u32);
impl<T: Config> ExtrinsicParams<T> for CheckTxVersion {
@@ -169,19 +249,19 @@ impl<T: Config> ExtrinsicParams<T> for CheckTxVersion {
}
impl ExtrinsicParamsEncoder for CheckTxVersion {
fn encode_additional_to(&self, v: &mut Vec<u8>) {
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckTxVersion {
impl<T: Config> TransactionExtension<T> for CheckTxVersion {
type Decoded = ();
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckTxVersion"
}
}
/// The [`CheckGenesis`] signed extension.
/// The [`CheckGenesis`] transaction extension.
pub struct CheckGenesis<T: Config>(T::Hash);
impl<T: Config> ExtrinsicParams<T> for CheckGenesis<T> {
@@ -193,104 +273,149 @@ impl<T: Config> ExtrinsicParams<T> for CheckGenesis<T> {
}
impl<T: Config> ExtrinsicParamsEncoder for CheckGenesis<T> {
fn encode_additional_to(&self, v: &mut Vec<u8>) {
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckGenesis<T> {
impl<T: Config> TransactionExtension<T> for CheckGenesis<T> {
type Decoded = ();
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckGenesis"
}
}
/// The [`CheckMortality`] signed extension.
/// The [`CheckMortality`] transaction extension.
pub struct CheckMortality<T: Config> {
era: Era,
checkpoint: T::Hash,
}
/// Parameters to configure the [`CheckMortality`] signed extension.
pub struct CheckMortalityParams<T: Config>(Option<CheckMortalityParamsInner<T>>);
struct CheckMortalityParamsInner<T: Config> {
era: Era,
checkpoint: Option<T::Hash>,
}
impl<T: Config> Default for CheckMortalityParams<T> {
fn default() -> Self {
CheckMortalityParams(None)
}
}
impl<T: Config> RefineParams<T> for CheckMortalityParams<T> {
fn refine(&mut self, data: &RefineParamsData<T>) {
if self.0.is_none() {
// By default we refine the params to have a mortal transaction valid for 32 blocks.
const TX_VALID_FOR: u64 = 32;
*self =
CheckMortalityParams::mortal(TX_VALID_FOR, data.block_number(), data.block_hash());
}
}
}
impl<T: Config> CheckMortalityParams<T> {
/// Configure a mortal transaction. The `period` is (roughly) how many
/// blocks the transaction will be valid for. The `block_number` and
/// `block_hash` should both point to the same block, and are the block that
/// the transaction is mortal from.
pub fn mortal(period: u64, block_number: u64, block_hash: T::Hash) -> Self {
Self(Some(CheckMortalityParamsInner {
era: Era::mortal(period, block_number),
checkpoint: Some(block_hash),
}))
}
/// An immortal transaction.
pub fn immortal() -> Self {
Self(Some(CheckMortalityParamsInner {
era: Era::Immortal,
checkpoint: None,
}))
}
params: CheckMortalityParamsInner<T>,
genesis_hash: T::Hash,
}
impl<T: Config> ExtrinsicParams<T> for CheckMortality<T> {
type Params = CheckMortalityParams<T>;
fn new(client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
let check_mortality = if let Some(params) = params.0 {
CheckMortality {
era: params.era,
checkpoint: params.checkpoint.unwrap_or(client.genesis_hash),
}
} else {
CheckMortality {
era: Era::Immortal,
checkpoint: client.genesis_hash,
}
};
Ok(check_mortality)
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<T: Config> ExtrinsicParamsEncoder for CheckMortality<T> {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
self.era.encode_to(v);
fn encode_value_to(&self, v: &mut Vec<u8>) {
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_additional_to(&self, v: &mut Vec<u8>) {
self.checkpoint.encode_to(v);
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
match &self.params {
CheckMortalityParamsInner::MortalFromBlock {
from_block_hash, ..
} => {
from_block_hash.encode_to(v);
}
_ => {
self.genesis_hash.encode_to(v);
}
}
}
}
impl<T: Config> SignedExtension<T> for CheckMortality<T> {
impl<T: Config> TransactionExtension<T> for CheckMortality<T> {
type Decoded = Era;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckMortality"
}
}
/// The [`ChargeAssetTxPayment`] signed extension.
/// Parameters to configure the [`CheckMortality`] transaction extension.
pub struct CheckMortalityParams<T: Config>(CheckMortalityParamsInner<T>);
enum CheckMortalityParamsInner<T: Config> {
Immortal,
MortalForBlocks(u64),
MortalFromBlock {
for_n_blocks: u64,
from_block_n: u64,
from_block_hash: T::Hash,
},
}
impl<T: Config> Default for CheckMortalityParams<T> {
fn default() -> Self {
// default to being mortal for 32 blocks if possible:
CheckMortalityParams(CheckMortalityParamsInner::MortalForBlocks(32))
}
}
impl<T: Config> CheckMortalityParams<T> {
/// 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 header provided.
pub fn mortal_from(for_n_blocks: u64, from_block: T::Header) -> Self {
Self(CheckMortalityParamsInner::MortalFromBlock {
for_n_blocks,
from_block_n: from_block.number().into(),
from_block_hash: from_block.hash(),
})
}
/// Configure a transaction that will be mortal for the number of blocks given,
/// and from the block details provided. Prefer to use [`CheckMortalityParams::mortal()`]
/// or [`CheckMortalityParams::mortal_from()`] which both avoid the block number and hash
/// from being misaligned.
pub fn mortal_from_unchecked(
for_n_blocks: u64,
from_block_n: u64,
from_block_hash: T::Hash,
) -> Self {
Self(CheckMortalityParamsInner::MortalFromBlock {
for_n_blocks,
from_block_n,
from_block_hash,
})
}
/// An immortal transaction.
pub fn immortal() -> Self {
Self(CheckMortalityParamsInner::Immortal)
}
}
impl<T: Config> Params<T> for CheckMortalityParams<T> {
fn inject_block(&mut self, from_block_n: u64, from_block_hash: <T as Config>::Hash) {
match &self.0 {
CheckMortalityParamsInner::MortalForBlocks(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")]
@@ -311,7 +436,31 @@ impl<T: Config> ChargeAssetTxPayment<T> {
}
}
/// Parameters to configure the [`ChargeAssetTxPayment`] signed extension.
impl<T: Config> ExtrinsicParams<T> for ChargeAssetTxPayment<T> {
type Params = ChargeAssetTxPaymentParams<T>;
fn new(_client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(ChargeAssetTxPayment {
tip: Compact(params.tip),
asset_id: params.asset_id,
})
}
}
impl<T: Config> ExtrinsicParamsEncoder for ChargeAssetTxPayment<T> {
fn encode_value_to(&self, v: &mut Vec<u8>) {
(self.tip, &self.asset_id).encode_to(v);
}
}
impl<T: Config> TransactionExtension<T> for ChargeAssetTxPayment<T> {
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<T: Config> {
tip: u128,
asset_id: Option<T::AssetId>,
@@ -350,33 +499,9 @@ impl<T: Config> ChargeAssetTxPaymentParams<T> {
}
}
impl<T: Config> ExtrinsicParams<T> for ChargeAssetTxPayment<T> {
type Params = ChargeAssetTxPaymentParams<T>;
impl<T: Config> Params<T> for ChargeAssetTxPaymentParams<T> {}
fn new(_client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(ChargeAssetTxPayment {
tip: Compact(params.tip),
asset_id: params.asset_id,
})
}
}
impl<T: Config> RefineParams<T> for ChargeAssetTxPaymentParams<T> {}
impl<T: Config> ExtrinsicParamsEncoder for ChargeAssetTxPayment<T> {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
(self.tip, &self.asset_id).encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for ChargeAssetTxPayment<T> {
type Decoded = Self;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "ChargeAssetTxPayment"
}
}
/// The [`ChargeTransactionPayment`] signed extension.
/// The [`ChargeTransactionPayment`] transaction extension.
#[derive(Clone, Debug, DecodeAsType)]
pub struct ChargeTransactionPayment {
tip: Compact<u128>,
@@ -389,7 +514,30 @@ impl ChargeTransactionPayment {
}
}
/// Parameters to configure the [`ChargeTransactionPayment`] signed extension.
impl<T: Config> ExtrinsicParams<T> for ChargeTransactionPayment {
type Params = ChargeTransactionPaymentParams;
fn new(_client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(ChargeTransactionPayment {
tip: Compact(params.tip),
})
}
}
impl ExtrinsicParamsEncoder for ChargeTransactionPayment {
fn encode_value_to(&self, v: &mut Vec<u8>) {
self.tip.encode_to(v);
}
}
impl<T: Config> TransactionExtension<T> 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,
@@ -406,32 +554,9 @@ impl ChargeTransactionPaymentParams {
}
}
impl<T: Config> ExtrinsicParams<T> for ChargeTransactionPayment {
type Params = ChargeTransactionPaymentParams;
impl<T: Config> Params<T> for ChargeTransactionPaymentParams {}
fn new(_client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(ChargeTransactionPayment {
tip: Compact(params.tip),
})
}
}
impl<T: Config> RefineParams<T> for ChargeTransactionPaymentParams {}
impl ExtrinsicParamsEncoder for ChargeTransactionPayment {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
self.tip.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for ChargeTransactionPayment {
type Decoded = Self;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "ChargeTransactionPayment"
}
}
/// This accepts a tuple of [`SignedExtension`]s, and will dynamically make use of whichever
/// 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<T, Params> {
@@ -447,7 +572,7 @@ macro_rules! impl_tuples {
impl <T, $($ident),+> ExtrinsicParams<T> for AnyOf<T, ($($ident,)+)>
where
T: Config,
$($ident: SignedExtension<T>,)+
$($ident: TransactionExtension<T>,)+
{
type Params = ($($ident::Params,)+);
@@ -458,11 +583,11 @@ macro_rules! impl_tuples {
let metadata = &client.metadata;
let types = metadata.types();
// For each signed extension in the tuple, find the matching index in the metadata, if
// 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().signed_extensions().iter().enumerate() {
for (idx, e) in metadata.extrinsic().transaction_extensions().iter().enumerate() {
// Skip over any exts that have a match already:
if exts_by_index.contains_key(&idx) {
continue
@@ -479,12 +604,12 @@ macro_rules! impl_tuples {
// 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() {
for (idx, e) in metadata.extrinsic().transaction_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()));
return Err(ExtrinsicParamsError::UnknownTransactionExtension(e.identifier().to_owned()));
}
};
params.push(ext);
@@ -500,16 +625,26 @@ macro_rules! impl_tuples {
impl <T, $($ident),+> ExtrinsicParamsEncoder for AnyOf<T, ($($ident,)+)>
where
T: Config,
$($ident: SignedExtension<T>,)+
$($ident: TransactionExtension<T>,)+
{
fn encode_extra_to(&self, v: &mut Vec<u8>) {
fn encode_value_to(&self, v: &mut Vec<u8>) {
for ext in &self.params {
ext.encode_extra_to(v);
ext.encode_value_to(v);
}
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
fn encode_signer_payload_value_to(&self, v: &mut Vec<u8>) {
for ext in &self.params {
ext.encode_additional_to(v);
ext.encode_signer_payload_value_to(v);
}
}
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
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);
}
}
}
+22 -4
View File
@@ -27,9 +27,9 @@ pub enum Error {
/// Error encoding from a [`crate::dynamic::Value`].
#[error("Error encoding from dynamic value: {0}")]
Encode(#[from] scale_encode::Error),
/// Error constructing the appropriate extrinsic params.
#[error(transparent)]
ExtrinsicParams(#[from] ExtrinsicParamsError),
/// Error constructing an extrinsic.
#[error("Error constructing transaction: {0}")]
Extrinsic(#[from] ExtrinsicError),
/// Block body error.
#[error("Error working with block_body: {0}")]
Block(#[from] BlockError),
@@ -162,6 +162,24 @@ pub enum StorageAddressError {
},
}
/// An error that can be encountered when constructing a transaction.
#[derive(Debug, DeriveError)]
#[non_exhaustive]
pub enum ExtrinsicError {
/// Transaction version not supported by Subxt.
#[error("Subxt does not support the extrinsic versions expected by the chain")]
UnsupportedVersion,
/// Issue encoding transaction extensions.
#[error("Cannot construct the required transaction extensions: {0}")]
Params(#[from] ExtrinsicParamsError),
}
impl From<ExtrinsicParamsError> for Error {
fn from(value: ExtrinsicParamsError) -> Self {
Error::Extrinsic(value.into())
}
}
/// An error that can be emitted when trying to construct an instance of [`crate::config::ExtrinsicParams`],
/// encode data from the instance, or match on signed extensions.
#[derive(Debug, DeriveError)]
@@ -178,7 +196,7 @@ pub enum ExtrinsicParamsError {
},
/// 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),
UnknownTransactionExtension(String),
/// Some custom error.
#[error("Error constructing extrinsic parameters: {0}")]
Custom(Box<dyn CustomError>),
+204 -71
View File
@@ -48,7 +48,9 @@
//! tx::validate(&call, &state.metadata).unwrap();
//!
//! // We can build a signed transaction:
//! let signed_call = tx::create_signed(&call, &state, &dev::alice(), params).unwrap();
//! let signed_call = tx::create_v4_signed(&call, &state, params)
//! .unwrap()
//! .sign(&dev::alice());
//!
//! // And log it:
//! println!("Tx: 0x{}", hex::encode(signed_call.encoded()));
@@ -58,7 +60,7 @@ pub mod payload;
pub mod signer;
use crate::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, Hasher};
use crate::error::{Error, MetadataError};
use crate::error::{Error, ExtrinsicError, MetadataError};
use crate::metadata::Metadata;
use crate::utils::Encoded;
use alloc::borrow::{Cow, ToOwned};
@@ -89,6 +91,32 @@ pub fn validate<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<(), E
Ok(())
}
/// Returns the suggested transaction versions to build for a given chain, or an error
/// if Subxt doesn't support any version expected by the chain.
///
/// If the result is [`TransactionVersion::V4`], use the `v4` methods in this module. If it's
/// [`TransactionVersion::V5`], use the `v5` ones.
pub fn suggested_version(metadata: &Metadata) -> Result<TransactionVersion, Error> {
let versions = metadata.extrinsic().supported_versions();
if versions.contains(&4) {
Ok(TransactionVersion::V4)
} else if versions.contains(&5) {
Ok(TransactionVersion::V5)
} else {
Err(ExtrinsicError::UnsupportedVersion.into())
}
}
/// The transaction versions supported by Subxt.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum TransactionVersion {
/// v4 transactions (signed and unsigned transactions)
V4,
/// v5 transactions (bare and general transactions)
V5,
}
/// Return the SCALE encoded bytes representing the call data of the transaction.
pub fn call_data<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut bytes = Vec::new();
@@ -96,10 +124,27 @@ pub fn call_data<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<Vec<
Ok(bytes)
}
/// Creates an unsigned extrinsic without submitting it.
pub fn create_unsigned<T: Config, Call: Payload>(
/// Creates a V4 "unsigned" transaction without submitting it.
pub fn create_v4_unsigned<T: Config, Call: Payload>(
call: &Call,
metadata: &Metadata,
) -> Result<Transaction<T>, Error> {
create_unsigned_at_version(call, 4, metadata)
}
/// Creates a V5 "bare" transaction without submitting it.
pub fn create_v5_bare<T: Config, Call: Payload>(
call: &Call,
metadata: &Metadata,
) -> Result<Transaction<T>, Error> {
create_unsigned_at_version(call, 5, metadata)
}
// Create a V4 "unsigned" transaction or V5 "bare" transaction.
fn create_unsigned_at_version<T: Config, Call: Payload>(
call: &Call,
tx_version: u8,
metadata: &Metadata,
) -> Result<Transaction<T>, Error> {
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
@@ -108,8 +153,8 @@ pub fn create_unsigned<T: Config, Call: Payload>(
// 2. Encode extrinsic
let extrinsic = {
let mut encoded_inner = Vec::new();
// transaction protocol version (4) (is not signed, so no 1 bit at the front).
4u8.encode_to(&mut encoded_inner);
// encode the transaction version first.
tx_version.encode_to(&mut encoded_inner);
// encode call data after this byte.
call.encode_call_data_to(metadata, &mut encoded_inner)?;
// now, prefix byte length:
@@ -126,15 +171,12 @@ pub fn create_unsigned<T: Config, Call: Payload>(
Ok(Transaction::from_bytes(extrinsic))
}
/// Create a partial extrinsic.
///
/// Note: if not provided, the default account nonce will be set to 0 and the default mortality will be _immortal_.
/// This is because this method runs offline, and so is unable to fetch the data needed for more appropriate values.
pub fn create_partial_signed<T: Config, Call: Payload>(
/// Construct a v4 extrinsic, ready to be signed.
pub fn create_v4_signed<T: Config, Call: Payload>(
call: &Call,
client_state: &ClientState<T>,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransaction<T>, Error> {
) -> Result<PartialTransactionV4<T>, Error> {
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
validate(call, &client_state.metadata)?;
@@ -147,81 +189,83 @@ pub fn create_partial_signed<T: Config, Call: Payload>(
<T::ExtrinsicParams as ExtrinsicParams<T>>::new(client_state, params)?;
// Return these details, ready to construct a signed extrinsic from.
Ok(PartialTransaction {
Ok(PartialTransactionV4 {
call_data,
additional_and_extra_params,
})
}
/// Creates a signed extrinsic without submitting it.
///
/// Note: if not provided, the default account nonce will be set to 0 and the default mortality will be _immortal_.
/// This is because this method runs offline, and so is unable to fetch the data needed for more appropriate values.
pub fn create_signed<T, Call, Signer>(
/// Construct a v5 "general" extrinsic, ready to be signed or emitted as is.
pub fn create_v5_general<T: Config, Call: Payload>(
call: &Call,
client_state: &ClientState<T>,
signer: &Signer,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<Transaction<T>, Error>
where
T: Config,
Call: Payload,
Signer: SignerT<T>,
{
) -> Result<PartialTransactionV5<T>, Error> {
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
validate(call, &client_state.metadata)?;
// 2. Gather the "additional" and "extra" params along with the encoded call data,
// ready to be signed.
let partial_signed = create_partial_signed(call, client_state, params)?;
// 2. Work out which TX extension version to target based on metadata (unless we
// explicitly ask for a specific transaction version at a later step).
let tx_extensions_version = client_state
.metadata
.extrinsic()
.transaction_extensions_version();
// 3. Sign and construct an extrinsic from these details.
Ok(partial_signed.sign(signer))
// 3. SCALE encode call data to bytes (pallet u8, call u8, call params).
let call_data = call_data(call, &client_state.metadata)?;
// 4. Construct our custom additional/extra params.
let additional_and_extra_params =
<T::ExtrinsicParams as ExtrinsicParams<T>>::new(client_state, params)?;
// Return these details, ready to construct a signed extrinsic from.
Ok(PartialTransactionV5 {
call_data,
additional_and_extra_params,
tx_extensions_version,
})
}
/// This represents a partially constructed transaction that needs signing before it is ready
/// to submit. Use [`PartialTransaction::signer_payload()`] to return the payload that needs signing,
/// [`PartialTransaction::sign()`] to sign the transaction using a [`SignerT`] impl, or
/// [`PartialTransaction::sign_with_address_and_signature()`] to apply an existing signature and address
/// to the transaction.
pub struct PartialTransaction<T: Config> {
/// A partially constructed V4 extrinsic, ready to be signed.
pub struct PartialTransactionV4<T: Config> {
call_data: Vec<u8>,
additional_and_extra_params: T::ExtrinsicParams,
}
impl<T: Config> PartialTransaction<T> {
// Obtain bytes representing the signer payload and run call some function
// with them. This can avoid an allocation in some cases when compared to
// [`PartialExtrinsic::signer_payload()`].
fn with_signer_payload<F, R>(&self, f: F) -> R
where
F: for<'a> FnOnce(Cow<'a, [u8]>) -> R,
{
let mut bytes = self.call_data.clone();
self.additional_and_extra_params.encode_extra_to(&mut bytes);
self.additional_and_extra_params
.encode_additional_to(&mut bytes);
if bytes.len() > 256 {
f(Cow::Borrowed(blake2_256(&bytes).as_ref()))
} else {
f(Cow::Owned(bytes))
}
}
/// Return the signer payload for this extrinsic. These are the bytes that must
/// be signed in order to produce a valid signature for the extrinsic.
pub fn signer_payload(&self) -> Vec<u8> {
self.with_signer_payload(|bytes| bytes.to_vec())
}
impl<T: Config> PartialTransactionV4<T> {
/// Return the bytes representing the call data for this partially constructed
/// extrinsic.
pub fn call_data(&self) -> &[u8] {
&self.call_data
}
/// Convert this [`PartialTransaction`] into a [`Transaction`], ready to submit.
// Obtain bytes representing the signer payload and run call some function
// with them. This can avoid an allocation in some cases.
fn with_signer_payload<F, R>(&self, f: F) -> R
where
F: for<'a> FnOnce(Cow<'a, [u8]>) -> R,
{
let mut bytes = self.call_data.clone();
self.additional_and_extra_params
.encode_signer_payload_value_to(&mut bytes);
self.additional_and_extra_params
.encode_implicit_to(&mut bytes);
if bytes.len() > 256 {
f(Cow::Borrowed(&blake2_256(&bytes)))
} else {
f(Cow::Owned(bytes))
}
}
/// Return the V4 signer payload for this extrinsic. These are the bytes that must
/// be signed in order to produce a valid signature for the extrinsic.
pub fn signer_payload(&self) -> Vec<u8> {
self.with_signer_payload(|bytes| bytes.to_vec())
}
/// Convert this [`PartialTransactionV4`] into a V4 signed [`Transaction`], ready to submit.
/// The provided `signer` is responsible for providing the "from" address for the transaction,
/// as well as providing a signature to attach to it.
pub fn sign<Signer>(&self, signer: &Signer) -> Transaction<T>
@@ -231,30 +275,28 @@ impl<T: Config> PartialTransaction<T> {
// Given our signer, we can sign the payload representing this extrinsic.
let signature = self.with_signer_payload(|bytes| signer.sign(&bytes));
// Now, use the signature and "from" address to build the extrinsic.
self.sign_with_address_and_signature(&signer.address(), &signature)
self.sign_with_account_and_signature(signer.account_id(), &signature)
}
/// Convert this [`PartialTransaction`] into a [`Transaction`], ready to submit.
/// An address, and something representing a signature that can be SCALE encoded, are both
/// needed in order to construct it. If you have a `Signer` to hand, you can use
/// [`PartialTransaction::sign()`] instead.
pub fn sign_with_address_and_signature(
/// Convert this [`PartialTransactionV4`] into a V4 signed [`Transaction`], ready to submit.
/// The provided `address` and `signature` will be used.
pub fn sign_with_account_and_signature(
&self,
address: &T::Address,
account_id: T::AccountId,
signature: &T::Signature,
) -> Transaction<T> {
// Encode the extrinsic (into the format expected by protocol version 4)
let extrinsic = {
let mut encoded_inner = Vec::new();
// "is signed" + transaction protocol version (4)
(0b10000000 + 4u8).encode_to(&mut encoded_inner);
// from address for signature
let address: T::Address = account_id.into();
address.encode_to(&mut encoded_inner);
// the signature
signature.encode_to(&mut encoded_inner);
// attach custom extra params
self.additional_and_extra_params
.encode_extra_to(&mut encoded_inner);
.encode_value_to(&mut encoded_inner);
// and now, call data (remembering that it's been encoded already and just needs appending)
encoded_inner.extend(&self.call_data);
// now, prefix byte length:
@@ -272,6 +314,97 @@ impl<T: Config> PartialTransaction<T> {
}
}
/// A partially constructed V5 general extrinsic, ready to be signed or emitted as-is.
pub struct PartialTransactionV5<T: Config> {
call_data: Vec<u8>,
additional_and_extra_params: T::ExtrinsicParams,
tx_extensions_version: u8,
}
impl<T: Config> PartialTransactionV5<T> {
/// Return the bytes representing the call data for this partially constructed
/// extrinsic.
pub fn call_data(&self) -> &[u8] {
&self.call_data
}
/// Return the V5 signer payload for this extrinsic. These are the bytes that must
/// be signed in order to produce a valid signature for the extrinsic.
pub fn signer_payload(&self) -> [u8; 32] {
let mut bytes = self.call_data.clone();
self.additional_and_extra_params
.encode_signer_payload_value_to(&mut bytes);
self.additional_and_extra_params
.encode_implicit_to(&mut bytes);
blake2_256(&bytes)
}
/// Convert this [`PartialTransactionV5`] into a V5 "general" [`Transaction`].
///
/// This transaction has not been explicitly signed. Use [`Self::sign`]
/// or [`Self::sign_with_account_and_signature`] if you wish to provide a
/// signature (this is usually a necessary step).
pub fn to_transaction(&self) -> Transaction<T> {
let extrinsic = {
let mut encoded_inner = Vec::new();
// "is general" + transaction protocol version (5)
(0b01000000 + 5u8).encode_to(&mut encoded_inner);
// Encode versions for the transaction extensions
self.tx_extensions_version.encode_to(&mut encoded_inner);
// Encode the actual transaction extensions values
self.additional_and_extra_params
.encode_value_to(&mut encoded_inner);
// and now, call data (remembering that it's been encoded already and just needs appending)
encoded_inner.extend(&self.call_data);
// now, prefix byte length:
let len = Compact(
u32::try_from(encoded_inner.len()).expect("extrinsic size expected to be <4GB"),
);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(encoded_inner);
encoded
};
// Return an extrinsic ready to be submitted.
Transaction::from_bytes(extrinsic)
}
/// Convert this [`PartialTransactionV5`] into a V5 "general" [`Transaction`] with a signature.
///
/// Signing the transaction injects the signature into the transaction extension data, which is why
/// this method borrows self mutably. Signing repeatedly will override the previous signature.
pub fn sign<Signer>(&mut self, signer: &Signer) -> Transaction<T>
where
Signer: SignerT<T>,
{
// Given our signer, we can sign the payload representing this extrinsic.
let signature = signer.sign(&self.signer_payload());
// Now, use the signature and "from" account to build the extrinsic.
self.sign_with_account_and_signature(&signer.account_id(), &signature)
}
/// Convert this [`PartialTransactionV5`] into a V5 "general" [`Transaction`] with a signature.
/// Prefer [`Self::sign`] if you have a [`SignerT`] instance to use.
///
/// Signing the transaction injects the signature into the transaction extension data, which is why
/// this method borrows self mutably. Signing repeatedly will override the previous signature.
pub fn sign_with_account_and_signature(
&mut self,
account_id: &T::AccountId,
signature: &T::Signature,
) -> Transaction<T> {
// Inject the signature into the transaction extensions
// before constructing it.
self.additional_and_extra_params
.inject_signature(account_id, signature);
self.to_transaction()
}
}
/// This represents a signed transaction that's ready to be submitted.
/// Use [`Transaction::encoded()`] or [`Transaction::into_encoded()`] to
/// get the bytes for it, or [`Transaction::hash()`] to get the hash.
-3
View File
@@ -14,9 +14,6 @@ pub trait Signer<T: Config> {
/// Return the "from" account ID.
fn account_id(&self) -> T::AccountId;
/// Return the "from" address.
fn address(&self) -> T::Address;
/// Takes a signer payload for an extrinsic, and returns a signature based on it.
///
/// Some signers may fail, for instance because the hardware on which the keys are located has