leo_lang/cli/helpers/
check_transaction.rs1use leo_errors::Result;
18use leo_package::NetworkName;
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) -> Result<usize> {
67 let height_url = format!("{endpoint}/{network}/block/height/latest");
68 let height_str = leo_package::fetch_from_network_plain(&height_url)?;
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) -> Result<Option<TransactionStatus>> {
81 for i in 0usize.. {
83 if current_height(endpoint, network)? >= height {
84 break;
85 } else if i >= max_wait {
86 return Ok(None);
88 } else {
89 std::thread::sleep(std::time::Duration::from_secs(1));
90 }
91 }
92
93 let block_url = format!("{endpoint}/{network}/block/{height}");
94 let block_str = leo_package::fetch_from_network_plain(&block_url)?;
95 let block: Block = serde_json::from_str(&block_str).map_err(|e| anyhow!("Deserialization failure X: {e}."))?;
96 let maybe_this_transaction =
97 block.transactions.iter().find(|transaction_result| transaction_result.transaction.id == id);
98
99 if let Some(transaction_result) = maybe_this_transaction {
100 return Ok(Some(transaction_result.status));
102 }
103
104 if block.aborted_transaction_ids.iter().any(|aborted_id| aborted_id == id) {
105 return Ok(Some(TransactionStatus::Aborted));
107 }
108
109 for rejected in &block.transactions {
110 if rejected.status != TransactionStatus::Rejected {
111 continue;
112 }
113
114 let url = format!("{endpoint}/{network}/transaction/unconfirmed/{}", rejected.transaction.id);
115 let transaction_str = leo_package::fetch_from_network_plain(&url)?;
116 let transaction: RejectedTransaction =
117 serde_json::from_str(&transaction_str).map_err(|e| anyhow!("Deserialization failure: {e}"))?;
118 if transaction.fee.map(|fee| fee.transition.id).as_deref() == maybe_fee_id {
120 return Ok(Some(TransactionStatus::Rejected));
122 }
123 }
124
125 Ok(None)
126}
127
128struct CheckedTransaction {
129 blocks_checked: usize,
130 status: Option<TransactionStatus>,
131}
132
133fn check_transaction(
134 id: &str,
135 maybe_fee_id: Option<&str>,
136 endpoint: &str,
137 network: NetworkName,
138 start_height: usize,
139 max_wait: usize,
140 blocks_to_check: usize,
141) -> Result<CheckedTransaction> {
142 const DELAY_MILLIS: u64 = 101;
145
146 for use_height in start_height..start_height + blocks_to_check {
147 let status = status_at_height(id, maybe_fee_id, endpoint, network, use_height, max_wait)?;
148 if status.is_some() {
149 return Ok(CheckedTransaction { blocks_checked: use_height - start_height + 1, status });
150 }
151
152 std::thread::sleep(std::time::Duration::from_millis(DELAY_MILLIS));
154 }
155
156 Ok(CheckedTransaction { blocks_checked: blocks_to_check, status: None })
157}
158
159pub fn check_transaction_with_message(
162 id: &str,
163 maybe_fee_id: Option<&str>,
164 endpoint: &str,
165 network: NetworkName,
166 start_height: usize,
167 max_wait: usize,
168 blocks_to_check: usize,
169) -> Result<Option<TransactionStatus>> {
170 println!("Searching up to {blocks_to_check} blocks to find transaction (this may take several seconds)...");
171 let checked = crate::cli::check_transaction::check_transaction(
172 id,
173 maybe_fee_id,
174 endpoint,
175 network,
176 start_height,
177 max_wait,
178 blocks_to_check,
179 )?;
180 println!("Explored {} blocks.", checked.blocks_checked);
181 match checked.status {
182 Some(TransactionStatus::Accepted) => println!("Transaction accepted."),
183 Some(TransactionStatus::Rejected) => println!("Transaction rejected."),
184 Some(TransactionStatus::Aborted) => println!("Transaction aborted."),
185 None => println!("Couldn't find the transaction."),
186 }
187 Ok(checked.status)
188}