1#![allow(clippy::result_large_err)]
68
69use std::collections::HashMap;
70use std::fmt;
71use std::num::TryFromIntError;
72use std::time::Duration;
73
74#[cfg(feature = "async")]
75pub use r#async::Sleeper;
76
77pub mod api;
78#[cfg(feature = "async")]
79pub mod r#async;
80#[cfg(feature = "blocking")]
81pub mod blocking;
82
83pub use api::*;
84#[cfg(feature = "blocking")]
85pub use blocking::BlockingClient;
86#[cfg(feature = "async")]
87pub use r#async::AsyncClient;
88
89const RETRYABLE_ERROR_CODES: [u16; 3] = [
91 429, 500, 503, ];
95
96const BASE_BACKOFF_MILLIS: Duration = Duration::from_millis(256);
98
99const DEFAULT_MAX_RETRIES: usize = 6;
101
102pub fn convert_fee_rate(target: usize, estimates: HashMap<u16, f64>) -> Option<f32> {
108 estimates
109 .into_iter()
110 .filter(|(k, _)| *k as usize <= target)
111 .max_by_key(|(k, _)| *k)
112 .map(|(_, v)| v as f32)
113}
114
115#[derive(Debug, Clone)]
116pub struct Builder {
117 pub base_url: String,
119 pub proxy: Option<String>,
132 pub timeout: Option<u64>,
134 pub headers: HashMap<String, String>,
136 pub max_retries: usize,
138}
139
140impl Builder {
141 pub fn new(base_url: &str) -> Self {
143 Builder {
144 base_url: base_url.to_string(),
145 proxy: None,
146 timeout: None,
147 headers: HashMap::new(),
148 max_retries: DEFAULT_MAX_RETRIES,
149 }
150 }
151
152 pub fn proxy(mut self, proxy: &str) -> Self {
154 self.proxy = Some(proxy.to_string());
155 self
156 }
157
158 pub fn timeout(mut self, timeout: u64) -> Self {
160 self.timeout = Some(timeout);
161 self
162 }
163
164 pub fn header(mut self, key: &str, value: &str) -> Self {
166 self.headers.insert(key.to_string(), value.to_string());
167 self
168 }
169
170 pub fn max_retries(mut self, count: usize) -> Self {
173 self.max_retries = count;
174 self
175 }
176
177 #[cfg(feature = "blocking")]
179 pub fn build_blocking(self) -> BlockingClient {
180 BlockingClient::from_builder(self)
181 }
182
183 #[cfg(all(feature = "async", feature = "tokio"))]
185 pub fn build_async(self) -> Result<AsyncClient, Error> {
186 AsyncClient::from_builder(self)
187 }
188
189 #[cfg(feature = "async")]
192 pub fn build_async_with_sleeper<S: Sleeper>(self) -> Result<AsyncClient<S>, Error> {
193 AsyncClient::from_builder(self)
194 }
195}
196
197#[derive(Debug)]
199pub enum Error {
200 #[cfg(feature = "blocking")]
202 Minreq(::minreq::Error),
203 #[cfg(feature = "async")]
205 Reqwest(::reqwest::Error),
206 HttpResponse { status: u16, message: String },
208 Parsing(std::num::ParseIntError),
210 StatusCode(TryFromIntError),
212 BitcoinEncoding(bitcoin::consensus::encode::Error),
214 HexToArray(bitcoin::hex::HexToArrayError),
216 HexToBytes(bitcoin::hex::HexToBytesError),
218 TransactionNotFound(Txid),
220 HeaderHeightNotFound(u32),
222 HeaderHashNotFound(BlockHash),
224 InvalidHttpHeaderName(String),
226 InvalidHttpHeaderValue(String),
228 InvalidResponse,
230}
231
232impl fmt::Display for Error {
233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234 write!(f, "{:?}", self)
235 }
236}
237
238macro_rules! impl_error {
239 ( $from:ty, $to:ident ) => {
240 impl_error!($from, $to, Error);
241 };
242 ( $from:ty, $to:ident, $impl_for:ty ) => {
243 impl std::convert::From<$from> for $impl_for {
244 fn from(err: $from) -> Self {
245 <$impl_for>::$to(err)
246 }
247 }
248 };
249}
250
251impl std::error::Error for Error {}
252#[cfg(feature = "blocking")]
253impl_error!(::minreq::Error, Minreq, Error);
254#[cfg(feature = "async")]
255impl_error!(::reqwest::Error, Reqwest, Error);
256impl_error!(std::num::ParseIntError, Parsing, Error);
257impl_error!(bitcoin::consensus::encode::Error, BitcoinEncoding, Error);
258impl_error!(bitcoin::hex::HexToArrayError, HexToArray, Error);
259impl_error!(bitcoin::hex::HexToBytesError, HexToBytes, Error);
260
261#[cfg(test)]
262mod test {
263 use super::*;
264 use electrsd::{bitcoind, bitcoind::BitcoinD, ElectrsD};
265 use lazy_static::lazy_static;
266 use std::env;
267 use tokio::sync::Mutex;
268 #[cfg(all(feature = "blocking", feature = "async"))]
269 use {
270 bitcoin::hashes::Hash,
271 bitcoin::Amount,
272 electrsd::{
273 bitcoind::bitcoincore_rpc::json::AddressType, bitcoind::bitcoincore_rpc::RpcApi,
274 electrum_client::ElectrumApi,
275 },
276 std::time::Duration,
277 tokio::sync::OnceCell,
278 };
279
280 lazy_static! {
281 static ref BITCOIND: BitcoinD = {
282 let bitcoind_exe = env::var("BITCOIND_EXE")
283 .ok()
284 .or_else(|| bitcoind::downloaded_exe_path().ok())
285 .expect(
286 "you need to provide an env var BITCOIND_EXE or specify a bitcoind version feature",
287 );
288 let conf = bitcoind::Conf::default();
289 BitcoinD::with_conf(bitcoind_exe, &conf).unwrap()
290 };
291 static ref ELECTRSD: ElectrsD = {
292 let electrs_exe = env::var("ELECTRS_EXE")
293 .ok()
294 .or_else(electrsd::downloaded_exe_path)
295 .expect(
296 "you need to provide env var ELECTRS_EXE or specify an electrsd version feature",
297 );
298 let mut conf = electrsd::Conf::default();
299 conf.http_enabled = true;
300 ElectrsD::with_conf(electrs_exe, &BITCOIND, &conf).unwrap()
301 };
302 static ref MINER: Mutex<()> = Mutex::new(());
303 }
304
305 #[cfg(all(feature = "blocking", feature = "async"))]
306 static PREMINE: OnceCell<()> = OnceCell::const_new();
307
308 #[cfg(all(feature = "blocking", feature = "async"))]
309 async fn setup_clients() -> (BlockingClient, AsyncClient) {
310 setup_clients_with_headers(HashMap::new()).await
311 }
312
313 #[cfg(all(feature = "blocking", feature = "async"))]
314 async fn setup_clients_with_headers(
315 headers: HashMap<String, String>,
316 ) -> (BlockingClient, AsyncClient) {
317 PREMINE
318 .get_or_init(|| async {
319 let _miner = MINER.lock().await;
320 generate_blocks_and_wait(101);
321 })
322 .await;
323
324 let esplora_url = ELECTRSD.esplora_url.as_ref().unwrap();
325
326 let mut builder = Builder::new(&format!("http://{}", esplora_url));
327 if !headers.is_empty() {
328 builder.headers = headers;
329 }
330
331 let blocking_client = builder.build_blocking();
332
333 let builder_async = Builder::new(&format!("http://{}", esplora_url));
334
335 #[cfg(feature = "tokio")]
336 let async_client = builder_async.build_async().unwrap();
337
338 #[cfg(not(feature = "tokio"))]
339 let async_client = builder_async
340 .build_async_with_sleeper::<r#async::DefaultSleeper>()
341 .unwrap();
342
343 (blocking_client, async_client)
344 }
345
346 #[cfg(all(feature = "blocking", feature = "async"))]
347 fn generate_blocks_and_wait(num: usize) {
348 let cur_height = BITCOIND.client.get_block_count().unwrap();
349 generate_blocks(num);
350 wait_for_block(cur_height as usize + num);
351 }
352
353 #[cfg(all(feature = "blocking", feature = "async"))]
354 fn generate_blocks(num: usize) {
355 let address = BITCOIND
356 .client
357 .get_new_address(Some("test"), Some(AddressType::Legacy))
358 .unwrap()
359 .assume_checked();
360 let _block_hashes = BITCOIND
361 .client
362 .generate_to_address(num as u64, &address)
363 .unwrap();
364 }
365
366 #[cfg(all(feature = "blocking", feature = "async"))]
367 fn wait_for_block(min_height: usize) {
368 let mut header = ELECTRSD.client.block_headers_subscribe().unwrap();
369 loop {
370 if header.height >= min_height {
371 break;
372 }
373 header = exponential_backoff_poll(|| {
374 ELECTRSD.trigger().unwrap();
375 ELECTRSD.client.ping().unwrap();
376 ELECTRSD.client.block_headers_pop().unwrap()
377 });
378 }
379 }
380
381 #[cfg(all(feature = "blocking", feature = "async"))]
382 fn exponential_backoff_poll<T, F>(mut poll: F) -> T
383 where
384 F: FnMut() -> Option<T>,
385 {
386 let mut delay = Duration::from_millis(64);
387 loop {
388 match poll() {
389 Some(data) => break data,
390 None if delay.as_millis() < 512 => delay = delay.mul_f32(2.0),
391 None => {}
392 }
393
394 std::thread::sleep(delay);
395 }
396 }
397
398 #[test]
399 fn feerate_parsing() {
400 let esplora_fees = serde_json::from_str::<HashMap<u16, f64>>(
401 r#"{
402 "25": 1.015,
403 "5": 2.3280000000000003,
404 "12": 2.0109999999999997,
405 "15": 1.018,
406 "17": 1.018,
407 "11": 2.0109999999999997,
408 "3": 3.01,
409 "2": 4.9830000000000005,
410 "6": 2.2359999999999998,
411 "21": 1.018,
412 "13": 1.081,
413 "7": 2.2359999999999998,
414 "8": 2.2359999999999998,
415 "16": 1.018,
416 "20": 1.018,
417 "22": 1.017,
418 "23": 1.017,
419 "504": 1,
420 "9": 2.2359999999999998,
421 "14": 1.018,
422 "10": 2.0109999999999997,
423 "24": 1.017,
424 "1008": 1,
425 "1": 4.9830000000000005,
426 "4": 2.3280000000000003,
427 "19": 1.018,
428 "144": 1,
429 "18": 1.018
430}
431"#,
432 )
433 .unwrap();
434 assert!(convert_fee_rate(1, HashMap::new()).is_none());
435 assert_eq!(convert_fee_rate(6, esplora_fees.clone()).unwrap(), 2.236);
436 assert_eq!(
437 convert_fee_rate(26, esplora_fees.clone()).unwrap(),
438 1.015,
439 "should inherit from value for 25"
440 );
441 assert!(
442 convert_fee_rate(0, esplora_fees).is_none(),
443 "should not return feerate for 0 target"
444 );
445 }
446
447 #[cfg(all(feature = "blocking", feature = "async"))]
448 #[tokio::test]
449 async fn test_get_tx() {
450 let (blocking_client, async_client) = setup_clients().await;
451
452 let address = BITCOIND
453 .client
454 .get_new_address(Some("test"), Some(AddressType::Legacy))
455 .unwrap()
456 .assume_checked();
457 let txid = BITCOIND
458 .client
459 .send_to_address(
460 &address,
461 Amount::from_sat(1000),
462 None,
463 None,
464 None,
465 None,
466 None,
467 None,
468 )
469 .unwrap();
470 let _miner = MINER.lock().await;
471 generate_blocks_and_wait(1);
472
473 let tx = blocking_client.get_tx(&txid).unwrap();
474 let tx_async = async_client.get_tx(&txid).await.unwrap();
475 assert_eq!(tx, tx_async);
476 }
477
478 #[cfg(all(feature = "blocking", feature = "async"))]
479 #[tokio::test]
480 async fn test_get_tx_no_opt() {
481 let (blocking_client, async_client) = setup_clients().await;
482
483 let address = BITCOIND
484 .client
485 .get_new_address(Some("test"), Some(AddressType::Legacy))
486 .unwrap()
487 .assume_checked();
488 let txid = BITCOIND
489 .client
490 .send_to_address(
491 &address,
492 Amount::from_sat(1000),
493 None,
494 None,
495 None,
496 None,
497 None,
498 None,
499 )
500 .unwrap();
501 let _miner = MINER.lock().await;
502 generate_blocks_and_wait(1);
503
504 let tx_no_opt = blocking_client.get_tx_no_opt(&txid).unwrap();
505 let tx_no_opt_async = async_client.get_tx_no_opt(&txid).await.unwrap();
506 assert_eq!(tx_no_opt, tx_no_opt_async);
507 }
508
509 #[cfg(all(feature = "blocking", feature = "async"))]
510 #[tokio::test]
511 async fn test_get_tx_status() {
512 let (blocking_client, async_client) = setup_clients().await;
513
514 let address = BITCOIND
515 .client
516 .get_new_address(Some("test"), Some(AddressType::Legacy))
517 .unwrap()
518 .assume_checked();
519 let txid = BITCOIND
520 .client
521 .send_to_address(
522 &address,
523 Amount::from_sat(1000),
524 None,
525 None,
526 None,
527 None,
528 None,
529 None,
530 )
531 .unwrap();
532 let _miner = MINER.lock().await;
533 generate_blocks_and_wait(1);
534
535 let tx_status = blocking_client.get_tx_status(&txid).unwrap();
536 let tx_status_async = async_client.get_tx_status(&txid).await.unwrap();
537 assert_eq!(tx_status, tx_status_async);
538 assert!(tx_status.confirmed);
539
540 let txid = Txid::hash(b"ayyyy lmao");
542 let tx_status = blocking_client.get_tx_status(&txid).unwrap();
543 let tx_status_async = async_client.get_tx_status(&txid).await.unwrap();
544 assert_eq!(tx_status, tx_status_async);
545 assert!(!tx_status.confirmed);
546 assert!(tx_status.block_height.is_none());
547 assert!(tx_status.block_hash.is_none());
548 assert!(tx_status.block_time.is_none());
549 }
550
551 #[cfg(all(feature = "blocking", feature = "async"))]
552 #[tokio::test]
553 async fn test_get_tx_info() {
554 let (blocking_client, async_client) = setup_clients().await;
555
556 let address = BITCOIND
557 .client
558 .get_new_address(Some("test"), Some(AddressType::Legacy))
559 .unwrap()
560 .assume_checked();
561 let txid = BITCOIND
562 .client
563 .send_to_address(
564 &address,
565 Amount::from_sat(1000),
566 None,
567 None,
568 None,
569 None,
570 None,
571 None,
572 )
573 .unwrap();
574 let _miner = MINER.lock().await;
575 generate_blocks_and_wait(1);
576
577 let tx_res = BITCOIND.client.get_transaction(&txid, None).unwrap();
578 let tx_exp = tx_res.transaction().expect("must decode");
579
580 let tx_info = blocking_client
581 .get_tx_info(&txid)
582 .unwrap()
583 .expect("must get tx");
584 let tx_info_async = async_client
585 .get_tx_info(&txid)
586 .await
587 .unwrap()
588 .expect("must get tx");
589 assert_eq!(tx_info, tx_info_async);
590 assert_eq!(tx_info.txid, txid);
591 assert_eq!(tx_info.to_tx(), tx_exp);
592 assert_eq!(tx_info.size, tx_exp.total_size());
593 assert_eq!(tx_info.weight(), tx_exp.weight());
594 assert_eq!(tx_info.fee(), tx_res.fee.unwrap().unsigned_abs());
595 assert!(tx_info.status.confirmed);
596 assert_eq!(tx_info.status.block_height, tx_res.info.blockheight);
597 assert_eq!(tx_info.status.block_hash, tx_res.info.blockhash);
598 assert_eq!(tx_info.status.block_time, tx_res.info.blocktime);
599
600 let txid = Txid::hash(b"not exist");
601 assert_eq!(blocking_client.get_tx_info(&txid).unwrap(), None);
602 assert_eq!(async_client.get_tx_info(&txid).await.unwrap(), None);
603 }
604
605 #[cfg(all(feature = "blocking", feature = "async"))]
606 #[tokio::test]
607 async fn test_get_header_by_hash() {
608 let (blocking_client, async_client) = setup_clients().await;
609
610 let block_hash = BITCOIND.client.get_block_hash(23).unwrap();
611
612 let block_header = blocking_client.get_header_by_hash(&block_hash).unwrap();
613 let block_header_async = async_client.get_header_by_hash(&block_hash).await.unwrap();
614 assert_eq!(block_header, block_header_async);
615 }
616
617 #[cfg(all(feature = "blocking", feature = "async"))]
618 #[tokio::test]
619 async fn test_get_block_status() {
620 let (blocking_client, async_client) = setup_clients().await;
621
622 let block_hash = BITCOIND.client.get_block_hash(21).unwrap();
623 let next_block_hash = BITCOIND.client.get_block_hash(22).unwrap();
624
625 let expected = BlockStatus {
626 in_best_chain: true,
627 height: Some(21),
628 next_best: Some(next_block_hash),
629 };
630
631 let block_status = blocking_client.get_block_status(&block_hash).unwrap();
632 let block_status_async = async_client.get_block_status(&block_hash).await.unwrap();
633 assert_eq!(expected, block_status);
634 assert_eq!(expected, block_status_async);
635 }
636
637 #[cfg(all(feature = "blocking", feature = "async"))]
638 #[tokio::test]
639 async fn test_get_non_existing_block_status() {
640 let (blocking_client, async_client) = setup_clients().await;
647
648 let block_hash = BlockHash::all_zeros();
649
650 let expected = BlockStatus {
651 in_best_chain: false,
652 height: None,
653 next_best: None,
654 };
655
656 let block_status = blocking_client.get_block_status(&block_hash).unwrap();
657 let block_status_async = async_client.get_block_status(&block_hash).await.unwrap();
658 assert_eq!(expected, block_status);
659 assert_eq!(expected, block_status_async);
660 }
661
662 #[cfg(all(feature = "blocking", feature = "async"))]
663 #[tokio::test]
664 async fn test_get_block_by_hash() {
665 let (blocking_client, async_client) = setup_clients().await;
666
667 let block_hash = BITCOIND.client.get_block_hash(21).unwrap();
668
669 let expected = Some(BITCOIND.client.get_block(&block_hash).unwrap());
670
671 let block = blocking_client.get_block_by_hash(&block_hash).unwrap();
672 let block_async = async_client.get_block_by_hash(&block_hash).await.unwrap();
673 assert_eq!(expected, block);
674 assert_eq!(expected, block_async);
675 }
676
677 #[cfg(all(feature = "blocking", feature = "async"))]
678 #[tokio::test]
679 async fn test_that_errors_are_propagated() {
680 let (blocking_client, async_client) = setup_clients().await;
681
682 let address = BITCOIND
683 .client
684 .get_new_address(Some("test"), Some(AddressType::Legacy))
685 .unwrap()
686 .assume_checked();
687 let txid = BITCOIND
688 .client
689 .send_to_address(
690 &address,
691 Amount::from_sat(1000),
692 None,
693 None,
694 None,
695 None,
696 None,
697 None,
698 )
699 .unwrap();
700 let _miner = MINER.lock().await;
701 generate_blocks_and_wait(1);
702
703 let tx = blocking_client.get_tx(&txid).unwrap();
704 let async_res = async_client.broadcast(tx.as_ref().unwrap()).await;
705 let blocking_res = blocking_client.broadcast(tx.as_ref().unwrap());
706 assert!(async_res.is_err());
707 assert_eq!(async_res.unwrap_err().to_string(),"HttpResponse { status: 400, message: \"sendrawtransaction RPC error: {\\\"code\\\":-27,\\\"message\\\":\\\"Transaction already in block chain\\\"}\" }");
708 assert!(blocking_res.is_err());
709 assert_eq!(blocking_res.unwrap_err().to_string(),"HttpResponse { status: 400, message: \"sendrawtransaction RPC error: {\\\"code\\\":-27,\\\"message\\\":\\\"Transaction already in block chain\\\"}\" }");
710 }
711
712 #[cfg(all(feature = "blocking", feature = "async"))]
713 #[tokio::test]
714 async fn test_get_block_by_hash_not_existing() {
715 let (blocking_client, async_client) = setup_clients().await;
716
717 let block = blocking_client
718 .get_block_by_hash(&BlockHash::all_zeros())
719 .unwrap();
720 let block_async = async_client
721 .get_block_by_hash(&BlockHash::all_zeros())
722 .await
723 .unwrap();
724 assert!(block.is_none());
725 assert!(block_async.is_none());
726 }
727
728 #[cfg(all(feature = "blocking", feature = "async"))]
729 #[tokio::test]
730 async fn test_get_merkle_proof() {
731 let (blocking_client, async_client) = setup_clients().await;
732
733 let address = BITCOIND
734 .client
735 .get_new_address(Some("test"), Some(AddressType::Legacy))
736 .unwrap()
737 .assume_checked();
738 let txid = BITCOIND
739 .client
740 .send_to_address(
741 &address,
742 Amount::from_sat(1000),
743 None,
744 None,
745 None,
746 None,
747 None,
748 None,
749 )
750 .unwrap();
751 let _miner = MINER.lock().await;
752 generate_blocks_and_wait(1);
753
754 let merkle_proof = blocking_client.get_merkle_proof(&txid).unwrap().unwrap();
755 let merkle_proof_async = async_client.get_merkle_proof(&txid).await.unwrap().unwrap();
756 assert_eq!(merkle_proof, merkle_proof_async);
757 assert!(merkle_proof.pos > 0);
758 }
759
760 #[cfg(all(feature = "blocking", feature = "async"))]
761 #[tokio::test]
762 async fn test_get_merkle_block() {
763 let (blocking_client, async_client) = setup_clients().await;
764
765 let address = BITCOIND
766 .client
767 .get_new_address(Some("test"), Some(AddressType::Legacy))
768 .unwrap()
769 .assume_checked();
770 let txid = BITCOIND
771 .client
772 .send_to_address(
773 &address,
774 Amount::from_sat(1000),
775 None,
776 None,
777 None,
778 None,
779 None,
780 None,
781 )
782 .unwrap();
783 let _miner = MINER.lock().await;
784 generate_blocks_and_wait(1);
785
786 let merkle_block = blocking_client.get_merkle_block(&txid).unwrap().unwrap();
787 let merkle_block_async = async_client.get_merkle_block(&txid).await.unwrap().unwrap();
788 assert_eq!(merkle_block, merkle_block_async);
789
790 let mut matches = vec![txid];
791 let mut indexes = vec![];
792 let root = merkle_block
793 .txn
794 .extract_matches(&mut matches, &mut indexes)
795 .unwrap();
796 assert_eq!(root, merkle_block.header.merkle_root);
797 assert_eq!(indexes.len(), 1);
798 assert!(indexes[0] > 0);
799 }
800
801 #[cfg(all(feature = "blocking", feature = "async"))]
802 #[tokio::test]
803 async fn test_get_output_status() {
804 let (blocking_client, async_client) = setup_clients().await;
805
806 let address = BITCOIND
807 .client
808 .get_new_address(Some("test"), Some(AddressType::Legacy))
809 .unwrap()
810 .assume_checked();
811 let txid = BITCOIND
812 .client
813 .send_to_address(
814 &address,
815 Amount::from_sat(1000),
816 None,
817 None,
818 None,
819 None,
820 None,
821 None,
822 )
823 .unwrap();
824 let _miner = MINER.lock().await;
825 generate_blocks_and_wait(1);
826
827 let output_status = blocking_client
828 .get_output_status(&txid, 1)
829 .unwrap()
830 .unwrap();
831 let output_status_async = async_client
832 .get_output_status(&txid, 1)
833 .await
834 .unwrap()
835 .unwrap();
836
837 assert_eq!(output_status, output_status_async);
838 }
839
840 #[cfg(all(feature = "blocking", feature = "async"))]
841 #[tokio::test]
842 async fn test_get_height() {
843 let (blocking_client, async_client) = setup_clients().await;
844 let block_height = blocking_client.get_height().unwrap();
845 let block_height_async = async_client.get_height().await.unwrap();
846 assert!(block_height > 0);
847 assert_eq!(block_height, block_height_async);
848 }
849
850 #[cfg(all(feature = "blocking", feature = "async"))]
851 #[tokio::test]
852 async fn test_get_tip_hash() {
853 let (blocking_client, async_client) = setup_clients().await;
854 let tip_hash = blocking_client.get_tip_hash().unwrap();
855 let tip_hash_async = async_client.get_tip_hash().await.unwrap();
856 assert_eq!(tip_hash, tip_hash_async);
857 }
858
859 #[cfg(all(feature = "blocking", feature = "async"))]
860 #[tokio::test]
861 async fn test_get_block_hash() {
862 let (blocking_client, async_client) = setup_clients().await;
863
864 let block_hash = BITCOIND.client.get_block_hash(21).unwrap();
865
866 let block_hash_blocking = blocking_client.get_block_hash(21).unwrap();
867 let block_hash_async = async_client.get_block_hash(21).await.unwrap();
868 assert_eq!(block_hash, block_hash_blocking);
869 assert_eq!(block_hash, block_hash_async);
870 }
871
872 #[cfg(all(feature = "blocking", feature = "async"))]
873 #[tokio::test]
874 async fn test_get_txid_at_block_index() {
875 let (blocking_client, async_client) = setup_clients().await;
876
877 let block_hash = BITCOIND.client.get_block_hash(23).unwrap();
878
879 let txid_at_block_index = blocking_client
880 .get_txid_at_block_index(&block_hash, 0)
881 .unwrap()
882 .unwrap();
883 let txid_at_block_index_async = async_client
884 .get_txid_at_block_index(&block_hash, 0)
885 .await
886 .unwrap()
887 .unwrap();
888 assert_eq!(txid_at_block_index, txid_at_block_index_async);
889 }
890
891 #[cfg(all(feature = "blocking", feature = "async"))]
892 #[tokio::test]
893 async fn test_get_fee_estimates() {
894 let (blocking_client, async_client) = setup_clients().await;
895 let fee_estimates = blocking_client.get_fee_estimates().unwrap();
896 let fee_estimates_async = async_client.get_fee_estimates().await.unwrap();
897 assert_eq!(fee_estimates.len(), fee_estimates_async.len());
898 }
899
900 #[cfg(all(feature = "blocking", feature = "async"))]
901 #[tokio::test]
902 async fn test_scripthash_txs() {
903 let (blocking_client, async_client) = setup_clients().await;
904
905 let address = BITCOIND
906 .client
907 .get_new_address(Some("test"), Some(AddressType::Legacy))
908 .unwrap()
909 .assume_checked();
910 let txid = BITCOIND
911 .client
912 .send_to_address(
913 &address,
914 Amount::from_sat(1000),
915 None,
916 None,
917 None,
918 None,
919 None,
920 None,
921 )
922 .unwrap();
923 let _miner = MINER.lock().await;
924 generate_blocks_and_wait(1);
925
926 let expected_tx = BITCOIND
927 .client
928 .get_transaction(&txid, None)
929 .unwrap()
930 .transaction()
931 .unwrap();
932 let script = &expected_tx.output[0].script_pubkey;
933 let scripthash_txs_txids: Vec<Txid> = blocking_client
934 .scripthash_txs(script, None)
935 .unwrap()
936 .iter()
937 .map(|tx| tx.txid)
938 .collect();
939 let scripthash_txs_txids_async: Vec<Txid> = async_client
940 .scripthash_txs(script, None)
941 .await
942 .unwrap()
943 .iter()
944 .map(|tx| tx.txid)
945 .collect();
946 assert_eq!(scripthash_txs_txids, scripthash_txs_txids_async);
947 }
948
949 #[cfg(all(feature = "blocking", feature = "async"))]
950 #[tokio::test]
951 async fn test_get_blocks() {
952 let (blocking_client, async_client) = setup_clients().await;
953 let start_height = BITCOIND.client.get_block_count().unwrap();
954 let blocks1 = blocking_client.get_blocks(None).unwrap();
955 let blocks_async1 = async_client.get_blocks(None).await.unwrap();
956 assert_eq!(blocks1[0].time.height, start_height as u32);
957 assert_eq!(blocks1, blocks_async1);
958 generate_blocks_and_wait(10);
959 let blocks2 = blocking_client.get_blocks(None).unwrap();
960 let blocks_async2 = async_client.get_blocks(None).await.unwrap();
961 assert_eq!(blocks2, blocks_async2);
962 assert_ne!(blocks2, blocks1);
963 let blocks3 = blocking_client
964 .get_blocks(Some(start_height as u32))
965 .unwrap();
966 let blocks_async3 = async_client
967 .get_blocks(Some(start_height as u32))
968 .await
969 .unwrap();
970 assert_eq!(blocks3, blocks_async3);
971 assert_eq!(blocks3[0].time.height, start_height as u32);
972 assert_eq!(blocks3, blocks1);
973 let blocks_genesis = blocking_client.get_blocks(Some(0)).unwrap();
974 let blocks_genesis_async = async_client.get_blocks(Some(0)).await.unwrap();
975 assert_eq!(blocks_genesis, blocks_genesis_async);
976 }
977
978 #[cfg(all(feature = "blocking", feature = "async"))]
979 #[tokio::test]
980 async fn test_get_tx_with_http_header() {
981 let headers = [(
982 "Authorization".to_string(),
983 "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==".to_string(),
984 )]
985 .into();
986 let (blocking_client, async_client) = setup_clients_with_headers(headers).await;
987
988 let address = BITCOIND
989 .client
990 .get_new_address(Some("test"), Some(AddressType::Legacy))
991 .unwrap()
992 .assume_checked();
993 let txid = BITCOIND
994 .client
995 .send_to_address(
996 &address,
997 Amount::from_sat(1000),
998 None,
999 None,
1000 None,
1001 None,
1002 None,
1003 None,
1004 )
1005 .unwrap();
1006 let _miner = MINER.lock().await;
1007 generate_blocks_and_wait(1);
1008
1009 let tx = blocking_client.get_tx(&txid).unwrap();
1010 let tx_async = async_client.get_tx(&txid).await.unwrap();
1011 assert_eq!(tx, tx_async);
1012 }
1013
1014 #[cfg(all(feature = "blocking", feature = "async"))]
1015 #[tokio::test]
1016 async fn test_get_address_stats() {
1017 let (blocking_client, async_client) = setup_clients().await;
1018
1019 let address = BITCOIND
1020 .client
1021 .get_new_address(Some("test"), Some(AddressType::Legacy))
1022 .unwrap()
1023 .assume_checked();
1024
1025 let _txid = BITCOIND
1026 .client
1027 .send_to_address(
1028 &address,
1029 Amount::from_sat(1000),
1030 None,
1031 None,
1032 None,
1033 None,
1034 None,
1035 None,
1036 )
1037 .unwrap();
1038
1039 let address_stats_blocking = blocking_client.get_address_stats(&address).unwrap();
1040 let address_stats_async = async_client.get_address_stats(&address).await.unwrap();
1041 assert_eq!(address_stats_blocking, address_stats_async);
1042 assert_eq!(address_stats_async.chain_stats.funded_txo_count, 0);
1043
1044 let _miner = MINER.lock().await;
1045 generate_blocks_and_wait(1);
1046
1047 let address_stats_blocking = blocking_client.get_address_stats(&address).unwrap();
1048 let address_stats_async = async_client.get_address_stats(&address).await.unwrap();
1049 assert_eq!(address_stats_blocking, address_stats_async);
1050 assert_eq!(address_stats_async.chain_stats.funded_txo_count, 1);
1051 assert_eq!(address_stats_async.chain_stats.funded_txo_sum, 1000);
1052 }
1053
1054 #[cfg(all(feature = "blocking", feature = "async"))]
1055 #[tokio::test]
1056 async fn test_get_address_txs() {
1057 let (blocking_client, async_client) = setup_clients().await;
1058
1059 let address = BITCOIND
1060 .client
1061 .get_new_address(Some("test"), Some(AddressType::Legacy))
1062 .unwrap()
1063 .assume_checked();
1064
1065 let txid = BITCOIND
1066 .client
1067 .send_to_address(
1068 &address,
1069 Amount::from_sat(1000),
1070 None,
1071 None,
1072 None,
1073 None,
1074 None,
1075 None,
1076 )
1077 .unwrap();
1078
1079 let _miner = MINER.lock().await;
1080 generate_blocks_and_wait(1);
1081
1082 let address_txs_blocking = blocking_client.get_address_txs(&address, None).unwrap();
1083 let address_txs_async = async_client.get_address_txs(&address, None).await.unwrap();
1084
1085 assert_eq!(address_txs_blocking, address_txs_async);
1086 assert_eq!(address_txs_async[0].txid, txid);
1087 }
1088}