ckb_sdk/rpc/
mod.rs

1mod ckb;
2pub mod ckb_indexer;
3pub mod ckb_light_client;
4
5use anyhow::anyhow;
6
7#[cfg(not(target_arch = "wasm32"))]
8pub use ckb::CkbRpcClient;
9#[cfg(not(target_arch = "wasm32"))]
10pub use ckb_indexer::IndexerRpcClient;
11#[cfg(not(target_arch = "wasm32"))]
12pub use ckb_light_client::LightClientRpcClient;
13
14pub use ckb::CkbRpcAsyncClient;
15pub use ckb_indexer::IndexerRpcAsyncClient;
16use ckb_jsonrpc_types::{JsonBytes, ResponseFormat};
17pub use ckb_light_client::LightClientRpcAsyncClient;
18#[cfg(not(target_arch = "wasm32"))]
19use std::future::Future;
20use thiserror::Error;
21
22#[cfg(not(target_arch = "wasm32"))]
23pub(crate) fn block_on<F: Send>(future: impl Future<Output = F> + Send) -> F {
24    match tokio::runtime::Handle::try_current() {
25        Ok(h)
26            if matches!(
27                h.runtime_flavor(),
28                tokio::runtime::RuntimeFlavor::MultiThread
29            ) =>
30        {
31            tokio::task::block_in_place(|| h.block_on(future))
32        }
33        // if we on the current runtime, it must use another thread to poll this future,
34        // can't block on current runtime, it will block current reactor to stop forever
35        // in tokio runtime, this time will panic
36        Ok(_) => std::thread::scope(|s| {
37            s.spawn(|| {
38                tokio::runtime::Builder::new_current_thread()
39                    .enable_all()
40                    .build()
41                    .unwrap()
42                    .block_on(future)
43            })
44            .join()
45            .unwrap()
46        }),
47        Err(_) => tokio::runtime::Builder::new_current_thread()
48            .enable_all()
49            .build()
50            .unwrap()
51            .block_on(future),
52    }
53}
54
55#[derive(Error, Debug)]
56pub enum RpcError {
57    #[error("parse json error: `{0}`")]
58    Json(#[from] serde_json::Error),
59    #[error("http error: `{0}`")]
60    Http(#[from] reqwest::Error),
61    #[error("jsonrpc error: `{0}`")]
62    Rpc(#[from] jsonrpc_core::Error),
63    #[error(transparent)]
64    Other(#[from] anyhow::Error),
65}
66
67#[cfg(not(target_arch = "wasm32"))]
68#[macro_export]
69macro_rules! jsonrpc {
70    (
71        $(#[$struct_attr:meta])*
72        pub struct $struct_name:ident {$(
73            $(#[$attr:meta])*
74            pub fn $method:ident(& $selff:ident $(, $arg_name:ident: $arg_ty:ty)*)
75                -> $return_ty:ty;
76        )*}
77    ) => (
78        $(#[$struct_attr])*
79        pub struct $struct_name {
80            pub(crate) client: $crate::rpc::RpcClient,
81            pub(crate) id: std::sync::atomic::AtomicU64,
82        }
83
84        impl Clone for $struct_name {
85            fn clone(&self) -> Self {
86                Self {
87                    client: self.client.clone(),
88                    id: 0.into()
89                }
90            }
91        }
92
93        impl $struct_name {
94            pub fn new(uri: &str) -> Self {
95                $struct_name {  id: 0.into(), client: $crate::rpc::RpcClient::new(uri), }
96            }
97
98            pub fn new_with_cookie(uri: &str) -> Self {
99                $struct_name { id: 0.into(), client: $crate::rpc::RpcClient::new_with_cookie(uri), }
100            }
101
102            pub fn with_builder<F>(uri: &str, f: F) -> Result<Self, anyhow::Error>
103            where
104                F: FnOnce(reqwest::ClientBuilder) -> reqwest::ClientBuilder,
105            {
106                let client = $crate::rpc::RpcClient::with_builder(uri, f)?;
107                Ok($struct_name { id: 0.into(), client })
108            }
109
110            pub fn post<PARAM, RET>(&self, method:&str, params: PARAM)->Result<RET, $crate::rpc::RpcError>
111            where
112                PARAM:serde::ser::Serialize + Send + 'static,
113                RET: serde::de::DeserializeOwned + Send + 'static,
114            {
115                let id = self.id.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
116                let params_fn = || -> Result<_,_> {
117                    let params = serde_json::to_value(params)?;
118                    let mut req_json = serde_json::Map::new();
119                    req_json.insert("id".to_owned(), serde_json::json!(id));
120                    req_json.insert("jsonrpc".to_owned(), serde_json::json!("2.0"));
121                    req_json.insert("method".to_owned(), serde_json::json!(method));
122                    req_json.insert("params".to_owned(), params);
123                    Ok(req_json)
124                };
125
126                let task = self.client.post(params_fn);
127                $crate::rpc::block_on(task)
128
129            }
130
131            $(
132                $(#[$attr])*
133                pub fn $method(&$selff $(, $arg_name: $arg_ty)*) -> Result<$return_ty, $crate::rpc::RpcError> {
134                    let method = String::from(stringify!($method));
135                    let params = $crate::serialize_parameters!($($arg_name,)*);
136                    let id = $selff.id.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
137
138                    let params_fn = || -> Result<_,_> {
139                        let mut req_json = serde_json::Map::new();
140                        req_json.insert("id".to_owned(), serde_json::json!(id));
141                        req_json.insert("jsonrpc".to_owned(), serde_json::json!("2.0"));
142                        req_json.insert("method".to_owned(), serde_json::json!(method));
143                        req_json.insert("params".to_owned(), params);
144                        Ok(req_json)
145                    };
146
147                    let task = $selff.client.post(params_fn);
148                    $crate::rpc::block_on(task)
149                }
150            )*
151        }
152    )
153}
154
155#[macro_export]
156macro_rules! jsonrpc_async {
157    (
158        $(#[$struct_attr:meta])*
159        pub struct $struct_name:ident {$(
160            $(#[$attr:meta])*
161            pub fn $method:ident(& $selff:ident $(, $arg_name:ident: $arg_ty:ty)*)
162                -> $return_ty:ty;
163        )*}
164    ) => (
165        $(#[$struct_attr])*
166        pub struct $struct_name {
167            pub(crate) client: $crate::rpc::RpcClient,
168            pub(crate) id: std::sync::atomic::AtomicU64,
169        }
170
171        impl Clone for $struct_name {
172            fn clone(&self) -> Self {
173                Self {
174                    client: self.client.clone(),
175                    id: 0.into()
176                }
177            }
178        }
179
180        impl $struct_name {
181            pub fn new(uri: &str) -> Self {
182                $struct_name {  id: 0.into(), client: $crate::rpc::RpcClient::new(uri), }
183            }
184
185            #[cfg(not(target_arch="wasm32"))]
186            pub fn new_with_cookie(uri: &str) -> Self {
187                $struct_name { id: 0.into(), client: $crate::rpc::RpcClient::new_with_cookie(uri), }
188            }
189
190            pub fn with_builder<F>(uri: &str, f: F) -> Result<Self, anyhow::Error>
191            where
192                F: FnOnce(reqwest::ClientBuilder) -> reqwest::ClientBuilder,
193            {
194                let client = $crate::rpc::RpcClient::with_builder(uri, f)?;
195                Ok($struct_name { id: 0.into(), client })
196            }
197
198            #[cfg(not(target_arch="wasm32"))]
199            pub fn post<PARAM, RET>(&self, method:&str, params: PARAM)->impl std::future::Future<Output =Result<RET, $crate::rpc::RpcError>> + Send + 'static
200            where
201                PARAM:serde::ser::Serialize + Send + 'static,
202                RET: serde::de::DeserializeOwned + Send + 'static,
203            {
204                let id = self.id.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
205                let method = serde_json::json!(method);
206
207                let params_fn = move || -> Result<_,_> {
208                    let params = serde_json::to_value(params)?;
209                    let mut req_json = serde_json::Map::new();
210                    req_json.insert("id".to_owned(), serde_json::json!(id));
211                    req_json.insert("jsonrpc".to_owned(), serde_json::json!("2.0"));
212                    req_json.insert("method".to_owned(), method);
213                    req_json.insert("params".to_owned(), params);
214                    Ok(req_json)
215                };
216
217                self.client.post(params_fn)
218
219            }
220            #[cfg(target_arch="wasm32")]
221            pub fn post<PARAM, RET>(&self, method:&str, params: PARAM)->impl std::future::Future<Output =Result<RET, $crate::rpc::RpcError>> + 'static
222            where
223                PARAM:serde::ser::Serialize + Send + 'static,
224                RET: serde::de::DeserializeOwned + Send + 'static,
225            {
226                let id = self.id.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
227                let method = serde_json::json!(method);
228
229                let params_fn = move || -> Result<_,_> {
230                    let params = serde_json::to_value(params)?;
231                    let mut req_json = serde_json::Map::new();
232                    req_json.insert("id".to_owned(), serde_json::json!(id));
233                    req_json.insert("jsonrpc".to_owned(), serde_json::json!("2.0"));
234                    req_json.insert("method".to_owned(), method);
235                    req_json.insert("params".to_owned(), params);
236                    Ok(req_json)
237                };
238
239                self.client.post(params_fn)
240
241            }
242            $(
243                $(#[$attr])*
244                pub fn $method(&$selff $(, $arg_name: $arg_ty)*) -> impl std::future::Future<Output =Result<$return_ty, $crate::rpc::RpcError>> {
245                    let id = $selff.id.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
246
247                    let params_fn = move || -> Result<_,_> {
248                        let method = String::from(stringify!($method));
249                        let params = $crate::serialize_parameters!($($arg_name,)*);
250                        let mut req_json = serde_json::Map::new();
251                        req_json.insert("id".to_owned(), serde_json::json!(id));
252                        req_json.insert("jsonrpc".to_owned(), serde_json::json!("2.0"));
253                        req_json.insert("method".to_owned(), serde_json::json!(method));
254                        req_json.insert("params".to_owned(), params);
255                        Ok(req_json)
256                    };
257
258                    $selff.client.post(params_fn)
259                }
260            )*
261        }
262    )
263}
264
265#[derive(Debug, Clone)]
266pub(crate) struct RpcClient {
267    client: reqwest::Client,
268    url: reqwest::Url,
269}
270
271impl RpcClient {
272    pub fn new(uri: &str) -> Self {
273        let url = reqwest::Url::parse(uri).expect("ckb uri, e.g. \"http://127.0.0.1:8114\"");
274        Self {
275            client: reqwest::Client::new(),
276            url,
277        }
278    }
279
280    pub fn with_builder<F>(uri: &str, f: F) -> Result<Self, anyhow::Error>
281    where
282        F: FnOnce(reqwest::ClientBuilder) -> reqwest::ClientBuilder,
283    {
284        let url = reqwest::Url::parse(uri)?;
285        let client = f(reqwest::Client::builder())
286            .build()
287            .map_err(|e| anyhow::anyhow!("Failed to build HTTP client: {}", e))?;
288        Ok(Self { client, url })
289    }
290
291    #[cfg(not(target_arch = "wasm32"))]
292    pub fn new_with_cookie(uri: &str) -> Self {
293        let url = reqwest::Url::parse(uri).expect("ckb uri, e.g. \"http://127.0.0.1:8114\"");
294        let mut client_builder = reqwest::Client::builder();
295        client_builder = client_builder.cookie_store(true);
296        Self {
297            client: client_builder
298                .build()
299                .expect("failed to build reqwest client"),
300            url,
301        }
302    }
303
304    pub fn post<PARAM, RET, T>(
305        &self,
306        json_post_params: T,
307    ) -> impl std::future::Future<Output = Result<RET, crate::rpc::RpcError>>
308    where
309        PARAM: serde::ser::Serialize + Send + 'static,
310        RET: serde::de::DeserializeOwned + Send + 'static,
311        T: FnOnce() -> Result<PARAM, crate::rpc::RpcError>,
312    {
313        let url = self.url.clone();
314        let client = self.client.clone();
315
316        async move {
317            let resp = client.post(url).json(&json_post_params()?).send().await?;
318            let output = resp.json::<jsonrpc_core::response::Output>().await?;
319            match output {
320                jsonrpc_core::response::Output::Success(success) => {
321                    serde_json::from_value(success.result).map_err(Into::into)
322                }
323                jsonrpc_core::response::Output::Failure(failure) => Err(failure.error.into()),
324            }
325        }
326    }
327}
328
329#[macro_export]
330macro_rules! serialize_parameters {
331    () => ( serde_json::Value::Null );
332    ($($arg_name:ident,)+) => ( serde_json::to_value(($($arg_name,)+))?)
333}
334
335pub trait ResponseFormatGetter<V> {
336    fn get_value(self) -> Result<V, crate::rpc::RpcError>;
337    fn get_json_bytes(self) -> Result<JsonBytes, crate::rpc::RpcError>;
338}
339
340impl<V> ResponseFormatGetter<V> for ResponseFormat<V> {
341    fn get_value(self) -> Result<V, crate::rpc::RpcError> {
342        match self.inner {
343            ckb_jsonrpc_types::Either::Left(v) => Ok(v),
344            ckb_jsonrpc_types::Either::Right(_) => Err(crate::rpc::RpcError::Other(anyhow!(
345                "It's a JsonBytes, can't get the inner value directly"
346            ))),
347        }
348    }
349
350    fn get_json_bytes(self) -> Result<JsonBytes, crate::rpc::RpcError> {
351        match self.inner {
352            ckb_jsonrpc_types::Either::Left(_v) => Err(crate::rpc::RpcError::Other(anyhow!(
353                "It's not a JsonBytes, can't get the json bytes directly"
354            ))),
355            ckb_jsonrpc_types::Either::Right(json_bytes) => Ok(json_bytes),
356        }
357    }
358}
359
360#[cfg(test)]
361mod anyhow_tests {
362    use anyhow::anyhow;
363    #[test]
364    fn test_rpc_error() {
365        let json_rpc_error = jsonrpc_core::Error {
366            code: jsonrpc_core::ErrorCode::ParseError,
367            message: "parse error".to_string(),
368            data: None,
369        };
370        let error = super::RpcError::from(json_rpc_error);
371        let error = anyhow!(error);
372        println!("{}", error)
373    }
374}