redgold_common/client/
http.rs

1use redgold_schema::message::{Request, Response};
2use redgold_schema::{error_info, structs, ErrorInfoContext, RgResult, SafeOption};
3use redgold_schema::structs::{AboutNodeRequest, AboutNodeResponse, Address, AddressInfo, CurrencyAmount, ErrorInfo, GetActivePartyKeyRequest, GetPeersInfoRequest, HashSearchRequest, HashSearchResponse, NetworkEnvironment, NodeMetadata, PublicKey, Seed, SubmitTransactionRequest, SubmitTransactionResponse, Transaction};
4use std::time::Duration;
5use redgold_schema::explorer::DetailedAddress;
6use std::collections::HashMap;
7use serde::Serialize;
8use serde::de::DeserializeOwned;
9use tracing::debug;
10use uuid::Uuid;
11use redgold_schema::helpers::easy_json::{EasyJson, EasyJsonDeser};
12use redgold_schema::helpers::with_metadata_hashable::WithMetadataHashable;
13use redgold_schema::observability::errors::{EnhanceErrorInfo, Loggable};
14use redgold_schema::party::party_internal_data::PartyInternalData;
15use redgold_schema::proto_serde::ProtoSerde;
16use redgold_schema::util::lang_util::WithMaxLengthString;
17use redgold_schema::errors::into_error::ToErrorInfo;
18
19
20#[cfg(not(target_arch = "wasm32"))]
21use reqwest::ClientBuilder;
22
23
24pub trait RequestResponseAuth: Send + Sync  {
25    fn sign_request(&self, r: &Request) -> RgResult<Request>;
26    fn verify(&self, response: Response, intended_pk: Option<&PublicKey>) -> RgResult<Response>;
27
28    // Add this instead of deriving Clone
29    fn clone_box(&self) -> Box<dyn RequestResponseAuth>;
30}
31
32// 2. Implement clone for boxed trait object
33impl Clone for Box<dyn RequestResponseAuth> {
34    fn clone(&self) -> Self {
35        self.clone_box()
36    }
37}
38
39
40#[derive(Clone)]
41pub struct RgHttpClient {
42    pub url: String,
43    pub port: u16,
44    pub timeout: Duration,
45    pub http_proxy: Option<String>,
46    pub auth: Option<Box<dyn RequestResponseAuth>>
47}
48
49impl RgHttpClient {
50    pub async fn address_info_for_pk(&self, p0: &PublicKey) -> RgResult<AddressInfo> {
51        let mut req = Request::default();
52        req.get_address_info_public_key_request = Some(p0.clone());
53        let resp = self.proto_post_request(req, None, None).await?;
54        resp.get_address_info_public_key_response.ok_or(error_info("Missing get_address_info_response"))
55    }
56}
57
58impl RgHttpClient {
59    pub fn new(url: String, port: u16, signer: Option<Box<dyn RequestResponseAuth>>) -> Self {
60        Self {
61            url,
62            port,
63            timeout: Duration::from_secs(150),
64            http_proxy: None,
65            auth: signer,
66        }
67    }
68
69    pub fn with_http_proxy(mut self, http_proxy: String) -> Self {
70        self.http_proxy = Some(http_proxy);
71        self
72    }
73
74
75    pub async fn address_info(
76        &self,
77        address: Address,
78    ) -> Result<AddressInfo, ErrorInfo> {
79        let response = self.query_hash(address.render_string().expect("")).await?;
80        let ai = response.address_info.safe_get_msg("missing address_info")?;
81        Ok(ai.clone())
82    }
83
84    pub async fn send_transaction(
85        &self,
86        t: &Transaction,
87        sync: bool,
88    ) -> Result<SubmitTransactionResponse, ErrorInfo> {
89
90        let mut c = self.clone();
91        c.timeout = Duration::from_secs(180);
92
93        let mut request = Request::default();
94        request.submit_transaction_request = Some(SubmitTransactionRequest {
95            transaction: Some(t.clone()),
96            sync_query_response: sync,
97        });
98        debug!("Sending transaction: {}", t.clone().hash_hex());
99        let response = c.proto_post_request(request, None, None).await?;
100        response.as_error_info()?;
101        Ok(response.submit_transaction_response.safe_get()?.clone())
102    }
103
104
105
106    pub async fn balance(
107        &self,
108        address: &Address,
109    ) -> Result<i64, ErrorInfo> {
110        let response = self.query_hash(address.render_string().expect("")).await?;
111        let ai = response.address_info.safe_get_msg("missing address_info")?;
112        Ok(ai.balance)
113    }
114
115
116    pub fn url(&self) -> String {
117        format!("{}:{}", self.url, self.port)
118    }
119
120    pub fn from_env(url: String, network_environment: &NetworkEnvironment, signer: Option<Box<dyn RequestResponseAuth>>) -> Self {
121        Self {
122            url,
123            port: network_environment.default_port_offset() + 1,
124            timeout: Duration::from_secs(150),
125            http_proxy: None,
126            auth: signer,
127        }
128    }
129
130    #[allow(dead_code)]
131    fn formatted_url(&self) -> String {
132        return "http://".to_owned() + &*self.url.clone() + ":" + &*self.port.to_string();
133    }
134
135    fn metrics_url(&self) -> String {
136        format!("http://{}:{}/metrics", self.url, self.port - 2)
137    }
138
139    #[cfg(target_arch = "wasm32")]
140    pub async fn metrics(&self) -> RgResult<Vec<(String, String)>>  {
141        "error".to_error()
142    }
143    #[cfg(not(target_arch = "wasm32"))]
144    pub async fn metrics(&self) -> RgResult<Vec<(String, String)>>  {
145        let client = ClientBuilder::new().timeout(self.timeout).build().unwrap();
146        let sent = client
147            .get(self.metrics_url())
148            .send();
149        let response = sent.await.map_err(|e | error_info(e.to_string()))?;
150        let x = response.text().await;
151        let text = x.map_err(|e | error_info(e.to_string()))?;
152        let res = text.split("\n")
153            .filter(|x| !x.starts_with("#"))
154            .filter(|x| !x.trim().is_empty())
155            .map(|x| x.split(" "))
156            .map(|x| x.collect::<Vec<&str>>())
157            .flat_map(|x| x.get(0).as_ref().and_then(|k| x.get(1).as_ref().map(|v| (k.to_string(), v.to_string()))))
158            // .map(|(k, v)| (k.to_string(), v.to_string()))
159            .collect::<Vec<(String, String)>>();
160        Ok(res)
161    }
162
163    pub async fn table_sizes(&self) -> RgResult<Vec<(String, i64)>> {
164        self.json_get("v1/tables").await
165    }
166
167    pub async fn explorer_public_address(&self, pk: &PublicKey) -> RgResult<Vec<DetailedAddress>> {
168        self.json_get(format!("v1/explorer/public/address/{}", pk.hex())).await
169    }
170
171    pub async fn table_sizes_map(&self) -> RgResult<HashMap<String, i64>> {
172        self.table_sizes().await.map(|v| v.into_iter().collect())
173    }
174    pub async fn metrics_map(&self) -> RgResult<HashMap<String, String>> {
175        self.metrics().await.map(|v| v.into_iter().collect())
176    }
177
178    #[cfg(not(target_arch = "wasm32"))]
179    #[allow(dead_code)]
180    pub async fn json_post<Req: Serialize + ?Sized, Resp: DeserializeOwned>(
181        &self,
182        r: &Req,
183        endpoint: String,
184    ) -> Result<Resp, ErrorInfo> {
185        self.client()?.post(format!("{}/{}", self.formatted_url(), endpoint))
186            .json::<Req>(r)
187            .send().await.error_info("error")?.text().await.error_info("error")?.json_from()
188    }
189
190    #[cfg(not(target_arch = "wasm32"))]
191    pub fn client(&self) -> RgResult<reqwest::Client> {
192        let mut builder = ClientBuilder::new().timeout(self.timeout);
193        if let Some(h) = self.http_proxy.as_ref() {
194            builder = builder.proxy(reqwest::Proxy::http(h).error_info("Failed to build proxy")?);
195        }
196        builder.build().error_info("Failed to build client")
197    }
198
199
200    #[cfg(target_arch = "wasm32")]
201    pub async fn json_get<Resp: DeserializeOwned>(
202        &self,
203        endpoint: impl Into<String>,
204    ) -> RgResult<Resp> {
205        "error".to_error()
206    }
207
208    #[cfg(not(target_arch = "wasm32"))]
209    pub async fn json_get<Resp: DeserializeOwned>(
210        &self,
211        endpoint: impl Into<String>,
212    ) -> RgResult<Resp> {
213        self.client()?
214            .get(format!("{}/{}", self.formatted_url(), endpoint.into()))
215            .send()
216            .await
217            .error_info("Failed to send get request")?
218            .text().await.error_info("Failed to get response text")?
219            .json_from::<Resp>()
220    }
221
222    #[cfg(target_arch = "wasm32")]
223    pub async fn proto_post<Req: Sized + ProtoSerde>(
224        &self,
225        r: &Req,
226        endpoint: String,
227    ) -> Result<Response, ErrorInfo> {
228        "not".to_error()
229    }
230
231    #[cfg(not(target_arch = "wasm32"))]
232    pub async fn proto_post<Req: Sized + ProtoSerde>(
233        &self,
234        r: &Req,
235        endpoint: String,
236    ) -> Result<Response, ErrorInfo> {
237        let sent = self.client()?
238            .post(format!("{}/{}", self.formatted_url(), endpoint))
239            .body(r.encode_to_vec())
240            .send();
241        let response = sent.await.map_err(|e| ErrorInfo::error_info(
242            format!("Proto request failure: {}", e.to_string())))
243            .with_detail("url", self.url.clone())
244            .with_detail("port", self.port.clone().to_string())?;
245        let bytes = response.bytes().await.map_err(|e| ErrorInfo::error_info(
246            format!("Proto request bytes decode failure: {}", e.to_string())))?;
247        let vec = bytes.to_vec();
248        let deser = Response::deserialize(vec).map_err(|e| ErrorInfo::error_info(
249            format!("Proto request response decode failure: {}", e.to_string())))?;
250        Ok(deser)
251    }
252
253
254    #[cfg(target_arch = "wasm32")]
255    pub async fn proto_post_request(
256        &self,
257        mut r: Request,
258        nmd: Option<NodeMetadata>,
259        intended_pk: Option<&PublicKey>
260    ) -> Result<Response, ErrorInfo> {
261        "not".to_error()
262    }
263    #[cfg(not(target_arch = "wasm32"))]
264    pub async fn proto_post_request(
265        &self,
266        mut r: Request,
267        nmd: Option<NodeMetadata>,
268        intended_pk: Option<&PublicKey>
269    ) -> Result<Response, ErrorInfo> {
270        let mut self2 = self.clone();
271        if let Some(ms) = &r.request_client_timeout_millis {
272            self2.timeout = Duration::from_millis(ms.clone() as u64);
273        }
274        if r.trace_id.is_none() {
275            r.trace_id = Some(Uuid::new_v4().to_string());
276        }
277
278        if let Some(nmd) = nmd {
279            r = r.with_metadata(nmd)
280        };
281        if let Some(signer) = self.auth.as_ref() {
282            r = signer.sign_request(&r)?;
283        }
284        let result = self2.proto_post(&r, "request_proto".to_string()).await?;
285        result.as_error_info().add("Response metadata found as errorInfo")?;
286        let string = result.json_or();
287        if let Some(signer) = self.auth.as_ref() {
288            signer.verify(result, intended_pk).add("Response authentication verification failure").add(string.with_max_length(1000))
289        } else {
290            Ok(result)
291        }
292    }
293
294    pub async fn get_peers(&self) -> Result<Response, ErrorInfo> {
295        let mut req = Request::default();
296        req.get_peers_info_request = Some(GetPeersInfoRequest::default());
297        let response = self.proto_post_request(req, None, None).await?;
298        Ok(response)
299    }
300
301    pub async fn contract_state(&self, address: &Address
302                                // , utxo_id: &UtxoId
303    ) -> RgResult<structs::ContractStateMarker> {
304        let mut req = Request::default();
305        let mut cmr = structs::GetContractStateMarkerRequest::default();
306        // cmr.utxo_id = Some(utxo_id.clone());
307        cmr.address = Some(address.clone());
308        req.get_contract_state_marker_request = Some(cmr);
309        let response = self.proto_post_request(req, None, None).await?;
310        Ok(response.get_contract_state_marker_response.ok_or(error_info("Missing get_contract_state_marker_response"))?)
311    }
312
313    pub async fn about(&self) -> RgResult<AboutNodeResponse> {
314        let mut req = Request::default();
315        req.about_node_request = Some(AboutNodeRequest::default());
316        let response = self.proto_post_request(req, None, None).await?;
317        Ok(response.about_node_response.ok_or(error_info("Missing about node response"))?)
318    }
319
320    pub async fn seeds(&self) -> RgResult<Vec<Seed>> {
321        let mut req = Request::default();
322        req.get_seeds_request = Some(structs::GetSeedsRequest::default());
323        let response = self.proto_post_request(req, None, None).await?;
324        Ok(response.get_seeds_response.clone())
325    }
326
327    pub async fn active_party_key(&self) -> RgResult<PublicKey> {
328        let mut req = Request::default();
329        req.get_active_party_key_request = Some(GetActivePartyKeyRequest::default());
330        let response = self.proto_post_request(req, None, None).await?;
331        Ok(response.get_active_party_key_response.ok_or(error_info("Missing get_active_party_key_response response"))?)
332    }
333
334    pub async fn balance_pk(&self, pk: &PublicKey) -> RgResult<CurrencyAmount> {
335        let mut req = Request::default();
336        req.get_public_key_balance_request = Some(pk.clone());
337        let response = self.proto_post_request(req, None, None).await?;
338        Ok(response.get_public_key_balance_response.ok_or(error_info("Missing get_public_key_balance_response response"))?)
339    }
340
341    #[cfg(not(target_arch = "wasm32"))]
342    pub async fn party_data(&self) -> RgResult<HashMap<PublicKey, PartyInternalData>> {
343        let pid = self.json_get::<Vec<PartyInternalData>>("v1/party/data").await?;
344        let mut hm = HashMap::new();
345        for pd in pid {
346            hm.insert(pd.proposer_key.clone(), pd);
347        }
348    
349        Ok(hm)
350    }
351    #[cfg(not(target_arch = "wasm32"))]
352    pub async fn enriched_party_data(&self) -> HashMap<PublicKey, PartyInternalData> {
353        self.party_data().await.log_error().map(|mut r| {
354            r.iter_mut().for_each(|(_, v)| {
355                v.party_events.as_mut().map(|pev| {
356                    pev.portfolio_request_events.enriched_events = Some(pev.portfolio_request_events.calculate_current_fulfillment_by_event());
357                });
358            });
359            r.clone()
360        }).unwrap_or_default()
361    }
362
363    pub async fn executable_checksum(&self) -> RgResult<String> {
364        let abt = self.about().await?;
365        let latest = abt.latest_node_metadata.safe_get_msg("Missing about node metadata latest node metadata")?;
366        let checksum = latest.node_metadata()?.version_info.map(|v| v.executable_checksum.clone());
367        checksum.safe_get_msg("Missing executable checksum").cloned()
368    }
369
370    pub async fn resolve_code(&self, address: &Address) -> RgResult<structs::ResolveCodeResponse> {
371        let mut req = Request::default();
372        req.resolve_code_request = Some(address.clone());
373        let response = self.proto_post_request(req, None, None).await?;
374        Ok(response.resolve_code_response.ok_or(error_info("Missing resolve code response"))?)
375    }
376
377    pub async fn genesis(&self) -> RgResult<Transaction> {
378        let mut req = Request::default();
379        req.genesis_request = Some(structs::GenesisRequest::default());
380        let response = self.proto_post_request(req, None, None).await?;
381        response.genesis_response.ok_msg("Missing genesis response")
382    }
383
384    #[allow(dead_code)]
385    pub async fn query_hash(
386        &self,
387        input: String,
388    ) -> Result<HashSearchResponse, ErrorInfo> {
389        let mut request = Request::default();
390        request.hash_search_request = Some(HashSearchRequest {
391            search_string: input
392        });
393        Ok(self.proto_post_request(request, None, None).await?.hash_search_response.safe_get()?.clone())
394    }
395
396}