1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![cfg_attr(not(feature = "use-reqwest"), doc = "```ignore")]
23#![cfg_attr(feature = "use-reqwest", doc = "```rust")]
24#![cfg_attr(not(feature = "use-reqwest"), doc = "```rust")]
117#![cfg_attr(feature = "use-reqwest", doc = "```ignore")]
118use std::{
151 collections::HashMap,
152 io::{Cursor, Read},
153};
154
155pub mod error;
157pub mod responses;
158pub mod prelude {
160 #[cfg(feature = "use-reqwest")]
161 pub use super::RestClient;
162 pub use super::{
163 responses::{GetDeploymentInfoResult, GetMempoolTxidsAndSequenceResult, GetUtxosResult},
164 Error, RestApi,
165 };
166 pub use bitcoin::{
167 bip158::BlockFilter, block::Header, hash_types::FilterHeader, Block, BlockHash, Network,
168 OutPoint, Transaction, Txid,
169 };
170 pub use bitcoincore_rpc_json::{
171 GetBlockchainInfoResult, GetMempoolEntryResult, GetMempoolInfoResult,
172 };
173}
174
175#[doc(inline)]
176pub use crate::error::Error;
177use crate::responses::{
178 deployment_info::GetDeploymentInfoResult,
179 get_utxos::{GetUtxosResult, Utxo},
180 GetMempoolTxidsAndSequenceResult,
181};
182
183#[cfg(feature = "use-reqwest")]
184use bitcoin::Network;
185use bitcoin::{
186 bip158::BlockFilter,
187 block::Header,
188 consensus::encode::{deserialize, Decodable, ReadExt},
189 hash_types::FilterHeader,
190 Block, BlockHash, OutPoint, Transaction, Txid, VarInt,
191};
192use bitcoincore_rpc_json::{GetBlockchainInfoResult, GetMempoolEntryResult, GetMempoolInfoResult};
193use bytes::Bytes;
194#[cfg(feature = "use-reqwest")]
195use http::StatusCode;
196#[cfg(feature = "use-reqwest")]
197use reqwest::{Client, IntoUrl};
198use serde::Deserialize;
199#[cfg(feature = "use-reqwest")]
200use url::Url;
201
202#[doc(hidden)]
203#[cfg(not(feature = "use-reqwest"))]
204pub use async_trait;
205#[doc(hidden)]
206#[cfg(not(feature = "use-reqwest"))]
207pub use bitcoin;
208#[doc(hidden)]
209#[cfg(not(feature = "use-reqwest"))]
210pub use bitcoincore_rpc_json;
211#[doc(hidden)]
212#[cfg(not(feature = "use-reqwest"))]
213pub use bytes;
214#[doc(hidden)]
215#[cfg(not(feature = "use-reqwest"))]
216pub use http;
217#[doc(hidden)]
218#[cfg(not(feature = "use-reqwest"))]
219pub use serde;
220
221#[async_trait::async_trait]
229pub trait RestApi {
230 async fn get_json<T: for<'a> Deserialize<'a>>(&self, path: &str) -> Result<T, Error>;
232
233 async fn get_bin(&self, path: &str) -> Result<Bytes, Error>;
235
236 async fn get_block_headers(
240 &self,
241 start_hash: BlockHash,
242 count: u32,
243 ) -> Result<Vec<Header>, Error> {
244 let path = format!("rest/headers/{count}/{start_hash}.bin",);
245 let resp = self.get_bin(&path).await?;
246
247 const BLOCK_HEADER_SIZE: usize = 80usize;
248 let num = resp.len() / BLOCK_HEADER_SIZE;
249 let mut vec = Vec::<Header>::with_capacity(num);
250 let mut decoder = Cursor::new(resp);
251 for _ in 0..num {
252 vec.push(Header::consensus_decode_from_finite_reader(&mut decoder)?);
253 }
254 Ok(vec)
255 }
256
257 async fn get_block_at_height(&self, height: u64) -> Result<Block, Error> {
259 let hash = self.get_block_hash(height).await?;
260 self.get_block(hash).await
261 }
262
263 async fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
267 let path = format!("rest/blockhashbyheight/{height}.bin");
268 let resp = self.get_bin(&path).await?;
269 Ok(deserialize(&resp)?)
270 }
271
272 async fn get_block(&self, hash: BlockHash) -> Result<Block, Error> {
276 let path = format!("rest/block/{hash}.bin");
277 let resp = self.get_bin(&path).await?;
278 Ok(deserialize(&resp)?)
279 }
280
281 async fn get_transaction(&self, txid: Txid) -> Result<Transaction, Error> {
285 let path = format!("rest/tx/{txid}.bin");
286 let resp = self.get_bin(&path).await?;
287 Ok(deserialize(&resp)?)
288 }
289
290 async fn get_block_filter_headers(
294 &self,
295 start_hash: BlockHash,
296 count: u32,
297 ) -> Result<Vec<FilterHeader>, Error> {
298 let path = format!("rest/blockfilterheaders/basic/{count}/{start_hash}.bin");
299 let resp = self.get_bin(&path).await?;
300
301 const BLOCK_FILTER_HEADER_SIZE: usize = 32usize;
302
303 let num = resp.len() / BLOCK_FILTER_HEADER_SIZE;
304 let mut vec = Vec::<FilterHeader>::with_capacity(num);
305 let mut decoder = Cursor::new(resp);
306 for _ in 0..num {
307 vec.push(FilterHeader::consensus_decode_from_finite_reader(
308 &mut decoder,
309 )?);
310 }
311 Ok(vec)
312 }
313
314 async fn get_block_filter(&self, hash: BlockHash) -> Result<BlockFilter, Error> {
318 let path = format!("rest/blockfilter/basic/{hash}.bin");
319 let resp = self.get_bin(&path).await?;
320 let mut contents: Vec<u8> = vec![];
321 let mut cursor = Cursor::new(&resp);
322 cursor
323 .read_to_end(&mut contents)
324 .map_err(|e| Error::BitcoinEncodeError(bitcoin::consensus::encode::Error::Io(e)))?;
325 Ok(BlockFilter::new(&contents))
326 }
327
328 async fn get_chain_info(&self) -> Result<GetBlockchainInfoResult, Error> {
332 let path = "rest/chaininfo.json";
333 self.get_json(path).await
334 }
335
336 async fn get_utxos(
342 &self,
343 outpoints: &[OutPoint],
344 check_mempool: bool,
345 ) -> Result<GetUtxosResult, Error> {
346 let mut path = Vec::with_capacity(1 + if check_mempool { 1 } else { 0 } + outpoints.len());
347 path.push("rest/getutxos".to_string());
348 if check_mempool {
349 path.push("checkmempool".to_string());
350 }
351 for outpoint in outpoints {
352 path.push([outpoint.txid.to_string(), outpoint.vout.to_string()].join("-"));
353 }
354 let mut path = path.join("/");
355 path.push_str(".bin");
356 let resp = self.get_bin(&path).await?;
357
358 let mut cursor = Cursor::new(&resp);
359 decode_utxos_result(&mut cursor)
360 }
361
362 async fn get_mempool_info(&self) -> Result<GetMempoolInfoResult, Error> {
366 let path = "rest/mempool/info.json";
367 self.get_json(path).await
368 }
369
370 async fn get_mempool(&self) -> Result<HashMap<Txid, GetMempoolEntryResult>, Error> {
374 let path = "rest/mempool/contents.json";
375 self.get_json(path).await
376 }
377
378 async fn get_mempool_txids(&self) -> Result<Vec<Txid>, Error> {
383 let path = "rest/mempool/contents.json?verbose=false";
384 self.get_json(path).await
385 }
386
387 async fn get_mempool_txids_and_sequence(
392 &self,
393 ) -> Result<GetMempoolTxidsAndSequenceResult, Error> {
394 let path = "rest/mempool/contents.json?mempool_sequence=true&verbose=false";
395 self.get_json(path).await
396 }
397
398 async fn get_deployment_info(&self) -> Result<GetDeploymentInfoResult, Error> {
403 let path = "rest/deploymentinfo.json";
404 self.get_json(path).await
405 }
406
407 async unsafe fn get_deployment_info_at_block(
416 &self,
417 hash: BlockHash,
418 ) -> Result<GetDeploymentInfoResult, Error> {
419 let path = format!("rest/deploymentinfo/{hash}.json");
420 self.get_json(&path).await
421 }
422}
423
424#[cfg(feature = "use-reqwest")]
428#[cfg_attr(docsrs, doc(cfg(feature = "use-reqwest")))]
429#[derive(Clone)]
430pub struct RestClient {
431 client: Client,
432 endpoint: Url,
433}
434
435#[cfg(feature = "use-reqwest")]
436#[cfg_attr(docsrs, doc(cfg(feature = "use-reqwest")))]
437impl RestClient {
438 pub fn new(endpoint: impl IntoUrl) -> Result<Self, Error> {
440 Ok(RestClient {
441 client: Client::new(),
442 endpoint: endpoint.into_url()?,
443 })
444 }
445
446 pub fn network_default(network: Network) -> Self {
450 let endpoint = match network {
451 Network::Testnet => "http://localhost:18332",
452 Network::Signet => "http://localhost:38332",
453 Network::Regtest => "http://localhost:18443",
454 _ => "http://localhost:8332",
455 };
456
457 RestClient {
458 client: Client::new(),
459 endpoint: endpoint.parse().unwrap(),
460 }
461 }
462}
463
464#[cfg(feature = "use-reqwest")]
465#[cfg_attr(docsrs, doc(cfg(feature = "use-reqwest")))]
466#[async_trait::async_trait]
467impl RestApi for RestClient {
468 async fn get_json<T: for<'a> Deserialize<'a>>(&self, path: &str) -> Result<T, Error> {
469 let url = self.endpoint.join(path).unwrap();
470 let response = self.client.get(url).send().await?;
471
472 if response.status() != StatusCode::OK {
473 return Err(Error::NotOkError(response.status()));
474 }
475
476 response.json::<T>().await.map_err(Error::ReqwestError)
477 }
478
479 async fn get_bin(&self, path: &str) -> Result<Bytes, Error> {
480 let url = self.endpoint.join(path).unwrap();
481 let response = self.client.get(url).send().await?;
482
483 if response.status() != StatusCode::OK {
484 return Err(Error::NotOkError(response.status()));
485 }
486
487 response.bytes().await.map_err(Error::ReqwestError)
488 }
489}
490
491fn decode_utxos_result(reader: &mut impl Read) -> Result<GetUtxosResult, Error> {
492 let chain_height: u32 = Decodable::consensus_decode_from_finite_reader(reader)?;
493 let chain_tip_hash: BlockHash = Decodable::consensus_decode_from_finite_reader(reader)?;
494 let bitmap_byte_count: VarInt = Decodable::consensus_decode_from_finite_reader(reader)?;
495 let bitmap: Vec<u8> = read_bytes(reader, bitmap_byte_count.0 as usize)?;
496
497 let utxo_count: VarInt = Decodable::consensus_decode_from_finite_reader(reader)?;
498
499 let mut utxos = Vec::with_capacity(utxo_count.0 as usize);
500 for _ in 0..utxo_count.0 {
501 let utxo = decode_utxo(reader)?;
502 utxos.push(utxo);
503 }
504
505 Ok(GetUtxosResult {
506 chain_height,
507 chain_tip_hash,
508 bitmap,
509 utxos,
510 })
511}
512
513fn decode_utxo(reader: &mut impl Read) -> Result<Utxo, Error> {
514 let _: [u8; 4] = Decodable::consensus_decode_from_finite_reader(reader)?;
516 Ok(Utxo {
517 height: Decodable::consensus_decode_from_finite_reader(reader)?,
518 output: Decodable::consensus_decode_from_finite_reader(reader)?,
519 })
520}
521
522fn read_bytes(reader: &mut impl Read, num: usize) -> Result<Vec<u8>, Error> {
523 let mut ret = Vec::with_capacity(num);
524 for _ in 0..num {
525 ret.push(reader.read_u8()?);
526 }
527 Ok(ret)
528}
529
530#[cfg(all(test, feature = "use-reqwest"))]
531mod tests {
532
533 use super::{Error, RestApi, RestClient, StatusCode};
534
535 use bitcoin::{Amount, Network, OutPoint};
536 use bitcoind::{bitcoincore_rpc::RpcApi, downloaded_exe_path, BitcoinD, Conf};
537
538 const NUM_BLOCKS: u32 = 101;
539
540 #[tokio::test]
541 async fn test_rest() {
542 let _ = env_logger::builder().is_test(true).try_init();
543
544 let mut conf = Conf::default();
545 conf.args = vec![
546 "-rest",
547 "-blockfilterindex",
548 "-regtest",
549 "-fallbackfee=0.0001",
550 ];
551 let bitcoind = BitcoinD::with_conf(downloaded_exe_path().unwrap(), &conf).unwrap();
552 let address = bitcoind
553 .client
554 .get_new_address(None, None)
555 .unwrap()
556 .require_network(Network::Regtest)
557 .unwrap();
558 bitcoind
559 .client
560 .generate_to_address(NUM_BLOCKS as u64, &address)
561 .unwrap();
562
563 let rpc_socket = bitcoind.params.rpc_socket;
564
565 let bitcoin_rest = RestClient::new(format!("http://{}", rpc_socket)).unwrap();
566
567 let hash = bitcoin_rest
568 .get_block_hash(NUM_BLOCKS as u64)
569 .await
570 .unwrap();
571 assert_eq!(
572 hash,
573 bitcoind.client.get_block_hash(NUM_BLOCKS as u64).unwrap()
574 );
575
576 let block = bitcoin_rest
577 .get_block_at_height(NUM_BLOCKS as u64)
578 .await
579 .unwrap();
580 assert_eq!(block, bitcoind.client.get_block(&hash).unwrap());
581 assert_eq!(block, bitcoin_rest.get_block(hash).await.unwrap());
582
583 let first_hash = bitcoin_rest.get_block_hash(0).await.unwrap();
584 let headers = bitcoin_rest
585 .get_block_headers(first_hash, NUM_BLOCKS)
586 .await
587 .unwrap();
588 assert_eq!(headers.len(), NUM_BLOCKS as usize);
589 assert_eq!(headers[1].prev_blockhash, first_hash);
590
591 let filter_headers = bitcoin_rest
592 .get_block_filter_headers(first_hash, NUM_BLOCKS)
593 .await
594 .unwrap();
595 assert_eq!(filter_headers.len(), NUM_BLOCKS as usize);
596
597 let _ = bitcoin_rest.get_block_filter(hash).await.unwrap();
598
599 let txid = bitcoind
600 .client
601 .send_to_address(
602 &address,
603 Amount::ONE_BTC,
604 None,
605 None,
606 None,
607 None,
608 None,
609 None,
610 )
611 .unwrap();
612 let txid2 = bitcoind
613 .client
614 .send_to_address(
615 &address,
616 Amount::from_btc(0.5).unwrap(),
617 None,
618 None,
619 None,
620 None,
621 None,
622 None,
623 )
624 .unwrap();
625
626 let chain_info = bitcoin_rest.get_chain_info().await.unwrap();
627 assert_eq!(chain_info.chain, "regtest");
628 assert_eq!(chain_info.blocks, NUM_BLOCKS as u64);
629 assert_eq!(chain_info.best_block_hash, hash);
630
631 let deployment_info = bitcoin_rest.get_deployment_info().await.unwrap();
632 assert_eq!(deployment_info.hash, hash);
633 assert_eq!(deployment_info.height, NUM_BLOCKS);
634
635 let deployment_info =
636 unsafe { bitcoin_rest.get_deployment_info_at_block(hash).await }.unwrap();
637 assert_eq!(deployment_info.hash, hash);
638 assert_eq!(deployment_info.height, NUM_BLOCKS);
639
640 let mempool_info = bitcoin_rest.get_mempool_info().await.unwrap();
641 assert!(mempool_info.loaded);
642 assert_eq!(mempool_info.size, 2);
643
644 let mempool = bitcoin_rest.get_mempool().await.unwrap();
645 assert_eq!(mempool.len(), 2);
646 let entry = mempool.get(&txid);
647 assert_ne!(entry, None);
648
649 let txids = bitcoin_rest.get_mempool_txids().await.unwrap();
650 assert_eq!(txids, vec![txid, txid2]);
651
652 let txids_and_sequence = bitcoin_rest.get_mempool_txids_and_sequence().await.unwrap();
653 assert_eq!(txids_and_sequence.txids, vec![txid, txid2]);
654 assert_eq!(txids_and_sequence.mempool_sequence, 3);
655
656 let tx = bitcoin_rest.get_transaction(txid).await.unwrap();
657 assert_eq!(tx.txid(), txid);
658
659 let outpoints = [
660 OutPoint::new(txid, 0),
661 OutPoint::new(txid, 1),
662 OutPoint::new(txid2, 0),
663 OutPoint::new(txid2, 1),
664 ];
665 let utxo_result = bitcoin_rest.get_utxos(&outpoints, true).await.unwrap();
666 assert_eq!(utxo_result.chain_height, NUM_BLOCKS);
667 assert_eq!(utxo_result.chain_tip_hash, hash);
668 assert_eq!(utxo_result.utxos.len(), 3);
669 let utxo = &utxo_result.utxos[0];
670 assert_eq!(utxo.height, i32::MAX);
671
672 bitcoind.client.generate_to_address(1, &address).unwrap();
673 let utxo_result = bitcoin_rest.get_utxos(&outpoints, true).await.unwrap();
674 let utxo = &utxo_result.utxos[0];
675 assert_eq!(utxo.height, (NUM_BLOCKS + 1) as i32);
676 let utxo = &utxo_result.utxos[1];
677 if Amount::from_sat(utxo.output.value) == Amount::from_btc(0.5).unwrap() {
678 assert_eq!(utxo.output.script_pubkey, address.script_pubkey());
679 } else {
680 let utxo = &utxo_result.utxos[2];
681 assert_eq!(utxo.output.script_pubkey, address.script_pubkey());
682 }
683
684 let result = bitcoin_rest.get_block_hash((NUM_BLOCKS + 2) as u64).await;
685 match result {
686 Err(Error::NotOkError(StatusCode::NOT_FOUND)) => (),
687 Err(_) => panic!(),
688 Ok(_) => panic!(),
689 }
690 }
691}