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 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}