1use std::{borrow::Borrow, sync::RwLock};
6
7use log::{info, warn};
8
9use bitcoin::{Script, Txid};
10
11use crate::api::ElectrumApi;
12use crate::batch::Batch;
13use crate::config::Config;
14use crate::raw_client::*;
15use crate::types::*;
16use std::convert::TryFrom;
17
18pub enum ClientType {
24 #[allow(missing_docs)]
25 TCP(RawClient<ElectrumPlaintextStream>),
26 #[allow(missing_docs)]
27 #[cfg(any(feature = "openssl", feature = "rustls", feature = "rustls-ring"))]
28 SSL(RawClient<ElectrumSslStream>),
29 #[allow(missing_docs)]
30 #[cfg(feature = "proxy")]
31 Socks5(RawClient<ElectrumProxyStream>),
32}
33
34pub struct Client {
37 client_type: RwLock<ClientType>,
38 config: Config,
39 url: String,
40}
41
42macro_rules! impl_inner_call {
43 ( $self:expr, $name:ident $(, $args:expr)* ) => {
44 {
45 let mut errors = vec![];
46 loop {
47 let read_client = $self.client_type.read().unwrap();
48 let res = match &*read_client {
49 ClientType::TCP(inner) => inner.$name( $($args, )* ),
50 #[cfg(any(feature = "openssl", feature = "rustls", feature = "rustls-ring"))]
51 ClientType::SSL(inner) => inner.$name( $($args, )* ),
52 #[cfg(feature = "proxy")]
53 ClientType::Socks5(inner) => inner.$name( $($args, )* ),
54 };
55 drop(read_client);
56 match res {
57 Ok(val) => return Ok(val),
58 Err(Error::Protocol(_) | Error::AlreadySubscribed(_)) => {
59 return res;
60 },
61 Err(e) => {
62 let failed_attempts = errors.len() + 1;
63
64 if retries_exhausted(failed_attempts, $self.config.retry()) {
65 warn!("call '{}' failed after {} attempts", stringify!($name), failed_attempts);
66 return Err(Error::AllAttemptsErrored(errors));
67 }
68
69 warn!("call '{}' failed with {}, retry: {}/{}", stringify!($name), e, failed_attempts, $self.config.retry());
70
71 errors.push(e);
72
73 if let Ok(mut write_client) = $self.client_type.try_write() {
77 loop {
78 std::thread::sleep(std::time::Duration::from_secs((1 << errors.len()).min(30) as u64));
79 match ClientType::from_config(&$self.url, &$self.config) {
80 Ok(new_client) => {
81 info!("Succesfully created new client");
82 *write_client = new_client;
83 break;
84 },
85 Err(e) => {
86 let failed_attempts = errors.len() + 1;
87
88 if retries_exhausted(failed_attempts, $self.config.retry()) {
89 warn!("re-creating client failed after {} attempts", failed_attempts);
90 return Err(Error::AllAttemptsErrored(errors));
91 }
92
93 warn!("re-creating client failed with {}, retry: {}/{}", e, failed_attempts, $self.config.retry());
94
95 errors.push(e);
96 }
97 }
98 }
99 }
100 },
101 }
102 }}
103 }
104}
105
106fn retries_exhausted(failed_attempts: usize, configured_retries: u8) -> bool {
107 match u8::try_from(failed_attempts) {
108 Ok(failed_attempts) => failed_attempts > configured_retries,
109 Err(_) => true, }
111}
112
113impl ClientType {
114 pub fn from_config(url: &str, config: &Config) -> Result<Self, Error> {
117 let auth_provider = config.authorization_provider().cloned();
118
119 #[cfg(any(feature = "openssl", feature = "rustls", feature = "rustls-ring"))]
120 if url.starts_with("ssl://") {
121 let url = url.replacen("ssl://", "", 1);
122 #[cfg(feature = "proxy")]
123 let raw_client = match config.socks5() {
124 Some(socks5) => RawClient::new_proxy_ssl(
125 url.as_str(),
126 config.validate_domain(),
127 socks5,
128 config.timeout(),
129 auth_provider,
130 )?,
131 None => RawClient::new_ssl(
132 url.as_str(),
133 config.validate_domain(),
134 config.timeout(),
135 auth_provider,
136 )?,
137 };
138 #[cfg(not(feature = "proxy"))]
139 let raw_client = RawClient::new_ssl(
140 url.as_str(),
141 config.validate_domain(),
142 config.timeout(),
143 auth_provider,
144 )?;
145
146 return Ok(ClientType::SSL(raw_client));
147 }
148
149 #[cfg(not(any(feature = "openssl", feature = "rustls", feature = "rustls-ring")))]
150 if url.starts_with("ssl://") {
151 return Err(Error::Message(
152 "SSL connections require one of the following features to be enabled: openssl, rustls, or rustls-ring".to_string()
153 ));
154 }
155
156 {
157 let url = url.replacen("tcp://", "", 1);
158
159 #[cfg(feature = "proxy")]
160 let client = match config.socks5() {
161 Some(socks5) => ClientType::Socks5(RawClient::new_proxy(
162 url.as_str(),
163 socks5,
164 config.timeout(),
165 auth_provider,
166 )?),
167 None => ClientType::TCP(RawClient::new(
168 url.as_str(),
169 config.timeout(),
170 auth_provider,
171 )?),
172 };
173
174 #[cfg(not(feature = "proxy"))]
175 let client = ClientType::TCP(RawClient::new(
176 url.as_str(),
177 config.timeout(),
178 auth_provider,
179 )?);
180
181 Ok(client)
182 }
183 }
184}
185
186impl Client {
187 pub fn new(url: &str) -> Result<Self, Error> {
197 Self::from_config(url, Config::default())
198 }
199
200 pub fn from_config(url: &str, config: Config) -> Result<Self, Error> {
203 let client_type = RwLock::new(ClientType::from_config(url, &config)?);
204
205 Ok(Client {
206 client_type,
207 config,
208 url: url.to_string(),
209 })
210 }
211}
212
213impl ElectrumApi for Client {
214 #[inline]
215 fn raw_call(
216 &self,
217 method_name: &str,
218 params: impl IntoIterator<Item = Param>,
219 ) -> Result<serde_json::Value, Error> {
220 let vec = params.into_iter().collect::<Vec<Param>>();
226 impl_inner_call!(self, internal_raw_call_with_vec, method_name, vec.clone());
227 }
228
229 #[inline]
230 fn batch_call(&self, batch: &Batch) -> Result<Vec<serde_json::Value>, Error> {
231 impl_inner_call!(self, batch_call, batch)
232 }
233
234 #[inline]
235 fn block_headers_subscribe_raw(&self) -> Result<RawHeaderNotification, Error> {
236 impl_inner_call!(self, block_headers_subscribe_raw)
237 }
238
239 #[inline]
240 fn block_headers_pop_raw(&self) -> Result<Option<RawHeaderNotification>, Error> {
241 impl_inner_call!(self, block_headers_pop_raw)
242 }
243
244 #[inline]
245 fn block_header_raw(&self, height: usize) -> Result<Vec<u8>, Error> {
246 impl_inner_call!(self, block_header_raw, height)
247 }
248
249 #[inline]
250 fn block_headers(&self, start_height: usize, count: usize) -> Result<GetHeadersRes, Error> {
251 impl_inner_call!(self, block_headers, start_height, count)
252 }
253
254 #[inline]
255 fn estimate_fee(&self, number: usize, mode: Option<EstimationMode>) -> Result<f64, Error> {
256 impl_inner_call!(self, estimate_fee, number, mode)
257 }
258
259 #[inline]
260 fn relay_fee(&self) -> Result<f64, Error> {
261 impl_inner_call!(self, relay_fee)
262 }
263
264 #[inline]
265 fn script_subscribe(&self, script: &Script) -> Result<Option<ScriptStatus>, Error> {
266 impl_inner_call!(self, script_subscribe, script)
267 }
268
269 #[inline]
270 fn batch_script_subscribe<'s, I>(&self, scripts: I) -> Result<Vec<Option<ScriptStatus>>, Error>
271 where
272 I: IntoIterator + Clone,
273 I::Item: Borrow<&'s Script>,
274 {
275 impl_inner_call!(self, batch_script_subscribe, scripts.clone())
276 }
277
278 #[inline]
279 fn script_unsubscribe(&self, script: &Script) -> Result<bool, Error> {
280 impl_inner_call!(self, script_unsubscribe, script)
281 }
282
283 #[inline]
284 fn script_pop(&self, script: &Script) -> Result<Option<ScriptStatus>, Error> {
285 impl_inner_call!(self, script_pop, script)
286 }
287
288 #[inline]
289 fn script_get_balance(&self, script: &Script) -> Result<GetBalanceRes, Error> {
290 impl_inner_call!(self, script_get_balance, script)
291 }
292
293 #[inline]
294 fn batch_script_get_balance<'s, I>(&self, scripts: I) -> Result<Vec<GetBalanceRes>, Error>
295 where
296 I: IntoIterator + Clone,
297 I::Item: Borrow<&'s Script>,
298 {
299 impl_inner_call!(self, batch_script_get_balance, scripts.clone())
300 }
301
302 #[inline]
303 fn script_get_history(&self, script: &Script) -> Result<Vec<GetHistoryRes>, Error> {
304 impl_inner_call!(self, script_get_history, script)
305 }
306
307 #[inline]
308 fn batch_script_get_history<'s, I>(&self, scripts: I) -> Result<Vec<Vec<GetHistoryRes>>, Error>
309 where
310 I: IntoIterator + Clone,
311 I::Item: Borrow<&'s Script>,
312 {
313 impl_inner_call!(self, batch_script_get_history, scripts.clone())
314 }
315
316 #[inline]
317 fn script_list_unspent(&self, script: &Script) -> Result<Vec<ListUnspentRes>, Error> {
318 impl_inner_call!(self, script_list_unspent, script)
319 }
320
321 #[inline]
322 fn batch_script_list_unspent<'s, I>(
323 &self,
324 scripts: I,
325 ) -> Result<Vec<Vec<ListUnspentRes>>, Error>
326 where
327 I: IntoIterator + Clone,
328 I::Item: Borrow<&'s Script>,
329 {
330 impl_inner_call!(self, batch_script_list_unspent, scripts.clone())
331 }
332
333 #[inline]
334 fn transaction_get_raw(&self, txid: &Txid) -> Result<Vec<u8>, Error> {
335 impl_inner_call!(self, transaction_get_raw, txid)
336 }
337
338 #[inline]
339 fn batch_transaction_get_raw<'t, I>(&self, txids: I) -> Result<Vec<Vec<u8>>, Error>
340 where
341 I: IntoIterator + Clone,
342 I::Item: Borrow<&'t Txid>,
343 {
344 impl_inner_call!(self, batch_transaction_get_raw, txids.clone())
345 }
346
347 #[inline]
348 fn batch_block_header_raw<'s, I>(&self, heights: I) -> Result<Vec<Vec<u8>>, Error>
349 where
350 I: IntoIterator + Clone,
351 I::Item: Borrow<u32>,
352 {
353 impl_inner_call!(self, batch_block_header_raw, heights.clone())
354 }
355
356 #[inline]
357 fn batch_estimate_fee<'s, I>(&self, numbers: I) -> Result<Vec<f64>, Error>
358 where
359 I: IntoIterator + Clone,
360 I::Item: Borrow<usize>,
361 {
362 impl_inner_call!(self, batch_estimate_fee, numbers.clone())
363 }
364
365 #[inline]
366 fn transaction_broadcast_raw(&self, raw_tx: &[u8]) -> Result<Txid, Error> {
367 impl_inner_call!(self, transaction_broadcast_raw, raw_tx)
368 }
369
370 #[inline]
371 fn transaction_broadcast_package_raw<T: AsRef<[u8]>>(
372 &self,
373 raw_txs: &[T],
374 ) -> Result<BroadcastPackageRes, Error> {
375 impl_inner_call!(self, transaction_broadcast_package_raw, raw_txs)
376 }
377
378 #[inline]
379 fn transaction_get_merkle(&self, txid: &Txid, height: usize) -> Result<GetMerkleRes, Error> {
380 impl_inner_call!(self, transaction_get_merkle, txid, height)
381 }
382
383 #[inline]
384 fn batch_transaction_get_merkle<I>(
385 &self,
386 txids_and_heights: I,
387 ) -> Result<Vec<GetMerkleRes>, Error>
388 where
389 I: IntoIterator + Clone,
390 I::Item: Borrow<(Txid, usize)>,
391 {
392 impl_inner_call!(
393 self,
394 batch_transaction_get_merkle,
395 txids_and_heights.clone()
396 )
397 }
398
399 #[inline]
400 fn txid_from_pos(&self, height: usize, tx_pos: usize) -> Result<Txid, Error> {
401 impl_inner_call!(self, txid_from_pos, height, tx_pos)
402 }
403
404 #[inline]
405 fn txid_from_pos_with_merkle(
406 &self,
407 height: usize,
408 tx_pos: usize,
409 ) -> Result<TxidFromPosRes, Error> {
410 impl_inner_call!(self, txid_from_pos_with_merkle, height, tx_pos)
411 }
412
413 #[inline]
414 fn server_features(&self) -> Result<ServerFeaturesRes, Error> {
415 impl_inner_call!(self, server_features)
416 }
417
418 #[inline]
419 fn mempool_get_info(&self) -> Result<MempoolInfoRes, Error> {
420 impl_inner_call!(self, mempool_get_info)
421 }
422
423 #[inline]
424 fn ping(&self) -> Result<(), Error> {
425 impl_inner_call!(self, ping)
426 }
427
428 #[inline]
429 fn calls_made(&self) -> Result<usize, Error> {
430 impl_inner_call!(self, calls_made)
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 #[test]
439 fn more_failed_attempts_than_retries_means_exhausted() {
440 let exhausted = retries_exhausted(10, 5);
441
442 assert!(exhausted)
443 }
444
445 #[test]
446 fn failed_attempts_bigger_than_u8_means_exhausted() {
447 let failed_attempts = u8::MAX as usize + 1;
448
449 let exhausted = retries_exhausted(failed_attempts, u8::MAX);
450
451 assert!(exhausted)
452 }
453
454 #[test]
455 fn less_failed_attempts_means_not_exhausted() {
456 let exhausted = retries_exhausted(2, 5);
457
458 assert!(!exhausted)
459 }
460
461 #[test]
462 fn attempts_equals_retries_means_not_exhausted_yet() {
463 let exhausted = retries_exhausted(2, 2);
464
465 assert!(!exhausted)
466 }
467}