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#![warn(missing_docs)]
72
73use std::collections::HashMap;
74use std::fmt;
75use std::num::TryFromIntError;
76use std::time::Duration;
77
78#[cfg(feature = "async")]
79pub use r#async::Sleeper;
80
81pub mod api;
82#[cfg(feature = "async")]
83pub mod r#async;
84#[cfg(feature = "blocking")]
85pub mod blocking;
86
87pub use api::*;
88#[cfg(feature = "blocking")]
89pub use blocking::BlockingClient;
90#[cfg(feature = "async")]
91pub use r#async::AsyncClient;
92
93pub const RETRYABLE_ERROR_CODES: [u16; 3] = [
95 429, 500, 503, ];
99
100const BASE_BACKOFF_MILLIS: Duration = Duration::from_millis(256);
102
103const DEFAULT_MAX_RETRIES: usize = 6;
105
106pub fn convert_fee_rate(target: usize, estimates: HashMap<u16, f64>) -> Option<f32> {
112 estimates
113 .into_iter()
114 .filter(|(k, _)| *k as usize <= target)
115 .max_by_key(|(k, _)| *k)
116 .map(|(_, v)| v as f32)
117}
118
119#[derive(Debug, Clone)]
121pub struct Builder {
122 pub base_url: String,
124 pub proxy: Option<String>,
137 pub timeout: Option<u64>,
139 pub headers: HashMap<String, String>,
141 pub max_retries: usize,
143}
144
145impl Builder {
146 pub fn new(base_url: &str) -> Self {
148 Builder {
149 base_url: base_url.to_string(),
150 proxy: None,
151 timeout: None,
152 headers: HashMap::new(),
153 max_retries: DEFAULT_MAX_RETRIES,
154 }
155 }
156
157 pub fn proxy(mut self, proxy: &str) -> Self {
159 self.proxy = Some(proxy.to_string());
160 self
161 }
162
163 pub fn timeout(mut self, timeout: u64) -> Self {
165 self.timeout = Some(timeout);
166 self
167 }
168
169 pub fn header(mut self, key: &str, value: &str) -> Self {
171 self.headers.insert(key.to_string(), value.to_string());
172 self
173 }
174
175 pub fn max_retries(mut self, count: usize) -> Self {
178 self.max_retries = count;
179 self
180 }
181
182 #[cfg(feature = "blocking")]
184 pub fn build_blocking(self) -> BlockingClient {
185 BlockingClient::from_builder(self)
186 }
187
188 #[cfg(all(feature = "async", feature = "tokio"))]
190 pub fn build_async(self) -> Result<AsyncClient, Error> {
191 AsyncClient::from_builder(self)
192 }
193
194 #[cfg(feature = "async")]
197 pub fn build_async_with_sleeper<S: Sleeper>(self) -> Result<AsyncClient<S>, Error> {
198 AsyncClient::from_builder(self)
199 }
200}
201
202#[derive(Debug)]
204pub enum Error {
205 #[cfg(feature = "blocking")]
207 Minreq(::minreq::Error),
208 #[cfg(feature = "async")]
210 Reqwest(::reqwest::Error),
211 HttpResponse {
213 status: u16,
215 message: String,
217 },
218 Parsing(std::num::ParseIntError),
220 StatusCode(TryFromIntError),
222 BitcoinEncoding(bitcoin::consensus::encode::Error),
224 HexToArray(bitcoin::hex::HexToArrayError),
226 HexToBytes(bitcoin::hex::HexToBytesError),
228 TransactionNotFound(Txid),
230 HeaderHeightNotFound(u32),
232 HeaderHashNotFound(BlockHash),
234 InvalidHttpHeaderName(String),
236 InvalidHttpHeaderValue(String),
238 InvalidResponse,
240}
241
242impl fmt::Display for Error {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 write!(f, "{self:?}")
245 }
246}
247
248macro_rules! impl_error {
249 ( $from:ty, $to:ident ) => {
250 impl_error!($from, $to, Error);
251 };
252 ( $from:ty, $to:ident, $impl_for:ty ) => {
253 impl std::convert::From<$from> for $impl_for {
254 fn from(err: $from) -> Self {
255 <$impl_for>::$to(err)
256 }
257 }
258 };
259}
260
261impl std::error::Error for Error {}
262#[cfg(feature = "blocking")]
263impl_error!(::minreq::Error, Minreq, Error);
264#[cfg(feature = "async")]
265impl_error!(::reqwest::Error, Reqwest, Error);
266impl_error!(std::num::ParseIntError, Parsing, Error);
267impl_error!(bitcoin::consensus::encode::Error, BitcoinEncoding, Error);
268impl_error!(bitcoin::hex::HexToArrayError, HexToArray, Error);
269impl_error!(bitcoin::hex::HexToBytesError, HexToBytes, Error);
270
271#[cfg(test)]
272mod test {
273 use super::*;
274 use electrsd::{corepc_node, ElectrsD};
275 use lazy_static::lazy_static;
276 use std::env;
277 use std::str::FromStr;
278 use tokio::sync::Mutex;
279 #[cfg(all(feature = "blocking", feature = "async"))]
280 use {
281 bitcoin::{hashes::Hash, Amount},
282 corepc_node::AddressType,
283 electrsd::electrum_client::ElectrumApi,
284 std::time::Duration,
285 tokio::sync::OnceCell,
286 };
287
288 lazy_static! {
289 static ref BITCOIND: corepc_node::Node = {
290 let bitcoind_exe = env::var("BITCOIND_EXE")
291 .ok()
292 .or_else(|| corepc_node::downloaded_exe_path().ok())
293 .expect(
294 "you need to provide an env var BITCOIND_EXE or specify a bitcoind version feature",
295 );
296 let conf = corepc_node::Conf::default();
297 corepc_node::Node::with_conf(bitcoind_exe, &conf).unwrap()
298 };
299 static ref ELECTRSD: ElectrsD = {
300 let electrs_exe = env::var("ELECTRS_EXE")
301 .ok()
302 .or_else(electrsd::downloaded_exe_path)
303 .expect(
304 "you need to provide env var ELECTRS_EXE or specify an electrsd version feature",
305 );
306 let mut conf = electrsd::Conf::default();
307 conf.http_enabled = true;
308 ElectrsD::with_conf(electrs_exe, &BITCOIND, &conf).unwrap()
309 };
310 static ref MINER: Mutex<()> = Mutex::new(());
311 }
312
313 #[cfg(all(feature = "blocking", feature = "async"))]
314 static PREMINE: OnceCell<()> = OnceCell::const_new();
315
316 #[cfg(all(feature = "blocking", feature = "async"))]
317 async fn setup_clients() -> (BlockingClient, AsyncClient) {
318 setup_clients_with_headers(HashMap::new()).await
319 }
320
321 #[cfg(all(feature = "blocking", feature = "async"))]
322 async fn setup_clients_with_headers(
323 headers: HashMap<String, String>,
324 ) -> (BlockingClient, AsyncClient) {
325 PREMINE
326 .get_or_init(|| async {
327 let _miner = MINER.lock().await;
328 generate_blocks_and_wait(101);
329 })
330 .await;
331
332 let esplora_url = ELECTRSD.esplora_url.as_ref().unwrap();
333
334 let mut builder = Builder::new(&format!("http://{esplora_url}"));
335 if !headers.is_empty() {
336 builder.headers = headers;
337 }
338
339 let blocking_client = builder.build_blocking();
340
341 let builder_async = Builder::new(&format!("http://{esplora_url}"));
342
343 #[cfg(feature = "tokio")]
344 let async_client = builder_async.build_async().unwrap();
345
346 #[cfg(not(feature = "tokio"))]
347 let async_client = builder_async
348 .build_async_with_sleeper::<r#async::DefaultSleeper>()
349 .unwrap();
350
351 (blocking_client, async_client)
352 }
353
354 #[cfg(all(feature = "blocking", feature = "async"))]
355 fn generate_blocks_and_wait(num: usize) {
356 let cur_height = BITCOIND.client.get_block_count().unwrap().0;
357 generate_blocks(num);
358 wait_for_block(cur_height as usize + num);
359 }
360
361 #[cfg(all(feature = "blocking", feature = "async"))]
362 fn generate_blocks(num: usize) {
363 let address = BITCOIND
364 .client
365 .new_address_with_type(AddressType::Legacy)
366 .unwrap();
367 let _block_hashes = BITCOIND.client.generate_to_address(num, &address).unwrap();
368 }
369
370 #[cfg(all(feature = "blocking", feature = "async"))]
371 fn wait_for_block(min_height: usize) {
372 let mut header = ELECTRSD.client.block_headers_subscribe().unwrap();
373 loop {
374 if header.height >= min_height {
375 break;
376 }
377 header = exponential_backoff_poll(|| {
378 ELECTRSD.trigger().unwrap();
379 ELECTRSD.client.ping().unwrap();
380 ELECTRSD.client.block_headers_pop().unwrap()
381 });
382 }
383 }
384
385 #[cfg(all(feature = "blocking", feature = "async"))]
386 fn exponential_backoff_poll<T, F>(mut poll: F) -> T
387 where
388 F: FnMut() -> Option<T>,
389 {
390 let mut delay = Duration::from_millis(64);
391 loop {
392 match poll() {
393 Some(data) => break data,
394 None if delay.as_millis() < 512 => delay = delay.mul_f32(2.0),
395 None => {}
396 }
397
398 std::thread::sleep(delay);
399 }
400 }
401
402 #[test]
403 fn feerate_parsing() {
404 let esplora_fees = serde_json::from_str::<HashMap<u16, f64>>(
405 r#"{
406 "25": 1.015,
407 "5": 2.3280000000000003,
408 "12": 2.0109999999999997,
409 "15": 1.018,
410 "17": 1.018,
411 "11": 2.0109999999999997,
412 "3": 3.01,
413 "2": 4.9830000000000005,
414 "6": 2.2359999999999998,
415 "21": 1.018,
416 "13": 1.081,
417 "7": 2.2359999999999998,
418 "8": 2.2359999999999998,
419 "16": 1.018,
420 "20": 1.018,
421 "22": 1.017,
422 "23": 1.017,
423 "504": 1,
424 "9": 2.2359999999999998,
425 "14": 1.018,
426 "10": 2.0109999999999997,
427 "24": 1.017,
428 "1008": 1,
429 "1": 4.9830000000000005,
430 "4": 2.3280000000000003,
431 "19": 1.018,
432 "144": 1,
433 "18": 1.018
434}
435"#,
436 )
437 .unwrap();
438 assert!(convert_fee_rate(1, HashMap::new()).is_none());
439 assert_eq!(convert_fee_rate(6, esplora_fees.clone()).unwrap(), 2.236);
440 assert_eq!(
441 convert_fee_rate(26, esplora_fees.clone()).unwrap(),
442 1.015,
443 "should inherit from value for 25"
444 );
445 assert!(
446 convert_fee_rate(0, esplora_fees).is_none(),
447 "should not return feerate for 0 target"
448 );
449 }
450
451 #[cfg(all(feature = "blocking", feature = "async"))]
452 #[tokio::test]
453 async fn test_get_tx() {
454 let (blocking_client, async_client) = setup_clients().await;
455
456 let address = BITCOIND
457 .client
458 .new_address_with_type(AddressType::Legacy)
459 .unwrap();
460 let txid = BITCOIND
461 .client
462 .send_to_address(&address, Amount::from_sat(1000))
463 .unwrap()
464 .txid()
465 .unwrap();
466 let _miner = MINER.lock().await;
467 generate_blocks_and_wait(1);
468
469 let tx = blocking_client.get_tx(&txid).unwrap();
470 let tx_async = async_client.get_tx(&txid).await.unwrap();
471 assert_eq!(tx, tx_async);
472 }
473
474 #[cfg(all(feature = "blocking", feature = "async"))]
475 #[tokio::test]
476 async fn test_get_tx_no_opt() {
477 let (blocking_client, async_client) = setup_clients().await;
478
479 let address = BITCOIND
480 .client
481 .new_address_with_type(AddressType::Legacy)
482 .unwrap();
483 let txid = BITCOIND
484 .client
485 .send_to_address(&address, Amount::from_sat(1000))
486 .unwrap()
487 .txid()
488 .unwrap();
489 let _miner = MINER.lock().await;
490 generate_blocks_and_wait(1);
491
492 let tx_no_opt = blocking_client.get_tx_no_opt(&txid).unwrap();
493 let tx_no_opt_async = async_client.get_tx_no_opt(&txid).await.unwrap();
494 assert_eq!(tx_no_opt, tx_no_opt_async);
495 }
496
497 #[cfg(all(feature = "blocking", feature = "async"))]
498 #[tokio::test]
499 async fn test_get_tx_status() {
500 let (blocking_client, async_client) = setup_clients().await;
501
502 let address = BITCOIND
503 .client
504 .new_address_with_type(AddressType::Legacy)
505 .unwrap();
506 let txid = BITCOIND
507 .client
508 .send_to_address(&address, Amount::from_sat(1000))
509 .unwrap()
510 .txid()
511 .unwrap();
512 let _miner = MINER.lock().await;
513 generate_blocks_and_wait(1);
514
515 let tx_status = blocking_client.get_tx_status(&txid).unwrap();
516 let tx_status_async = async_client.get_tx_status(&txid).await.unwrap();
517 assert_eq!(tx_status, tx_status_async);
518 assert!(tx_status.confirmed);
519
520 let txid = Txid::hash(b"ayyyy lmao");
522 let tx_status = blocking_client.get_tx_status(&txid).unwrap();
523 let tx_status_async = async_client.get_tx_status(&txid).await.unwrap();
524 assert_eq!(tx_status, tx_status_async);
525 assert!(!tx_status.confirmed);
526 assert!(tx_status.block_height.is_none());
527 assert!(tx_status.block_hash.is_none());
528 assert!(tx_status.block_time.is_none());
529 }
530
531 #[cfg(all(feature = "blocking", feature = "async"))]
532 #[tokio::test]
533 async fn test_get_tx_info() {
534 let (blocking_client, async_client) = setup_clients().await;
535
536 let address = BITCOIND
537 .client
538 .new_address_with_type(AddressType::Legacy)
539 .unwrap();
540 let txid = BITCOIND
541 .client
542 .send_to_address(&address, Amount::from_sat(1000))
543 .unwrap()
544 .txid()
545 .unwrap();
546 let _miner = MINER.lock().await;
547 generate_blocks_and_wait(1);
548
549 let tx_res = BITCOIND
550 .client
551 .get_transaction(txid)
552 .unwrap()
553 .into_model()
554 .unwrap();
555 let tx_exp: Transaction = tx_res.tx;
556 let tx_block_height = BITCOIND
557 .client
558 .get_block_header_verbose(&tx_res.block_hash.unwrap())
559 .unwrap()
560 .into_model()
561 .unwrap()
562 .height;
563
564 let tx_info = blocking_client
565 .get_tx_info(&txid)
566 .unwrap()
567 .expect("must get tx");
568 let tx_info_async = async_client
569 .get_tx_info(&txid)
570 .await
571 .unwrap()
572 .expect("must get tx");
573 assert_eq!(tx_info, tx_info_async);
574 assert_eq!(tx_info.txid, txid);
575 assert_eq!(tx_info.to_tx(), tx_exp);
576 assert_eq!(tx_info.size, tx_exp.total_size());
577 assert_eq!(tx_info.weight(), tx_exp.weight());
578 assert_eq!(tx_info.fee(), tx_res.fee.unwrap().unsigned_abs());
579 assert!(tx_info.status.confirmed);
580 assert_eq!(tx_info.status.block_height, Some(tx_block_height));
581 assert_eq!(tx_info.status.block_hash, tx_res.block_hash);
582 assert_eq!(
583 tx_info.status.block_time,
584 tx_res.block_time.map(|bt| bt as u64)
585 );
586
587 let txid = Txid::hash(b"not exist");
588 assert_eq!(blocking_client.get_tx_info(&txid).unwrap(), None);
589 assert_eq!(async_client.get_tx_info(&txid).await.unwrap(), None);
590 }
591
592 #[cfg(all(feature = "blocking", feature = "async"))]
593 #[tokio::test]
594 async fn test_get_header_by_hash() {
595 let (blocking_client, async_client) = setup_clients().await;
596
597 let block_hash = BITCOIND
598 .client
599 .get_block_hash(23)
600 .unwrap()
601 .block_hash()
602 .unwrap();
603
604 let block_header = blocking_client.get_header_by_hash(&block_hash).unwrap();
605 let block_header_async = async_client.get_header_by_hash(&block_hash).await.unwrap();
606 assert_eq!(block_header, block_header_async);
607 }
608
609 #[cfg(all(feature = "blocking", feature = "async"))]
610 #[tokio::test]
611 async fn test_get_block_status() {
612 let (blocking_client, async_client) = setup_clients().await;
613
614 let block_hash = BITCOIND
615 .client
616 .get_block_hash(21)
617 .unwrap()
618 .block_hash()
619 .unwrap();
620 let next_block_hash = BITCOIND
621 .client
622 .get_block_hash(22)
623 .unwrap()
624 .block_hash()
625 .unwrap();
626
627 let expected = BlockStatus {
628 in_best_chain: true,
629 height: Some(21),
630 next_best: Some(next_block_hash),
631 };
632
633 let block_status = blocking_client.get_block_status(&block_hash).unwrap();
634 let block_status_async = async_client.get_block_status(&block_hash).await.unwrap();
635 assert_eq!(expected, block_status);
636 assert_eq!(expected, block_status_async);
637 }
638
639 #[cfg(all(feature = "blocking", feature = "async"))]
640 #[tokio::test]
641 async fn test_get_non_existing_block_status() {
642 let (blocking_client, async_client) = setup_clients().await;
649
650 let block_hash = BlockHash::all_zeros();
651
652 let expected = BlockStatus {
653 in_best_chain: false,
654 height: None,
655 next_best: None,
656 };
657
658 let block_status = blocking_client.get_block_status(&block_hash).unwrap();
659 let block_status_async = async_client.get_block_status(&block_hash).await.unwrap();
660 assert_eq!(expected, block_status);
661 assert_eq!(expected, block_status_async);
662 }
663
664 #[cfg(all(feature = "blocking", feature = "async"))]
665 #[tokio::test]
666 async fn test_get_block_by_hash() {
667 let (blocking_client, async_client) = setup_clients().await;
668
669 let block_hash = BITCOIND
670 .client
671 .get_block_hash(21)
672 .unwrap()
673 .block_hash()
674 .unwrap();
675
676 let expected = Some(BITCOIND.client.get_block(block_hash).unwrap());
677
678 let block = blocking_client.get_block_by_hash(&block_hash).unwrap();
679 let block_async = async_client.get_block_by_hash(&block_hash).await.unwrap();
680 assert_eq!(expected, block);
681 assert_eq!(expected, block_async);
682 }
683
684 #[cfg(all(feature = "blocking", feature = "async"))]
685 #[tokio::test]
686 async fn test_that_errors_are_propagated() {
687 let (blocking_client, async_client) = setup_clients().await;
688
689 let address = BITCOIND
690 .client
691 .new_address_with_type(AddressType::Legacy)
692 .unwrap();
693 let txid = BITCOIND
694 .client
695 .send_to_address(&address, Amount::from_sat(1000))
696 .unwrap()
697 .txid()
698 .unwrap();
699 let _miner = MINER.lock().await;
700 generate_blocks_and_wait(1);
701
702 let tx = blocking_client.get_tx(&txid).unwrap();
703 let async_res = async_client.broadcast(tx.as_ref().unwrap()).await;
704 let blocking_res = blocking_client.broadcast(tx.as_ref().unwrap());
705 assert!(async_res.is_err());
706 assert!(matches!(
707 async_res.unwrap_err(),
708 Error::HttpResponse { status: 400, message } if message.contains("-27")
709 ));
710 assert!(blocking_res.is_err());
711 assert!(matches!(
712 blocking_res.unwrap_err(),
713 Error::HttpResponse { status: 400, message } if message.contains("-27")
714 ));
715 }
716
717 #[cfg(all(feature = "blocking", feature = "async"))]
718 #[tokio::test]
719 async fn test_get_block_by_hash_not_existing() {
720 let (blocking_client, async_client) = setup_clients().await;
721
722 let block = blocking_client
723 .get_block_by_hash(&BlockHash::all_zeros())
724 .unwrap();
725 let block_async = async_client
726 .get_block_by_hash(&BlockHash::all_zeros())
727 .await
728 .unwrap();
729 assert!(block.is_none());
730 assert!(block_async.is_none());
731 }
732
733 #[cfg(all(feature = "blocking", feature = "async"))]
734 #[tokio::test]
735 async fn test_get_merkle_proof() {
736 let (blocking_client, async_client) = setup_clients().await;
737
738 let address = BITCOIND
739 .client
740 .new_address_with_type(AddressType::Legacy)
741 .unwrap();
742 let txid = BITCOIND
743 .client
744 .send_to_address(&address, Amount::from_sat(1000))
745 .unwrap()
746 .txid()
747 .unwrap();
748 let _miner = MINER.lock().await;
749 generate_blocks_and_wait(1);
750
751 let merkle_proof = blocking_client.get_merkle_proof(&txid).unwrap().unwrap();
752 let merkle_proof_async = async_client.get_merkle_proof(&txid).await.unwrap().unwrap();
753 assert_eq!(merkle_proof, merkle_proof_async);
754 assert!(merkle_proof.pos > 0);
755 }
756
757 #[cfg(all(feature = "blocking", feature = "async"))]
758 #[tokio::test]
759 async fn test_get_merkle_block() {
760 let (blocking_client, async_client) = setup_clients().await;
761
762 let address = BITCOIND
763 .client
764 .new_address_with_type(AddressType::Legacy)
765 .unwrap();
766 let txid = BITCOIND
767 .client
768 .send_to_address(&address, Amount::from_sat(1000))
769 .unwrap()
770 .txid()
771 .unwrap();
772 let _miner = MINER.lock().await;
773 generate_blocks_and_wait(1);
774
775 let merkle_block = blocking_client.get_merkle_block(&txid).unwrap().unwrap();
776 let merkle_block_async = async_client.get_merkle_block(&txid).await.unwrap().unwrap();
777 assert_eq!(merkle_block, merkle_block_async);
778
779 let mut matches = vec![txid];
780 let mut indexes = vec![];
781 let root = merkle_block
782 .txn
783 .extract_matches(&mut matches, &mut indexes)
784 .unwrap();
785 assert_eq!(root, merkle_block.header.merkle_root);
786 assert_eq!(indexes.len(), 1);
787 assert!(indexes[0] > 0);
788 }
789
790 #[cfg(all(feature = "blocking", feature = "async"))]
791 #[tokio::test]
792 async fn test_get_output_status() {
793 let (blocking_client, async_client) = setup_clients().await;
794
795 let address = BITCOIND
796 .client
797 .new_address_with_type(AddressType::Legacy)
798 .unwrap();
799 let txid = BITCOIND
800 .client
801 .send_to_address(&address, Amount::from_sat(1000))
802 .unwrap()
803 .txid()
804 .unwrap();
805 let _miner = MINER.lock().await;
806 generate_blocks_and_wait(1);
807
808 let output_status = blocking_client
809 .get_output_status(&txid, 1)
810 .unwrap()
811 .unwrap();
812 let output_status_async = async_client
813 .get_output_status(&txid, 1)
814 .await
815 .unwrap()
816 .unwrap();
817
818 assert_eq!(output_status, output_status_async);
819 }
820
821 #[cfg(all(feature = "blocking", feature = "async"))]
822 #[tokio::test]
823 async fn test_get_height() {
824 let (blocking_client, async_client) = setup_clients().await;
825 let block_height = blocking_client.get_height().unwrap();
826 let block_height_async = async_client.get_height().await.unwrap();
827 assert!(block_height > 0);
828 assert_eq!(block_height, block_height_async);
829 }
830
831 #[cfg(all(feature = "blocking", feature = "async"))]
832 #[tokio::test]
833 async fn test_get_tip_hash() {
834 let (blocking_client, async_client) = setup_clients().await;
835 let tip_hash = blocking_client.get_tip_hash().unwrap();
836 let tip_hash_async = async_client.get_tip_hash().await.unwrap();
837 assert_eq!(tip_hash, tip_hash_async);
838 }
839
840 #[cfg(all(feature = "blocking", feature = "async"))]
841 #[tokio::test]
842 async fn test_get_block_hash() {
843 let (blocking_client, async_client) = setup_clients().await;
844
845 let block_hash = BITCOIND
846 .client
847 .get_block_hash(21)
848 .unwrap()
849 .block_hash()
850 .unwrap();
851
852 let block_hash_blocking = blocking_client.get_block_hash(21).unwrap();
853 let block_hash_async = async_client.get_block_hash(21).await.unwrap();
854 assert_eq!(block_hash, block_hash_blocking);
855 assert_eq!(block_hash, block_hash_async);
856 }
857
858 #[cfg(all(feature = "blocking", feature = "async"))]
859 #[tokio::test]
860 async fn test_get_txid_at_block_index() {
861 let (blocking_client, async_client) = setup_clients().await;
862
863 let block_hash = BITCOIND
864 .client
865 .get_block_hash(23)
866 .unwrap()
867 .block_hash()
868 .unwrap();
869
870 let txid_at_block_index = blocking_client
871 .get_txid_at_block_index(&block_hash, 0)
872 .unwrap()
873 .unwrap();
874 let txid_at_block_index_async = async_client
875 .get_txid_at_block_index(&block_hash, 0)
876 .await
877 .unwrap()
878 .unwrap();
879 assert_eq!(txid_at_block_index, txid_at_block_index_async);
880 }
881
882 #[cfg(all(feature = "blocking", feature = "async"))]
883 #[tokio::test]
884 async fn test_get_fee_estimates() {
885 let (blocking_client, async_client) = setup_clients().await;
886 let fee_estimates = blocking_client.get_fee_estimates().unwrap();
887 let fee_estimates_async = async_client.get_fee_estimates().await.unwrap();
888 assert_eq!(fee_estimates.len(), fee_estimates_async.len());
889 }
890
891 #[cfg(all(feature = "blocking", feature = "async"))]
892 #[tokio::test]
893 async fn test_scripthash_txs() {
894 let (blocking_client, async_client) = setup_clients().await;
895
896 let address = BITCOIND
897 .client
898 .new_address_with_type(AddressType::Legacy)
899 .unwrap();
900 let txid = BITCOIND
901 .client
902 .send_to_address(&address, Amount::from_sat(1000))
903 .unwrap()
904 .txid()
905 .unwrap();
906 let _miner = MINER.lock().await;
907 generate_blocks_and_wait(1);
908
909 let expected_tx = BITCOIND
910 .client
911 .get_transaction(txid)
912 .unwrap()
913 .into_model()
914 .unwrap()
915 .tx;
916 let script = &expected_tx.output[0].script_pubkey;
917 let scripthash_txs_txids: Vec<Txid> = blocking_client
918 .scripthash_txs(script, None)
919 .unwrap()
920 .iter()
921 .map(|tx| tx.txid)
922 .collect();
923 let scripthash_txs_txids_async: Vec<Txid> = async_client
924 .scripthash_txs(script, None)
925 .await
926 .unwrap()
927 .iter()
928 .map(|tx| tx.txid)
929 .collect();
930 assert_eq!(scripthash_txs_txids, scripthash_txs_txids_async);
931 }
932
933 #[cfg(all(feature = "blocking", feature = "async"))]
934 #[tokio::test]
935 async fn test_get_block_info() {
936 let (blocking_client, async_client) = setup_clients().await;
937
938 let blockhash_genesis =
940 BlockHash::from_str("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206")
941 .unwrap();
942
943 let block_info_blocking = blocking_client.get_block_info(&blockhash_genesis).unwrap();
944 let block_info_async = async_client
945 .get_block_info(&blockhash_genesis)
946 .await
947 .unwrap();
948
949 assert_eq!(block_info_async, block_info_blocking);
950 assert_eq!(block_info_async.id, blockhash_genesis);
951 assert_eq!(block_info_async.height, 0);
952 assert_eq!(block_info_async.previousblockhash, None);
953 }
954
955 #[cfg(all(feature = "blocking", feature = "async"))]
956 #[tokio::test]
957 async fn test_get_block_txids() {
958 let (blocking_client, async_client) = setup_clients().await;
959
960 let address = BITCOIND
961 .client
962 .new_address_with_type(AddressType::Legacy)
963 .unwrap();
964
965 let txids: Vec<_> = (0..5)
967 .map(|_| {
968 BITCOIND
969 .client
970 .send_to_address(&address, Amount::from_sat(1000))
971 .unwrap()
972 .txid()
973 .unwrap()
974 })
975 .collect();
976
977 let _miner = MINER.lock().await;
978 generate_blocks_and_wait(1);
979
980 let blockhash = blocking_client.get_tip_hash().unwrap();
982
983 let txids_async = async_client.get_block_txids(&blockhash).await.unwrap();
984 let txids_blocking = blocking_client.get_block_txids(&blockhash).unwrap();
985
986 assert_eq!(txids_async, txids_blocking);
987
988 for expected_txid in txids.iter() {
990 assert!(txids_async.contains(expected_txid));
991 }
992 }
993
994 #[cfg(all(feature = "blocking", feature = "async"))]
995 #[tokio::test]
996 async fn test_get_block_txs() {
997 let (blocking_client, async_client) = setup_clients().await;
998
999 let _miner = MINER.lock().await;
1000 let blockhash = blocking_client.get_tip_hash().unwrap();
1001
1002 let txs_blocking = blocking_client.get_block_txs(&blockhash, None).unwrap();
1003 let txs_async = async_client.get_block_txs(&blockhash, None).await.unwrap();
1004
1005 assert_ne!(txs_blocking.len(), 0);
1006 assert_eq!(txs_blocking.len(), txs_async.len());
1007 }
1008
1009 #[cfg(all(feature = "blocking", feature = "async"))]
1010 #[tokio::test]
1011 async fn test_get_blocks() {
1012 let (blocking_client, async_client) = setup_clients().await;
1013 let start_height = BITCOIND.client.get_block_count().unwrap().0;
1014 let blocks1 = blocking_client.get_blocks(None).unwrap();
1015 let blocks_async1 = async_client.get_blocks(None).await.unwrap();
1016 assert_eq!(blocks1[0].time.height, start_height as u32);
1017 assert_eq!(blocks1, blocks_async1);
1018 generate_blocks_and_wait(10);
1019 let blocks2 = blocking_client.get_blocks(None).unwrap();
1020 let blocks_async2 = async_client.get_blocks(None).await.unwrap();
1021 assert_eq!(blocks2, blocks_async2);
1022 assert_ne!(blocks2, blocks1);
1023 let blocks3 = blocking_client
1024 .get_blocks(Some(start_height as u32))
1025 .unwrap();
1026 let blocks_async3 = async_client
1027 .get_blocks(Some(start_height as u32))
1028 .await
1029 .unwrap();
1030 assert_eq!(blocks3, blocks_async3);
1031 assert_eq!(blocks3[0].time.height, start_height as u32);
1032 assert_eq!(blocks3, blocks1);
1033 let blocks_genesis = blocking_client.get_blocks(Some(0)).unwrap();
1034 let blocks_genesis_async = async_client.get_blocks(Some(0)).await.unwrap();
1035 assert_eq!(blocks_genesis, blocks_genesis_async);
1036 }
1037
1038 #[cfg(all(feature = "blocking", feature = "async"))]
1039 #[tokio::test]
1040 async fn test_get_tx_with_http_header() {
1041 let headers = [(
1042 "Authorization".to_string(),
1043 "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==".to_string(),
1044 )]
1045 .into();
1046 let (blocking_client, async_client) = setup_clients_with_headers(headers).await;
1047
1048 let address = BITCOIND
1049 .client
1050 .new_address_with_type(AddressType::Legacy)
1051 .unwrap();
1052 let txid = BITCOIND
1053 .client
1054 .send_to_address(&address, Amount::from_sat(1000))
1055 .unwrap()
1056 .txid()
1057 .unwrap();
1058 let _miner = MINER.lock().await;
1059 generate_blocks_and_wait(1);
1060
1061 let tx = blocking_client.get_tx(&txid).unwrap();
1062 let tx_async = async_client.get_tx(&txid).await.unwrap();
1063 assert_eq!(tx, tx_async);
1064 }
1065
1066 #[cfg(all(feature = "blocking", feature = "async"))]
1067 #[tokio::test]
1068 async fn test_get_address_stats() {
1069 let (blocking_client, async_client) = setup_clients().await;
1070
1071 let address = BITCOIND
1072 .client
1073 .new_address_with_type(AddressType::Legacy)
1074 .unwrap();
1075
1076 let _txid = BITCOIND
1077 .client
1078 .send_to_address(&address, Amount::from_sat(1000))
1079 .unwrap()
1080 .txid()
1081 .unwrap();
1082
1083 let address_stats_blocking = blocking_client.get_address_stats(&address).unwrap();
1084 let address_stats_async = async_client.get_address_stats(&address).await.unwrap();
1085 assert_eq!(address_stats_blocking, address_stats_async);
1086 assert_eq!(address_stats_async.chain_stats.funded_txo_count, 0);
1087
1088 let _miner = MINER.lock().await;
1089 generate_blocks_and_wait(1);
1090
1091 let address_stats_blocking = blocking_client.get_address_stats(&address).unwrap();
1092 let address_stats_async = async_client.get_address_stats(&address).await.unwrap();
1093 assert_eq!(address_stats_blocking, address_stats_async);
1094 assert_eq!(address_stats_async.chain_stats.funded_txo_count, 1);
1095 assert_eq!(address_stats_async.chain_stats.funded_txo_sum, 1000);
1096 }
1097
1098 #[cfg(all(feature = "blocking", feature = "async"))]
1099 #[tokio::test]
1100 async fn test_get_scripthash_stats() {
1101 let (blocking_client, async_client) = setup_clients().await;
1102
1103 let address_legacy = BITCOIND
1105 .client
1106 .new_address_with_type(AddressType::Legacy)
1107 .unwrap();
1108 let address_p2sh_segwit = BITCOIND
1109 .client
1110 .new_address_with_type(AddressType::P2shSegwit)
1111 .unwrap();
1112 let address_bech32 = BITCOIND
1113 .client
1114 .new_address_with_type(AddressType::Bech32)
1115 .unwrap();
1116 let address_bech32m = BITCOIND
1117 .client
1118 .new_address_with_type(AddressType::Bech32m)
1119 .unwrap();
1120
1121 let _txid = BITCOIND
1123 .client
1124 .send_to_address(&address_legacy, Amount::from_sat(1000))
1125 .unwrap()
1126 .txid()
1127 .unwrap();
1128 let _txid = BITCOIND
1129 .client
1130 .send_to_address(&address_p2sh_segwit, Amount::from_sat(1000))
1131 .unwrap()
1132 .txid()
1133 .unwrap();
1134 let _txid = BITCOIND
1135 .client
1136 .send_to_address(&address_bech32, Amount::from_sat(1000))
1137 .unwrap()
1138 .txid()
1139 .unwrap();
1140 let _txid = BITCOIND
1141 .client
1142 .send_to_address(&address_bech32m, Amount::from_sat(1000))
1143 .unwrap()
1144 .txid()
1145 .unwrap();
1146
1147 let _miner = MINER.lock().await;
1148 generate_blocks_and_wait(1);
1149
1150 let script_legacy = address_legacy.script_pubkey();
1152 let script_p2sh_segwit = address_p2sh_segwit.script_pubkey();
1153 let script_bech32 = address_bech32.script_pubkey();
1154 let script_bech32m = address_bech32m.script_pubkey();
1155
1156 let scripthash_stats_blocking_legacy = blocking_client
1158 .get_scripthash_stats(&script_legacy)
1159 .unwrap();
1160 let scripthash_stats_async_legacy = async_client
1161 .get_scripthash_stats(&script_legacy)
1162 .await
1163 .unwrap();
1164 assert_eq!(
1165 scripthash_stats_blocking_legacy,
1166 scripthash_stats_async_legacy
1167 );
1168 assert_eq!(
1169 scripthash_stats_blocking_legacy.chain_stats.funded_txo_sum,
1170 1000
1171 );
1172 assert_eq!(scripthash_stats_blocking_legacy.chain_stats.tx_count, 1);
1173
1174 let scripthash_stats_blocking_p2sh_segwit = blocking_client
1176 .get_scripthash_stats(&script_p2sh_segwit)
1177 .unwrap();
1178 let scripthash_stats_async_p2sh_segwit = async_client
1179 .get_scripthash_stats(&script_p2sh_segwit)
1180 .await
1181 .unwrap();
1182 assert_eq!(
1183 scripthash_stats_blocking_p2sh_segwit,
1184 scripthash_stats_async_p2sh_segwit
1185 );
1186 assert_eq!(
1187 scripthash_stats_blocking_p2sh_segwit
1188 .chain_stats
1189 .funded_txo_sum,
1190 1000
1191 );
1192 assert_eq!(
1193 scripthash_stats_blocking_p2sh_segwit.chain_stats.tx_count,
1194 1
1195 );
1196
1197 let scripthash_stats_blocking_bech32 = blocking_client
1199 .get_scripthash_stats(&script_bech32)
1200 .unwrap();
1201 let scripthash_stats_async_bech32 = async_client
1202 .get_scripthash_stats(&script_bech32)
1203 .await
1204 .unwrap();
1205 assert_eq!(
1206 scripthash_stats_blocking_bech32,
1207 scripthash_stats_async_bech32
1208 );
1209 assert_eq!(
1210 scripthash_stats_blocking_bech32.chain_stats.funded_txo_sum,
1211 1000
1212 );
1213 assert_eq!(scripthash_stats_blocking_bech32.chain_stats.tx_count, 1);
1214
1215 let scripthash_stats_blocking_bech32m = blocking_client
1217 .get_scripthash_stats(&script_bech32m)
1218 .unwrap();
1219 let scripthash_stats_async_bech32m = async_client
1220 .get_scripthash_stats(&script_bech32m)
1221 .await
1222 .unwrap();
1223 assert_eq!(
1224 scripthash_stats_blocking_bech32m,
1225 scripthash_stats_async_bech32m
1226 );
1227 assert_eq!(
1228 scripthash_stats_blocking_bech32m.chain_stats.funded_txo_sum,
1229 1000
1230 );
1231 assert_eq!(scripthash_stats_blocking_bech32m.chain_stats.tx_count, 1);
1232 }
1233
1234 #[cfg(all(feature = "blocking", feature = "async"))]
1235 #[tokio::test]
1236 async fn test_get_address_txs() {
1237 let (blocking_client, async_client) = setup_clients().await;
1238
1239 let address = BITCOIND
1240 .client
1241 .new_address_with_type(AddressType::Legacy)
1242 .unwrap();
1243
1244 let txid = BITCOIND
1245 .client
1246 .send_to_address(&address, Amount::from_sat(1000))
1247 .unwrap()
1248 .txid()
1249 .unwrap();
1250
1251 let _miner = MINER.lock().await;
1252 generate_blocks_and_wait(1);
1253
1254 let address_txs_blocking = blocking_client.get_address_txs(&address, None).unwrap();
1255 let address_txs_async = async_client.get_address_txs(&address, None).await.unwrap();
1256
1257 assert_eq!(address_txs_blocking, address_txs_async);
1258 assert_eq!(address_txs_async[0].txid, txid);
1259 }
1260
1261 #[cfg(all(feature = "blocking", feature = "async"))]
1262 #[tokio::test]
1263 async fn test_get_address_utxos() {
1264 let (blocking_client, async_client) = setup_clients().await;
1265
1266 let address = BITCOIND
1267 .client
1268 .new_address_with_type(AddressType::Legacy)
1269 .unwrap();
1270
1271 let _txid = BITCOIND
1272 .client
1273 .send_to_address(&address, Amount::from_sat(21000))
1274 .unwrap()
1275 .txid()
1276 .unwrap();
1277
1278 let _miner = MINER.lock().await;
1279 generate_blocks_and_wait(1);
1280
1281 let address_utxos_blocking = blocking_client.get_address_utxos(&address).unwrap();
1282 let address_utxos_async = async_client.get_address_utxos(&address).await.unwrap();
1283
1284 assert_ne!(address_utxos_blocking.len(), 0);
1285 assert_ne!(address_utxos_async.len(), 0);
1286 assert_eq!(address_utxos_blocking, address_utxos_async);
1287 }
1288
1289 #[cfg(all(feature = "blocking", feature = "async"))]
1290 #[tokio::test]
1291 async fn test_get_scripthash_utxos() {
1292 let (blocking_client, async_client) = setup_clients().await;
1293
1294 let address = BITCOIND
1295 .client
1296 .new_address_with_type(AddressType::Legacy)
1297 .unwrap();
1298 let script = address.script_pubkey();
1299
1300 let _txid = BITCOIND
1301 .client
1302 .send_to_address(&address, Amount::from_sat(21000))
1303 .unwrap()
1304 .txid()
1305 .unwrap();
1306
1307 let _miner = MINER.lock().await;
1308 generate_blocks_and_wait(1);
1309
1310 let scripthash_utxos_blocking = blocking_client.get_scripthash_utxos(&script).unwrap();
1311 let scripthash_utxos_async = async_client.get_scripthash_utxos(&script).await.unwrap();
1312
1313 assert_ne!(scripthash_utxos_blocking.len(), 0);
1314 assert_ne!(scripthash_utxos_async.len(), 0);
1315 assert_eq!(scripthash_utxos_blocking, scripthash_utxos_async);
1316 }
1317
1318 #[cfg(all(feature = "blocking", feature = "async"))]
1319 #[tokio::test]
1320 async fn test_get_tx_outspends() {
1321 let (blocking_client, async_client) = setup_clients().await;
1322
1323 let address = BITCOIND
1324 .client
1325 .new_address_with_type(AddressType::Legacy)
1326 .unwrap();
1327
1328 let txid = BITCOIND
1329 .client
1330 .send_to_address(&address, Amount::from_sat(21000))
1331 .unwrap()
1332 .txid()
1333 .unwrap();
1334
1335 let _miner = MINER.lock().await;
1336 generate_blocks_and_wait(1);
1337
1338 let outspends_blocking = blocking_client.get_tx_outspends(&txid).unwrap();
1339 let outspends_async = async_client.get_tx_outspends(&txid).await.unwrap();
1340
1341 assert_eq!(outspends_blocking.len(), 2);
1343 assert_eq!(outspends_async.len(), 2);
1344 assert_eq!(outspends_blocking, outspends_async);
1345
1346 assert!(outspends_blocking.iter().all(|output| !output.spent));
1348 }
1349
1350 #[cfg(all(feature = "blocking", feature = "async"))]
1351 #[tokio::test]
1352 async fn test_mempool_methods() {
1353 let (blocking_client, async_client) = setup_clients().await;
1354
1355 let address = BITCOIND
1356 .client
1357 .new_address_with_type(AddressType::Legacy)
1358 .unwrap();
1359
1360 for _ in 0..5 {
1361 let _txid = BITCOIND
1362 .client
1363 .send_to_address(&address, Amount::from_sat(1000))
1364 .unwrap()
1365 .txid()
1366 .unwrap();
1367 }
1368
1369 tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
1371
1372 let stats_blocking = blocking_client.get_mempool_stats().unwrap();
1374 let stats_async = async_client.get_mempool_stats().await.unwrap();
1375 assert_eq!(stats_blocking, stats_async);
1376 assert!(stats_blocking.count >= 5);
1377
1378 let recent_blocking = blocking_client.get_mempool_recent_txs().unwrap();
1380 let recent_async = async_client.get_mempool_recent_txs().await.unwrap();
1381 assert_eq!(recent_blocking, recent_async);
1382 assert!(recent_blocking.len() <= 10);
1383 assert!(!recent_blocking.is_empty());
1384
1385 let txids_blocking = blocking_client.get_mempool_txids().unwrap();
1387 let txids_async = async_client.get_mempool_txids().await.unwrap();
1388 assert_eq!(txids_blocking, txids_async);
1389 assert!(txids_blocking.len() >= 5);
1390
1391 let script = address.script_pubkey();
1393 let scripthash_txs_blocking = blocking_client.get_mempool_scripthash_txs(&script).unwrap();
1394 let scripthash_txs_async = async_client
1395 .get_mempool_scripthash_txs(&script)
1396 .await
1397 .unwrap();
1398 assert_eq!(scripthash_txs_blocking, scripthash_txs_async);
1399 assert_eq!(scripthash_txs_blocking.len(), 5);
1400
1401 let mempool_address_txs_blocking =
1403 blocking_client.get_mempool_address_txs(&address).unwrap();
1404 let mempool_address_txs_async = async_client
1405 .get_mempool_address_txs(&address)
1406 .await
1407 .unwrap();
1408 assert_eq!(mempool_address_txs_blocking, mempool_address_txs_async);
1409 assert_eq!(mempool_address_txs_blocking.len(), 5);
1410 }
1411}