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
+45 -16
View File
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "addr2line"
@@ -1784,15 +1784,6 @@ dependencies = [
"thiserror 1.0.58",
]
[[package]]
name = "polkadot-sdk"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb819108697967452fa6d8d96ab4c0d48cbaa423b3156499dcb24f1cf95d6775"
dependencies = [
"sp-crypto-hashing",
]
[[package]]
name = "poly1305"
version = "0.8.0"
@@ -2485,15 +2476,11 @@ dependencies = [
"async-trait",
"derive-where",
"either",
"finito",
"frame-metadata",
"futures",
"getrandom",
"hex",
"impl-serde",
"jsonrpsee",
"parity-scale-codec",
"polkadot-sdk",
"primitive-types",
"scale-bits",
"scale-decode",
@@ -2502,10 +2489,12 @@ dependencies = [
"scale-value",
"serde",
"serde_json",
"sp-crypto-hashing",
"subxt-core",
"subxt-lightclient",
"subxt-macro",
"subxt-metadata",
"subxt-rpcs",
"thiserror 2.0.11",
"tokio",
"tracing",
@@ -2544,7 +2533,6 @@ dependencies = [
"impl-serde",
"keccak-hash",
"parity-scale-codec",
"polkadot-sdk",
"primitive-types",
"scale-bits",
"scale-decode",
@@ -2553,6 +2541,7 @@ dependencies = [
"scale-value",
"serde",
"serde_json",
"sp-crypto-hashing",
"subxt-metadata",
"thiserror 2.0.11",
"tracing",
@@ -2605,11 +2594,36 @@ dependencies = [
"frame-metadata",
"hashbrown 0.14.5",
"parity-scale-codec",
"polkadot-sdk",
"scale-info",
"sp-crypto-hashing",
"thiserror 2.0.11",
]
[[package]]
name = "subxt-rpcs"
version = "0.39.0"
dependencies = [
"derive-where",
"finito",
"frame-metadata",
"futures",
"getrandom",
"hex",
"impl-serde",
"jsonrpsee",
"parity-scale-codec",
"primitive-types",
"serde",
"serde_json",
"subxt-core",
"subxt-lightclient",
"thiserror 2.0.11",
"tokio-util",
"tracing",
"url",
"wasm-bindgen-futures",
]
[[package]]
name = "subxt-utils-fetchmetadata"
version = "0.39.0"
@@ -2765,6 +2779,20 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
dependencies = [
"bytes",
"futures-core",
"futures-io",
"futures-sink",
"pin-project-lite",
"tokio",
]
[[package]]
name = "toml_datetime"
version = "0.6.8"
@@ -2821,6 +2849,7 @@ checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
dependencies = [
"cfg-if",
"digest 0.10.7",
"rand",
"static_assertions",
]
+8 -8
View File
@@ -6,7 +6,7 @@ use subxt::{OnlineClient, PolkadotConfig};
use subxt::config::DefaultExtrinsicParamsBuilder;
use subxt::ext::codec::{Decode, Encode};
use subxt::tx::Payload as _;
use subxt::tx::SubmittableExtrinsic;
use subxt::tx::SubmittableTransaction;
use subxt::utils::{AccountId32, MultiSignature};
use crate::services::{extension_signature_for_extrinsic, get_accounts, polkadot, Account};
@@ -51,7 +51,7 @@ pub enum SigningStage {
pub enum SubmittingStage {
Initial {
signed_extrinsic: SubmittableExtrinsic<PolkadotConfig, OnlineClient<PolkadotConfig>>,
signed_extrinsic: SubmittableTransaction<PolkadotConfig, OnlineClient<PolkadotConfig>>,
},
Submitting,
Success {
@@ -70,7 +70,7 @@ pub enum Message {
SignWithAccount(usize),
ReceivedSignature(
MultiSignature,
SubmittableExtrinsic<PolkadotConfig, OnlineClient<PolkadotConfig>>,
SubmittableTransaction<PolkadotConfig, OnlineClient<PolkadotConfig>>,
),
SubmitSigned,
ExtrinsicFinalized {
@@ -166,15 +166,15 @@ impl Component for SigningExamplesComponent {
let params = DefaultExtrinsicParamsBuilder::new()
.nonce(account_nonce)
.build();
let Ok(partial_signed) =
api.tx().create_partial_signed_offline(&remark_call, params)
let Ok(mut partial_signed) =
api.tx().create_partial_offline(&remark_call, params)
else {
return Message::Error(anyhow!("PartialExtrinsic creation failed"));
return Message::Error(anyhow!("PartialTransaction creation failed"));
};
// Apply the signature
let signed_extrinsic = partial_signed
.sign_with_address_and_signature(&account_id.into(), &multi_signature);
.sign_with_account_and_signature(&account_id, &multi_signature);
// check the TX validity (to debug in the js console if the extrinsic would work)
let dry_res = signed_extrinsic.validate().await;
@@ -394,7 +394,7 @@ impl Component for SigningExamplesComponent {
}
async fn submit_wait_finalized_and_get_extrinsic_success_event(
extrinsic: SubmittableExtrinsic<PolkadotConfig, OnlineClient<PolkadotConfig>>,
extrinsic: SubmittableTransaction<PolkadotConfig, OnlineClient<PolkadotConfig>>,
) -> Result<polkadot::system::events::ExtrinsicSuccess, anyhow::Error> {
let events = extrinsic
.submit_and_watch()
+1 -1
View File
@@ -140,7 +140,7 @@ pub async fn extension_signature_for_extrinsic(
let signed_extensions: Vec<String> = api
.metadata()
.extrinsic()
.signed_extensions()
.transaction_extensions()
.iter()
.map(|e| e.identifier().to_string())
.collect();
+25 -9
View File
@@ -8,10 +8,11 @@ use crate::utils::variant_index::VariantIndex;
use crate::{
utils::ordered_map::OrderedMap, ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata,
OuterEnumsMetadata, PalletMetadataInner, RuntimeApiMetadataInner, RuntimeApiMethodMetadata,
RuntimeApiMethodParamMetadata, SignedExtensionMetadata, StorageEntryMetadata,
StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata,
RuntimeApiMethodParamMetadata, StorageEntryMetadata, StorageEntryModifier, StorageEntryType,
StorageHasher, StorageMetadata, TransactionExtensionMetadata,
};
use alloc::borrow::ToOwned;
use alloc::vec;
use frame_metadata::v15;
use hashbrown::HashMap;
use scale_info::form::PortableForm;
@@ -102,8 +103,8 @@ mod from_v15 {
fn from_signed_extension_metadata(
value: v15::SignedExtensionMetadata<PortableForm>,
) -> SignedExtensionMetadata {
SignedExtensionMetadata {
) -> TransactionExtensionMetadata {
TransactionExtensionMetadata {
identifier: value.identifier,
extra_ty: value.ty.id,
additional_ty: value.additional_signed.id,
@@ -112,8 +113,8 @@ mod from_v15 {
fn from_extrinsic_metadata(value: v15::ExtrinsicMetadata<PortableForm>) -> ExtrinsicMetadata {
ExtrinsicMetadata {
version: value.version,
signed_extensions: value
supported_versions: vec![value.version],
transaction_extensions: value
.signed_extensions
.into_iter()
.map(from_signed_extension_metadata)
@@ -122,6 +123,7 @@ mod from_v15 {
call_ty: value.call_ty.id,
signature_ty: value.signature_ty.id,
extra_ty: value.extra_ty.id,
transaction_extensions_version: 0,
}
}
@@ -330,9 +332,23 @@ mod into_v15 {
fn from_extrinsic_metadata(e: ExtrinsicMetadata) -> v15::ExtrinsicMetadata<PortableForm> {
v15::ExtrinsicMetadata {
version: e.version,
// V16 and above metadata can have multiple supported extrinsic versions. We have to
// pick just one of these if converting back to V14/V15 metadata.
//
// - Picking the largest may mean that older tooling won't be compatible (it may only
// check/support older version).
// - Picking the smallest may mean that newer tooling won't work, or newer methods won't
// work.
//
// Either could make sense, but we keep the oldest to prioritize backward compat with
// older tooling.
version: *e
.supported_versions
.iter()
.min()
.expect("at least one extrinsic version expected"),
signed_extensions: e
.signed_extensions
.transaction_extensions
.into_iter()
.map(from_signed_extension_metadata)
.collect(),
@@ -344,7 +360,7 @@ mod into_v15 {
}
fn from_signed_extension_metadata(
s: SignedExtensionMetadata,
s: TransactionExtensionMetadata,
) -> v15::SignedExtensionMetadata<PortableForm> {
v15::SignedExtensionMetadata {
identifier: s.identifier,
+20 -11
View File
@@ -131,7 +131,7 @@ impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
Ok(ExtrinsicExtensionInfo {
extension_ids: self
.extrinsic()
.signed_extensions()
.transaction_extensions()
.iter()
.map(|f| ExtrinsicInfoArg {
name: Cow::Borrowed(f.identifier()),
@@ -601,10 +601,14 @@ pub struct ExtrinsicMetadata {
signature_ty: u32,
/// The type of the outermost Extra enum.
extra_ty: u32,
/// Extrinsic version.
version: u8,
/// Which extrinsic versions are supported by this chain.
supported_versions: Vec<u8>,
/// The signed extensions in the order they appear in the extrinsic.
signed_extensions: Vec<SignedExtensionMetadata>,
transaction_extensions: Vec<TransactionExtensionMetadata>,
/// Version of the transaction extensions.
// TODO [jsdw]: V16 metadata groups transaction extensions by version.
// need to work out what to do once there is more than one version to deal with.
transaction_extensions_version: u8,
}
impl ExtrinsicMetadata {
@@ -626,20 +630,25 @@ impl ExtrinsicMetadata {
self.extra_ty
}
/// Extrinsic version.
pub fn version(&self) -> u8 {
self.version
/// Which extrinsic versions are supported.
pub fn supported_versions(&self) -> &[u8] {
&self.supported_versions
}
/// The extra/additional information associated with the extrinsic.
pub fn signed_extensions(&self) -> &[SignedExtensionMetadata] {
&self.signed_extensions
pub fn transaction_extensions(&self) -> &[TransactionExtensionMetadata] {
&self.transaction_extensions
}
/// Which version are these transaction extensions?
pub fn transaction_extensions_version(&self) -> u8 {
self.transaction_extensions_version
}
}
/// Metadata for the signed extensions used by extrinsics.
#[derive(Debug, Clone)]
pub struct SignedExtensionMetadata {
pub struct TransactionExtensionMetadata {
/// The unique signed extension identifier, which may be different from the type name.
identifier: String,
/// The type of the signed extension, with the data to be included in the extrinsic.
@@ -648,7 +657,7 @@ pub struct SignedExtensionMetadata {
additional_ty: u32,
}
impl SignedExtensionMetadata {
impl TransactionExtensionMetadata {
/// The unique signed extension identifier, which may be different from the type name.
pub fn identifier(&self) -> &str {
&self.identifier
+2 -2
View File
@@ -95,7 +95,7 @@ impl TypeSet {
self.insert(ty);
}
for signed in &extrinsic.signed_extensions {
for signed in &extrinsic.transaction_extensions {
self.insert(signed.extra_ty);
self.insert(signed.additional_ty);
}
@@ -294,7 +294,7 @@ fn iterate_metadata_types(metadata: &mut Metadata) -> impl Iterator<Item = &mut
types.push(ty);
}
for signed in &mut metadata.extrinsic.signed_extensions {
for signed in &mut metadata.extrinsic.transaction_extensions {
types.push(&mut signed.extra_ty);
types.push(&mut signed.additional_ty);
}
+13 -2
View File
@@ -292,14 +292,25 @@ fn get_extrinsic_hash(
let signature_hash = get_type_hash(registry, extrinsic.signature_ty, outer_enum_hashes);
let extra_hash = get_type_hash(registry, extrinsic.extra_ty, outer_enum_hashes);
// Supported versions are just u8s and we will likely never have more than 32 of these, so put them into
// an array of u8s and panic if more than 32.
if extrinsic.supported_versions.len() > 32 {
panic!("The metadata validation logic does not support more than 32 extrinsic versions.");
}
let supported_extrinsic_versions = {
let mut a = [0u8; 32];
a[0..extrinsic.supported_versions.len()].copy_from_slice(&extrinsic.supported_versions);
a
};
let mut bytes = concat_and_hash4(
&address_hash,
&signature_hash,
&extra_hash,
&[extrinsic.version; 32],
&supported_extrinsic_versions,
);
for signed_extension in extrinsic.signed_extensions.iter() {
for signed_extension in extrinsic.transaction_extensions.iter() {
bytes = concat_and_hash4(
&bytes,
&hash(signed_extension.identifier.as_bytes()),
-4
View File
@@ -345,10 +345,6 @@ mod subxt_compat {
self.public_key().into()
}
fn address(&self) -> T::Address {
self.public_key().into()
}
fn sign(&self, signer_payload: &[u8]) -> T::Signature {
self.sign(signer_payload).into()
}
+1 -10
View File
@@ -164,7 +164,7 @@ impl FromStr for DerivationPath {
}
/// A signature generated by [`Keypair::sign()`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, codec::Encode)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, codec::Encode, codec::Decode)]
pub struct Signature(pub [u8; 65]);
impl AsRef<[u8; 65]> for Signature {
@@ -267,10 +267,6 @@ mod subxt_compat {
self.public_key().into()
}
fn address(&self) -> T::Address {
self.public_key().into()
}
fn sign(&self, signer_payload: &[u8]) -> T::Signature {
self.sign(signer_payload).into()
}
@@ -404,11 +400,6 @@ mod test {
}
#[test]
fn check_account_id_eq_address(keypair in keypair()) {
assert_eq!(SubxtSigner::account_id(&keypair), SubxtSigner::address(&keypair));
}
#[test]
fn check_signing_and_verifying_matches(keypair in keypair(), msg in ".*") {
let sig = SubxtSigner::sign(&keypair, msg.as_bytes());
-4
View File
@@ -338,10 +338,6 @@ mod subxt_compat {
self.public_key().into()
}
fn address(&self) -> T::Address {
self.public_key().into()
}
fn sign(&self, signer_payload: &[u8]) -> T::Signature {
self.sign(signer_payload).into()
}
+3 -3
View File
@@ -17,7 +17,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Decode each signed extrinsic in the block dynamically
let extrinsics = block.extrinsics().await?;
for ext in extrinsics.iter() {
let Some(signed_extensions) = ext.signed_extensions() else {
let Some(transaction_extensions) = ext.transaction_extensions() else {
continue; // we do not look at inherents in this example
};
@@ -25,8 +25,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let fields = ext.field_values()?;
println!(" {}/{}", meta.pallet.name(), meta.variant.name);
println!(" Signed Extensions:");
for signed_ext in signed_extensions.iter() {
println!(" Transaction Extensions:");
for signed_ext in transaction_extensions.iter() {
// We only want to take a look at these 3 signed extensions, because the others all just have unit fields.
if ["CheckMortality", "CheckNonce", "ChargeTransactionPayment"]
.contains(&signed_ext.name())
+1 -1
View File
@@ -30,7 +30,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
for transfer in extrinsics.find::<TransferKeepAlive>() {
let transfer = transfer?;
let Some(extensions) = transfer.details.signed_extensions() else {
let Some(extensions) = transfer.details.transaction_extensions() else {
panic!("TransferKeepAlive should be signed")
};
+5 -5
View File
@@ -48,11 +48,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!(" {}", event_values);
}
println!(" Signed Extensions:");
if let Some(signed_extensions) = ext.signed_extensions() {
for signed_extension in signed_extensions.iter() {
let name = signed_extension.name();
let value = signed_extension.value()?.to_string();
println!(" Transaction Extensions:");
if let Some(transaction_extensions) = ext.transaction_extensions() {
for transaction_extension in transaction_extensions.iter() {
let name = transaction_extension.name();
let value = transaction_extension.value()?.to_string();
println!(" {name}: {value}");
}
}
+3 -6
View File
@@ -38,12 +38,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let current_nonce = rpc
.system_account_next_index(&alice.public_key().into())
.await?;
let current_header = rpc.chain_get_header(None).await?.unwrap();
let ext_params = Params::new()
.mortal(&current_header, 8)
.nonce(current_nonce)
.build();
let ext_params = Params::new().mortal(8).nonce(current_nonce).build();
let balance_transfer = polkadot::tx()
.balances()
@@ -51,7 +47,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let ext_hash = api
.tx()
.create_signed_offline(&balance_transfer, &alice, ext_params)?
.create_partial_offline(&balance_transfer, ext_params)?
.sign(&alice)
.submit()
.await?;
+5 -6
View File
@@ -2,7 +2,8 @@
use codec::Encode;
use subxt::client::ClientState;
use subxt::config::{
Config, ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError, RefineParams,
transaction_extensions::Params, Config, ExtrinsicParams, ExtrinsicParamsEncoder,
ExtrinsicParamsError,
};
use subxt_signer::sr25519::dev;
@@ -53,7 +54,7 @@ impl CustomExtrinsicParamsBuilder {
}
}
impl<T: Config> RefineParams<T> for CustomExtrinsicParamsBuilder {}
impl<T: Config> Params<T> for CustomExtrinsicParamsBuilder {}
// Describe how to fetch and then encode the params:
impl<T: Config> ExtrinsicParams<T> for CustomExtrinsicParams<T> {
@@ -69,14 +70,12 @@ impl<T: Config> ExtrinsicParams<T> for CustomExtrinsicParams<T> {
}
}
impl<T: Config> RefineParams<T> for CustomExtrinsicParams<T> {}
// Encode the relevant params when asked:
impl<T: Config> ExtrinsicParamsEncoder for CustomExtrinsicParams<T> {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
fn encode_value_to(&self, v: &mut Vec<u8>) {
(self.tip, self.foo).encode_to(v);
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
self.genesis_hash.encode_to(v)
}
}
@@ -3,7 +3,7 @@ use codec::Encode;
use scale_encode::EncodeAsType;
use scale_info::PortableRegistry;
use subxt::client::ClientState;
use subxt::config::signed_extensions;
use subxt::config::transaction_extensions;
use subxt::config::{
Config, DefaultExtrinsicParamsBuilder, ExtrinsicParams, ExtrinsicParamsEncoder,
ExtrinsicParamsError,
@@ -25,53 +25,54 @@ impl Config for CustomConfig {
type Signature = subxt::utils::MultiSignature;
type Hasher = subxt::config::substrate::BlakeTwo256;
type Header = subxt::config::substrate::SubstrateHeader<u32, Self::Hasher>;
type ExtrinsicParams = signed_extensions::AnyOf<
type ExtrinsicParams = transaction_extensions::AnyOf<
Self,
(
// Load in the existing signed extensions we're interested in
// (if the extension isn't actually needed it'll just be ignored):
signed_extensions::CheckSpecVersion,
signed_extensions::CheckTxVersion,
signed_extensions::CheckNonce,
signed_extensions::CheckGenesis<Self>,
signed_extensions::CheckMortality<Self>,
signed_extensions::ChargeAssetTxPayment<Self>,
signed_extensions::ChargeTransactionPayment,
signed_extensions::CheckMetadataHash,
transaction_extensions::VerifySignature<Self>,
transaction_extensions::CheckSpecVersion,
transaction_extensions::CheckTxVersion,
transaction_extensions::CheckNonce,
transaction_extensions::CheckGenesis<Self>,
transaction_extensions::CheckMortality<Self>,
transaction_extensions::ChargeAssetTxPayment<Self>,
transaction_extensions::ChargeTransactionPayment,
transaction_extensions::CheckMetadataHash,
// And add a new one of our own:
CustomSignedExtension,
CustomTransactionExtension,
),
>;
type AssetId = u32;
}
// Our custom signed extension doesn't do much:
pub struct CustomSignedExtension;
pub struct CustomTransactionExtension;
// 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 {
impl<T: Config> transaction_extensions::TransactionExtension<T> for CustomTransactionExtension {
type Decoded = ();
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CustomSignedExtension"
identifier == "CustomTransactionExtension"
}
}
// Gather together any params we need for our signed extension, here none.
impl<T: Config> ExtrinsicParams<T> for CustomSignedExtension {
impl<T: Config> ExtrinsicParams<T> for CustomTransactionExtension {
type Params = ();
fn new(_client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(CustomSignedExtension)
Ok(CustomTransactionExtension)
}
}
// Encode whatever the extension needs to provide when asked:
impl ExtrinsicParamsEncoder for CustomSignedExtension {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
impl ExtrinsicParamsEncoder for CustomTransactionExtension {
fn encode_value_to(&self, v: &mut Vec<u8>) {
"Hello".encode_to(v);
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
true.encode_to(v)
}
}
@@ -84,8 +85,8 @@ impl ExtrinsicParamsEncoder for CustomSignedExtension {
pub fn custom(
params: DefaultExtrinsicParamsBuilder<CustomConfig>,
) -> <<CustomConfig as Config>::ExtrinsicParams as ExtrinsicParams<CustomConfig>>::Params {
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, h, i) = params.build();
(a, b, c, d, e, f, g, h, i, ())
}
#[tokio::main]
@@ -69,10 +69,6 @@ mod pair_signer {
self.account_id.clone()
}
fn address(&self) -> <PolkadotConfig as Config>::Address {
self.account_id.clone().into()
}
fn sign(&self, signer_payload: &[u8]) -> <PolkadotConfig as Config>::Signature {
let signature = self.signer.sign(signer_payload);
MultiSignature::Sr25519(signature.0)
+5 -5
View File
@@ -10,7 +10,7 @@ pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), BoxedError> {
// Spawned tasks require things held across await points to impl Send,
// so we use one to demonstrate that this is possible with `PartialExtrinsic`
// so we use one to demonstrate that this is possible with `PartialTransaction`
tokio::spawn(signing_example()).await??;
Ok(())
}
@@ -25,9 +25,9 @@ async fn signing_example() -> Result<(), BoxedError> {
let alice = dev::alice();
// Create partial tx, ready to be signed.
let partial_tx = api
let mut partial_tx = api
.tx()
.create_partial_signed(
.create_partial(
&balance_transfer_tx,
&alice.public_key().to_account_id(),
Default::default(),
@@ -35,13 +35,13 @@ async fn signing_example() -> Result<(), BoxedError> {
.await?;
// Simulate taking some time to get a signature back, in part to
// show that the `PartialExtrinsic` can be held across await points.
// show that the `PartialTransaction` can be held across await points.
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
let signature = alice.sign(&partial_tx.signer_payload());
// Sign the transaction.
let tx = partial_tx
.sign_with_address_and_signature(&alice.public_key().to_address(), &signature.into());
.sign_with_account_and_signature(&alice.public_key().to_account_id(), &signature.into());
// Submit it.
tx.submit_and_watch()
+1 -6
View File
@@ -15,14 +15,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let dest = dev::bob().public_key().into();
let tx = polkadot::tx().balances().transfer_allow_death(dest, 10_000);
let latest_block = api.blocks().at_latest().await?;
// Configure the transaction parameters; we give a small tip and set the
// transaction to live for 32 blocks from the `latest_block` above.
let tx_params = Params::new()
.tip(1_000)
.mortal(latest_block.header(), 32)
.build();
let tx_params = Params::new().tip(1_000).mortal(32).build();
// submit the transaction:
let from = dev::alice();
+8 -7
View File
@@ -16,7 +16,8 @@ use subxt_core::blocks::{ExtrinsicDetails as CoreExtrinsicDetails, Extrinsics as
// Re-export anything that's directly returned/used in the APIs below.
pub use subxt_core::blocks::{
ExtrinsicMetadataDetails, ExtrinsicSignedExtension, ExtrinsicSignedExtensions, StaticExtrinsic,
ExtrinsicMetadataDetails, ExtrinsicTransactionExtension, ExtrinsicTransactionExtensions,
StaticExtrinsic,
};
/// The body of a block.
@@ -191,14 +192,14 @@ where
self.inner.signature_bytes()
}
/// See [`subxt_core::blocks::ExtrinsicDetails::signed_extensions_bytes()`].
pub fn signed_extensions_bytes(&self) -> Option<&[u8]> {
self.inner.signed_extensions_bytes()
/// See [`subxt_core::blocks::ExtrinsicDetails::transaction_extensions_bytes()`].
pub fn transaction_extensions_bytes(&self) -> Option<&[u8]> {
self.inner.transaction_extensions_bytes()
}
/// See [`subxt_core::blocks::ExtrinsicDetails::signed_extensions()`].
pub fn signed_extensions(&self) -> Option<ExtrinsicSignedExtensions<'_, T>> {
self.inner.signed_extensions()
/// See [`subxt_core::blocks::ExtrinsicDetails::transaction_extensions()`].
pub fn transaction_extensions(&self) -> Option<ExtrinsicTransactionExtensions<'_, T>> {
self.inner.transaction_extensions()
}
/// See [`subxt_core::blocks::ExtrinsicDetails::pallet_index()`].
+2 -2
View File
@@ -14,8 +14,8 @@ pub use crate::backend::BlockRef;
pub use block_types::Block;
pub use blocks_client::BlocksClient;
pub use extrinsic_types::{
ExtrinsicDetails, ExtrinsicEvents, ExtrinsicSignedExtension, ExtrinsicSignedExtensions,
Extrinsics, FoundExtrinsic, StaticExtrinsic,
ExtrinsicDetails, ExtrinsicEvents, ExtrinsicTransactionExtension,
ExtrinsicTransactionExtensions, Extrinsics, FoundExtrinsic, StaticExtrinsic,
};
// We get account nonce info in tx_client, too, so re-use the logic:
+19 -19
View File
@@ -64,10 +64,10 @@
//!
//! ## ExtrinsicParams
//!
//! Chains each have a set of "signed extensions" configured. Signed extensions provide a means to extend how transactions
//! work. Each signed extension can potentially encode some "extra" data which is sent along with a transaction, as well as some
//! Chains each have a set of "transaction extensions" (formally called "signed extensions") configured. Transaction extensions provide
//! a means to extend how transactions work. Each transaction extension can potentially encode some "extra" data which is sent along with a transaction, as well as some
//! "additional" data which is included in the transaction signer payload, but not transmitted along with the transaction. On
//! a node, signed extensions can then perform additional checks on the submitted transactions to ensure their validity.
//! a node, transaction extensions can then perform additional checks on the submitted transactions to ensure their validity.
//!
//! The `ExtrinsicParams` config type expects to be given an implementation of the [`crate::config::ExtrinsicParams`] trait.
//! Implementations of the [`crate::config::ExtrinsicParams`] trait are handed some parameters from Subxt itself, and can
@@ -75,26 +75,26 @@
//! via the required [`crate::config::ExtrinsicParamsEncoder`] impl.
//!
//! **In most cases, the default [crate::config::DefaultExtrinsicParams] type will work**: it understands the "standard"
//! signed extensions that are in use, and allows the user to provide things like a tip, and set the extrinsic mortality via
//! [`crate::config::DefaultExtrinsicParamsBuilder`]. It will use the chain metadata to decide which signed extensions to use
//! and in which order. It will return an error if the chain uses a signed extension which it doesn't know how to handle.
//! transaction extensions that are in use, and allows the user to provide things like a tip, and set the extrinsic mortality via
//! [`crate::config::DefaultExtrinsicParamsBuilder`]. It will use the chain metadata to decide which transaction extensions to use
//! and in which order. It will return an error if the chain uses a transaction extension which it doesn't know how to handle.
//!
//! If the chain uses novel signed extensions (or if you just wish to provide a different interface for users to configure
//! If the chain uses novel transaction extensions (or if you just wish to provide a different interface for users to configure
//! transactions), you can either:
//!
//! 1. Implement a new signed extension and add it to the list.
//! 1. Implement a new transaction extension and add it to the list.
//! 2. Implement [`crate::config::DefaultExtrinsicParams`] from scratch.
//!
//! See below for examples of each.
//!
//! ### Finding out which signed extensions a chain is using.
//! ### Finding out which transaction extensions a chain is using.
//!
//! In either case, you'll want to find out which signed extensions a chain is using. This information can be obtained from
//! the `SignedExtra` parameter of the `UncheckedExtrinsic` of your parachain, which will be a tuple of signed extensions.
//! In either case, you'll want to find out which transaction extensions a chain is using. This information can be obtained from
//! the `SignedExtra` parameter of the `UncheckedExtrinsic` of your parachain, which will be a tuple of transaction extensions.
//! It can also be obtained from the metadata (see [`frame_metadata::v15::SignedExtensionMetadata`]).
//!
//! For statemint, the signed extensions look like
//! [this](https://github.com/paritytech/cumulus/tree/master/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs#L779):
//! For statemint, the transaction extensions look like
//! [this](https://github.com/paritytech/cumulus/blob/d4bb2215bb28ee05159c4c7df1b3435177b5bf4e/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs#L786):
//!
//! ```rs
//! pub type SignedExtra = (
@@ -126,20 +126,20 @@
//!
//! All types in the `struct type` column make up the "extra" data that we're expected to provide. All types in the
//! `AdditionalSigned` column make up the "additional" data that we're expected to provide. This information will be useful
//! whether we want to implement [`crate::config::SignedExtension`] for a signed extension, or implement
//! whether we want to implement [`crate::config::TransactionExtension`] for a transaction extension, or implement
//! [`crate::config::ExtrinsicParams`] from scratch.
//!
//! As it happens, all of the signed extensions in the table are either already exported in [`crate::config::signed_extensions`],
//! As it happens, all of the transaction extensions in the table are either already exported in [`crate::config::transaction_extensions`],
//! or they hand back no "additional" or "extra" data. In both of these cases, the default `ExtrinsicParams` configuration will
//! work out of the box.
//!
//! ### Implementing and adding new signed extensions to the config
//! ### Implementing and adding new transaction extensions to the config
//!
//! If you do need to implement a novel signed extension, then you can implement [`crate::config::signed_extensions::SignedExtension`]
//! on a custom type and place it into a new set of signed extensions, like so:
//! If you do need to implement a novel transaction extension, then you can implement [`crate::config::transaction_extensions::TransactionExtension`]
//! on a custom type and place it into a new set of transaction extensions, like so:
//!
//! ```rust,ignore
#![doc = include_str ! ("../../../examples/setup_config_signed_extension.rs")]
#![doc = include_str ! ("../../../examples/setup_config_transaction_extension.rs")]
//! ```
//!
//! ### Implementing [`crate::config::ExtrinsicParams`] from scratch
+13 -12
View File
@@ -49,7 +49,7 @@
//!
//! This example shows how to subscribe to blocks and decode the extrinsics in each block into the root extrinsic type.
//! Once we get hold of the [ExtrinsicDetails](crate::blocks::ExtrinsicDetails), we can decode it statically or dynamically.
//! We can also access details about the extrinsic, including the associated events and signed extensions.
//! We can also access details about the extrinsic, including the associated events and transaction extensions.
//!
//! ```rust,ignore
#![doc = include_str!("../../../examples/blocks_subscribing.rs")]
@@ -64,7 +64,8 @@
//! get only [the first one](crate::blocks::Extrinsics::find_first), or [the last one](crate::blocks::Extrinsics::find_last).
//!
//! The following example monitors `TransferKeepAlive` extrinsics on the Polkadot network.
//! We statically decode them and access the [tip](crate::blocks::ExtrinsicSignedExtensions::tip()) and [account nonce](crate::blocks::ExtrinsicSignedExtensions::nonce()) signed extensions.
//! We statically decode them and access the [tip](crate::blocks::ExtrinsicTransactionExtensions::tip()) and [account nonce](crate::blocks::ExtrinsicTransactionExtensions::nonce())
//! transaction extensions.
//!
//! ```rust,ignore
#![doc = include_str!("../../../examples/block_decoding_static.rs")]
@@ -75,7 +76,7 @@
//! Sometimes you might use subxt with metadata that is not known at compile time. In this case, you do not have access to a statically generated
//! interface module that contains the relevant Rust types. You can [decode ExtrinsicDetails dynamically](crate::blocks::ExtrinsicDetails::field_values()),
//! which gives you access to it's fields as a [scale value composite](scale_value::Composite).
//! The following example looks for signed extrinsics on the Polkadot network and retrieves their pallet name, variant name, data fields and signed extensions dynamically.
//! The following example looks for signed extrinsics on the Polkadot network and retrieves their pallet name, variant name, data fields and transaction extensions dynamically.
//! Notice how we do not need to use code generation via the subxt macro. The only fixed component we provide is the [PolkadotConfig](crate::config::PolkadotConfig).
//! Other than that it works in a chain-agnostic way:
//!
@@ -83,16 +84,16 @@
#![doc = include_str!("../../../examples/block_decoding_dynamic.rs")]
//! ```
//!
//! ## Decoding signed extensions
//! ## Decoding transaction extensions
//!
//! Extrinsics can contain signed extensions. The signed extensions can be different across chains.
//! The [Config](crate::Config) implementation for your chain defines which signed extensions you expect.
//! Extrinsics can contain transaction extensions. The transaction extensions can be different across chains.
//! The [Config](crate::Config) implementation for your chain defines which transaction extensions you expect.
//! Once you get hold of the [ExtrinsicDetails](crate::blocks::ExtrinsicDetails) for an extrinsic you are interested in,
//! you can try to [get its signed extensions](crate::blocks::ExtrinsicDetails::signed_extensions()).
//! These are only available on signed extrinsics. You can try to [find a specific signed extension](crate::blocks::ExtrinsicSignedExtensions::find),
//! in the returned [signed extensions](crate::blocks::ExtrinsicSignedExtensions).
//! you can try to [get its transaction extensions](crate::blocks::ExtrinsicDetails::transaction_extensions()).
//! These are only available on V4 signed extrinsics or V5 general extrinsics. You can try to [find a specific transaction extension](crate::blocks::ExtrinsicTransactionExtensions::find),
//! in the returned [transaction extensions](crate::blocks::ExtrinsicTransactionExtensions).
//!
//! Subxt also provides utility functions to get the [tip](crate::blocks::ExtrinsicSignedExtensions::tip()) and the
//! [account nonce](crate::blocks::ExtrinsicSignedExtensions::tip()) associated with an extrinsic, given its signed extensions.
//! If you prefer to do things dynamically you can get the data of the signed extension as a [scale value](crate::blocks::ExtrinsicSignedExtension::value()).
//! Subxt also provides utility functions to get the [tip](crate::blocks::ExtrinsicTransactionExtensions::tip()) and the
//! [account nonce](crate::blocks::ExtrinsicTransactionExtensions::tip()) associated with an extrinsic, given its transaction extensions.
//! If you prefer to do things dynamically you can get the data of the transaction extension as a [scale value](crate::blocks::ExtrinsicTransactionExtension::value()).
//!
+6 -6
View File
@@ -113,8 +113,8 @@
//! ]);
//!
//! // Construct the tx but don't sign it. The account nonce here defaults to 0.
//! // You can use `create_partial_signed` to fetch the correct nonce.
//! let partial_tx = client.tx().create_partial_signed_offline(
//! // You can use `create_partial` to fetch the correct nonce.
//! let mut partial_tx = client.tx().create_partial_offline(
//! &payload,
//! Default::default()
//! )?;
@@ -126,16 +126,16 @@
//! // Ultimately we need to be given back a `signature` (or really, anything
//! // that can be SCALE encoded) and an `address`:
//! let signature;
//! let address;
//! let account_id;
//! # use subxt::tx::Signer;
//! # let signer = subxt_signer::sr25519::dev::alice();
//! # signature = signer.sign(&signer_payload).into();
//! # address = signer.public_key().to_address();
//! # account_id = signer.public_key().to_account_id();
//!
//! // Now we can build an tx, which one can call `submit` or `submit_and_watch`
//! // on to submit to a node and optionally watch the status.
//! let tx = partial_tx.sign_with_address_and_signature(
//! &address,
//! let tx = partial_tx.sign_with_account_and_signature(
//! &account_id,
//! &signature
//! );
//! # Ok(())
+3 -3
View File
@@ -21,7 +21,7 @@ pub use dispatch_error::{
pub use crate::metadata::Metadata;
pub use scale_decode::Error as DecodeError;
pub use scale_encode::Error as EncodeError;
pub use subxt_core::error::{ExtrinsicParamsError, MetadataError, StorageAddressError};
pub use subxt_core::error::{ExtrinsicError, MetadataError, StorageAddressError};
pub use subxt_metadata::TryFromError as MetadataTryFromError;
/// The underlying error enum, generic over the type held by the `Runtime`
@@ -62,7 +62,7 @@ pub enum Error {
Transaction(#[from] TransactionError),
/// Error constructing the appropriate extrinsic params.
#[error("Extrinsic params error: {0}")]
ExtrinsicParams(#[from] ExtrinsicParamsError),
Extrinsic(#[from] ExtrinsicError),
/// Block related error.
#[error("Block error: {0}")]
Block(#[from] BlockError),
@@ -90,7 +90,7 @@ impl From<CoreError> for Error {
CoreError::StorageAddress(e) => Error::StorageAddress(e),
CoreError::Decode(e) => Error::Decode(e),
CoreError::Encode(e) => Error::Encode(e),
CoreError::ExtrinsicParams(e) => Error::ExtrinsicParams(e),
CoreError::Extrinsic(e) => Error::Extrinsic(e),
CoreError::Block(e) => Error::Block(e.into()),
}
}
+3 -3
View File
@@ -57,10 +57,10 @@ pub mod utils;
/// Polkadot node.
pub mod config {
pub use subxt_core::config::{
polkadot, signed_extensions, substrate, BlockHash, Config, DefaultExtrinsicParams,
polkadot, substrate, transaction_extensions, BlockHash, Config, DefaultExtrinsicParams,
DefaultExtrinsicParamsBuilder, ExtrinsicParams, ExtrinsicParamsEncoder, Hasher, Header,
PolkadotConfig, PolkadotExtrinsicParams, RefineParams, RefineParamsData, SignedExtension,
SubstrateConfig, SubstrateExtrinsicParams,
PolkadotConfig, PolkadotExtrinsicParams, SubstrateConfig, SubstrateExtrinsicParams,
TransactionExtension,
};
pub use subxt_core::error::ExtrinsicParamsError;
}
+1 -1
View File
@@ -15,7 +15,7 @@ mod tx_progress;
pub use subxt_core::tx::payload::{dynamic, DefaultPayload, DynamicPayload, Payload};
pub use subxt_core::tx::signer::{self, Signer};
pub use tx_client::{
PartialExtrinsic, SubmittableExtrinsic, TransactionInvalid, TransactionUnknown, TxClient,
PartialTransaction, SubmittableTransaction, TransactionInvalid, TransactionUnknown, TxClient,
ValidationResult,
};
pub use tx_progress::{TxInBlock, TxProgress, TxStatus};
+276 -144
View File
@@ -5,13 +5,14 @@
use crate::{
backend::{BackendExt, BlockRef, TransactionStatus},
client::{OfflineClientT, OnlineClientT},
config::{Config, ExtrinsicParams, Header, RefineParams, RefineParamsData},
config::{Config, ExtrinsicParams, Header},
error::{BlockError, Error},
tx::{Payload, Signer as SignerT, TxProgress},
utils::PhantomDataSendSync,
};
use codec::{Compact, Decode, Encode};
use derive_where::derive_where;
use subxt_core::tx::TransactionVersion;
/// A client for working with transactions.
#[derive_where(Clone; Client)]
@@ -31,7 +32,7 @@ impl<T: Config, Client> TxClient<T, Client> {
}
impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
/// Run the validation logic against some extrinsic you'd like to submit. Returns `Ok(())`
/// Run the validation logic against some transaction you'd like to submit. Returns `Ok(())`
/// if the call is valid (or if it's not possible to check since the call has no validation hash).
/// Return an error if the call was not valid or something went wrong trying to validate it (ie
/// the pallet or call in question do not exist at all).
@@ -50,59 +51,143 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
subxt_core::tx::call_data(call, &self.client.metadata()).map_err(Into::into)
}
/// Creates an unsigned extrinsic without submitting it.
pub fn create_unsigned<Call>(&self, call: &Call) -> Result<SubmittableExtrinsic<T, C>, Error>
/// Creates an unsigned transaction without submitting it. Depending on the metadata, we might end
/// up constructing either a v4 or v5 transaction. See [`Self::create_v4_unsigned`] or
/// [`Self::create_v5_bare`] if you'd like to explicitly create an unsigned transaction of a certain version.
pub fn create_unsigned<Call>(&self, call: &Call) -> Result<SubmittableTransaction<T, C>, Error>
where
Call: Payload,
{
subxt_core::tx::create_unsigned(call, &self.client.metadata())
.map(|tx| SubmittableExtrinsic {
client: self.client.clone(),
inner: tx,
})
.map_err(Into::into)
let metadata = self.client.metadata();
let tx = match subxt_core::tx::suggested_version(&metadata)? {
TransactionVersion::V4 => subxt_core::tx::create_v4_unsigned(call, &metadata),
TransactionVersion::V5 => subxt_core::tx::create_v5_bare(call, &metadata),
}?;
Ok(SubmittableTransaction {
client: self.client.clone(),
inner: tx,
})
}
/// Create a partial extrinsic.
/// Creates a v4 unsigned (no signature or transaction extensions) transaction without submitting it.
///
/// Prefer [`Self::create_unsigned()`] if you don't know which version to create; this will pick the
/// most suitable one for the given chain.
pub fn create_v4_unsigned<Call>(
&self,
call: &Call,
) -> Result<SubmittableTransaction<T, C>, Error>
where
Call: Payload,
{
let metadata = self.client.metadata();
let tx = subxt_core::tx::create_v4_unsigned(call, &metadata)?;
Ok(SubmittableTransaction {
client: self.client.clone(),
inner: tx,
})
}
/// Creates a v5 "bare" (no signature or transaction extensions) transaction without submitting it.
///
/// Prefer [`Self::create_unsigned()`] if you don't know which version to create; this will pick the
/// most suitable one for the given chain.
pub fn create_v5_bare<Call>(&self, call: &Call) -> Result<SubmittableTransaction<T, C>, Error>
where
Call: Payload,
{
let metadata = self.client.metadata();
let tx = subxt_core::tx::create_v5_bare(call, &metadata)?;
Ok(SubmittableTransaction {
client: self.client.clone(),
inner: tx,
})
}
/// Create a partial transaction. Depending on the metadata, we might end up constructing either a v4 or
/// v5 transaction. See [`subxt_core::tx`] if you'd like to manually pick the version to construct
///
/// 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_offline<Call>(
pub fn create_partial_offline<Call>(
&self,
call: &Call,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialExtrinsic<T, C>, Error>
) -> Result<PartialTransaction<T, C>, Error>
where
Call: Payload,
{
subxt_core::tx::create_partial_signed(call, &self.client.client_state(), params)
.map(|tx| PartialExtrinsic {
client: self.client.clone(),
inner: tx,
})
.map_err(Into::into)
let metadata = self.client.metadata();
let tx = match subxt_core::tx::suggested_version(&metadata)? {
TransactionVersion::V4 => PartialTransactionInner::V4(
subxt_core::tx::create_v4_signed(call, &self.client.client_state(), params)?,
),
TransactionVersion::V5 => PartialTransactionInner::V5(
subxt_core::tx::create_v5_general(call, &self.client.client_state(), params)?,
),
};
Ok(PartialTransaction {
client: self.client.clone(),
inner: tx,
})
}
/// Creates a signed extrinsic without submitting it.
/// Create a v4 partial transaction, ready to sign.
///
/// 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_offline<Call, Signer>(
///
/// Prefer [`Self::create_partial_offline()`] if you don't know which version to create; this will pick the
/// most suitable one for the given chain.
pub fn create_v4_partial_offline<Call>(
&self,
call: &Call,
signer: &Signer,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<SubmittableExtrinsic<T, C>, Error>
) -> Result<PartialTransaction<T, C>, Error>
where
Call: Payload,
Signer: SignerT<T>,
{
subxt_core::tx::create_signed(call, &self.client.client_state(), signer, params)
.map(|tx| SubmittableExtrinsic {
client: self.client.clone(),
inner: tx,
})
.map_err(Into::into)
let tx = PartialTransactionInner::V4(subxt_core::tx::create_v4_signed(
call,
&self.client.client_state(),
params,
)?);
Ok(PartialTransaction {
client: self.client.clone(),
inner: tx,
})
}
/// Create a v5 partial transaction, ready to sign.
///
/// 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.
///
/// Prefer [`Self::create_partial_offline()`] if you don't know which version to create; this will pick the
/// most suitable one for the given chain.
pub fn create_v5_partial_offline<Call>(
&self,
call: &Call,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransaction<T, C>, Error>
where
Call: Payload,
{
let tx = PartialTransactionInner::V5(subxt_core::tx::create_v5_general(
call,
&self.client.client_state(),
params,
)?);
Ok(PartialTransaction {
client: self.client.clone(),
inner: tx,
})
}
}
@@ -111,84 +196,85 @@ where
T: Config,
C: OnlineClientT<T>,
{
/// Fetch the latest block header and account nonce from the backend and use them to refine [`ExtrinsicParams::Params`].
async fn refine_params(
&self,
account_id: &T::AccountId,
params: &mut <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<(), Error> {
let block_ref = self.client.backend().latest_finalized_block_ref().await?;
let block_header = self
.client
.backend()
.block_header(block_ref.hash())
.await?
.ok_or_else(|| Error::Block(BlockError::not_found(block_ref.hash())))?;
let account_nonce =
crate::blocks::get_account_nonce(&self.client, account_id, block_ref.hash()).await?;
params.refine(&RefineParamsData::new(
account_nonce,
block_header.number().into(),
block_header.hash(),
));
Ok(())
}
/// Get the account nonce for a given account ID.
pub async fn account_nonce(&self, account_id: &T::AccountId) -> Result<u64, Error> {
let block_ref = self.client.backend().latest_finalized_block_ref().await?;
crate::blocks::get_account_nonce(&self.client, account_id, block_ref.hash()).await
}
/// Creates a partial signed extrinsic, without submitting it.
pub async fn create_partial_signed<Call>(
/// Creates a partial transaction, without submitting it. This can then be signed and submitted.
pub async fn create_partial<Call>(
&self,
call: &Call,
account_id: &T::AccountId,
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialExtrinsic<T, C>, Error>
) -> Result<PartialTransaction<T, C>, Error>
where
Call: Payload,
{
// Refine the params by adding account nonce and latest block information:
self.refine_params(account_id, &mut params).await?;
// Create the partial extrinsic with the refined params:
self.create_partial_signed_offline(call, params)
inject_account_nonce_and_block(&self.client, account_id, &mut params).await?;
self.create_partial_offline(call, params)
}
/// Creates a signed extrinsic, without submitting it.
pub async fn create_signed<Call, Signer>(
/// Creates a partial V4 transaction, without submitting it. This can then be signed and submitted.
///
/// Prefer [`Self::create_partial()`] if you don't know which version to create; this will pick the
/// most suitable one for the given chain.
pub async fn create_v4_partial<Call>(
&self,
call: &Call,
account_id: &T::AccountId,
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransaction<T, C>, Error>
where
Call: Payload,
{
inject_account_nonce_and_block(&self.client, account_id, &mut params).await?;
self.create_v4_partial_offline(call, params)
}
/// Creates a partial V5 transaction, without submitting it. This can then be signed and submitted.
///
/// Prefer [`Self::create_partial()`] if you don't know which version to create; this will pick the
/// most suitable one for the given chain.
pub async fn create_v5_partial<Call>(
&self,
call: &Call,
account_id: &T::AccountId,
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransaction<T, C>, Error>
where
Call: Payload,
{
inject_account_nonce_and_block(&self.client, account_id, &mut params).await?;
self.create_v5_partial_offline(call, params)
}
/// Creates a signed transaction, without submitting it.
pub async fn create_signed<Call, Signer>(
&mut self,
call: &Call,
signer: &Signer,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<SubmittableExtrinsic<T, C>, Error>
) -> Result<SubmittableTransaction<T, C>, Error>
where
Call: Payload,
Signer: SignerT<T>,
{
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
self.validate(call)?;
// 2. Gather the "additional" and "extra" params along with the encoded call data,
// ready to be signed.
let partial_signed = self
.create_partial_signed(call, &signer.account_id(), params)
let mut partial = self
.create_partial(call, &signer.account_id(), params)
.await?;
// 3. Sign and construct an extrinsic from these details.
Ok(partial_signed.sign(signer))
Ok(partial.sign(signer))
}
/// Creates and signs an extrinsic and submits it to the chain. Passes default parameters
/// to construct the "signed extra" and "additional" payloads needed by the extrinsic.
/// Creates and signs an transaction and submits it to the chain. Passes default parameters
/// to construct the "signed extra" and "additional" payloads needed by the transaction.
///
/// Returns a [`TxProgress`], which can be used to track the status of the transaction
/// and obtain details about it, once it has made it into a block.
pub async fn sign_and_submit_then_watch_default<Call, Signer>(
&self,
&mut self,
call: &Call,
signer: &Signer,
) -> Result<TxProgress<T, C>, Error>
@@ -201,12 +287,12 @@ where
.await
}
/// Creates and signs an extrinsic and submits it to the chain.
/// Creates and signs an transaction and submits it to the chain.
///
/// Returns a [`TxProgress`], which can be used to track the status of the transaction
/// and obtain details about it, once it has made it into a block.
pub async fn sign_and_submit_then_watch<Call, Signer>(
&self,
&mut self,
call: &Call,
signer: &Signer,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
@@ -221,18 +307,18 @@ where
.await
}
/// Creates and signs an extrinsic and submits to the chain for block inclusion. Passes
/// Creates and signs an transaction and submits to the chain for block inclusion. Passes
/// default parameters to construct the "signed extra" and "additional" payloads needed
/// by the extrinsic.
/// by the transaction.
///
/// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
/// Returns `Ok` with the transaction hash if it is valid transaction.
///
/// # Note
///
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// Success does not mean the transaction has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn sign_and_submit_default<Call, Signer>(
&self,
&mut self,
call: &Call,
signer: &Signer,
) -> Result<T::Hash, Error>
@@ -244,16 +330,16 @@ where
self.sign_and_submit(call, signer, Default::default()).await
}
/// Creates and signs an extrinsic and submits to the chain for block inclusion.
/// Creates and signs an transaction and submits to the chain for block inclusion.
///
/// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
/// Returns `Ok` with the transaction hash if it is valid transaction.
///
/// # Note
///
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// Success does not mean the transaction has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn sign_and_submit<Call, Signer>(
&self,
&mut self,
call: &Call,
signer: &Signer,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
@@ -269,76 +355,99 @@ where
}
}
/// This payload contains the information needed to produce an extrinsic.
pub struct PartialExtrinsic<T: Config, C> {
/// This payload contains the information needed to produce an transaction.
pub struct PartialTransaction<T: Config, C> {
client: C,
inner: subxt_core::tx::PartialTransaction<T>,
inner: PartialTransactionInner<T>,
}
impl<T, C> PartialExtrinsic<T, C>
enum PartialTransactionInner<T: Config> {
V4(subxt_core::tx::PartialTransactionV4<T>),
V5(subxt_core::tx::PartialTransactionV5<T>),
}
impl<T, C> PartialTransaction<T, C>
where
T: Config,
C: OfflineClientT<T>,
{
/// 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.
/// Return the signer payload for this transaction. These are the bytes that must
/// be signed in order to produce a valid signature for the transaction.
pub fn signer_payload(&self) -> Vec<u8> {
self.inner.signer_payload()
}
/// Return the bytes representing the call data for this partially constructed
/// extrinsic.
pub fn call_data(&self) -> &[u8] {
self.inner.call_data()
}
/// Convert this [`PartialExtrinsic`] into a [`SubmittableExtrinsic`], 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) -> SubmittableExtrinsic<T, C>
where
Signer: SignerT<T>,
{
SubmittableExtrinsic {
client: self.client.clone(),
inner: self.inner.sign(signer),
match &self.inner {
PartialTransactionInner::V4(tx) => tx.signer_payload(),
PartialTransactionInner::V5(tx) => tx.signer_payload().to_vec(),
}
}
/// Convert this [`PartialExtrinsic`] into a [`SubmittableExtrinsic`], ready to submit.
/// Return the bytes representing the call data for this partially constructed
/// transaction.
pub fn call_data(&self) -> &[u8] {
match &self.inner {
PartialTransactionInner::V4(tx) => tx.call_data(),
PartialTransactionInner::V5(tx) => tx.call_data(),
}
}
/// Convert this [`PartialTransaction`] into a [`SubmittableTransaction`], 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>(&mut self, signer: &Signer) -> SubmittableTransaction<T, C>
where
Signer: SignerT<T>,
{
let tx = match &mut self.inner {
PartialTransactionInner::V4(tx) => tx.sign(signer),
PartialTransactionInner::V5(tx) => tx.sign(signer),
};
SubmittableTransaction {
client: self.client.clone(),
inner: tx,
}
}
/// Convert this [`PartialTransaction`] into a [`SubmittableTransaction`], 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
/// [`PartialExtrinsic::sign()`] instead.
pub fn sign_with_address_and_signature(
&self,
address: &T::Address,
/// [`PartialTransaction::sign()`] instead.
pub fn sign_with_account_and_signature(
&mut self,
account_id: &T::AccountId,
signature: &T::Signature,
) -> SubmittableExtrinsic<T, C> {
SubmittableExtrinsic {
) -> SubmittableTransaction<T, C> {
let tx = match &mut self.inner {
PartialTransactionInner::V4(tx) => {
tx.sign_with_account_and_signature(account_id.clone(), signature)
}
PartialTransactionInner::V5(tx) => {
tx.sign_with_account_and_signature(account_id, signature)
}
};
SubmittableTransaction {
client: self.client.clone(),
inner: self
.inner
.sign_with_address_and_signature(address, signature),
inner: tx,
}
}
}
/// This represents an extrinsic that has been signed and is ready to submit.
pub struct SubmittableExtrinsic<T, C> {
/// This represents an transaction that has been signed and is ready to submit.
pub struct SubmittableTransaction<T, C> {
client: C,
inner: subxt_core::tx::Transaction<T>,
}
impl<T, C> SubmittableExtrinsic<T, C>
impl<T, C> SubmittableTransaction<T, C>
where
T: Config,
C: OfflineClientT<T>,
{
/// Create a [`SubmittableExtrinsic`] from some already-signed and prepared
/// extrinsic bytes, and some client (anything implementing [`OfflineClientT`]
/// Create a [`SubmittableTransaction`] from some already-signed and prepared
/// transaction bytes, and some client (anything implementing [`OfflineClientT`]
/// or [`OnlineClientT`]).
///
/// Prefer to use [`TxClient`] to create and sign extrinsics. This is simply
/// Prefer to use [`TxClient`] to create and sign transactions. This is simply
/// exposed in case you want to skip this process and submit something you've
/// already created.
pub fn from_bytes(client: C, tx_bytes: Vec<u8>) -> Self {
@@ -348,34 +457,34 @@ where
}
}
/// Calculate and return the hash of the extrinsic, based on the configured hasher.
/// Calculate and return the hash of the transaction, based on the configured hasher.
pub fn hash(&self) -> T::Hash {
self.inner.hash()
}
/// Returns the SCALE encoded extrinsic bytes.
/// Returns the SCALE encoded transaction bytes.
pub fn encoded(&self) -> &[u8] {
self.inner.encoded()
}
/// Consumes [`SubmittableExtrinsic`] and returns the SCALE encoded
/// extrinsic bytes.
/// Consumes [`SubmittableTransaction`] and returns the SCALE encoded
/// transaction bytes.
pub fn into_encoded(self) -> Vec<u8> {
self.inner.into_encoded()
}
}
impl<T, C> SubmittableExtrinsic<T, C>
impl<T, C> SubmittableTransaction<T, C>
where
T: Config,
C: OnlineClientT<T>,
{
/// Submits the extrinsic to the chain.
/// Submits the transaction to the chain.
///
/// Returns a [`TxProgress`], which can be used to track the status of the transaction
/// and obtain details about it, once it has made it into a block.
pub async fn submit_and_watch(&self) -> Result<TxProgress<T, C>, Error> {
// Get a hash of the extrinsic (we'll need this later).
// Get a hash of the transaction (we'll need this later).
let ext_hash = self.hash();
// Submit and watch for transaction progress.
@@ -388,7 +497,7 @@ where
Ok(TxProgress::new(sub, self.client.clone(), ext_hash))
}
/// Submits the extrinsic to the chain for block inclusion.
/// Submits the transaction to the chain for block inclusion.
///
/// It's usually better to call `submit_and_watch` to get an idea of the progress of the
/// submission and whether it's eventually successful or not. This call does not guarantee
@@ -429,7 +538,7 @@ where
/// Validate a transaction by submitting it to the relevant Runtime API. A transaction that is
/// valid can be added to a block, but may still end up in an error state.
///
/// Returns `Ok` with a [`ValidationResult`], which is the result of attempting to dry run the extrinsic.
/// Returns `Ok` with a [`ValidationResult`], which is the result of attempting to dry run the transaction.
pub async fn validate(&self) -> Result<ValidationResult, Error> {
let latest_block_ref = self.client.backend().latest_finalized_block_ref().await?;
self.validate_at(latest_block_ref).await
@@ -438,7 +547,7 @@ where
/// Validate a transaction by submitting it to the relevant Runtime API. A transaction that is
/// valid can be added to a block, but may still end up in an error state.
///
/// Returns `Ok` with a [`ValidationResult`], which is the result of attempting to dry run the extrinsic.
/// Returns `Ok` with a [`ValidationResult`], which is the result of attempting to dry run the transaction.
pub async fn validate_at(
&self,
at: impl Into<BlockRef<T::Hash>>,
@@ -464,7 +573,7 @@ where
ValidationResult::try_from_bytes(res)
}
/// This returns an estimate for what the extrinsic is expected to cost to execute, less any tips.
/// This returns an estimate for what the transaction is expected to cost to execute, less any tips.
/// The actual amount paid can vary from block to block based on node traffic and other factors.
pub async fn partial_fee_estimate(&self) -> Result<u128, Error> {
let mut params = self.encoded().to_vec();
@@ -486,6 +595,29 @@ where
}
}
/// Fetch the latest block header and account nonce from the backend and use them to refine [`ExtrinsicParams::Params`].
async fn inject_account_nonce_and_block<T: Config, Client: OnlineClientT<T>>(
client: &Client,
account_id: &T::AccountId,
params: &mut <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<(), Error> {
use subxt_core::config::transaction_extensions::Params;
let block_ref = client.backend().latest_finalized_block_ref().await?;
let block_header = client
.backend()
.block_header(block_ref.hash())
.await?
.ok_or_else(|| Error::Block(BlockError::not_found(block_ref.hash())))?;
let account_nonce =
crate::blocks::get_account_nonce(client, account_id, block_ref.hash()).await?;
params.inject_account_nonce(account_nonce);
params.inject_block(block_header.number().into(), block_header.hash());
Ok(())
}
impl ValidationResult {
#[allow(clippy::get_first)]
fn try_from_bytes(bytes: Vec<u8>) -> Result<ValidationResult, crate::Error> {
@@ -512,7 +644,7 @@ impl ValidationResult {
}
}
/// The result of performing [`SubmittableExtrinsic::validate()`].
/// The result of performing [`SubmittableTransaction::validate()`].
#[derive(Clone, Debug, PartialEq)]
pub enum ValidationResult {
/// The transaction is valid
@@ -611,12 +743,12 @@ pub enum TransactionInvalid {
ExhaustsResources,
/// Any other custom invalid validity that is not covered by this enum.
Custom(u8),
/// An extrinsic with a Mandatory dispatch resulted in Error. This is indicative of either a
/// An transaction with a Mandatory dispatch resulted in Error. This is indicative of either a
/// malicious validator or a buggy `provide_inherent`. In any case, it can result in
/// dangerously overweight blocks and therefore if found, invalidates the block.
BadMandatory,
/// An extrinsic with a mandatory dispatch tried to be validated.
/// This is invalid; only inherent extrinsics are allowed to have mandatory dispatches.
/// An transaction with a mandatory dispatch tried to be validated.
/// This is invalid; only inherent transactions are allowed to have mandatory dispatches.
MandatoryValidation,
/// The sending address is disabled or known to be invalid.
BadSigner,
+1
View File
@@ -28,6 +28,7 @@ chainhead-backend = []
[dev-dependencies]
assert_matches = { workspace = true }
codec = { package = "parity-scale-codec", workspace = true, features = ["derive", "bit-vec"] }
frame-decode = { workspace = true }
frame-metadata = { workspace = true }
futures = { workspace = true }
hex = { workspace = true }
@@ -12,7 +12,7 @@ use crate::utils::node_runtime;
#[cfg(fullclient)]
use subxt::{
config::{
signed_extensions::{ChargeAssetTxPayment, CheckMortality, CheckNonce},
transaction_extensions::{ChargeAssetTxPayment, CheckMortality, CheckNonce},
DefaultExtrinsicParamsBuilder, SubstrateConfig,
},
utils::Era,
@@ -262,7 +262,7 @@ async fn fetch_block_and_decode_extrinsic_details() {
#[cfg(fullclient)]
#[subxt_test]
async fn decode_signed_extensions_from_blocks() {
async fn decode_transaction_extensions_from_blocks() {
let ctx = test_context().await;
let api = ctx.client();
let alice = dev::alice();
@@ -301,7 +301,7 @@ async fn decode_signed_extensions_from_blocks() {
}
let transaction1 = submit_transfer_extrinsic_and_get_it_back!(1234);
let extensions1 = transaction1.signed_extensions().unwrap();
let extensions1 = transaction1.transaction_extensions().unwrap();
let nonce1 = extensions1.nonce().unwrap();
let nonce1_static = extensions1.find::<CheckNonce>().unwrap().unwrap();
@@ -313,7 +313,7 @@ async fn decode_signed_extensions_from_blocks() {
.tip();
let transaction2 = submit_transfer_extrinsic_and_get_it_back!(5678);
let extensions2 = transaction2.signed_extensions().unwrap();
let extensions2 = transaction2.transaction_extensions().unwrap();
let nonce2 = extensions2.nonce().unwrap();
let nonce2_static = extensions2.find::<CheckNonce>().unwrap().unwrap();
let tip2 = extensions2.tip().unwrap();
@@ -332,7 +332,7 @@ async fn decode_signed_extensions_from_blocks() {
assert_eq!(tip2, 5678);
assert_eq!(tip2, tip2_static);
let expected_signed_extensions = [
let expected_transaction_extensions = [
"CheckNonZeroSender",
"CheckSpecVersion",
"CheckTxVersion",
@@ -345,13 +345,25 @@ async fn decode_signed_extensions_from_blocks() {
"WeightReclaim",
];
assert_eq!(extensions1.iter().count(), expected_signed_extensions.len());
for (e, expected_name) in extensions1.iter().zip(expected_signed_extensions.iter()) {
assert_eq!(
extensions1.iter().count(),
expected_transaction_extensions.len()
);
for (e, expected_name) in extensions1
.iter()
.zip(expected_transaction_extensions.iter())
{
assert_eq!(e.name(), *expected_name);
}
assert_eq!(extensions2.iter().count(), expected_signed_extensions.len());
for (e, expected_name) in extensions2.iter().zip(expected_signed_extensions.iter()) {
assert_eq!(
extensions2.iter().count(),
expected_transaction_extensions.len()
);
for (e, expected_name) in extensions2
.iter()
.zip(expected_transaction_extensions.iter())
{
assert_eq!(e.name(), *expected_name);
}
@@ -271,8 +271,9 @@ async fn transactionwatch_v1_submit_and_watch() {
let tx_bytes = ctx
.client()
.tx()
.create_signed_offline(&payload, &dev::alice(), Default::default())
.create_partial_offline(&payload, Default::default())
.unwrap()
.sign(&dev::alice())
.into_encoded();
// Test submitting it:
@@ -337,8 +338,9 @@ async fn transaction_v1_broadcast() {
let tx = ctx
.client()
.tx()
.create_signed_offline(&tx_payload, &dev::alice(), Default::default())
.unwrap();
.create_partial_offline(&tx_payload, Default::default())
.unwrap()
.sign(&dev::alice());
let tx_hash = tx.hash();
let tx_bytes = tx.into_encoded();
@@ -407,8 +409,9 @@ async fn transaction_v1_stop() {
let tx_bytes = ctx
.client()
.tx()
.create_signed_offline(&tx, &dev::alice(), Default::default())
.create_partial_offline(&tx, Default::default())
.unwrap()
.sign(&dev::alice())
.into_encoded();
// Submit the transaction.
@@ -190,9 +190,9 @@ async fn external_signing() {
// Create a partial extrinsic. We can get the signer payload at this point, to be
// signed externally.
let tx = node_runtime::tx().preimage().note_preimage(vec![0u8]);
let partial_extrinsic = api
let mut partial_extrinsic = api
.tx()
.create_partial_signed(&tx, &alice.public_key().into(), Default::default())
.create_partial(&tx, &alice.public_key().into(), Default::default())
.await
.unwrap();
@@ -202,7 +202,7 @@ async fn external_signing() {
let signature = alice.sign(&signer_payload);
// Use this to build a signed extrinsic.
let extrinsic = partial_extrinsic
.sign_with_address_and_signature(&alice.public_key().into(), &signature.into());
.sign_with_account_and_signature(&alice.public_key().into(), &signature.into());
// And now submit it.
extrinsic
@@ -1,5 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
mod validation;
@@ -6,6 +6,7 @@ mod blocks;
mod client;
mod codegen;
mod frame;
mod metadata;
mod metadata_validation;
mod runtime_api;
mod storage;
mod transactions;
@@ -0,0 +1,149 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::utils::node_runtime;
use crate::{subxt_test, test_context};
use core::ops::Deref;
use frame_decode::extrinsics::ExtrinsicType;
use subxt_signer::sr25519::dev;
// TODO: When VerifySignature exists on the substrate kitchensink runtime,
// let's try actuallty submitting v4 and v5 signed extrinsics to verify that
// they are actually accepted by the node.
#[subxt_test]
async fn v4_unsigned_encode_decode() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let md = api.metadata();
let call = node_runtime::tx()
.balances()
.transfer_allow_death(dev::bob().public_key().into(), 1000);
let tx_bytes = api.tx().create_v4_unsigned(&call).unwrap().into_encoded();
let tx_bytes_cursor = &mut &*tx_bytes;
let decoded = frame_decode::extrinsics::decode_extrinsic(
tx_bytes_cursor,
md.deref(),
api.metadata().types(),
)
.unwrap();
assert_eq!(tx_bytes_cursor.len(), 0);
assert_eq!(decoded.version(), 4);
assert_eq!(decoded.ty(), ExtrinsicType::Bare);
assert_eq!(decoded.pallet_name(), "Balances");
assert_eq!(decoded.call_name(), "transfer_allow_death");
assert!(decoded.signature_payload().is_none());
Ok(())
}
#[subxt_test]
async fn v5_bare_encode_decode() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let md = api.metadata();
let call = node_runtime::tx()
.balances()
.transfer_allow_death(dev::bob().public_key().into(), 1000);
let tx_bytes = api.tx().create_v5_bare(&call).unwrap().into_encoded();
let tx_bytes_cursor = &mut &*tx_bytes;
let decoded = frame_decode::extrinsics::decode_extrinsic(
tx_bytes_cursor,
md.deref(),
api.metadata().types(),
)
.unwrap();
assert_eq!(tx_bytes_cursor.len(), 0);
assert_eq!(decoded.version(), 5);
assert_eq!(decoded.ty(), ExtrinsicType::Bare);
assert_eq!(decoded.pallet_name(), "Balances");
assert_eq!(decoded.call_name(), "transfer_allow_death");
assert!(decoded.transaction_extension_payload().is_none());
assert!(decoded.signature_payload().is_none());
Ok(())
}
#[subxt_test]
async fn v4_signed_encode_decode() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let md = api.metadata();
let call = node_runtime::tx()
.balances()
.transfer_allow_death(dev::bob().public_key().into(), 1000);
let tx_bytes = api
.tx()
.create_v4_partial(&call, &dev::alice().public_key().into(), Default::default())
.await
.unwrap()
.sign(&dev::alice())
.into_encoded();
let tx_bytes_cursor = &mut &*tx_bytes;
let decoded = frame_decode::extrinsics::decode_extrinsic(
tx_bytes_cursor,
md.deref(),
api.metadata().types(),
)
.unwrap();
assert_eq!(tx_bytes_cursor.len(), 0);
assert_eq!(decoded.version(), 4);
assert_eq!(decoded.ty(), ExtrinsicType::Signed);
assert_eq!(decoded.pallet_name(), "Balances");
assert_eq!(decoded.call_name(), "transfer_allow_death");
assert!(decoded.signature_payload().is_some());
Ok(())
}
#[subxt_test]
async fn v5_general_encode_decode() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let md = api.metadata();
let dummy_signer = dev::alice();
let call = node_runtime::tx()
.balances()
.transfer_allow_death(dev::bob().public_key().into(), 1000);
let tx_bytes = api
.tx()
.create_v5_partial(&call, &dev::alice().public_key().into(), Default::default())
.await
.unwrap()
.sign(&dummy_signer) // No signature payload is added, but may be inserted into tx extensions.
.into_encoded();
let tx_bytes_cursor = &mut &*tx_bytes;
let decoded = frame_decode::extrinsics::decode_extrinsic(
tx_bytes_cursor,
md.deref(),
api.metadata().types(),
)
.unwrap();
assert_eq!(tx_bytes_cursor.len(), 0);
assert_eq!(decoded.version(), 5);
assert_eq!(decoded.ty(), ExtrinsicType::General);
assert_eq!(decoded.pallet_name(), "Balances");
assert_eq!(decoded.call_name(), "transfer_allow_death");
assert!(decoded.transaction_extension_payload().is_some());
// v5 general extrinsics have no signature payload; signature in tx extensions:
assert!(decoded.signature_payload().is_none());
Ok(())
}