leo_lang/cli/helpers/
check_transaction.rs1use leo_ast::NetworkName;
18use leo_errors::Result;
19
20use anyhow::anyhow;
21use serde::Deserialize;
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize)]
24pub enum TransactionStatus {
25 #[serde(rename = "accepted")]
26 Accepted,
27 #[serde(rename = "aborted")]
28 Aborted,
29 #[serde(rename = "rejected")]
30 Rejected,
31}
32
33#[derive(Debug, Deserialize)]
34struct Transaction {
35 id: String,
36}
37
38#[derive(Debug, Deserialize)]
39struct TransactionResult {
40 status: TransactionStatus,
41
42 transaction: Transaction,
43}
44
45#[derive(Debug, Deserialize)]
46struct Block {
47 transactions: Vec<TransactionResult>,
48 aborted_transaction_ids: Vec<String>,
49}
50
51#[derive(Debug, Deserialize)]
52struct Transition {
53 id: String,
54}
55
56#[derive(Debug, Deserialize)]
57struct Fee {
58 transition: Transition,
59}
60
61#[derive(Debug, Deserialize)]
62struct RejectedTransaction {
63 fee: Option<Fee>,
64}
65
66pub fn current_height(endpoint: &str, network: NetworkName, network_retries: u32) -> Result<usize> {
67 let height_url = format!("{endpoint}/{network}/block/height/latest");
68 let height_str = leo_package::fetch_from_network_plain(&height_url, network_retries)?;
69 let height: usize = height_str.parse().map_err(|e| anyhow!("error parsing height: {e}"))?;
70 Ok(height)
71}
72
73fn status_at_height(
74 id: &str,
75 maybe_fee_id: Option<&str>,
76 endpoint: &str,
77 network: NetworkName,
78 height: usize,
79 max_wait: usize,
80 network_retries: u32,
81) -> Result<Option<TransactionStatus>> {
82 for i in 0usize.. {
87 if current_height(endpoint, network, 1)? >= height {
88 break;
89 } else if i >= max_wait {
90 return Ok(None);
92 } else {
93 std::thread::sleep(std::time::Duration::from_secs(1));
94 }
95 }
96
97 let block_url = format!("{endpoint}/{network}/block/{height}");
98 let block_str = leo_package::fetch_from_network_plain(&block_url, network_retries)?;
99 let block: Block = serde_json::from_str(&block_str).map_err(|e| anyhow!("Deserialization failure X: {e}."))?;
100 let maybe_this_transaction =
101 block.transactions.iter().find(|transaction_result| transaction_result.transaction.id == id);
102
103 if let Some(transaction_result) = maybe_this_transaction {
104 return Ok(Some(transaction_result.status));
106 }
107
108 if block.aborted_transaction_ids.iter().any(|aborted_id| aborted_id == id) {
109 return Ok(Some(TransactionStatus::Aborted));
111 }
112
113 for rejected in &block.transactions {
114 if rejected.status != TransactionStatus::Rejected {
115 continue;
116 }
117
118 let url = format!("{endpoint}/{network}/transaction/unconfirmed/{}", rejected.transaction.id);
119 let transaction_str = leo_package::fetch_from_network_plain(&url, network_retries)?;
120 let transaction: RejectedTransaction =
121 serde_json::from_str(&transaction_str).map_err(|e| anyhow!("Deserialization failure: {e}"))?;
122 if transaction.fee.map(|fee| fee.transition.id).as_deref() == maybe_fee_id {
124 return Ok(Some(TransactionStatus::Rejected));
126 }
127 }
128
129 Ok(None)
130}
131
132struct CheckedTransaction {
133 blocks_checked: usize,
134 status: Option<TransactionStatus>,
135}
136
137#[allow(clippy::too_many_arguments)]
138fn check_transaction(
139 id: &str,
140 maybe_fee_id: Option<&str>,
141 endpoint: &str,
142 network: NetworkName,
143 start_height: usize,
144 max_wait: usize,
145 blocks_to_check: usize,
146 network_retries: u32,
147) -> Result<CheckedTransaction> {
148 const DELAY_MILLIS: u64 = 201;
151
152 for use_height in start_height..start_height + blocks_to_check {
153 let status = status_at_height(id, maybe_fee_id, endpoint, network, use_height, max_wait, network_retries)?;
154 if status.is_some() {
155 return Ok(CheckedTransaction { blocks_checked: use_height - start_height + 1, status });
156 }
157
158 std::thread::sleep(std::time::Duration::from_millis(DELAY_MILLIS));
160 }
161
162 Ok(CheckedTransaction { blocks_checked: blocks_to_check, status: None })
163}
164
165#[allow(clippy::too_many_arguments)]
168pub fn check_transaction_with_message(
169 id: &str,
170 maybe_fee_id: Option<&str>,
171 endpoint: &str,
172 network: NetworkName,
173 start_height: usize,
174 max_wait: usize,
175 blocks_to_check: usize,
176 network_retries: u32,
177) -> Result<Option<TransactionStatus>> {
178 println!("🔄 Searching up to {blocks_to_check} blocks to confirm transaction (this may take several seconds)...");
179 let checked = crate::cli::check_transaction::check_transaction(
180 id,
181 maybe_fee_id,
182 endpoint,
183 network,
184 start_height,
185 max_wait,
186 blocks_to_check,
187 network_retries,
188 )?;
189 println!("Explored {} blocks.", checked.blocks_checked);
190 match checked.status {
191 Some(TransactionStatus::Accepted) => println!("Transaction accepted."),
192 Some(TransactionStatus::Rejected) => println!("Transaction rejected."),
193 Some(TransactionStatus::Aborted) => println!("Transaction aborted."),
194 None => println!("Could not find the transaction."),
195 }
196 Ok(checked.status)
197}