use crate::{
backend::{BackendExt, BlockRef, TransactionStatus},
client::{OfflineClientT, OnlineClientT},
config::{Config, ExtrinsicParams, HashFor, Header},
error::{ExtrinsicError, TransactionStatusError},
tx::{Payload, Signer as SignerT, TxProgress},
utils::PhantomDataSendSync,
};
use codec::{Compact, Decode, Encode};
use derive_where::derive_where;
use futures::future::{try_join, TryFutureExt};
use pezkuwi_subxt_core::tx::TransactionVersion;
#[derive_where(Clone; Client)]
pub struct TxClient<T: Config, Client> {
client: Client,
_marker: PhantomDataSendSync<T>,
}
impl<T: Config, Client> TxClient<T, Client> {
pub fn new(client: Client) -> Self {
Self { client, _marker: PhantomDataSendSync::new() }
}
}
impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
pub fn validate<Call>(&self, call: &Call) -> Result<(), ExtrinsicError>
where
Call: Payload,
{
pezkuwi_subxt_core::tx::validate(call, &self.client.metadata()).map_err(Into::into)
}
pub fn call_data<Call>(&self, call: &Call) -> Result<Vec<u8>, ExtrinsicError>
where
Call: Payload,
{
pezkuwi_subxt_core::tx::call_data(call, &self.client.metadata()).map_err(Into::into)
}
pub fn create_unsigned<Call>(
&self,
call: &Call,
) -> Result<SubmittableTransaction<T, C>, ExtrinsicError>
where
Call: Payload,
{
let metadata = self.client.metadata();
let tx = match pezkuwi_subxt_core::tx::suggested_version(&metadata)? {
TransactionVersion::V4 => pezkuwi_subxt_core::tx::create_v4_unsigned(call, &metadata),
TransactionVersion::V5 => pezkuwi_subxt_core::tx::create_v5_bare(call, &metadata),
}?;
Ok(SubmittableTransaction { client: self.client.clone(), inner: tx })
}
pub fn create_v4_unsigned<Call>(
&self,
call: &Call,
) -> Result<SubmittableTransaction<T, C>, ExtrinsicError>
where
Call: Payload,
{
let metadata = self.client.metadata();
let tx = pezkuwi_subxt_core::tx::create_v4_unsigned(call, &metadata)?;
Ok(SubmittableTransaction { client: self.client.clone(), inner: tx })
}
pub fn create_v5_bare<Call>(
&self,
call: &Call,
) -> Result<SubmittableTransaction<T, C>, ExtrinsicError>
where
Call: Payload,
{
let metadata = self.client.metadata();
let tx = pezkuwi_subxt_core::tx::create_v5_bare(call, &metadata)?;
Ok(SubmittableTransaction { client: self.client.clone(), inner: tx })
}
pub fn create_partial_offline<Call>(
&self,
call: &Call,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
where
Call: Payload,
{
let metadata = self.client.metadata();
let tx = match pezkuwi_subxt_core::tx::suggested_version(&metadata)? {
TransactionVersion::V4 => {
PartialTransactionInner::V4(pezkuwi_subxt_core::tx::create_v4_signed(
call,
&self.client.client_state(),
params,
)?)
},
TransactionVersion::V5 => {
PartialTransactionInner::V5(pezkuwi_subxt_core::tx::create_v5_general(
call,
&self.client.client_state(),
params,
)?)
},
};
Ok(PartialTransaction { client: self.client.clone(), inner: tx })
}
pub fn create_v4_partial_offline<Call>(
&self,
call: &Call,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
where
Call: Payload,
{
let tx = PartialTransactionInner::V4(pezkuwi_subxt_core::tx::create_v4_signed(
call,
&self.client.client_state(),
params,
)?);
Ok(PartialTransaction { client: self.client.clone(), inner: tx })
}
pub fn create_v5_partial_offline<Call>(
&self,
call: &Call,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
where
Call: Payload,
{
let tx = PartialTransactionInner::V5(pezkuwi_subxt_core::tx::create_v5_general(
call,
&self.client.client_state(),
params,
)?);
Ok(PartialTransaction { client: self.client.clone(), inner: tx })
}
}
impl<T, C> TxClient<T, C>
where
T: Config,
C: OnlineClientT<T>,
{
pub async fn account_nonce(&self, account_id: &T::AccountId) -> Result<u64, ExtrinsicError> {
let block_ref = self
.client
.backend()
.latest_finalized_block_ref()
.await
.map_err(ExtrinsicError::CannotGetLatestFinalizedBlock)?;
crate::blocks::get_account_nonce(&self.client, account_id, block_ref.hash())
.await
.map_err(|e| ExtrinsicError::AccountNonceError {
block_hash: block_ref.hash().into(),
account_id: account_id.encode().into(),
reason: e,
})
}
pub async fn create_partial<Call>(
&self,
call: &Call,
account_id: &T::AccountId,
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
where
Call: Payload,
{
inject_account_nonce_and_block(&self.client, account_id, &mut params).await?;
self.create_partial_offline(call, params)
}
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>, ExtrinsicError>
where
Call: Payload,
{
inject_account_nonce_and_block(&self.client, account_id, &mut params).await?;
self.create_v4_partial_offline(call, params)
}
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>, ExtrinsicError>
where
Call: Payload,
{
inject_account_nonce_and_block(&self.client, account_id, &mut params).await?;
self.create_v5_partial_offline(call, params)
}
pub async fn create_signed<Call, Signer>(
&mut self,
call: &Call,
signer: &Signer,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<SubmittableTransaction<T, C>, ExtrinsicError>
where
Call: Payload,
Signer: SignerT<T>,
{
let mut partial = self.create_partial(call, &signer.account_id(), params).await?;
Ok(partial.sign(signer))
}
pub async fn sign_and_submit_then_watch_default<Call, Signer>(
&mut self,
call: &Call,
signer: &Signer,
) -> Result<TxProgress<T, C>, ExtrinsicError>
where
Call: Payload,
Signer: SignerT<T>,
<T::ExtrinsicParams as ExtrinsicParams<T>>::Params: DefaultParams,
{
self.sign_and_submit_then_watch(call, signer, DefaultParams::default_params())
.await
}
pub async fn sign_and_submit_then_watch<Call, Signer>(
&mut self,
call: &Call,
signer: &Signer,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<TxProgress<T, C>, ExtrinsicError>
where
Call: Payload,
Signer: SignerT<T>,
{
self.create_signed(call, signer, params).await?.submit_and_watch().await
}
pub async fn sign_and_submit_default<Call, Signer>(
&mut self,
call: &Call,
signer: &Signer,
) -> Result<HashFor<T>, ExtrinsicError>
where
Call: Payload,
Signer: SignerT<T>,
<T::ExtrinsicParams as ExtrinsicParams<T>>::Params: DefaultParams,
{
self.sign_and_submit(call, signer, DefaultParams::default_params()).await
}
pub async fn sign_and_submit<Call, Signer>(
&mut self,
call: &Call,
signer: &Signer,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<HashFor<T>, ExtrinsicError>
where
Call: Payload,
Signer: SignerT<T>,
{
self.create_signed(call, signer, params).await?.submit().await
}
}
pub struct PartialTransaction<T: Config, C> {
client: C,
inner: PartialTransactionInner<T>,
}
enum PartialTransactionInner<T: Config> {
V4(pezkuwi_subxt_core::tx::PartialTransactionV4<T>),
V5(pezkuwi_subxt_core::tx::PartialTransactionV5<T>),
}
impl<T, C> PartialTransaction<T, C>
where
T: Config,
C: OfflineClientT<T>,
{
pub fn signer_payload(&self) -> Vec<u8> {
match &self.inner {
PartialTransactionInner::V4(tx) => tx.signer_payload(),
PartialTransactionInner::V5(tx) => tx.signer_payload().to_vec(),
}
}
pub fn call_data(&self) -> &[u8] {
match &self.inner {
PartialTransactionInner::V4(tx) => tx.call_data(),
PartialTransactionInner::V5(tx) => tx.call_data(),
}
}
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 }
}
pub fn sign_with_account_and_signature(
&mut self,
account_id: &T::AccountId,
signature: &T::Signature,
) -> 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: tx }
}
}
pub struct SubmittableTransaction<T, C> {
client: C,
inner: pezkuwi_subxt_core::tx::Transaction<T>,
}
impl<T, C> SubmittableTransaction<T, C>
where
T: Config,
C: OfflineClientT<T>,
{
pub fn from_bytes(client: C, tx_bytes: Vec<u8>) -> Self {
Self { client, inner: pezkuwi_subxt_core::tx::Transaction::from_bytes(tx_bytes) }
}
pub fn hash(&self) -> HashFor<T> {
self.inner.hash_with(self.client.hasher())
}
pub fn encoded(&self) -> &[u8] {
self.inner.encoded()
}
pub fn into_encoded(self) -> Vec<u8> {
self.inner.into_encoded()
}
}
impl<T, C> SubmittableTransaction<T, C>
where
T: Config,
C: OnlineClientT<T>,
{
pub async fn submit_and_watch(&self) -> Result<TxProgress<T, C>, ExtrinsicError> {
let ext_hash = self.hash();
let sub = self
.client
.backend()
.submit_transaction(self.encoded())
.await
.map_err(ExtrinsicError::ErrorSubmittingTransaction)?;
Ok(TxProgress::new(sub, self.client.clone(), ext_hash))
}
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)?;
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),
}
}
pub async fn validate(&self) -> Result<ValidationResult, ExtrinsicError> {
let latest_block_ref = self
.client
.backend()
.latest_finalized_block_ref()
.await
.map_err(ExtrinsicError::CannotGetLatestFinalizedBlock)?;
self.validate_at(latest_block_ref).await
}
pub async fn validate_at(
&self,
at: impl Into<BlockRef<HashFor<T>>>,
) -> Result<ValidationResult, ExtrinsicError> {
let block_hash = at.into().hash();
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)
}
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)?;
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)
}
}
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<(), ExtrinsicError> {
use pezkuwi_subxt_core::config::transaction_extensions::Params;
let block_ref = client
.backend()
.latest_finalized_block_ref()
.await
.map_err(ExtrinsicError::CannotGetLatestFinalizedBlock)?;
let (block_header, account_nonce) = try_join(
client
.backend()
.block_header(block_ref.hash())
.map_err(ExtrinsicError::CannotGetLatestFinalizedBlock),
crate::blocks::get_account_nonce(client, account_id, block_ref.hash()).map_err(|e| {
ExtrinsicError::AccountNonceError {
block_hash: block_ref.hash().into(),
account_id: account_id.encode().into(),
reason: e,
}
}),
)
.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().into(), block_ref.hash());
Ok(())
}
impl ValidationResult {
#[allow(clippy::get_first)]
fn try_from_bytes(bytes: Vec<u8>) -> Result<ValidationResult, ExtrinsicError> {
if bytes.get(0) == Some(&0) {
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) {
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) {
let res = TransactionUnknown::decode(&mut &bytes[2..])
.map_err(ExtrinsicError::CannotDecodeValidationResult)?;
Ok(ValidationResult::Unknown(res))
} else {
Err(ExtrinsicError::UnexpectedValidationResultBytes(bytes))
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ValidationResult {
Valid(TransactionValid),
Invalid(TransactionInvalid),
Unknown(TransactionUnknown),
}
impl ValidationResult {
pub fn is_valid(&self) -> bool {
matches!(self, ValidationResult::Valid(_))
}
}
#[derive(Decode, Clone, Debug, PartialEq)]
pub struct TransactionValid {
pub priority: u64,
pub requires: Vec<Vec<u8>>,
pub provides: Vec<Vec<u8>>,
pub longevity: u64,
pub propagate: bool,
}
#[derive(Decode, Clone, Debug, PartialEq)]
pub enum TransactionUnknown {
CannotLookup,
NoUnsignedValidator,
Custom(u8),
}
#[derive(Decode, Clone, Debug, PartialEq)]
pub enum TransactionInvalid {
Call,
Payment,
Future,
Stale,
BadProof,
AncientBirthBlock,
ExhaustsResources,
Custom(u8),
BadMandatory,
MandatoryValidation,
BadSigner,
}
pub trait DefaultParams: Sized {
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);
};
#[cfg(test)]
mod test {
use super::*;
#[test]
fn transaction_validity_decoding_empty_bytes() {
let decoded = ValidationResult::try_from_bytes(vec![]);
assert!(decoded.is_err())
}
#[test]
fn transaction_validity_decoding_is_ok() {
use pezsp_runtime::{
transaction_validity as sp, transaction_validity::TransactionValidity as T,
};
let pairs = vec![
(
T::Ok(sp::ValidTransaction { ..Default::default() }),
ValidationResult::Valid(TransactionValid {
longevity: u64::MAX,
propagate: true,
priority: 0,
provides: vec![],
requires: vec![],
}),
),
(
T::Err(sp::TransactionValidityError::Invalid(sp::InvalidTransaction::BadProof)),
ValidationResult::Invalid(TransactionInvalid::BadProof),
),
(
T::Err(sp::TransactionValidityError::Invalid(sp::InvalidTransaction::Call)),
ValidationResult::Invalid(TransactionInvalid::Call),
),
(
T::Err(sp::TransactionValidityError::Invalid(sp::InvalidTransaction::Payment)),
ValidationResult::Invalid(TransactionInvalid::Payment),
),
(
T::Err(sp::TransactionValidityError::Invalid(sp::InvalidTransaction::Future)),
ValidationResult::Invalid(TransactionInvalid::Future),
),
(
T::Err(sp::TransactionValidityError::Invalid(sp::InvalidTransaction::Stale)),
ValidationResult::Invalid(TransactionInvalid::Stale),
),
(
T::Err(sp::TransactionValidityError::Invalid(
sp::InvalidTransaction::AncientBirthBlock,
)),
ValidationResult::Invalid(TransactionInvalid::AncientBirthBlock),
),
(
T::Err(sp::TransactionValidityError::Invalid(
sp::InvalidTransaction::ExhaustsResources,
)),
ValidationResult::Invalid(TransactionInvalid::ExhaustsResources),
),
(
T::Err(sp::TransactionValidityError::Invalid(sp::InvalidTransaction::BadMandatory)),
ValidationResult::Invalid(TransactionInvalid::BadMandatory),
),
(
T::Err(sp::TransactionValidityError::Invalid(
sp::InvalidTransaction::MandatoryValidation,
)),
ValidationResult::Invalid(TransactionInvalid::MandatoryValidation),
),
(
T::Err(sp::TransactionValidityError::Invalid(sp::InvalidTransaction::BadSigner)),
ValidationResult::Invalid(TransactionInvalid::BadSigner),
),
(
T::Err(sp::TransactionValidityError::Invalid(sp::InvalidTransaction::Custom(123))),
ValidationResult::Invalid(TransactionInvalid::Custom(123)),
),
(
T::Err(sp::TransactionValidityError::Unknown(sp::UnknownTransaction::CannotLookup)),
ValidationResult::Unknown(TransactionUnknown::CannotLookup),
),
(
T::Err(sp::TransactionValidityError::Unknown(
sp::UnknownTransaction::NoUnsignedValidator,
)),
ValidationResult::Unknown(TransactionUnknown::NoUnsignedValidator),
),
(
T::Err(sp::TransactionValidityError::Unknown(sp::UnknownTransaction::Custom(123))),
ValidationResult::Unknown(TransactionUnknown::Custom(123)),
),
];
for (sp, validation_result) in pairs {
let encoded = sp.encode();
let decoded = ValidationResult::try_from_bytes(encoded).expect("should decode OK");
assert_eq!(decoded, validation_result);
}
}
}