use leo_ast::NetworkName;
use leo_errors::Result;
use anyhow::anyhow;
use serde::Deserialize;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize)]
pub enum TransactionStatus {
#[serde(rename = "accepted")]
Accepted,
#[serde(rename = "aborted")]
Aborted,
#[serde(rename = "rejected")]
Rejected,
}
#[derive(Debug, Deserialize)]
struct Transaction {
id: String,
}
#[derive(Debug, Deserialize)]
struct TransactionResult {
status: TransactionStatus,
transaction: Transaction,
}
#[derive(Debug, Deserialize)]
struct Block {
transactions: Vec<TransactionResult>,
aborted_transaction_ids: Vec<String>,
}
#[derive(Debug, Deserialize)]
struct Transition {
id: String,
}
#[derive(Debug, Deserialize)]
struct Fee {
transition: Transition,
}
#[derive(Debug, Deserialize)]
struct RejectedTransaction {
fee: Option<Fee>,
}
pub fn current_height(endpoint: &str, network: NetworkName, network_retries: u32) -> Result<usize> {
let height_url = format!("{endpoint}/{network}/block/height/latest");
let height_str = leo_package::fetch_from_network_plain(&height_url, network_retries)?;
let height: usize = height_str.parse().map_err(|e| anyhow!("error parsing height: {e}"))?;
Ok(height)
}
fn status_at_height(
id: &str,
maybe_fee_id: Option<&str>,
endpoint: &str,
network: NetworkName,
height: usize,
max_wait: usize,
network_retries: u32,
) -> Result<Option<TransactionStatus>> {
for i in 0usize.. {
if current_height(endpoint, network, 1)? >= height {
break;
} else if i >= max_wait {
return Ok(None);
} else {
std::thread::sleep(std::time::Duration::from_secs(1));
}
}
let block_url = format!("{endpoint}/{network}/block/{height}");
let block_str = leo_package::fetch_from_network_plain(&block_url, network_retries)?;
let block: Block = serde_json::from_str(&block_str).map_err(|e| anyhow!("Deserialization failure X: {e}."))?;
let maybe_this_transaction =
block.transactions.iter().find(|transaction_result| transaction_result.transaction.id == id);
if let Some(transaction_result) = maybe_this_transaction {
return Ok(Some(transaction_result.status));
}
if block.aborted_transaction_ids.iter().any(|aborted_id| aborted_id == id) {
return Ok(Some(TransactionStatus::Aborted));
}
for rejected in &block.transactions {
if rejected.status != TransactionStatus::Rejected {
continue;
}
let url = format!("{endpoint}/{network}/transaction/unconfirmed/{}", rejected.transaction.id);
let transaction_str = leo_package::fetch_from_network_plain(&url, network_retries)?;
let transaction: RejectedTransaction =
serde_json::from_str(&transaction_str).map_err(|e| anyhow!("Deserialization failure: {e}"))?;
if transaction.fee.map(|fee| fee.transition.id).as_deref() == maybe_fee_id {
return Ok(Some(TransactionStatus::Rejected));
}
}
Ok(None)
}
struct CheckedTransaction {
blocks_checked: usize,
status: Option<TransactionStatus>,
}
#[allow(clippy::too_many_arguments)]
fn check_transaction(
id: &str,
maybe_fee_id: Option<&str>,
endpoint: &str,
network: NetworkName,
start_height: usize,
max_wait: usize,
blocks_to_check: usize,
network_retries: u32,
) -> Result<CheckedTransaction> {
const DELAY_MILLIS: u64 = 201;
for use_height in start_height..start_height + blocks_to_check {
let status = status_at_height(id, maybe_fee_id, endpoint, network, use_height, max_wait, network_retries)?;
if status.is_some() {
return Ok(CheckedTransaction { blocks_checked: use_height - start_height + 1, status });
}
std::thread::sleep(std::time::Duration::from_millis(DELAY_MILLIS));
}
Ok(CheckedTransaction { blocks_checked: blocks_to_check, status: None })
}
#[allow(clippy::too_many_arguments)]
pub fn check_transaction_with_message(
id: &str,
maybe_fee_id: Option<&str>,
endpoint: &str,
network: NetworkName,
start_height: usize,
max_wait: usize,
blocks_to_check: usize,
network_retries: u32,
) -> Result<Option<TransactionStatus>> {
println!("🔄 Searching up to {blocks_to_check} blocks to confirm transaction (this may take several seconds)...");
let checked = crate::cli::check_transaction::check_transaction(
id,
maybe_fee_id,
endpoint,
network,
start_height,
max_wait,
blocks_to_check,
network_retries,
)?;
println!("Explored {} blocks.", checked.blocks_checked);
match checked.status {
Some(TransactionStatus::Accepted) => println!("Transaction accepted."),
Some(TransactionStatus::Rejected) => println!("Transaction rejected."),
Some(TransactionStatus::Aborted) => println!("Transaction aborted."),
None => println!("Could not find the transaction."),
}
Ok(checked.status)
}