mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 22:21:07 +00:00
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:
+20
-16
@@ -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);
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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> {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
+302
-167
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Generated
+45
-16
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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
@@ -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());
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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")
|
||||
};
|
||||
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(¤t_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?;
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+22
-21
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()`].
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()).
|
||||
//!
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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 }
|
||||
|
||||
+21
-9
@@ -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(())
|
||||
}
|
||||
Reference in New Issue
Block a user