ergo_node_interface/
scanning.rs

1//! A struct `Scan` is defined here which wraps the concept of UTXO-set
2//! scanning in a Rust-based struct interface.
3
4use crate::node_interface::NodeInterface;
5pub use crate::node_interface::{NodeError, Result};
6use crate::ScanId;
7use ergo_lib::ergotree_ir::chain::ergo_box::ErgoBox;
8use serde_json::{from_str, Value};
9use serde_json::{json, to_string_pretty};
10
11/// Scanning-related endpoints
12impl NodeInterface {
13    /// Registers a scan with the node and either returns the `scan_id`
14    /// or an error
15    pub fn register_scan(&self, scan_json: Value) -> Result<ScanId> {
16        let endpoint = "/scan/register";
17        let body = scan_json.to_string();
18        let res = self.send_post_req(endpoint, body);
19        let res_json = self.parse_response_to_json(res)?;
20
21        if res_json["error"].is_null() {
22            let scan_id = res_json["scanId"].to_string().parse::<ScanId>()?;
23            Ok(scan_id)
24        } else {
25            Err(NodeError::BadRequest(res_json["error"].to_string()))
26        }
27    }
28
29    pub fn deregister_scan(&self, scan_id: ScanId) -> Result<ScanId> {
30        let endpoint = "/scan/deregister";
31        let body = generate_deregister_scan_json(scan_id);
32        let res = self.send_post_req(endpoint, body);
33        let res_json = self.parse_response_to_json(res)?;
34
35        if res_json["error"].is_null() {
36            let scan_id = res_json["scanId"].to_string().parse::<ScanId>()?;
37            Ok(scan_id)
38        } else {
39            Err(NodeError::BadRequest(res_json["error"].to_string()))
40        }
41    }
42
43    /// Using the `scan_id` of a registered scan, acquires unspent boxes which have been found by said scan
44    pub fn scan_boxes(&self, scan_id: ScanId) -> Result<Vec<ErgoBox>> {
45        let endpoint = format!("/scan/unspentBoxes/{scan_id}");
46        let res = self.send_get_req(&endpoint);
47        let res_json = self.parse_response_to_json(res)?;
48
49        let mut box_list = vec![];
50        for i in 0.. {
51            let box_json = &res_json[i]["box"];
52            if box_json.is_null() {
53                break;
54            } else {
55                let res_ergo_box = from_str(&box_json.to_string());
56                if let Ok(ergo_box) = res_ergo_box {
57                    box_list.push(ergo_box);
58                } else if let Err(e) = res_ergo_box {
59                    let mess = format!("Box Json: {box_json}\nError: {e:?}");
60                    return Err(NodeError::FailedParsingBox(mess));
61                }
62            }
63        }
64        Ok(box_list)
65    }
66
67    /// Using the `scan_id` of a registered scan, manually adds a box to said
68    /// scan.
69    pub fn add_box_to_scan(&self, scan_id: ScanId, box_id: &String) -> Result<String> {
70        let ergo_box = serde_json::to_string(&self.box_from_id(box_id)?)
71            .map_err(|_| NodeError::FailedParsingBox(box_id.clone()))?;
72        let scan_id_int: u64 = scan_id.into();
73        let endpoint = "/scan/addBox";
74
75        let body = json! ({
76            "scanIds": vec![scan_id_int],
77            "box": ergo_box,
78        });
79        let res = self.send_post_req(endpoint, body.to_string());
80        let res_json = self.parse_response_to_json(res)?;
81        if res_json["error"].is_null() {
82            Ok(res_json.to_string())
83        } else {
84            Err(NodeError::BadRequest(res_json["error"].to_string()))
85        }
86    }
87}
88
89fn generate_deregister_scan_json(scan_id: ScanId) -> String {
90    let body = json!({
91        "scanId": scan_id,
92    });
93    to_string_pretty(&body).unwrap()
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_generate_deregister_scan_json() {
102        let scan_id = ScanId::from(100);
103        expect_test::expect![[r#"
104            {
105              "scanId": 100
106            }"#]]
107        .assert_eq(&generate_deregister_scan_json(scan_id));
108    }
109}