Skip to main content

bitcoincore_rest/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2//! A Rust REST client library for calling the Bitcoin Core REST API. It
3//! makes it easy to talk to the Bitcoin Core REST interface.
4//!
5//! The REST interface is useful for quickly iterating over the blockchain, because
6//! it can request blocks and transactions in binary form without having to
7//! serialize/deserialize into JSON. It is unauthenticated so there's no need to
8//! worry about storing credentials.
9//!
10//! It also has API for quickly retrieving large amounts of block headers and BIP157
11//! compact block filter headers. There is also support for getting block chain
12//! info, mempool info, the raw mempool, and querying the utxo set.
13//!
14//! See <https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md>
15//! for more information.
16//!
17//! # Usage
18//!
19//! The Bitcoin Core bitcoind instance must be started with `-rest` on the command
20//! line or `rest=1` in the `bitcoin.conf` file.
21//!
22#![cfg_attr(not(feature = "use-reqwest"), doc = "```ignore")]
23#![cfg_attr(feature = "use-reqwest", doc = "```rust")]
24//! use bitcoincore_rest::prelude::*;
25//!
26//! async fn get_block(height: u64) -> Result<Block, Error> {
27//!     let rest = RestClient::network_default(Network::Bitcoin);
28//!     rest.get_block_at_height(height).await
29//! }
30//!
31//! ```
32//!
33//! # API
34//!
35//! Unfortunately, `async_trait` trait functions are expanded in docsrs, so here are
36//! the unexpanded functions for `RestApi`, which `RestClient` implements:
37//!
38//! ```rust
39//! use std::collections::HashMap;
40//!
41//! use bitcoincore_rest::prelude::*;
42//!
43//! #[async_trait::async_trait]
44//! pub trait RestApi {
45//!     async fn get_block_headers(
46//!         &self,
47//!         start_hash: BlockHash,
48//!         count: u32,
49//!     ) -> Result<Vec<Header>, Error>;
50//!
51//!     async fn get_block_at_height(&self, height: u64) -> Result<Block, Error>;
52//!
53//!     async fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error>;
54//!
55//!     async fn get_block(&self, hash: BlockHash) -> Result<Block, Error>;
56//!
57//!     async fn get_transaction(&self, txid: Txid) -> Result<Transaction, Error>;
58//!
59//!     async fn get_block_filter_headers(
60//!         &self,
61//!         start_hash: BlockHash,
62//!         count: u32,
63//!     ) -> Result<Vec<FilterHeader>, Error>;
64//!
65//!     async fn get_block_filter(&self, hash: BlockHash) -> Result<BlockFilter, Error>;
66//!
67//!     async fn get_chain_info(&self) -> Result<GetBlockchainInfoResult, Error>;
68//!
69//!     async fn get_utxos(
70//!         &self,
71//!         outpoints: &[OutPoint],
72//!         check_mempool: bool,
73//!     ) -> Result<GetUtxosResult, Error>;
74//!
75//!     async fn get_mempool_info(&self) -> Result<GetMempoolInfoResult, Error>;
76//!
77//!     async fn get_mempool(&self) -> Result<HashMap<Txid, GetMempoolEntryResult>, Error>;
78//!
79//!     async fn get_mempool_txids(&self) -> Result<Vec<Txid>, Error>;
80//!
81//!     async fn get_mempool_txids_and_sequence(
82//!         &self,
83//!     ) -> Result<GetMempoolTxidsAndSequenceResult, Error>;
84//!
85//!     async fn get_deployment_info(&self) -> Result<GetDeploymentInfoResult, Error>;
86//!
87//!     /// Only available on Bitcoin Core v25.1 and later
88//!     ///
89//!     /// WARNING: CALLING THIS CONNECTED TO BITCOIN CORE V25.0 WILL CRASH BITCOIND
90//!     ///
91//!     /// IT IS MARKED UNSAFE TO ENSURE YOU ARE NOT USING BITCOIN CORE V25.0
92//!     async unsafe fn get_deployment_info_at_block(
93//!         &self,
94//!         hash: BlockHash,
95//!     ) -> Result<GetDeploymentInfoResult, Error>;
96//! }
97//!
98//! ```
99//!
100//! # Features
101//!
102//! By default, this library includes a struct `RestClient` which implements
103//! `RestApi` by using the `reqwest` library. To not use `reqwest` as a dependency
104//! and implement your own version of `RestApi`, set `default-features = false` in
105//! your `Cargo.toml`:
106//!
107//! ```toml
108//! bitcoincore-rest = { version = "4.0.2", default-features = false }
109//! ```
110//!
111//! You will have to implement the `get_json` and `get_bin` methods on `RestApi`
112//! with your own http functionality. All methods are `GET` requests. For example,
113//! using the [`surf`](https://docs.rs/surf/latest/surf/) http library and
114//! [`thiserror`](https://docs.rs/thiserror/latest/thiserror/):
115//!
116#![cfg_attr(not(feature = "use-reqwest"), doc = "```rust")]
117#![cfg_attr(feature = "use-reqwest", doc = "```ignore")]
118//! use bitcoincore_rest::{
119//!     async_trait::async_trait, bytes::Bytes, serde::Deserialize, Error, RestApi,
120//! };
121//!
122//! #[derive(thiserror::Error, Debug)]
123//! pub enum SurfError {
124//!     #[error("surf error")]
125//!     Surf(surf::Error),
126//! }
127//!
128//! struct NewClient;
129//!
130//! #[async_trait]
131//! impl RestApi for NewClient {
132//!     async fn get_json<T: for<'a> Deserialize<'a>>(&self, path: &str) -> Result<T, Error> {
133//!         surf::get(format!("http://localhost:8332/{path}"))
134//!             .recv_json()
135//!             .await
136//!             .map_err(|e| Error::CustomError(Box::new(SurfError::Surf(e))))
137//!     }
138//!
139//!     async fn get_bin(&self, path: &str) -> Result<Bytes, Error> {
140//!         surf::get(format!("http://localhost:8332/{path}"))
141//!             .recv_bytes()
142//!             .await
143//!             .map_err(|e| Error::CustomError(Box::new(SurfError::Surf(e))))
144//!             .map(Bytes::from)
145//!     }
146//! }
147//! ```
148//!
149//!
150use std::{
151    collections::HashMap,
152    io::{Cursor, Read},
153};
154
155/// Error type for RestApi responses.
156pub mod error;
157pub mod responses;
158/// Prelude includes RestClient and RestApi, and all parameter and return types
159pub 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/// Implements all the REST API calls for Bitcoin Core, except
222/// [`get_json`](RestApi::get_json) and [`get_bin`](RestApi::get_bin).
223///
224/// These are implemented using [`reqwest`](reqwest) in
225/// [`RestClient`](RestClient), but this dependency can be removed by using
226/// `default-features = false` in `Cargo.toml` and implementing `RestApi`
227/// yourself.
228#[async_trait::async_trait]
229pub trait RestApi {
230    /// Get a response from a `json` endpoint
231    async fn get_json<T: for<'a> Deserialize<'a>>(&self, path: &str) -> Result<T, Error>;
232
233    /// Get a response from a `bin` endpoint
234    async fn get_bin(&self, path: &str) -> Result<Bytes, Error>;
235
236    /// Get a series of block headers beginning from a block hash
237    ///
238    /// See <https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md#blockheaders>
239    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    /// Convenience function to get a block at a specific height
258    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    /// Get a block hash at a specific height
264    ///
265    /// See <https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md#blockhash-by-height>
266    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    /// Get a block by its hash
273    ///
274    /// See <https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md#blocks>
275    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    /// Get a transaction by its `txid`
282    ///
283    /// See <https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md#transactions>
284    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    /// Get a series of block filter headers beginning from a block hash
291    ///
292    /// See <https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md#blockfilter-headers>
293    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    /// Get a block filter for a given block hash
315    ///
316    /// See <https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md#blockfilters>
317    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    /// Get info on the block chain state
329    ///
330    /// See <https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md#chaininfos>
331    async fn get_chain_info(&self) -> Result<GetBlockchainInfoResult, Error> {
332        let path = "rest/chaininfo.json";
333        self.get_json(path).await
334    }
335
336    /// Get utxos for a given set of outpoints
337    ///
338    /// Optionally check unconfirmed utxos in the mempool
339    ///
340    /// See <https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md#query-utxo-set>
341    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    /// Get info on the mempool state
363    ///
364    /// See <https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md#memory-pool>
365    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    /// Get info for every transaction in the mempool
371    ///
372    /// See <https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md#memory-pool>
373    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    /// Get the txid for every transaction in the mempool
379    /// Only available on Bitcoin Core v25.0.0 and later
380    ///
381    /// See <https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md#memory-pool>
382    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    /// Get the txid for every transaction in the mempool and the mempool sequence
388    /// Only available on Bitcoin Core v25.0.0 and later
389    ///
390    /// See <https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md#memory-pool>
391    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    /// Get soft fork deployment status info
399    /// Only available on Bitcoin Core v25.0.0 and later
400    ///
401    /// See <https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md#deployment-info>
402    async fn get_deployment_info(&self) -> Result<GetDeploymentInfoResult, Error> {
403        let path = "rest/deploymentinfo.json";
404        self.get_json(path).await
405    }
406
407    /// Get soft fork deployment status info
408    /// Only available on Bitcoin Core v25.1.0 and later
409    ///
410    /// WARNING: CALLING THIS CONNECTED TO BITCOIN CORE V25.0 WILL CRASH BITCOIND
411    ///
412    /// IT IS MARKED UNSAFE TO ENSURE YOU ARE NOT USING BITCOIN CORE V25.0
413    ///
414    /// See <https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md#deployment-info>
415    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/// Creates HTTP REST requests to bitcoind.
425///
426/// See [`RestApi`] for the available methods.
427#[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    /// Create a new `RestClient` instance with given endpoint url
439    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    /// Create a new `RestClient` instance with the default endpoint for that network
447    ///
448    /// For example, [`Network::Bitcoin`] creates an instance with `"http://localhost:8332"`
449    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    // Unknown 4 bytes of zero data at start of utxo
515    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}