1extern crate serde_json;
10extern crate url;
11
12use reqwest::{header::CONTENT_TYPE, Client};
13use url::Url;
14
15use serde_json::Value;
16use std::{error::Error, time::Duration};
17
18use crate::utils::transaction::{Transaction, TransactionStatus};
19
20#[derive(Debug)]
28pub struct RestClient<'a> {
29 pub node_url: Vec<&'a str>,
31 pub request_time_out: u64,
33 pub poll_attemps: u64,
35 pub poll_attemp_interval_time: u64
37}
38
39#[derive(Debug)]
41pub enum RestResponse {
42 String(String),
44 Json(Value),
46 Bytes(Vec<u8>),
48}
49
50#[derive(PartialEq, Eq, Clone, Copy)]
52pub enum RestRequestMethod {
53 GET,
55 POST,
57}
58
59impl<'a> Default for RestClient<'a> {
60 fn default() -> Self {
61 return RestClient {
62 node_url: vec!["http://localhost:7740"],
63 request_time_out: 30,
64 poll_attemps: 5,
65 poll_attemp_interval_time: 5
66 };
67 }
68}
69
70#[derive(Debug)]
72pub enum TypeError {
73 FromReqClient,
75 FromRestApi,
77}
78
79#[derive(Debug)]
81pub struct RestError {
82 pub status_code: Option<String>,
84 pub error_str: Option<String>,
86 pub error_json: Option<Value>,
88 pub type_error: TypeError,
90}
91
92impl Error for RestError {}
93
94impl Default for RestError {
95 fn default() -> Self {
96 return RestError {
97 status_code: None,
98 error_str: None,
99 error_json: None,
100 type_error: TypeError::FromRestApi,
101 };
102 }
103}
104
105impl std::fmt::Display for RestError {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 let mut hsc = "N/A".to_string();
108 let mut err_str = "N/A".to_string();
109
110 if let Some(val) = &self.status_code {
111 hsc = val.clone();
112 }
113
114 if let Some(val) = &self.error_str {
115 err_str = val.clone();
116 }
117
118 write!(f, "{:?} {} {}", self.type_error, hsc, err_str)
119 }
120}
121
122impl<'a> RestClient<'a> {
123 pub async fn get_nodes_from_directory(&self, brid: &str) -> Result<Vec<String>, RestError> {
140 let directory_brid = self.get_blockchain_rid(0).await?;
141
142 let path_segments = &["query", &directory_brid];
143 let mut query_params = vec![
144 ("type", "cm_get_blockchain_api_urls"),
145 ("blockchain_rid", brid),
146 ];
147 let query_body_json = None;
148 let query_body_raw = None;
149
150 let resp = self
151 .postchain_rest_api(
152 RestRequestMethod::GET,
153 Some(path_segments),
154 Some(&mut query_params),
155 query_body_json,
156 query_body_raw
157 )
158 .await;
159
160 match resp {
161 Ok(RestResponse::Json(json_val)) => {
162 let list_of_nodes = json_val
163 .as_array()
164 .unwrap()
165 .iter()
166 .filter_map(|value| value.as_str().map(String::from))
167 .collect();
168 Ok(list_of_nodes)
169 }
170 Ok(RestResponse::String(str_val)) => Ok(vec![str_val]),
171 Ok(_) => Ok(vec!["nop".to_string()]),
172 Err(error) => {
173 tracing::error!("Can't get API urls from DC chain: {} because of error: {:?}", brid, error);
174 Err(error)
175 }
176 }
177 }
178
179 pub async fn get_blockchain_rid(&self, blockchain_iid: u8) -> Result<String, RestError> {
187 let resp: Result<RestResponse, RestError> = self
188 .postchain_rest_api(
189 RestRequestMethod::GET,
190 Some(&[&format!("/brid/iid_{}", blockchain_iid)]),
191 None,
192 None,
193 None
194 )
195 .await;
196
197 if let Err(error) = resp {
198 tracing::error!("Can't get blockchain RID with IID = {} because of error: {:?}", blockchain_iid, error);
199 return Err(error);
200 }
201
202 let resp_val: RestResponse = resp.unwrap();
203
204 match resp_val {
205 RestResponse::String(val) => Ok(val.to_string()),
206 _ => Ok("".to_string()),
207 }
208 }
209
210 pub fn print_error(&self, error: &RestError, ignore_all_errors: bool) -> bool {
219 println!(">> Error(s)");
220
221 if let Some(error_str) = &error.error_str {
222 println!("{}", error_str);
223 } else {
224 let val = &error.error_json.as_ref().unwrap();
225 let pprint = serde_json::to_string_pretty(val).unwrap();
226 println!("{}", pprint);
227 }
228
229 if ignore_all_errors {
230 println!("Allow ignore this error");
231 return false
232 }
233
234 true
235 }
236
237 pub async fn detect_merkle_hash_version(&self, brid: &str) -> u8 {
260 tracing::info!("Detecting merkle hash version of blockchain: {}", brid);
261
262 let mut merkle_hash_version = 1;
263
264 if let Ok(RestResponse::Json(json_val)) = self.postchain_rest_api(
265 RestRequestMethod::GET,
266 Some(&[&"config".to_string(), brid, &"features".to_string()]),
267 None,
268 None,
269 None
270 ).await {
271 if let Some(version) = json_val["merkle_hash_version"].as_u64() {
272 merkle_hash_version = version as u8;
273 tracing::info!("Found merkle hash version = {}", merkle_hash_version);
274 return merkle_hash_version;
275 }
276 }
277
278 tracing::warn!("Failed to detect merkle hash version, using default version = {}", merkle_hash_version);
279 merkle_hash_version
280 }
281
282 pub fn update_node_urls(&mut self, node_urls: &'a Vec<String>) {
287 self.node_url = node_urls.iter().map(String::as_str).collect();
288 }
289
290 pub async fn get_transaction_status(&self, blockchain_rid: &str, tx_rid: &str) -> Result<TransactionStatus, RestError> {
301 self.get_transaction_status_with_poll(blockchain_rid, tx_rid, 0).await
302 }
303
304 pub async fn get_transaction_status_with_poll(&self, blockchain_rid: &str, tx_rid: &str, attempts: u64) -> Result<TransactionStatus, RestError> {
314 tracing::info!("Waiting for transaction status of blockchain RID: {} with tx: {} | attempt: {}", blockchain_rid, tx_rid, attempts);
315
316 if attempts >= self.poll_attemps {
317 tracing::warn!("Transaction status still in waiting status after {} attempts", attempts);
318 return Ok(TransactionStatus::WAITING);
319 }
320
321 let resp = self.postchain_rest_api(RestRequestMethod::GET,
322 Some(&["tx", blockchain_rid, tx_rid, "status"]),
323 None,
324 None,
325 None).await?;
326 match resp {
327 RestResponse::Json(val) => {
328 let status: serde_json::Map<String, Value> = serde_json::from_value(val).unwrap();
329 if let Some(status_value) = status.get("status") {
330 let status_value = status_value.as_str();
331 match status_value {
332 Some("waiting") => {
333 tokio::time::sleep(Duration::from_secs(self.poll_attemp_interval_time)).await;
337 return Box::pin(self.get_transaction_status_with_poll(blockchain_rid, tx_rid, attempts + 1)).await;
338 },
339 Some("confirmed") => {
340 tracing::info!("Transaction confirmed!");
341 return Ok(TransactionStatus::CONFIRMED)
342 },
343 Some("rejected") => {
344 tracing::warn!("Transaction rejected!");
345 return Ok(TransactionStatus::REJECTED)
346 },
347 _ => return Ok(TransactionStatus::UNKNOWN)
348 };
349 }
350 Ok(TransactionStatus::UNKNOWN)
351 }
352 _ => {
353 Ok(TransactionStatus::UNKNOWN)
354 }
355 }
356 }
357
358 pub async fn send_transaction(&self, tx: &Transaction<'a>) -> Result<RestResponse, RestError> {
368 let txe = tx.gvt_hex_encoded();
369
370 let resq_body: serde_json::Map<String, Value> =
371 vec![("tx".to_string(), serde_json::json!(txe))]
372 .into_iter()
373 .collect();
374
375 let blockchain_rid = hex::encode(tx.blockchain_rid.clone()).as_str().to_owned();
376
377 tracing::info!("Sending transaction to {}", blockchain_rid);
378
379 self
380 .postchain_rest_api(
381 RestRequestMethod::POST,
382 Some(&["tx", &blockchain_rid]),
383 None,
384 Some(serde_json::json!(resq_body)),
385 None
386 )
387 .await
388 }
389
390 pub async fn query<T: AsRef<str>>(
404 &self,
405 brid: &str,
406 query_prefix: Option<&str>,
407 query_type: &'a str,
408 query_params: Option<&'a mut Vec<(&'a str, &'a str)>>,
409 query_args: Option<&'a mut Vec<(T, crate::utils::operation::Params)>>,
410 ) -> Result<RestResponse, RestError> {
411 let query_prefix_str = query_prefix.unwrap_or("query_gtv");
412
413 let mut query_args_converted: Option<Vec<(&str, crate::utils::operation::Params)>> = query_args.map(|args| {
414 args.iter()
415 .map(|(key, params)| (key.as_ref(), params.clone()))
416 .collect()
417 });
418
419 let encode_str = crate::encoding::gtv::encode(query_type, query_args_converted.as_mut().map(|v| v.as_mut()));
420
421 tracing::info!("Querying {} to {}", query_type, brid);
422
423 self.postchain_rest_api(
424 RestRequestMethod::POST,
425 Some(&[query_prefix_str, brid]),
426 query_params.as_deref(),
427 None,
428 Some(encode_str)
429 ).await
430 }
431
432 async fn postchain_rest_api(
444 &self,
445 method: RestRequestMethod,
446 path_segments: Option<&[&str]>,
447 query_params: Option<&'a Vec<(&'a str, &'a str)>>,
448 query_body_json: Option<Value>,
449 query_body_raw: Option<Vec<u8>>
450 ) -> Result<RestResponse, RestError> {
451 let mut node_index: usize = 0;
452 loop {
453 let result = self.postchain_rest_api_with_poll(method,
454 path_segments, query_params,
455 query_body_json.clone(), query_body_raw.clone(), node_index).await;
456
457 if let Err(ref error) = result {
458 node_index += 1;
459
460 if node_index >= self.node_url.len() || error.status_code.is_some() {
461 return result;
462 }
463 tracing::info!("The API endpoint can't be reached; will try another one!");
464 continue;
465 }
466 return result;
467 }
468 }
469
470 async fn postchain_rest_api_with_poll(
483 &self,
484 method: RestRequestMethod,
485 path_segments: Option<&[&str]>,
486 query_params: Option<&'a Vec<(&'a str, &'a str)>>,
487 query_body_json: Option<Value>,
488 query_body_raw: Option<Vec<u8>>,
489 node_index: usize,
490 ) -> Result<RestResponse, RestError> {
491
492 let mut url = Url::parse(&self.node_url[node_index]).unwrap();
493
494 tracing::info!("Requesting on API endpoint: {}", url);
495
496 if let Some(ps) = path_segments {
497 if !ps.is_empty() {
498 let psj = ps.join("/");
499 url.set_path(&psj);
500 }
501 }
502
503 if let Some(qp) = query_params {
504 if !qp.is_empty() {
505 for (name, value) in qp {
506 url.query_pairs_mut().append_pair(name, value);
507 }
508 }
509 }
510
511 if method == RestRequestMethod::POST
512 && query_body_json.is_none()
513 && query_body_raw.is_none()
514 {
515 let error_str = "Error: POST request need a body [json or binary].".to_string();
516
517 tracing::error!(error_str);
518
519 return Err(RestError {
520 type_error: TypeError::FromRestApi,
521 error_str: Some(error_str),
522 status_code: None,
523 ..Default::default()
524 });
525 }
526
527 let rest_client = Client::new();
528
529 let req_result = match method {
530 RestRequestMethod::GET => {
531 rest_client
532 .get(url.clone())
533 .timeout(Duration::from_secs(self.request_time_out))
534 .send()
535 .await
536 }
537
538 RestRequestMethod::POST => {
539 if let Some(qb) = query_body_json {
540 rest_client
541 .post(url.clone())
542 .timeout(Duration::from_secs(self.request_time_out))
543 .json(&qb)
544 .send()
545 .await
546 } else {
547 let r_body = reqwest::Body::from(query_body_raw.unwrap());
548 rest_client
549 .post(url.clone())
550 .timeout(Duration::from_secs(self.request_time_out))
551 .body(r_body)
552 .send()
553 .await
554 }
555 }
556 };
557
558 let req_result_match = match req_result {
559 Ok(resp) => {
560 let http_status_code = resp.status().to_string();
561 let http_resp_header = resp.headers().get(CONTENT_TYPE).unwrap().to_str().unwrap();
562 let json_resp = http_resp_header.contains("application/json");
563 let octet_stream_resp = http_resp_header.contains("application/octet-stream");
564
565 if http_status_code.starts_with('4') || http_status_code.starts_with('5') {
566 let mut err = RestError {
567 status_code: Some(http_status_code),
568 type_error: TypeError::FromRestApi,
569 ..Default::default()
570 };
571
572 if json_resp {
573 let error_json = resp.json().await.unwrap();
574 err.error_json = Some(error_json);
575 } else {
576 let error_str = resp.text().await.unwrap();
577 err.error_str = Some(error_str);
578 }
579
580 tracing::error!("{:?}", err);
581
582 return Err(err);
583 }
584
585 let rest_resp: RestResponse;
586
587 if json_resp {
588 let val = resp.json().await.unwrap();
589 rest_resp = RestResponse::Json(val);
590 } else if octet_stream_resp {
591 let bytes = resp.bytes().await.unwrap();
592 rest_resp = RestResponse::Bytes(bytes.to_vec());
593 } else {
594 let val = resp.text().await.unwrap();
595 rest_resp = RestResponse::String(val);
596 }
597
598 Ok(rest_resp)
599 }
600 Err(error) => {
601 let rest_error = RestError {
602 error_str: Some(error.to_string()),
603 type_error: TypeError::FromReqClient,
604 ..Default::default()};
605
606 tracing::error!("{:?}", rest_error);
607
608 Err(rest_error)
609 },
610 };
611
612 req_result_match
613 }
614}
615
616#[tokio::test]
617async fn client_detect_merkle_hash_version() {
618 let rc = RestClient{
619 node_url: vec!["https://node11.devnet1.chromia.dev:7740"],
620 ..Default::default()
621 };
622
623 let blockchain_rid = "DCE5D72ED7E1675291AFE7F9D649D898C8D3E7411E52882D03D1B3D240BDD91B";
624
625 let merkle_hash_version = rc.detect_merkle_hash_version(blockchain_rid).await;
626
627 assert_eq!(merkle_hash_version, 2);
628}