aleo_agent/
chain.rs

1//! Node APIs
2use anyhow::{bail, Result};
3
4use crate::agent::Agent;
5
6use super::*;
7
8// chain
9impl Agent {
10    /// Retrieves the latest block height from the network.
11    ///
12    /// # Returns
13    /// The `Ok` variant wraps the latest block height as `u32`.
14    pub fn get_latest_block_height(&self) -> Result<u32> {
15        let url = format!("{}/{}/block/height/latest", self.base_url(), self.network());
16        match self.client().get(&url).call()?.into_json() {
17            Ok(height) => Ok(height),
18            Err(error) => bail!("Failed to parse the latest block height: {error}"),
19        }
20    }
21
22    /// Retrieves the latest block hash from the network.
23    ///
24    /// # Returns
25    /// The `Ok` variant wraps the latest block hash as `BlockHash`.
26    pub fn get_latest_block_hash(&self) -> Result<BlockHash> {
27        let url = format!("{}/{}/block/hash/latest", self.base_url(), self.network());
28        match self.client().get(&url).call()?.into_json() {
29            Ok(hash) => Ok(hash),
30            Err(error) => bail!("Failed to parse the latest block hash: {error}"),
31        }
32    }
33
34    /// Retrieves the latest block from the network.
35    ///
36    /// # Returns
37    /// The `Ok` variant wraps the latest block as `Block`.
38    pub fn get_latest_block(&self) -> Result<Block> {
39        let url = format!("{}/{}/latest/block/height", self.base_url(), self.network());
40        match self.client().get(&url).call()?.into_json() {
41            Ok(block) => Ok(block),
42            Err(error) => bail!("Failed to parse the latest block: {error}"),
43        }
44    }
45
46    /// Retrieves the block of a specific height from the network.
47    ///
48    /// # Arguments
49    /// * `height` - The height of the block to retrieve.
50    ///
51    /// # Returns
52    /// The `Ok` variant wraps the block of the specific height as `Block`.
53    pub fn get_block_of_height(&self, height: u32) -> Result<Block> {
54        let url = format!("{}/{}/block/{height}", self.base_url(), self.network());
55        match self.client().get(&url).call()?.into_json() {
56            Ok(block) => Ok(block),
57            Err(error) => bail!("Failed to parse block {height}: {error}"),
58        }
59    }
60
61    /// Retrieves the transactions of a block of a specific height from the network.
62    ///
63    /// # Arguments
64    /// * `height` - The height of the block.
65    ///
66    /// # Returns
67    /// The `Ok` variant wraps the transactions of the block of the specific height as `Transactions`.
68    pub fn get_transactions_of_height(&self, height: u32) -> Result<Transactions> {
69        let url = format!(
70            "{}/{}/block/{height}/transactions",
71            self.base_url(),
72            self.network()
73        );
74        match self.client().get(&url).call()?.into_json() {
75            Ok(block) => Ok(block),
76            Err(error) => bail!("Failed to parse block {height}: {error}"),
77        }
78    }
79
80    /// Retrieves a range of blocks from the network.
81    ///
82    /// # Arguments
83    /// * `start_height` - The starting height of the range of blocks to retrieve.
84    /// * `end_height` - The ending height of the range of blocks to retrieve.
85    /// *  end_height - start_height must be less than or equal to 50.
86    ///
87    /// # Returns
88    /// The `Ok` variant wraps a vector of `Block`.
89    pub fn get_blocks_in_range(&self, start_height: u32, end_height: u32) -> Result<Vec<Block>> {
90        if start_height >= end_height {
91            bail!("Start height must be less than end height");
92        }
93
94        if end_height - start_height > 50 {
95            bail!("The range of blocks must be less than 50");
96        }
97
98        let url = format!(
99            "{}/{}/blocks?start={start_height}&end={end_height}",
100            self.base_url(),
101            self.network()
102        );
103        match self.client().get(&url).call()?.into_json() {
104            Ok(blocks) => Ok(blocks),
105            Err(error) => {
106                bail!("Failed to parse blocks {start_height} (inclusive) to {end_height} (exclusive): {error}")
107            }
108        }
109    }
110
111    /// Retrieves a transaction by its transaction id from the network.
112    ///
113    /// # Arguments
114    /// * `transaction_id` - The id of the transaction to retrieve.
115    ///
116    /// # Returns
117    /// The `Ok` variant wraps the transaction as `Transaction`.
118    pub fn get_transaction(&self, transaction_id: &str) -> Result<Transaction> {
119        let url = format!(
120            "{}/{}/transaction/{}",
121            self.base_url(),
122            self.network(),
123            transaction_id
124        ).replace('"', "");
125        match self.client().get(&url).call()?.into_json() {
126            Ok(transaction) => Ok(transaction),
127            Err(error) => bail!("Failed to parse transaction '{transaction_id}': {error}"),
128        }
129    }
130
131    /// Retrieves the confirmed transaction for a given transaction id from the network.
132    ///
133    /// # Arguments
134    /// * `transaction_id` - The id of the transaction to retrieve.
135    ///
136    /// # Returns
137    /// The `Ok` variant wraps the confirmed transaction as `ConfirmedTransaction`.
138    pub fn get_confirmed_transaction(&self, transaction_id: &str) -> Result<ConfirmedTransaction> {
139        let url = format!(
140            "{}/{}/transaction/confirmed/{}",
141            self.base_url(),
142            self.network(),
143            transaction_id
144        ).replace('"', "");
145        match self.client().get(&url).call()?.into_json() {
146            Ok(transaction) => Ok(transaction),
147            Err(error) => bail!("Failed to parse transaction '{transaction_id}': {error}"),
148        }
149    }
150
151    /// Retrieves the pending transactions currently in the mempool from the network.
152    ///
153    /// # Returns
154    /// The `Ok` variant wraps the pending transactions as a vector of `Transaction`.
155    // pub fn get_mempool_transactions(&self) -> Result<Vec<Transaction>> {
156    //     let url = format!(
157    //         "{}/{}/memoryPool/transactions",
158    //         self.base_url(),
159    //         self.network()
160    //     );
161    //     match self.client().get(&url).call()?.into_json() {
162    //         Ok(transactions) => Ok(transactions),
163    //         Err(error) => bail!("Failed to parse memory pool transactions: {error}"),
164    //     }
165    // }
166
167    /// Broadcasts a transaction to the Aleo network.
168    ///
169    /// # Arguments
170    /// * `transaction` - The transaction to broadcast.
171    ///
172    /// # Returns
173    /// The `Ok` variant wraps the Transaction ID from the network as a `String`.
174    pub fn broadcast_transaction(&self, transaction: &Transaction) -> Result<String> {
175        let url = format!(
176            "{}/{}/transaction/broadcast",
177            self.base_url(),
178            self.network()
179        );
180        match self.client().post(&url).send_json(transaction) {
181            Ok(response) => match response.into_string() {
182                Ok(success_response) => {
183                    Ok(success_response)
184                }
185                Err(error) => bail!("❌ Transaction response was malformed {}", error),
186            },
187            Err(error) => {
188                let error_message = match error {
189                    ureq::Error::Status(code, response) => {
190                        format!("(status code {code}: {:?})", response.into_string()?)
191                    }
192                    ureq::Error::Transport(err) => format!("({err})"),
193                };
194
195                match transaction {
196                    Transaction::Deploy(..) => {
197                        bail!("❌ Failed to deploy program to {}: {}", &url, error_message)
198                    }
199                    Transaction::Execute(..) => {
200                        bail!(
201                            "❌ Failed to broadcast execution to {}: {}",
202                            &url,
203                            error_message
204                        )
205                    }
206                    Transaction::Fee(..) => {
207                        bail!(
208                            "❌ Failed to broadcast fee execution to {}: {}",
209                            &url,
210                            error_message
211                        )
212                    }
213                }
214            }
215        }
216    }
217
218    /// Returns the block hash that contains the given transaction ID.
219    ///
220    /// # Arguments
221    /// * `transaction_id` - The id of the transaction to find the block hash for.
222    ///
223    /// # Returns
224    /// The `Ok` variant wraps the block hash as `BlockHash`.
225    pub fn find_block_hash_by_transaction_id(
226        &self,
227        transaction_id: &TransactionID,
228    ) -> Result<BlockHash> {
229        let url = format!(
230            "{}/{}/find/blockHash/{}",
231            self.base_url(),
232            self.network(),
233            transaction_id
234        ).replace('"', "");
235        match self.client().get(&url).call()?.into_json() {
236            Ok(hash) => Ok(hash),
237            Err(error) => bail!("Failed to parse block hash: {error}"),
238        }
239    }
240
241    /// Retrieves the transition ID that contains the given `input ID` or `output ID` from the network.
242    ///
243    /// # Arguments
244    /// * `input_or_output_id` - The `input ID` or `output ID` to find the transition ID for.
245    ///
246    /// # Returns
247    /// The `Ok` variant wraps the transition ID as `TransitionID`.
248    pub fn find_transition_id_by_input_or_output_id(
249        &self,
250        input_or_output_id: Field,
251    ) -> Result<TransitionID> {
252        let url = format!(
253            "{}/{}/find/transitionID/{input_or_output_id}",
254            self.base_url(),
255            self.network()
256        );
257        match self.client().get(&url).call()?.into_json() {
258            Ok(transition_id) => Ok(transition_id),
259            Err(error) => bail!("Failed to parse transition ID: {error}"),
260        }
261    }
262}
263
264#[cfg(test)]
265mod test{
266    use std::str::FromStr;
267    use snarkvm::prelude::AleoID;
268    use super::*;
269
270    #[test]
271    fn test_find_transition_id_by_private_input_id() {
272        let agent = Agent::default();
273        let input_id = Field::from_str("4264902728006919131372754675851572663419744328988144775456736509806946463832field").unwrap();
274        let res = agent
275            .find_transition_id_by_input_or_output_id(input_id)
276            .expect("Failed to find transition ID by input ID");
277        assert_eq!(res, AleoID::from_str("au175l9ljm7k0r7rgp3dxkr9upp5xdlxp97zsv0gq9h4f73w9y835rs5svnk9").unwrap())
278    }
279
280    #[test]
281    fn test_find_transition_id_by_public_input_id() {
282        let agent = Agent::default();
283        let input_id = Field::from_str("1189069982776259983430750750378210517138284966096341396999865832791709072861field").unwrap();
284        let res = agent
285            .find_transition_id_by_input_or_output_id(input_id)
286            .expect("Failed to find transition ID by input ID");
287        assert_eq!(res, AleoID::from_str("au175l9ljm7k0r7rgp3dxkr9upp5xdlxp97zsv0gq9h4f73w9y835rs5svnk9").unwrap())
288    }
289
290    #[test]
291    fn test_find_transition_id_by_output_id() {
292        let agent = Agent::default();
293        let input_or_output_id = Field::from_str("6367764211666803735912595104467722517989468495803138547640524449862797481066field").unwrap();
294        let res = agent
295            .find_transition_id_by_input_or_output_id(input_or_output_id)
296            .expect("Failed to find transition ID by input ID");
297        assert_eq!(res, AleoID::from_str("au175l9ljm7k0r7rgp3dxkr9upp5xdlxp97zsv0gq9h4f73w9y835rs5svnk9").unwrap())
298    }
299
300    #[test]
301    fn test_find_block_hash_by_transaction_id() {
302        let agent = Agent::default();
303        let transaction_id = TransactionID::from_str("at1dtzwtj4kucw5wvyrdjse5quuu5kzy0psrqqlqv5zu7nxhutpdyxqwhte0h").unwrap();
304        let res = agent
305            .find_block_hash_by_transaction_id(&transaction_id)
306            .expect("Failed to find block hash by transaction ID");
307        assert_eq!(res, BlockHash::from_str("ab192jlmwheq53yd8xmezx5vr3m2pc00vafcnwrf25jv0qgt23p6sxsju0ntk").unwrap())
308    }
309
310    // #[test]
311    // fn test_get_mempool_txs(){
312    //     let agent = Agent::default();
313    //     let res = agent.get_mempool_transactions().expect("Failed to get mempool transactions");
314    //     println!("Mempool Transactions: {:?}", res);
315    // }
316
317    #[test]
318    fn test_get_transaction_by_id(){
319        let agent = Agent::default();
320        let transaction_id = "at1dtzwtj4kucw5wvyrdjse5quuu5kzy0psrqqlqv5zu7nxhutpdyxqwhte0h";
321        let res = agent.get_transaction(transaction_id).expect("Failed to get transaction by id");
322        assert_eq!(res.id(), TransactionID::from_str(transaction_id).unwrap())
323    }
324
325    #[test]
326    fn test_get_confirmed_transaction_by_id() {
327        let agent = Agent::default();
328        let transaction_id = "at1dtzwtj4kucw5wvyrdjse5quuu5kzy0psrqqlqv5zu7nxhutpdyxqwhte0h";
329        let res = agent.get_confirmed_transaction(transaction_id).expect("Failed to get confirmed transaction by id");
330        assert_eq!(res.id(), TransactionID::from_str(transaction_id).unwrap())
331    }
332}