mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-28 23:47:56 +00:00
Impl most transaction APIs. TxProgress and Events next
This commit is contained in:
+6
-6
@@ -11,12 +11,12 @@ pub use online_client::{OnlineClient, OnlineClientAtBlock, OnlineClientAtBlockT}
|
||||
|
||||
/// This represents a client at a specific block number.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ClientAtBlock<Client, T> {
|
||||
pub struct ClientAtBlock<T, Client> {
|
||||
client: Client,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<Client, T> ClientAtBlock<Client, T> {
|
||||
impl<T, Client> ClientAtBlock<T, Client> {
|
||||
/// Construct a new client at some block.
|
||||
pub(crate) fn new(client: Client) -> Self {
|
||||
Self {
|
||||
@@ -26,14 +26,14 @@ impl<Client, T> ClientAtBlock<Client, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Client, T> ClientAtBlock<Client, T>
|
||||
impl<T, Client> ClientAtBlock<T, Client>
|
||||
where
|
||||
T: Config,
|
||||
Client: OfflineClientAtBlockT<T>,
|
||||
{
|
||||
/// Construct transactions.
|
||||
pub fn tx(&self) -> Transactions<'_, T, Client> {
|
||||
Transactions::new(&self.client)
|
||||
pub fn tx(&self) -> Transactions<T, Client> {
|
||||
Transactions::new(self.client.clone())
|
||||
}
|
||||
|
||||
/// Obtain a reference to the metadata.
|
||||
@@ -47,7 +47,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<Client, T> ClientAtBlock<Client, T>
|
||||
impl<T, Client> ClientAtBlock<T, Client>
|
||||
where
|
||||
T: Config,
|
||||
Client: OnlineClientAtBlockT<T>,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::client::ClientAtBlock;
|
||||
use crate::config::{Config, HashFor};
|
||||
use crate::config::{Config, HashFor, Hasher};
|
||||
use crate::error::OfflineClientAtBlockError;
|
||||
use std::sync::Arc;
|
||||
use subxt_metadata::Metadata;
|
||||
@@ -21,7 +21,7 @@ impl<T: Config> OfflineClient<T> {
|
||||
pub fn at_block(
|
||||
&self,
|
||||
block_number: impl Into<u64>,
|
||||
) -> Result<ClientAtBlock<OfflineClientAtBlock<T>, T>, OfflineClientAtBlockError> {
|
||||
) -> Result<ClientAtBlock<T, OfflineClientAtBlock<T>>, OfflineClientAtBlockError> {
|
||||
let block_number = block_number.into();
|
||||
let (spec_version, transaction_version) = self
|
||||
.config
|
||||
@@ -35,11 +35,14 @@ impl<T: Config> OfflineClient<T> {
|
||||
|
||||
let genesis_hash = self.config.genesis_hash();
|
||||
|
||||
let hasher = <T::Hasher as Hasher>::new(&metadata);
|
||||
|
||||
let offline_client_at_block = OfflineClientAtBlock {
|
||||
metadata,
|
||||
block_number,
|
||||
genesis_hash,
|
||||
spec_version,
|
||||
hasher,
|
||||
transaction_version,
|
||||
};
|
||||
|
||||
@@ -53,6 +56,7 @@ pub struct OfflineClientAtBlock<T: Config> {
|
||||
block_number: u64,
|
||||
genesis_hash: Option<HashFor<T>>,
|
||||
spec_version: u32,
|
||||
hasher: T::Hasher,
|
||||
transaction_version: u32,
|
||||
}
|
||||
|
||||
@@ -69,6 +73,8 @@ pub trait OfflineClientAtBlockT<T: Config>: Clone {
|
||||
fn genesis_hash(&self) -> Option<HashFor<T>>;
|
||||
/// The spec version at the current block.
|
||||
fn spec_version(&self) -> u32;
|
||||
/// Return a hasher that works at the current block.
|
||||
fn hasher(&self) -> &T::Hasher;
|
||||
/// The transaction version at the current block.
|
||||
///
|
||||
/// Note: This is _not_ the same as the transaction version that
|
||||
@@ -95,4 +101,7 @@ impl<T: Config> OfflineClientAtBlockT<T> for OfflineClientAtBlock<T> {
|
||||
fn transaction_version(&self) -> u32 {
|
||||
self.transaction_version
|
||||
}
|
||||
fn hasher(&self) -> &T::Hasher {
|
||||
&self.hasher
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
/// This does not track new blocks.
|
||||
pub async fn at_current_block(
|
||||
&self,
|
||||
) -> Result<ClientAtBlock<OnlineClientAtBlock<T>, T>, OnlineClientAtBlockError> {
|
||||
) -> Result<ClientAtBlock<T, OnlineClientAtBlock<T>>, OnlineClientAtBlockError> {
|
||||
let latest_block = self
|
||||
.inner
|
||||
.backend
|
||||
@@ -225,7 +225,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
pub async fn at_block(
|
||||
&self,
|
||||
number_or_hash: impl Into<BlockNumberOrRef<T>>,
|
||||
) -> Result<ClientAtBlock<OnlineClientAtBlock<T>, T>, OnlineClientAtBlockError> {
|
||||
) -> Result<ClientAtBlock<T, OnlineClientAtBlock<T>>, OnlineClientAtBlockError> {
|
||||
let number_or_hash = number_or_hash.into();
|
||||
|
||||
// We are given either a block hash or number. We need both.
|
||||
@@ -274,7 +274,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
&self,
|
||||
block_ref: impl Into<BlockRef<HashFor<T>>>,
|
||||
block_number: u64,
|
||||
) -> Result<ClientAtBlock<OnlineClientAtBlock<T>, T>, OnlineClientAtBlockError> {
|
||||
) -> Result<ClientAtBlock<T, OnlineClientAtBlock<T>>, OnlineClientAtBlockError> {
|
||||
let block_ref = block_ref.into();
|
||||
let block_hash = block_ref.hash();
|
||||
|
||||
@@ -452,8 +452,6 @@ pub trait OnlineClientAtBlockT<T: Config>: OfflineClientAtBlockT<T> {
|
||||
fn backend(&self) -> &dyn Backend<T>;
|
||||
/// Return the block hash for the current block.
|
||||
fn block_hash(&self) -> HashFor<T>;
|
||||
/// Return a hasher that works at the current block.
|
||||
fn hasher(&self) -> &T::Hasher;
|
||||
}
|
||||
|
||||
/// The inner type providing the necessary data to work online at a specific block.
|
||||
@@ -476,9 +474,6 @@ impl<T: Config> OnlineClientAtBlockT<T> for OnlineClientAtBlock<T> {
|
||||
fn block_hash(&self) -> HashFor<T> {
|
||||
self.block_ref.hash()
|
||||
}
|
||||
fn hasher(&self) -> &T::Hasher {
|
||||
&self.hasher
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> OfflineClientAtBlockT<T> for OnlineClientAtBlock<T> {
|
||||
@@ -500,6 +495,9 @@ impl<T: Config> OfflineClientAtBlockT<T> for OnlineClientAtBlock<T> {
|
||||
fn transaction_version(&self) -> u32 {
|
||||
self.transaction_version
|
||||
}
|
||||
fn hasher(&self) -> &T::Hasher {
|
||||
&self.hasher
|
||||
}
|
||||
}
|
||||
|
||||
fn get_legacy_types<'a, T: Config, Md: ToTypeRegistry>(
|
||||
|
||||
@@ -70,7 +70,7 @@ impl<T: Config> Block<T> {
|
||||
/// Instantiate a client at this block.
|
||||
pub async fn client(
|
||||
&self,
|
||||
) -> Result<ClientAtBlock<OnlineClientAtBlock<T>, T>, OnlineClientAtBlockError> {
|
||||
) -> Result<ClientAtBlock<T, OnlineClientAtBlock<T>>, OnlineClientAtBlockError> {
|
||||
self.client.at_block(self.block_ref.clone()).await
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
//! Polkadot node.
|
||||
|
||||
mod default_extrinsic_params;
|
||||
mod extrinsic_params;
|
||||
|
||||
pub mod extrinsic_params;
|
||||
pub mod polkadot;
|
||||
pub mod substrate;
|
||||
pub mod transaction_extensions;
|
||||
@@ -40,7 +40,7 @@ pub trait Config: Clone + Debug + Sized + Send + Sync + 'static {
|
||||
type AccountId: Debug + Clone + Encode + Decode + Serialize + Send;
|
||||
|
||||
/// The address type; required for constructing extrinsics.
|
||||
type Address: Debug + Encode + From<<Self as Config>::AccountId>;
|
||||
type Address: Debug + Encode + From<Self::AccountId>;
|
||||
|
||||
/// The signature type.
|
||||
type Signature: Debug + Clone + Encode + Decode + Send;
|
||||
|
||||
@@ -582,6 +582,8 @@ pub enum ExtrinsicError {
|
||||
},
|
||||
#[error("Can't encode the extrinsic call data: {0}")]
|
||||
CannotEncodeCallData(scale_encode::Error),
|
||||
#[error("Failed to encode an extrinsic: the genesis hash was not provided")]
|
||||
GenesisHashNotProvided,
|
||||
#[error("Subxt does not support the extrinsic versions expected by the chain")]
|
||||
UnsupportedVersion,
|
||||
#[error("Cannot construct the required transaction extensions: {0}")]
|
||||
|
||||
+612
-91
@@ -1,24 +1,38 @@
|
||||
mod account_nonce;
|
||||
mod default_params;
|
||||
mod payload;
|
||||
mod signer;
|
||||
mod validation_result;
|
||||
|
||||
use crate::backend::BackendExt;
|
||||
use crate::client::{OfflineClientAtBlockT, OnlineClientAtBlockT};
|
||||
use crate::config::{ClientState, Config};
|
||||
use crate::config::extrinsic_params::Params;
|
||||
use crate::config::{
|
||||
ClientState, Config, ExtrinsicParams, ExtrinsicParamsEncoder, HashFor, Hasher, Header,
|
||||
};
|
||||
use crate::error::ExtrinsicError;
|
||||
use codec::Compact;
|
||||
use codec::{Compact, Encode};
|
||||
use core::marker::PhantomData;
|
||||
use futures::{TryFutureExt, future::try_join};
|
||||
use sp_crypto_hashing::blake2_256;
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub use default_params::DefaultParams;
|
||||
pub use payload::Payload;
|
||||
pub use signer::Signer;
|
||||
pub use validation_result::{
|
||||
TransactionInvalid, TransactionUnknown, TransactionValid, ValidationResult,
|
||||
};
|
||||
|
||||
/// A client for working with transactions.
|
||||
#[derive(Clone)]
|
||||
pub struct Transactions<'atblock, T, Client> {
|
||||
client: &'atblock Client,
|
||||
pub struct Transactions<T, Client> {
|
||||
client: Client,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'atblock, T, Client> Transactions<'atblock, T, Client> {
|
||||
pub(crate) fn new(client: &'atblock Client) -> Self {
|
||||
impl<T, Client> Transactions<T, Client> {
|
||||
pub(crate) fn new(client: Client) -> Self {
|
||||
Transactions {
|
||||
client,
|
||||
marker: PhantomData,
|
||||
@@ -26,7 +40,7 @@ impl<'atblock, T, Client> Transactions<'atblock, T, Client> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'atblock, T: Config, C: OfflineClientAtBlockT<T>> Transactions<'atblock, T, C> {
|
||||
impl<T: Config, Client: OfflineClientAtBlockT<T>> Transactions<T, Client> {
|
||||
/// 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
|
||||
@@ -73,75 +87,62 @@ impl<'atblock, T: Config, C: OfflineClientAtBlockT<T>> Transactions<'atblock, T,
|
||||
|
||||
/// 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.
|
||||
/// [`Self::create_v5_unsigned`] 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>, ExtrinsicError>
|
||||
) -> Result<SubmittableTransaction<T, Client>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
let tx = match self.default_transaction_version()? {
|
||||
TransactionVersion::V4 => self.create_v4_unsigned(call),
|
||||
TransactionVersion::V5 => self.create_v5_bare(call),
|
||||
SupportedTransactionVersion::V4 => self.create_v4_unsigned(call),
|
||||
SupportedTransactionVersion::V5 => self.create_v5_unsigned(call),
|
||||
}?;
|
||||
|
||||
Ok(SubmittableTransaction {
|
||||
client: self.client.clone(),
|
||||
inner: tx,
|
||||
})
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
/// Creates a V4 "unsigned" transaction without submitting it.
|
||||
pub fn create_v4_unsigned<Call>(&self, call: &Call) -> Result<Transaction<T>, ExtrinsicError>
|
||||
pub fn create_v4_unsigned<Call>(
|
||||
&self,
|
||||
call: &Call,
|
||||
) -> Result<SubmittableTransaction<T, Client>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
self.create_unsigned_at_version(call, 4)
|
||||
self.create_unsigned_at_version(call, SupportedTransactionVersion::V4)
|
||||
}
|
||||
|
||||
/// Creates a V5 "bare" transaction without submitting it.
|
||||
pub fn create_v5_bare<Call>(&self, call: &Call) -> Result<Transaction<T>, ExtrinsicError>
|
||||
pub fn create_v5_unsigned<Call>(
|
||||
&self,
|
||||
call: &Call,
|
||||
) -> Result<SubmittableTransaction<T, Client>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
self.create_unsigned_at_version(call, 5)
|
||||
self.create_unsigned_at_version(call, SupportedTransactionVersion::V5)
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// Create a signable transaction. Depending on the metadata, we might end up constructing either a v4 or
|
||||
/// v5 transaction. Use [`Self::create_v4_signable_offline`] or [`Self::create_v5_signable_offline`] if you'd
|
||||
/// like to manually use a specific version.
|
||||
///
|
||||
/// 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_offline<Call>(
|
||||
pub fn create_signable_offline<Call>(
|
||||
&self,
|
||||
call: &Call,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
|
||||
) -> Result<SignableTransaction<T, Client>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
let metadata = self.client.metadata();
|
||||
let client_state = ClientState {
|
||||
genesis_hash: self.client.genesis_hash(),
|
||||
spec_version: self.client.spec_version(),
|
||||
transaction_version: self.client.transaction_version(),
|
||||
metadata: self.client.metadata(),
|
||||
};
|
||||
|
||||
let tx = match self.default_transaction_version()? {
|
||||
TransactionVersion::V4 => {
|
||||
PartialTransactionInner::V4(self.create_v4_signed(call, &client_state, params)?)
|
||||
}
|
||||
TransactionVersion::V5 => {
|
||||
PartialTransactionInner::V5(self.create_v5_general(call, &client_state, params)?)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(PartialTransaction {
|
||||
client: self.client.clone(),
|
||||
inner: tx,
|
||||
})
|
||||
match self.default_transaction_version()? {
|
||||
SupportedTransactionVersion::V4 => self.create_v4_signable_offline(call, params),
|
||||
SupportedTransactionVersion::V5 => self.create_v5_signable_offline(call, params),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a v4 partial transaction, ready to sign.
|
||||
@@ -149,26 +150,17 @@ impl<'atblock, T: Config, C: OfflineClientAtBlockT<T>> Transactions<'atblock, T,
|
||||
/// 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
|
||||
/// Prefer [`Self::create_signable_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>(
|
||||
pub fn create_v4_signable_offline<Call>(
|
||||
&self,
|
||||
call: &Call,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
|
||||
) -> Result<SignableTransaction<T, Client>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
let tx = PartialTransactionInner::V4(subxt_core::tx::create_v4_signed(
|
||||
call,
|
||||
&self.client.client_state(),
|
||||
params,
|
||||
)?);
|
||||
|
||||
Ok(PartialTransaction {
|
||||
client: self.client.clone(),
|
||||
inner: tx,
|
||||
})
|
||||
self.create_signable_at_version(call, params, SupportedTransactionVersion::V4)
|
||||
}
|
||||
|
||||
/// Create a v5 partial transaction, ready to sign.
|
||||
@@ -176,34 +168,45 @@ impl<'atblock, T: Config, C: OfflineClientAtBlockT<T>> Transactions<'atblock, T,
|
||||
/// 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
|
||||
/// Prefer [`Self::create_signable_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>(
|
||||
pub fn create_v5_signable_offline<Call>(
|
||||
&self,
|
||||
call: &Call,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
|
||||
) -> Result<SignableTransaction<T, Client>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
let tx = PartialTransactionInner::V5(subxt_core::tx::create_v5_general(
|
||||
call,
|
||||
&self.client.client_state(),
|
||||
params,
|
||||
)?);
|
||||
self.create_signable_at_version(call, params, SupportedTransactionVersion::V5)
|
||||
}
|
||||
|
||||
Ok(PartialTransaction {
|
||||
client: self.client.clone(),
|
||||
inner: tx,
|
||||
})
|
||||
/// 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.
|
||||
///
|
||||
/// When using methods like [`Self::create_signable_offline`] and [`Self::create_unsigned`],
|
||||
/// this will be used internally to decide which transaction version to construct.
|
||||
pub fn default_transaction_version(
|
||||
&self,
|
||||
) -> Result<SupportedTransactionVersion, ExtrinsicError> {
|
||||
let metadata = self.client.metadata_ref();
|
||||
let versions = metadata.extrinsic().supported_versions();
|
||||
|
||||
if versions.contains(&4) {
|
||||
Ok(SupportedTransactionVersion::V4)
|
||||
} else if versions.contains(&5) {
|
||||
Ok(SupportedTransactionVersion::V5)
|
||||
} else {
|
||||
Err(ExtrinsicError::UnsupportedVersion)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a V4 "unsigned" transaction or V5 "bare" transaction.
|
||||
fn create_unsigned_at_version<Call: Payload>(
|
||||
&self,
|
||||
call: &Call,
|
||||
tx_version: u8,
|
||||
) -> Result<Transaction<T>, ExtrinsicError> {
|
||||
tx_version: SupportedTransactionVersion,
|
||||
) -> Result<SubmittableTransaction<T, Client>, ExtrinsicError> {
|
||||
let metadata = self.client.metadata_ref();
|
||||
|
||||
// 1. Validate this call against the current node metadata if the call comes
|
||||
@@ -214,7 +217,7 @@ impl<'atblock, T: Config, C: OfflineClientAtBlockT<T>> Transactions<'atblock, T,
|
||||
let extrinsic = {
|
||||
let mut encoded_inner = Vec::new();
|
||||
// encode the transaction version first.
|
||||
tx_version.encode_to(&mut encoded_inner);
|
||||
(tx_version as u8).encode_to(&mut encoded_inner);
|
||||
// encode call data after this byte.
|
||||
call.encode_call_data_to(metadata, &mut encoded_inner)?;
|
||||
// now, prefix byte length:
|
||||
@@ -228,33 +231,551 @@ impl<'atblock, T: Config, C: OfflineClientAtBlockT<T>> Transactions<'atblock, T,
|
||||
};
|
||||
|
||||
// Wrap in Encoded to ensure that any more "encode" calls leave it in the right state.
|
||||
Ok(Transaction::from_bytes(extrinsic))
|
||||
Ok(SubmittableTransaction {
|
||||
client: self.client.clone(),
|
||||
encoded: extrinsic,
|
||||
marker: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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 default_transaction_version(&self) -> Result<TransactionVersion, ExtrinsicError> {
|
||||
let metadata = self.client.metadata_ref();
|
||||
let versions = metadata.extrinsic().supported_versions();
|
||||
// Create a V4 "signed" or a V5 "general" transaction.
|
||||
pub fn create_signable_at_version<Call>(
|
||||
&self,
|
||||
call: &Call,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
tx_version: SupportedTransactionVersion,
|
||||
) -> Result<SignableTransaction<T, Client>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
// 1. Validate this call against the current node metadata if the call comes
|
||||
// with a hash allowing us to do so.
|
||||
self.validate(call)?;
|
||||
|
||||
if versions.contains(&4) {
|
||||
Ok(TransactionVersion::V4)
|
||||
} else if versions.contains(&5) {
|
||||
Ok(TransactionVersion::V5)
|
||||
} else {
|
||||
Err(ExtrinsicError::UnsupportedVersion)
|
||||
}
|
||||
// 2. Work out which TX extension version to target based on metadata.
|
||||
let tx_extension_version = match tx_version {
|
||||
SupportedTransactionVersion::V4 => None,
|
||||
SupportedTransactionVersion::V5 => {
|
||||
let v = self
|
||||
.client
|
||||
.metadata_ref()
|
||||
.extrinsic()
|
||||
.transaction_extension_version_to_use_for_encoding();
|
||||
Some(v)
|
||||
}
|
||||
};
|
||||
|
||||
// 3. SCALE encode call data to bytes (pallet u8, call u8, call params).
|
||||
let call_data = self.call_data(call)?;
|
||||
|
||||
// 4. Construct our custom additional/extra params.
|
||||
let client_state = ClientState {
|
||||
genesis_hash: self
|
||||
.client
|
||||
.genesis_hash()
|
||||
.ok_or(ExtrinsicError::GenesisHashNotProvided)?,
|
||||
spec_version: self.client.spec_version(),
|
||||
transaction_version: self.client.transaction_version(),
|
||||
metadata: self.client.metadata(),
|
||||
};
|
||||
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(SignableTransaction {
|
||||
client: self.client.clone(),
|
||||
call_data,
|
||||
additional_and_extra_params,
|
||||
tx_extension_version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config, Client: OnlineClientAtBlockT<T>> Transactions<T, Client> {
|
||||
/// Get the account nonce for a given account ID.
|
||||
pub async fn account_nonce(&self, account_id: &T::AccountId) -> Result<u64, ExtrinsicError> {
|
||||
account_nonce::get_account_nonce(&self.client, account_id)
|
||||
.await
|
||||
.map_err(|e| ExtrinsicError::AccountNonceError {
|
||||
block_hash: self.client.block_hash().into(),
|
||||
account_id: account_id.clone().encode().into(),
|
||||
reason: e,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a signable transaction. This can then be signed and submitted.
|
||||
pub async fn create_signable<Call>(
|
||||
&self,
|
||||
call: &Call,
|
||||
account_id: &T::AccountId,
|
||||
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<SignableTransaction<T, Client>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
self.inject_account_nonce_and_block(account_id, &mut params)
|
||||
.await?;
|
||||
self.create_signable_offline(call, params)
|
||||
}
|
||||
|
||||
/// Creates a signable V4 transaction, without submitting it. This can then be signed and submitted.
|
||||
///
|
||||
/// Prefer [`Self::create_signable()`] 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_signable<Call>(
|
||||
&self,
|
||||
call: &Call,
|
||||
account_id: &T::AccountId,
|
||||
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<SignableTransaction<T, Client>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
self.inject_account_nonce_and_block(account_id, &mut params)
|
||||
.await?;
|
||||
self.create_v4_signable_offline(call, params)
|
||||
}
|
||||
|
||||
/// Creates a signable V5 transaction, without submitting it. This can then be signed and submitted.
|
||||
///
|
||||
/// Prefer [`Self::create_signable()`] 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_signable<Call>(
|
||||
&self,
|
||||
call: &Call,
|
||||
account_id: &T::AccountId,
|
||||
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<SignableTransaction<T, Client>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
self.inject_account_nonce_and_block(account_id, &mut params)
|
||||
.await?;
|
||||
self.create_v5_signable_offline(call, params)
|
||||
}
|
||||
|
||||
/// Creates a signed transaction, without submitting it.
|
||||
pub async fn create_signed<Call, S>(
|
||||
&mut self,
|
||||
call: &Call,
|
||||
signer: &S,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<SubmittableTransaction<T, Client>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
S: Signer<T>,
|
||||
{
|
||||
let mut signable = self
|
||||
.create_signable(call, &signer.account_id(), params)
|
||||
.await?;
|
||||
|
||||
Ok(signable.sign(signer))
|
||||
}
|
||||
|
||||
/// 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 [`TransactionProgress`], 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, S>(
|
||||
&mut self,
|
||||
call: &Call,
|
||||
signer: &S,
|
||||
) -> Result<TransactionProgress<T, Client>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
S: Signer<T>,
|
||||
<T::ExtrinsicParams as ExtrinsicParams<T>>::Params: DefaultParams,
|
||||
{
|
||||
self.sign_and_submit_then_watch(call, signer, DefaultParams::default_params())
|
||||
.await
|
||||
}
|
||||
|
||||
/// Creates and signs an transaction and submits it to the chain.
|
||||
///
|
||||
/// Returns a [`TransactionProgress`], 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, S>(
|
||||
&mut self,
|
||||
call: &Call,
|
||||
signer: &S,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<TransactionProgress<T, Client>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
S: Signer<T>,
|
||||
{
|
||||
self.create_signed(call, signer, params)
|
||||
.await?
|
||||
.submit_and_watch()
|
||||
.await
|
||||
}
|
||||
|
||||
/// 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 transaction.
|
||||
///
|
||||
/// Returns `Ok` with the transaction hash if it is valid transaction.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// 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, S>(
|
||||
&mut self,
|
||||
call: &Call,
|
||||
signer: &S,
|
||||
) -> Result<HashFor<T>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
S: Signer<T>,
|
||||
<T::ExtrinsicParams as ExtrinsicParams<T>>::Params: DefaultParams,
|
||||
{
|
||||
self.sign_and_submit(call, signer, DefaultParams::default_params())
|
||||
.await
|
||||
}
|
||||
|
||||
/// Creates and signs an transaction and submits to the chain for block inclusion.
|
||||
///
|
||||
/// Returns `Ok` with the transaction hash if it is valid transaction.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// 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, S>(
|
||||
&mut self,
|
||||
call: &Call,
|
||||
signer: &S,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<HashFor<T>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
S: Signer<T>,
|
||||
{
|
||||
self.create_signed(call, signer, params)
|
||||
.await?
|
||||
.submit()
|
||||
.await
|
||||
}
|
||||
|
||||
/// 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(
|
||||
&self,
|
||||
account_id: &T::AccountId,
|
||||
params: &mut <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<(), ExtrinsicError> {
|
||||
let block_ref = self
|
||||
.client
|
||||
.backend()
|
||||
.latest_finalized_block_ref()
|
||||
.await
|
||||
.map_err(ExtrinsicError::CannotGetLatestFinalizedBlock)?;
|
||||
|
||||
let (block_header, account_nonce) = try_join(
|
||||
self.client
|
||||
.backend()
|
||||
.block_header(block_ref.hash())
|
||||
.map_err(ExtrinsicError::CannotGetLatestFinalizedBlock),
|
||||
self.account_nonce(account_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let block_header = block_header.ok_or_else(|| ExtrinsicError::CannotFindBlockHeader {
|
||||
block_hash: block_ref.hash().into(),
|
||||
})?;
|
||||
|
||||
params.inject_account_nonce(account_nonce);
|
||||
params.inject_block(block_header.number(), block_ref.hash());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The transaction versions supported by Subxt.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub enum TransactionVersion {
|
||||
#[repr(u8)]
|
||||
pub enum SupportedTransactionVersion {
|
||||
/// v4 transactions (signed and unsigned transactions)
|
||||
V4,
|
||||
V4 = 4u8,
|
||||
/// v5 transactions (bare and general transactions)
|
||||
V5,
|
||||
V5 = 5u8,
|
||||
}
|
||||
|
||||
/// This is a transaction that requires signing before it can be submitted.
|
||||
pub struct SignableTransaction<T: Config, Client> {
|
||||
client: Client,
|
||||
call_data: Vec<u8>,
|
||||
additional_and_extra_params: <T as Config>::ExtrinsicParams,
|
||||
// For V4 transactions this doesn't exist, and for V5 it does.
|
||||
tx_extension_version: Option<u8>,
|
||||
}
|
||||
|
||||
impl<T: Config, Client: OfflineClientAtBlockT<T>> SignableTransaction<T, Client> {
|
||||
/// Return the bytes representing the call data for this partially constructed
|
||||
/// transaction.
|
||||
pub fn call_data(&self) -> &[u8] {
|
||||
&self.call_data
|
||||
}
|
||||
|
||||
/// 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.with_signer_payload(|bytes| bytes.to_vec())
|
||||
}
|
||||
|
||||
/// Convert this [`SignableTransaction`] 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<S: Signer<T>>(&mut self, signer: &S) -> SubmittableTransaction<T, Client> {
|
||||
// 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 [`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
|
||||
/// [`PartialTransaction::sign()`] instead.
|
||||
pub fn sign_with_account_and_signature(
|
||||
&mut self,
|
||||
account_id: &T::AccountId,
|
||||
signature: &T::Signature,
|
||||
) -> SubmittableTransaction<T, Client> {
|
||||
let encoded = if let Some(tx_extensions_version) = self.tx_extension_version {
|
||||
let mut encoded_inner = Vec::new();
|
||||
// Pass account and signature to extensions to be added.
|
||||
self.additional_and_extra_params
|
||||
.inject_signature(account_id, signature);
|
||||
// "is general" + transaction protocol version (5)
|
||||
(0b01000000 + 5u8).encode_to(&mut encoded_inner);
|
||||
// Encode versions for the transaction extensions
|
||||
(tx_extensions_version as u8).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
|
||||
} else {
|
||||
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.clone().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_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
|
||||
};
|
||||
|
||||
SubmittableTransaction {
|
||||
client: self.client.clone(),
|
||||
encoded,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// For V5 transactions we _always_ blake2 hash. For V4 we only
|
||||
// hash if more than 256 bytes in the payload.
|
||||
if self.is_v5() || bytes.len() > 256 {
|
||||
f(Cow::Borrowed(&blake2_256(&bytes)))
|
||||
} else {
|
||||
f(Cow::Owned(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
// Are we working with a V5 transaction? This is handled a bit differently.
|
||||
fn is_v5(&self) -> bool {
|
||||
self.tx_extension_version.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a transaction that is ready to submit.
|
||||
pub struct SubmittableTransaction<T, Client> {
|
||||
client: Client,
|
||||
encoded: Vec<u8>,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, Client> SubmittableTransaction<T, Client>
|
||||
where
|
||||
T: Config,
|
||||
Client: OfflineClientAtBlockT<T>,
|
||||
{
|
||||
/// Create a [`SubmittableTransaction`] from some already-signed and prepared
|
||||
/// transaction bytes, and some client (anything implementing [`OfflineClientAtBlockT`]
|
||||
/// or [`OnlineClientAtBlockT`]).
|
||||
pub fn from_bytes(client: Client, tx_bytes: Vec<u8>) -> Self {
|
||||
Self {
|
||||
client,
|
||||
encoded: tx_bytes,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate and return the hash of the transaction, based on the configured hasher.
|
||||
pub fn hash(&self) -> HashFor<T> {
|
||||
self.client.hasher().hash(&self.encoded)
|
||||
}
|
||||
|
||||
/// Returns the SCALE encoded transaction bytes.
|
||||
pub fn encoded(&self) -> &[u8] {
|
||||
&self.encoded
|
||||
}
|
||||
|
||||
/// Consumes [`SubmittableTransaction`] and returns the SCALE encoded
|
||||
/// transaction bytes.
|
||||
pub fn into_encoded(self) -> Vec<u8> {
|
||||
self.encoded.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config, Client: OnlineClientAtBlockT<T>> SubmittableTransaction<T, Client> {
|
||||
/// Submits the transaction to the chain.
|
||||
///
|
||||
/// Returns a [`TransactionProgress`], 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<TransactionProgress<T, Client>, ExtrinsicError> {
|
||||
// Get a hash of the transaction (we'll need this later).
|
||||
let ext_hash = self.hash();
|
||||
|
||||
// Submit and watch for transaction progress.
|
||||
let sub = self
|
||||
.client
|
||||
.backend()
|
||||
.submit_transaction(self.encoded())
|
||||
.await
|
||||
.map_err(ExtrinsicError::ErrorSubmittingTransaction)?;
|
||||
|
||||
Ok(TransactionProgress::new(sub, self.client.clone(), ext_hash))
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// success, and is just sending the transaction to the chain.
|
||||
pub async fn submit(&self) -> Result<HashFor<T>, ExtrinsicError> {
|
||||
let ext_hash = self.hash();
|
||||
let mut sub = self
|
||||
.client
|
||||
.backend()
|
||||
.submit_transaction(self.encoded())
|
||||
.await
|
||||
.map_err(ExtrinsicError::ErrorSubmittingTransaction)?;
|
||||
|
||||
// If we get a bad status or error back straight away then error, else return the hash.
|
||||
match sub.next().await {
|
||||
Some(Ok(status)) => match status {
|
||||
TransactionStatus::Validated
|
||||
| TransactionStatus::Broadcasted
|
||||
| TransactionStatus::InBestBlock { .. }
|
||||
| TransactionStatus::NoLongerInBestBlock
|
||||
| TransactionStatus::InFinalizedBlock { .. } => Ok(ext_hash),
|
||||
TransactionStatus::Error { message } => Err(
|
||||
ExtrinsicError::TransactionStatusError(TransactionStatusError::Error(message)),
|
||||
),
|
||||
TransactionStatus::Invalid { message } => {
|
||||
Err(ExtrinsicError::TransactionStatusError(
|
||||
TransactionStatusError::Invalid(message),
|
||||
))
|
||||
}
|
||||
TransactionStatus::Dropped { message } => {
|
||||
Err(ExtrinsicError::TransactionStatusError(
|
||||
TransactionStatusError::Dropped(message),
|
||||
))
|
||||
}
|
||||
},
|
||||
Some(Err(e)) => Err(ExtrinsicError::TransactionStatusStreamError(e)),
|
||||
None => Err(ExtrinsicError::UnexpectedEndOfTransactionStatusStream),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 transaction.
|
||||
pub async fn validate(&self) -> Result<ValidationResult, ExtrinsicError> {
|
||||
let block_hash = self.client.block_hash();
|
||||
|
||||
// Approach taken from https://github.com/paritytech/json-rpc-interface-spec/issues/55.
|
||||
let mut params = Vec::with_capacity(8 + self.encoded().len() + 8);
|
||||
2u8.encode_to(&mut params);
|
||||
params.extend(self.encoded().iter());
|
||||
block_hash.encode_to(&mut params);
|
||||
|
||||
let res: Vec<u8> = self
|
||||
.client
|
||||
.backend()
|
||||
.call(
|
||||
"TaggedTransactionQueue_validate_transaction",
|
||||
Some(¶ms),
|
||||
block_hash,
|
||||
)
|
||||
.await
|
||||
.map_err(ExtrinsicError::CannotGetValidationInfo)?;
|
||||
|
||||
ValidationResult::try_from_bytes(res)
|
||||
}
|
||||
|
||||
/// 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, ExtrinsicError> {
|
||||
let mut params = self.encoded().to_vec();
|
||||
(self.encoded().len() as u32).encode_to(&mut params);
|
||||
let latest_block_ref = self
|
||||
.client
|
||||
.backend()
|
||||
.latest_finalized_block_ref()
|
||||
.await
|
||||
.map_err(ExtrinsicError::CannotGetLatestFinalizedBlock)?;
|
||||
|
||||
// destructuring RuntimeDispatchInfo, see type information <https://paritytech.github.io/substrate/master/pallet_transaction_payment_rpc_runtime_api/struct.RuntimeDispatchInfo.html>
|
||||
// data layout: {weight_ref_time: Compact<u64>, weight_proof_size: Compact<u64>, class: u8, partial_fee: u128}
|
||||
let (_, _, _, partial_fee) = self
|
||||
.client
|
||||
.backend()
|
||||
.call_decoding::<(Compact<u64>, Compact<u64>, u8, u128)>(
|
||||
"TransactionPaymentApi_query_info",
|
||||
Some(¶ms),
|
||||
latest_block_ref.hash(),
|
||||
)
|
||||
.await
|
||||
.map_err(ExtrinsicError::CannotGetFeeInfo)?;
|
||||
|
||||
Ok(partial_fee)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
use crate::client::OnlineClientAtBlockT;
|
||||
use crate::config::Config;
|
||||
use crate::error::AccountNonceError;
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
/// Return the account nonce at some block hash for an account ID.
|
||||
pub async fn get_account_nonce<T, C>(
|
||||
client: &C,
|
||||
account_id: &T::AccountId,
|
||||
) -> Result<u64, AccountNonceError>
|
||||
where
|
||||
T: Config,
|
||||
C: OnlineClientAtBlockT<T>,
|
||||
{
|
||||
let block_hash = client.block_hash();
|
||||
let account_nonce_bytes = client
|
||||
.backend()
|
||||
.call(
|
||||
"AccountNonceApi_account_nonce",
|
||||
Some(&account_id.encode()),
|
||||
block_hash,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// custom decoding from a u16/u32/u64 into a u64, based on the number of bytes we got back.
|
||||
let cursor = &mut &account_nonce_bytes[..];
|
||||
let account_nonce: u64 = match account_nonce_bytes.len() {
|
||||
2 => u16::decode(cursor)?.into(),
|
||||
4 => u32::decode(cursor)?.into(),
|
||||
8 => u64::decode(cursor)?,
|
||||
_ => {
|
||||
return Err(AccountNonceError::WrongNumberOfBytes(
|
||||
account_nonce_bytes.len(),
|
||||
));
|
||||
}
|
||||
};
|
||||
Ok(account_nonce)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/// This trait is used to create default values for extrinsic params. We use this instead of
|
||||
/// [`Default`] because we want to be able to support params which are tuples of more than 12
|
||||
/// entries (which is the maximum tuple size Rust currently implements [`Default`] for on tuples),
|
||||
/// given that we aren't far off having more than 12 transaction extensions already.
|
||||
///
|
||||
/// If you have params which are _not_ a tuple and which you'd like to be instantiated automatically
|
||||
/// when calling [`TxClient::sign_and_submit_default()`] or [`TxClient::sign_and_submit_then_watch_default()`],
|
||||
/// then you'll need to implement this trait for them.
|
||||
pub trait DefaultParams: Sized {
|
||||
/// Instantiate a default instance of the parameters.
|
||||
fn default_params() -> Self;
|
||||
}
|
||||
|
||||
impl<const N: usize, P: Default> DefaultParams for [P; N] {
|
||||
fn default_params() -> Self {
|
||||
core::array::from_fn(|_| P::default())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_default_params_for_tuple {
|
||||
($($ident:ident),+) => {
|
||||
impl <$($ident : Default),+> DefaultParams for ($($ident,)+){
|
||||
fn default_params() -> Self {
|
||||
(
|
||||
$($ident::default(),)+
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
const _: () = {
|
||||
impl_default_params_for_tuple!(A);
|
||||
impl_default_params_for_tuple!(A, B);
|
||||
impl_default_params_for_tuple!(A, B, C);
|
||||
impl_default_params_for_tuple!(A, B, C, D);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H, I);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H, I, J);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H, I, J, K);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y);
|
||||
impl_default_params_for_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z);
|
||||
};
|
||||
@@ -0,0 +1,139 @@
|
||||
use crate::error::ExtrinsicError;
|
||||
use codec::Decode;
|
||||
|
||||
/// The result of performing [`SubmittableTransaction::validate()`].
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ValidationResult {
|
||||
/// The transaction is valid
|
||||
Valid(TransactionValid),
|
||||
/// The transaction is invalid
|
||||
Invalid(TransactionInvalid),
|
||||
/// Unable to validate the transaction
|
||||
Unknown(TransactionUnknown),
|
||||
}
|
||||
|
||||
impl ValidationResult {
|
||||
/// Is the transaction valid.
|
||||
pub fn is_valid(&self) -> bool {
|
||||
matches!(self, ValidationResult::Valid(_))
|
||||
}
|
||||
|
||||
#[allow(clippy::get_first)]
|
||||
pub(crate) fn try_from_bytes(bytes: Vec<u8>) -> Result<ValidationResult, ExtrinsicError> {
|
||||
// TaggedTransactionQueue_validate_transaction returns this:
|
||||
// https://github.com/paritytech/substrate/blob/0cdf7029017b70b7c83c21a4dc0aa1020e7914f6/primitives/runtime/src/transaction_validity.rs#L210
|
||||
// We copy some of the inner types and put the three states (valid, invalid, unknown) into one enum,
|
||||
// because from our perspective, the call was successful regardless.
|
||||
if bytes.get(0) == Some(&0) {
|
||||
// ok: valid. Decode but, for now we discard most of the information
|
||||
let res = TransactionValid::decode(&mut &bytes[1..])
|
||||
.map_err(ExtrinsicError::CannotDecodeValidationResult)?;
|
||||
Ok(ValidationResult::Valid(res))
|
||||
} else if bytes.get(0) == Some(&1) && bytes.get(1) == Some(&0) {
|
||||
// error: invalid
|
||||
let res = TransactionInvalid::decode(&mut &bytes[2..])
|
||||
.map_err(ExtrinsicError::CannotDecodeValidationResult)?;
|
||||
Ok(ValidationResult::Invalid(res))
|
||||
} else if bytes.get(0) == Some(&1) && bytes.get(1) == Some(&1) {
|
||||
// error: unknown
|
||||
let res = TransactionUnknown::decode(&mut &bytes[2..])
|
||||
.map_err(ExtrinsicError::CannotDecodeValidationResult)?;
|
||||
Ok(ValidationResult::Unknown(res))
|
||||
} else {
|
||||
// unable to decode the bytes; they aren't what we expect.
|
||||
Err(ExtrinsicError::UnexpectedValidationResultBytes(bytes))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Transaction is valid; here is some more information about it.
|
||||
#[derive(Decode, Clone, Debug, PartialEq)]
|
||||
pub struct TransactionValid {
|
||||
/// Priority of the transaction.
|
||||
///
|
||||
/// Priority determines the ordering of two transactions that have all
|
||||
/// their dependencies (required tags) satisfied.
|
||||
pub priority: u64,
|
||||
/// Transaction dependencies
|
||||
///
|
||||
/// A non-empty list signifies that some other transactions which provide
|
||||
/// given tags are required to be included before that one.
|
||||
pub requires: Vec<Vec<u8>>,
|
||||
/// Provided tags
|
||||
///
|
||||
/// A list of tags this transaction provides. Successfully importing the transaction
|
||||
/// will enable other transactions that depend on (require) those tags to be included as well.
|
||||
/// Provided and required tags allow Substrate to build a dependency graph of transactions
|
||||
/// and import them in the right (linear) order.
|
||||
pub provides: Vec<Vec<u8>>,
|
||||
/// Transaction longevity
|
||||
///
|
||||
/// Longevity describes minimum number of blocks the validity is correct.
|
||||
/// After this period transaction should be removed from the pool or revalidated.
|
||||
pub longevity: u64,
|
||||
/// A flag indicating if the transaction should be propagated to other peers.
|
||||
///
|
||||
/// By setting `false` here the transaction will still be considered for
|
||||
/// including in blocks that are authored on the current node, but will
|
||||
/// never be sent to other peers.
|
||||
pub propagate: bool,
|
||||
}
|
||||
|
||||
/// The runtime was unable to validate the transaction.
|
||||
#[derive(Decode, Clone, Debug, PartialEq)]
|
||||
pub enum TransactionUnknown {
|
||||
/// Could not lookup some information that is required to validate the transaction.
|
||||
CannotLookup,
|
||||
/// No validator found for the given unsigned transaction.
|
||||
NoUnsignedValidator,
|
||||
/// Any other custom unknown validity that is not covered by this enum.
|
||||
Custom(u8),
|
||||
}
|
||||
|
||||
/// The transaction is invalid.
|
||||
#[derive(Decode, Clone, Debug, PartialEq)]
|
||||
pub enum TransactionInvalid {
|
||||
/// The call of the transaction is not expected.
|
||||
Call,
|
||||
/// General error to do with the inability to pay some fees (e.g. account balance too low).
|
||||
Payment,
|
||||
/// General error to do with the transaction not yet being valid (e.g. nonce too high).
|
||||
Future,
|
||||
/// General error to do with the transaction being outdated (e.g. nonce too low).
|
||||
Stale,
|
||||
/// General error to do with the transaction's proofs (e.g. signature).
|
||||
///
|
||||
/// # Possible causes
|
||||
///
|
||||
/// When using a signed extension that provides additional data for signing, it is required
|
||||
/// that the signing and the verifying side use the same additional data. Additional
|
||||
/// data will only be used to generate the signature, but will not be part of the transaction
|
||||
/// itself. As the verifying side does not know which additional data was used while signing
|
||||
/// it will only be able to assume a bad signature and cannot express a more meaningful error.
|
||||
BadProof,
|
||||
/// The transaction birth block is ancient.
|
||||
///
|
||||
/// # Possible causes
|
||||
///
|
||||
/// For `FRAME`-based runtimes this would be caused by `current block number`
|
||||
/// - Era::birth block number > BlockHashCount`. (e.g. in Polkadot `BlockHashCount` = 2400, so
|
||||
/// a transaction with birth block number 1337 would be valid up until block number 1337 + 2400,
|
||||
/// after which point the transaction would be considered to have an ancient birth block.)
|
||||
AncientBirthBlock,
|
||||
/// The transaction would exhaust the resources of current block.
|
||||
///
|
||||
/// The transaction might be valid, but there are not enough resources
|
||||
/// left in the current block.
|
||||
ExhaustsResources,
|
||||
/// Any other custom invalid validity that is not covered by this enum.
|
||||
Custom(u8),
|
||||
/// 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 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,
|
||||
}
|
||||
Reference in New Issue
Block a user