nym_validator_client/rpc/
reqwest.rs1use crate::rpc::TendermintRpcClient;
5use async_trait::async_trait;
6use base64::Engine;
7use cosmrs::tendermint;
8use cosmrs::tendermint::{block::Height, evidence::Evidence, Hash};
9use reqwest::header::HeaderMap;
10use reqwest::{header, RequestBuilder};
11use tendermint_rpc::dialect::{v0_34, v0_37, v0_38, LatestDialect};
12use tendermint_rpc::{
13 client::CompatMode,
14 dialect::{self, Dialect},
15 endpoint::{self, *},
16 query::Query,
17 Error, Order, Response, SimpleRequest,
18};
19use url::Url;
20
21macro_rules! perform_with_compat {
23 ($self:expr, $request:expr) => {{
24 let request = $request;
25 match $self.compat {
26 CompatMode::V0_38 => {
27 $self
28 .perform_request_with_dialect(request, dialect::v0_38::Dialect)
29 .await
30 }
31 CompatMode::V0_37 => {
32 $self
33 .perform_request_with_dialect(request, dialect::v0_37::Dialect)
34 .await
35 }
36 CompatMode::V0_34 => {
37 $self
38 .perform_request_with_dialect(request, dialect::v0_34::Dialect)
39 .await
40 }
41 }
42 }};
43}
44
45#[deprecated(note = "use HttpClient directly instead")]
47pub struct ReqwestRpcClient {
48 compat: CompatMode,
49 inner: reqwest::Client,
50 url: Url,
51}
52
53#[allow(deprecated)]
54impl ReqwestRpcClient {
55 pub fn new(url: Url) -> Self {
56 ReqwestRpcClient {
57 compat: CompatMode::V0_37,
59 inner: reqwest::Client::new(),
60 url,
61 }
62 }
63
64 pub fn set_compat_mode(&mut self, compat: CompatMode) {
65 self.compat = compat;
66 }
67
68 fn build_request<R, S>(&self, request: R) -> RequestBuilder
69 where
70 R: SimpleRequest<S>,
71 S: Dialect,
72 {
73 let mut headers = HeaderMap::new();
74 headers.insert(header::CONTENT_TYPE, "application/json".parse().unwrap());
75 headers.insert(
76 header::USER_AGENT,
77 format!("nym-reqwest-rpc-client/{}", env!("CARGO_PKG_VERSION"))
78 .parse()
79 .unwrap(),
80 );
81 if let Some(auth) = extract_authorization(&self.url) {
82 headers.insert(header::AUTHORIZATION, auth.parse().unwrap());
83 }
84
85 self.inner
86 .post(self.url.clone())
87 .body(request.into_json().into_bytes())
88 .headers(headers)
89 }
90
91 async fn perform_request_with_dialect<R, S>(
92 &self,
93 request: R,
94 _dialect: S,
95 ) -> Result<R::Output, Error>
96 where
97 R: SimpleRequest<S>,
98 S: Dialect,
99 {
100 let request = self.build_request(request);
101 let response = request
103 .send()
104 .await
105 .map_err(TendermintRpcErrorMap::into_rpc_err)?;
106 let response_status = response.status();
107 let bytes = response
108 .bytes()
109 .await
110 .map_err(TendermintRpcErrorMap::into_rpc_err)?;
111
112 if response_status != reqwest::StatusCode::OK {
117 return Err(Error::http_request_failed(
119 response_status.as_u16().try_into().unwrap(),
120 ));
121 }
122
123 R::Response::from_string(bytes).map(Into::into)
124 }
125}
126
127trait TendermintRpcErrorMap {
128 fn into_rpc_err(self) -> Error;
129}
130
131impl TendermintRpcErrorMap for reqwest::Error {
132 fn into_rpc_err(self) -> Error {
133 todo!()
134 }
135}
136
137#[allow(deprecated)]
138#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
139#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
140impl TendermintRpcClient for ReqwestRpcClient {
141 async fn perform<R>(&self, request: R) -> Result<R::Output, Error>
142 where
143 R: SimpleRequest,
144 {
145 self.perform_request_with_dialect(request, LatestDialect)
146 .await
147 }
148
149 async fn block<H>(&self, height: H) -> Result<endpoint::block::Response, Error>
150 where
151 H: Into<Height> + Send,
152 {
153 perform_with_compat!(self, endpoint::block::Request::new(height.into()))
154 }
155
156 async fn block_by_hash(
157 &self,
158 hash: tendermint::Hash,
159 ) -> Result<endpoint::block_by_hash::Response, Error> {
160 perform_with_compat!(self, endpoint::block_by_hash::Request::new(hash))
161 }
162
163 async fn latest_block(&self) -> Result<endpoint::block::Response, Error> {
164 perform_with_compat!(self, endpoint::block::Request::default())
165 }
166
167 async fn block_results<H>(&self, height: H) -> Result<endpoint::block_results::Response, Error>
168 where
169 H: Into<Height> + Send,
170 {
171 perform_with_compat!(self, endpoint::block_results::Request::new(height.into()))
172 }
173
174 async fn latest_block_results(&self) -> Result<endpoint::block_results::Response, Error> {
175 perform_with_compat!(self, endpoint::block_results::Request::default())
176 }
177
178 async fn block_search(
179 &self,
180 query: Query,
181 page: u32,
182 per_page: u8,
183 order: Order,
184 ) -> Result<endpoint::block_search::Response, Error> {
185 perform_with_compat!(
186 self,
187 endpoint::block_search::Request::new(query, page, per_page, order)
188 )
189 }
190
191 async fn header<H>(&self, height: H) -> Result<endpoint::header::Response, Error>
192 where
193 H: Into<Height> + Send,
194 {
195 let height = height.into();
196 match self.compat {
197 CompatMode::V0_38 => {
198 self.perform_request_with_dialect(
199 endpoint::header::Request::new(height),
200 v0_38::Dialect,
201 )
202 .await
203 }
204 CompatMode::V0_37 => {
205 self.perform_request_with_dialect(
206 endpoint::header::Request::new(height),
207 v0_37::Dialect,
208 )
209 .await
210 }
211 CompatMode::V0_34 => {
212 let resp = self
215 .perform_request_with_dialect(block::Request::new(height), v0_34::Dialect)
216 .await?;
217 Ok(resp.into())
218 }
219 }
220 }
221
222 async fn header_by_hash(&self, hash: Hash) -> Result<header_by_hash::Response, Error> {
223 match self.compat {
224 CompatMode::V0_38 => {
225 self.perform_request_with_dialect(
226 header_by_hash::Request::new(hash),
227 v0_38::Dialect,
228 )
229 .await
230 }
231 CompatMode::V0_37 => {
232 self.perform_request_with_dialect(
233 header_by_hash::Request::new(hash),
234 v0_37::Dialect,
235 )
236 .await
237 }
238 CompatMode::V0_34 => {
239 let resp = self
242 .perform_request_with_dialect(block_by_hash::Request::new(hash), v0_34::Dialect)
243 .await?;
244 Ok(resp.into())
245 }
246 }
247 }
248
249 async fn broadcast_evidence(&self, e: Evidence) -> Result<evidence::Response, Error> {
251 match self.compat {
252 CompatMode::V0_38 => {
253 self.perform_request_with_dialect(evidence::Request::new(e), v0_38::Dialect)
254 .await
255 }
256 CompatMode::V0_37 => {
257 self.perform_request_with_dialect(evidence::Request::new(e), v0_37::Dialect)
258 .await
259 }
260 CompatMode::V0_34 => {
261 self.perform_request_with_dialect(evidence::Request::new(e), v0_34::Dialect)
262 .await
263 }
264 }
265 }
266
267 async fn tx(&self, hash: Hash, prove: bool) -> Result<tx::Response, Error> {
268 perform_with_compat!(self, tx::Request::new(hash, prove))
269 }
270
271 async fn tx_search(
272 &self,
273 query: Query,
274 prove: bool,
275 page: u32,
276 per_page: u8,
277 order: Order,
278 ) -> Result<tx_search::Response, Error> {
279 perform_with_compat!(
280 self,
281 tx_search::Request::new(query, prove, page, per_page, order)
282 )
283 }
284
285 async fn broadcast_tx_commit<T>(&self, tx: T) -> Result<broadcast::tx_commit::Response, Error>
286 where
287 T: Into<Vec<u8>> + Send,
288 {
289 perform_with_compat!(self, broadcast::tx_commit::Request::new(tx))
290 }
291}
292
293pub fn extract_authorization(url: &Url) -> Option<String> {
295 if !url.has_authority() {
296 return None;
297 }
298
299 let authority = url.authority();
300 if let Some((userpass, _)) = authority.split_once('@') {
301 Some(base64::prelude::BASE64_STANDARD.encode(userpass))
302 } else {
303 None
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310
311 #[cfg(test)]
312 mod extracting_url_authorization {
313 use super::*;
314 use std::str::FromStr;
315
316 #[test]
317 fn extract_auth_absent() {
318 let uri = Url::from_str("http://example.com").unwrap();
319 assert_eq!(extract_authorization(&uri), None);
320 }
321
322 #[test]
323 fn extract_auth_username_only() {
324 let uri = Url::from_str("http://toto@example.com").unwrap();
325 let base64 = "dG90bw==".to_string();
326 assert_eq!(extract_authorization(&uri), Some(base64));
327 }
328
329 #[test]
330 fn extract_auth_username_password() {
331 let uri = Url::from_str("http://toto:tata@example.com").unwrap();
332 let base64 = "dG90bzp0YXRh".to_string();
333 assert_eq!(extract_authorization(&uri), Some(base64));
334 }
335 }
336}