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