libmonero/blocks/
rpcs.rs

1/*
2 * This file is part of Monume's library libmonero
3 *
4 * Copyright (c) 2023-2024, Monume (monume.xyz)
5 * All Rights Reserved
6 * The code is distributed under MIT license, see LICENSE file for details.
7 * Generated by Monume
8 *
9 */
10
11use std::io::Cursor;
12
13use super::{block::{Block, BlockDetailsJSON, BlockHeader, EcdhInfo, Gen, KeyRawTx, MinerTxInfo, RawTx, RctSignatures, RctsigPrunable, TaggedKey, Target, Vin, VinRawTx, Vout, BPP, CLSAG}, nodes::DaemonNode};
14
15fn get_json_rpc_url(node: DaemonNode) -> String {
16    match node.tls {
17        true => format!("https://{}:{}/json_rpc", node.url, node.port),
18        false => format!("http://{}:{}/json_rpc", node.url, node.port),
19    }
20}
21
22fn get_rpc_url(node: DaemonNode) -> String {
23    match node.tls {
24        true => format!("https://{}:{}", node.url, node.port),
25        false => format!("http://{}:{}", node.url, node.port),
26    }
27}
28
29/// Gets the block from the given daemon by its height
30/// Returns the block as a Block struct if succesfull
31/// Returns an error message if not succesfull
32/// 
33/// Example:
34/// ```
35/// use libmonero::blocks::get_block_from_height;
36/// use libmonero::blocks::DaemonNode;
37/// 
38/// let block = get_block_from_height(3000000, DaemonNode::cake_wallet_default()).unwrap();
39/// println!("Block hash: {}", block.block_header.hash);
40/// ```
41pub fn get_block_from_height(block_height: u64, node: DaemonNode) -> Result<Block, String> {
42    let rpc_url = get_json_rpc_url(node);
43    let response = ureq::post(&rpc_url)
44        .set("Content-Type", "application/json")
45        .send_json(ureq::json!({
46            "jsonrpc": "2.0",
47            "id": "0",
48            "method": "get_block",
49            "params": {
50                "height": block_height
51            }
52        }));
53    if let Err(e) = response.as_ref() {
54        return Err(format!("Error while getting the block from daemon: {}", e));
55    }
56    let response:  serde_json::Value = response.unwrap().into_json().unwrap();
57    let block_header = BlockHeader {
58        block_size: response["result"]["block_header"]["block_size"].as_u64().unwrap(),
59        block_weight: response["result"]["block_header"]["block_weight"].as_u64().unwrap(),
60        cumulative_difficulty: response["result"]["block_header"]["cumulative_difficulty"].as_u64().unwrap(),
61        cumulative_difficulty_top64: response["result"]["block_header"]["cumulative_difficulty_top64"].as_u64().unwrap(),
62        depth: response["result"]["block_header"]["depth"].as_u64().unwrap(),
63        difficulty: response["result"]["block_header"]["difficulty"].as_u64().unwrap(),
64        difficulty_top64: response["result"]["block_header"]["difficulty_top64"].as_u64().unwrap(),
65        hash: response["result"]["block_header"]["hash"].as_str().unwrap().to_string(),
66        height: response["result"]["block_header"]["height"].as_u64().unwrap(),
67        long_term_weight: response["result"]["block_header"]["long_term_weight"].as_u64().unwrap(),
68        major_version: response["result"]["block_header"]["major_version"].as_u64().unwrap(),
69        miner_tx_hash: response["result"]["block_header"]["miner_tx_hash"].as_str().unwrap().to_string(),
70        minor_version: response["result"]["block_header"]["minor_version"].as_u64().unwrap(),
71        nonce: response["result"]["block_header"]["nonce"].as_u64().unwrap(),
72        num_txes: response["result"]["block_header"]["num_txes"].as_u64().unwrap(),
73        orphan_status: response["result"]["block_header"]["orphan_status"].as_bool().unwrap(),
74        pow_hash: response["result"]["block_header"]["pow_hash"].as_str().unwrap().to_string(),
75        prev_hash: response["result"]["block_header"]["prev_hash"].as_str().unwrap().to_string(),
76        reward: response["result"]["block_header"]["reward"].as_u64().unwrap(),
77        timestamp: response["result"]["block_header"]["timestamp"].as_u64().unwrap(),
78        wide_cumulative_difficulty: response["result"]["block_header"]["wide_cumulative_difficulty"].as_str().unwrap().to_string(),
79        wide_difficulty: response["result"]["block_header"]["wide_difficulty"].as_str().unwrap().to_string(),
80    };
81    let json = response["result"]["json"].as_str().unwrap().to_string();
82    let parsed_json: serde_json::Value = serde_json::from_str(&json).unwrap_or(serde_json::Value::Null);
83    if parsed_json.is_null() {
84        return Err("Error while parsing the block JSON".to_string());
85    }
86    let vin_array = parsed_json["miner_tx"]["vin"].as_array().unwrap();
87    let mut vin_vec: Vec<Vin> = Vec::new();
88    for vin in vin_array {
89        vin_vec.push(Vin {
90            gen: Gen {
91                height: vin["gen"]["height"].as_u64().unwrap_or(0),
92            }
93        });
94    };
95    let vout_array = parsed_json["miner_tx"]["vout"].as_array().unwrap();
96    let mut vout_vec: Vec<Vout> = Vec::new();
97    for vout in vout_array {
98        vout_vec.push(Vout {
99            amount: vout["amount"].as_u64().unwrap_or(0),
100            target: Target {
101                tagged_key: TaggedKey {
102                    key: vout["target"]["key"].as_str().unwrap_or("").to_string(),
103                    view_tag: vout["target"]["view_tag"].as_str().unwrap_or("").to_string(),
104                }
105            }
106        });
107    };
108    Ok(Block {
109        blob: response["result"]["blob"].as_str().unwrap_or("").to_string(),
110        block_header,
111        credits: response["result"]["credits"].as_u64().unwrap_or(0),
112        json: BlockDetailsJSON {
113            major_version: parsed_json["major_version"].as_u64().unwrap_or(0),
114            minor_version: parsed_json["minor_version"].as_u64().unwrap_or(0),
115            timestamp: parsed_json["timestamp"].as_u64().unwrap_or(0),
116            prev_id: parsed_json["prev_id"].as_str().unwrap_or("").to_string(),
117            nonce: parsed_json["nonce"].as_u64().unwrap_or(0),
118            miner_tx: MinerTxInfo {
119                version: parsed_json["miner_tx"]["version"].as_u64().unwrap_or(0),
120                unlock_time: parsed_json["miner_tx"]["unlock_time"].as_u64().unwrap_or(0),
121                vin: vin_vec,
122                vout: vout_vec,
123                extra: parsed_json["miner_tx"]["extra"].as_str().unwrap_or("").as_bytes().to_vec(),
124                rct_signatures: RctSignatures {
125                    type_int: parsed_json["miner_tx"]["rct_signatures"]["type"].as_u64().unwrap_or(0),
126                    txn_fee: 0,
127                    ecdh_info: Vec::new(),
128                    out_pk: Vec::new(),
129                }
130            },
131            tx_hashes: parsed_json["tx_hashes"].as_array().unwrap().to_vec().iter().map(|x| x.as_str().unwrap().to_string()).collect(),
132        },
133        miner_tx_hash: response["result"]["miner_tx_hash"].as_str().unwrap_or("").to_string(),
134        status: response["result"]["status"].as_str().unwrap_or("ERROR").to_string(),
135        top_hash: response["result"]["top_hash"].as_str().unwrap_or("").to_string(),
136        untrusted: response["result"]["untrusted"].as_bool().unwrap_or(false),
137    })
138}
139
140/// Gets the current height of the blockchain from the given daemon
141/// Returns the height as a u64 if succesfull
142/// Returns an error message if not succesfull
143/// 
144/// Example:
145/// ```
146/// use libmonero::blocks::get_height;
147/// use libmonero::blocks::DaemonNode;
148/// 
149/// let height = get_height(DaemonNode::cake_wallet_default()).unwrap();
150/// println!("Current height: {}", height);
151/// ```
152pub fn get_height(node: DaemonNode) -> Result<u64, String> {
153    let rpc_url = get_rpc_url(node);
154    let reader = Cursor::new(Vec::new());
155    let response = ureq::get(format!("{}/get_height", &rpc_url).as_str())
156        .set("Content-Type", "application/json").send(reader);
157    if let Err(e) = response.as_ref() {
158        return Err(format!("Error while getting the block count (height) from daemon: {}", e));
159    }
160    let response:  serde_json::Value = response.unwrap().into_json().unwrap_or(serde_json::Value::Null);
161    if response.is_null() {
162        return Err("Error while parsing the block count (height) JSON".to_string());
163    }
164    Ok(response["height"].as_u64().unwrap_or(0))
165}
166
167/// Gets the transaction from the given daemon by its hash
168/// Returns the transaction as a RawTx struct if succesfull
169/// Returns an error message if not succesfull
170/// 
171/// Example:
172/// ```
173/// use libmonero::blocks::get_transaction_from_hash;
174/// use libmonero::blocks::DaemonNode;
175/// 
176/// let tx_hash = "e4516854a5984eaf5f8750ac7af41d1e0b2c602a2297a673001e8c0af88eba11";
177/// let tx = get_transaction_from_hash(tx_hash.to_string(), DaemonNode::cake_wallet_default()).unwrap();
178/// println!("Unlock time: {}", tx.unlock_time);
179/// ```
180pub fn get_transaction_from_hash(hash: String, node: DaemonNode) -> Result<RawTx, String> {
181    let rpc_url = format!("{}/get_transactions", get_rpc_url(node));
182    let response = ureq::post(&rpc_url)
183        .set("Content-Type", "application/json")
184        .send_json(ureq::json!({
185            "txs_hashes": [hash],
186            "decode_as_json": true,
187        }));
188    if let Err(e) = response.as_ref() {
189        return Err(format!("Error while getting the transaction from daemon: {}", e));
190    }
191    let response: serde_json::Value = response.unwrap().into_json().unwrap_or(serde_json::Value::Null);
192    if response.is_null() {
193        return Err("Error while parsing the transaction JSON".to_string());
194    }
195    let json_part = response["txs"][0]["as_json"].as_str().unwrap_or("").to_string();
196    if json_part.is_empty() {
197        return Err("Error while getting the as_json part".to_string());
198    }
199    let json_final = serde_json::from_str(&json_part).unwrap_or(serde_json::Value::Null);
200    if json_final.is_null() {
201        return Err("Error while parsing the as_json part".to_string());
202    }
203    let mut vin_raw_tx = Vec::new();
204    for vin in json_final["vin"].as_array().unwrap().iter() {
205        vin_raw_tx.push(VinRawTx {
206            key: KeyRawTx {
207                amount: vin["key"]["amount"].as_u64().unwrap_or(0),
208                key_offsets: vin["key"]["key_offsets"].as_array().unwrap_or(&Vec::new()).to_vec().iter().map(|x| x.as_u64().unwrap_or(0)).collect(),
209                k_image: vin["key"]["k_image"].as_str().unwrap_or("").to_string(),
210            }
211        });
212    }
213    let mut vout_raw_tx = Vec::new();
214    for vout in json_final["vout"].as_array().unwrap().iter() {
215        vout_raw_tx.push(Vout {
216            amount: vout["amount"].as_u64().unwrap_or(0),
217            target: Target {
218                tagged_key: TaggedKey {
219                    key: vout["target"]["tagged_key"]["key"].as_str().unwrap_or("").to_string(),
220                    view_tag: vout["target"]["tagged_key"]["view_tag"].as_str().unwrap_or("").to_string(),
221                }
222            }
223        });
224    }
225    let mut ecdh_raw_tx = Vec::new();
226    for ecdh in json_final["rct_signatures"]["ecdhInfo"].as_array().unwrap().iter() {
227        ecdh_raw_tx.push(EcdhInfo {
228            trunc_amount: ecdh["trunc_amount"].as_str().unwrap_or("").to_string(),
229        });
230    }
231    let mut bpp_raw_tx = Vec::new();
232    for bpp in json_final["rctsig_prunable"]["bpp"].as_array().unwrap().iter() {
233        bpp_raw_tx.push(BPP {
234            A: bpp["A"].as_str().unwrap_or("").to_string(),
235            A1: bpp["A1"].as_str().unwrap_or("").to_string(),
236            B: bpp["B"].as_str().unwrap_or("").to_string(),
237            r1: bpp["r1"].as_str().unwrap_or("").to_string(),
238            s1: bpp["s1"].as_str().unwrap_or("").to_string(),
239            d1: bpp["d1"].as_str().unwrap_or("").to_string(),
240            L: {
241                let mut l = Vec::new();
242                for l_part in bpp["L"].as_array().unwrap_or(&Vec::new()).to_vec().iter() {
243                    l.push(l_part.as_str().unwrap_or("").to_string());
244                }
245                l
246            },
247            R: {
248                let mut r = Vec::new();
249                for r_part in bpp["R"].as_array().unwrap_or(&Vec::new()).to_vec().iter() {
250                    r.push(r_part.as_str().unwrap_or("").to_string());
251                }
252                r
253            }
254        });
255    }
256    let mut clsags_raw_tx = Vec::new();
257    for clsag in json_final["rctsig_prunable"]["CLSAGs"].as_array().unwrap().iter() {
258        clsags_raw_tx.push(CLSAG {
259            s: {
260                let mut s = Vec::new();
261                for s_part in clsag["s"].as_array().unwrap_or(&Vec::new()).to_vec().iter() {
262                    s.push(s_part.as_str().unwrap_or("").to_string());
263                }
264                s
265            },
266            c1: clsag["c1"].as_str().unwrap_or("").to_string(),
267            D: clsag["D"].as_str().unwrap_or("").to_string(),
268        });
269    }
270    Ok(RawTx {
271        version: json_final["version"].as_u64().unwrap_or(0),
272        unlock_time: json_final["unlock_time"].as_u64().unwrap_or(0),
273        vin: vin_raw_tx,
274        vout: vout_raw_tx,
275        extra: json_final["extra"].as_str().unwrap_or("").as_bytes().to_vec(),
276        rct_signatures: RctSignatures {
277            type_int: json_final["rct_signatures"]["type"].as_u64().unwrap_or(0),
278            txn_fee: json_final["rct_signatures"]["txnFee"].as_u64().unwrap_or(0),
279            ecdh_info: ecdh_raw_tx,
280            out_pk: json_final["rct_signatures"]["outPk"].as_array().unwrap().to_vec().iter().map(|x| x.as_str().unwrap_or("").to_string()).collect(),
281        },
282        rctsig_prunable: RctsigPrunable {
283            nbp: json_final["rctsig_prunable"]["nbp"].as_u64().unwrap_or(0),
284            bpp: bpp_raw_tx,
285            CLSAGs: clsags_raw_tx,
286            pseudo_outs: json_final["rctsig_prunable"]["pseudoOuts"].as_array().unwrap().to_vec().iter().map(|x| x.as_str().unwrap_or("").to_string()).collect(),
287        }
288    })
289}