use std::sync::Arc;
use subxt::ext::sp_core::H256;
use tokio::sync::{
mpsc::{Receiver, Sender},
RwLock,
};
use crate::{
tx::extrinsics::callback_executor::Url,
types::{MandalaTransactionProgress, SenderChannel},
};
use super::enums::{ExtrinsicStatus, Hash};
pub struct TransactionMessage {
pub(crate) status: ExtrinsicStatus,
pub(crate) callback: Option<Url>,
pub(crate) id: Hash,
}
impl TransactionMessage {
pub(crate) fn new(status: ExtrinsicStatus, callback: Option<Url>, id: Hash) -> Self {
Self {
status,
callback,
id,
}
}
pub fn inner_status(&self) -> ExtrinsicStatus {
self.status.clone()
}
pub fn callback(&self) -> Option<&Url> {
self.callback.as_ref()
}
pub fn id(&self) -> &Hash {
&self.id
}
}
#[cfg(feature = "tokio")]
pub struct Transaction {
id: H256,
status: Arc<RwLock<ExtrinsicStatus>>,
}
impl Transaction {
pub fn id(&self) -> Hash {
self.id.into()
}
pub async fn status(&self) -> ExtrinsicStatus {
let status = self.status.read().await;
status.clone()
}
fn infer_err(e: subxt::Error) -> ExtrinsicStatus {
match e {
subxt::Error::Io(_) => todo!(),
subxt::Error::Codec(_) => todo!(),
subxt::Error::Rpc(_) => todo!(),
subxt::Error::Serialization(_) => todo!(),
subxt::Error::Metadata(_) => todo!(),
subxt::Error::MetadataDecoding(_) => todo!(),
subxt::Error::Runtime(e) => ExtrinsicStatus::Failed(format!("{e}").into()),
subxt::Error::Decode(_) => todo!(),
subxt::Error::Encode(_) => todo!(),
subxt::Error::Transaction(_) => todo!(),
subxt::Error::Block(_) => todo!(),
subxt::Error::StorageAddress(_) => todo!(),
subxt::Error::Unknown(_) => todo!(),
subxt::Error::Other(_) => todo!(),
_ => todo!(),
}
}
}
impl Transaction {
pub fn new(
tx: MandalaTransactionProgress,
external_notifier: Option<SenderChannel<TransactionMessage>>,
callback: Option<Url>,
) -> Self {
let hash: H256 = tx.0.extrinsic_hash();
let task_channel = Self::process_transaction(tx, external_notifier, callback);
let default_status = Self::watch_transaction_status(task_channel);
Self {
id: hash,
status: default_status,
}
}
fn process_transaction(
tx: MandalaTransactionProgress,
external_status_notifier: Option<SenderChannel<TransactionMessage>>,
callback: Option<Url>,
) -> Receiver<ExtrinsicStatus> {
let (internal_status_notifier, receiver) = Self::create_channel();
let task = async move {
let id = tx.0.extrinsic_hash().into();
let status = Self::wait(tx).await;
internal_status_notifier
.send(status.clone())
.await
.expect("there should be only 1 message sent");
if let Some(external_status_notifier) = external_status_notifier {
let msg = TransactionMessage::new(status, callback, id);
external_status_notifier.send(msg);
}
};
tokio::task::spawn(task);
receiver
}
pub async fn wait(tx: MandalaTransactionProgress) -> ExtrinsicStatus {
let status = tx.0.wait_for_finalized_success().await;
match status {
Ok(tx) => ExtrinsicStatus::Success(tx.into()),
Err(e) => Self::infer_err(e),
}
}
fn create_channel() -> (Sender<ExtrinsicStatus>, Receiver<ExtrinsicStatus>) {
let default_buffer_size = 1_usize;
tokio::sync::mpsc::channel::<ExtrinsicStatus>(default_buffer_size)
}
fn watch_transaction_status(
mut task_channel: Receiver<ExtrinsicStatus>,
) -> Arc<RwLock<ExtrinsicStatus>> {
let default_status = Arc::new(RwLock::new(ExtrinsicStatus::default()));
let status_arc_clone = default_status.clone();
let watcher = async move {
let Some(new_status) = task_channel.recv().await else {
return;
};
let mut status = status_arc_clone.write().await;
*status = new_status;
};
tokio::task::spawn(watcher);
default_status
}
}
#[cfg(test)]
mod transaction_tests {
use super::*;
use crate::registrar::key_manager::prelude::PublicAddress;
use crate::registrar::signer::TxBuilder;
use crate::tx::extrinsics::extrinsics_submitter::ExtrinsicSubmitter;
use crate::tx::extrinsics::manager::facade::ExtrinsicFacade;
use crate::tx::extrinsics::prelude::transfer_balance::constructor::BalanceTransfer;
use crate::types::MandalaConfig;
use crate::types::MandalaExtrinsics;
use crate::types::Unit;
use std::str::FromStr;
use std::sync::mpsc;
pub(crate) use subxt::OnlineClient;
fn mock_pair() -> subxt::ext::sp_core::sr25519::Pair {
sp_keyring::Sr25519Keyring::Alice.pair()
}
async fn mock_client() -> crate::types::NodeClient {
OnlineClient::<MandalaConfig>::new().await.unwrap()
}
async fn create_tx_progress() -> MandalaTransactionProgress {
let address = "5DJk1gegyQJk6BNs7LceZ1akt5e9fpm4gUYGzcfpKaLG9Mmb";
let new_address = PublicAddress::from_str(address).unwrap();
let pair = mock_pair();
let node_client = mock_client().await;
let value = Unit::new("0.00001", None).expect("static conversion should not fail");
let payload = BalanceTransfer::construct(new_address, value);
let extrinsic = TxBuilder::signed(&node_client.into(), pair.into(), payload)
.await
.unwrap()
.0;
let tx = MandalaExtrinsics::new(extrinsic);
let tx_progress = ExtrinsicSubmitter::submit(tx).await.unwrap();
tx_progress
}
#[tokio::test]
async fn new_transaction_tests() {
let tx_progress = create_tx_progress().await;
let callback = "https://example.net/a/b/c.png";
let reqwest_url = reqwest::Url::parse(callback).expect("Failed to parse the callback URL");
let url = Url(reqwest_url);
let extrinsic_hash = tx_progress.0.extrinsic_hash();
let result = Transaction::new(tx_progress, None, Some(url));
assert_eq!(result.id, extrinsic_hash);
}
#[tokio::test]
async fn test_process_transaction() {
let tx_progress = create_tx_progress().await;
let callback = "https://example.net/a/b/c.png";
let reqwest_url = reqwest::Url::parse(callback).expect("Failed to parse the callback URL");
let url = Url(reqwest_url);
let mut result_receiver = Transaction::process_transaction(tx_progress, None, Some(url));
let result = result_receiver.try_recv();
assert!(
result.is_err(),
"Result receiver should not have received any values yet"
);
}
#[tokio::test]
async fn wait_tests() {
let tx_progress = create_tx_progress().await;
let status = Transaction::wait(tx_progress).await;
match status {
ExtrinsicStatus::Success(_) => {
assert!(true, "Extrinsic was finalized successfully");
}
ExtrinsicStatus::Failed(reason) => {
panic!("{:?}", reason);
}
ExtrinsicStatus::Pending => {
assert!(false, "Extrinsic is still pending");
}
}
}
}