Skip to main content

bitcoind_client/
simple_http.rs

1//! `simple_http` [`Client`].
2
3use std::collections::BTreeMap;
4use std::fs::File;
5use std::io::{BufRead, BufReader};
6use std::path::PathBuf;
7use std::string::{String, ToString};
8use std::vec::Vec;
9
10use bitcoin::{Address, Amount, Block, BlockHash, FeeRate, Transaction, Txid, block::Header};
11use corepc_types::bitcoin;
12use corepc_types::model::{self, GetBlockHeaderVerbose, GetBlockVerboseOne, MempoolEntry};
13use corepc_types::v31;
14use jsonrpc::Transport;
15use jsonrpc::{serde, serde_json};
16use serde::Deserialize;
17use serde_json::json;
18
19use crate::Error;
20use crate::Rpc::{self, *};
21use crate::types::{GetBlockFilter, ImportDescriptorsRequest, ImportDescriptorsResponse};
22
23// RPC Client.
24#[derive(Debug)]
25pub struct Client {
26    /// The inner JSON-RPC client.
27    inner: crate::Client,
28    /// Simple HTTP transport
29    tp: jsonrpc::simple_http::SimpleHttpTransport,
30}
31
32/// The way of authenticating to the JSON-RPC server.
33#[derive(Debug, Clone)]
34pub enum Auth {
35    /// User and password
36    UserPass(String, String),
37    /// Path to cookie file
38    CookieFile(PathBuf),
39}
40
41impl Auth {
42    /// Get the user:pass credentials from this [`Auth`].
43    fn get_user_pass(self) -> Result<(String, String), Error> {
44        match self {
45            Auth::UserPass(user, pass) => Ok((user, pass)),
46            Auth::CookieFile(path) => {
47                let line = BufReader::new(File::open(path)?)
48                    .lines()
49                    .next()
50                    .ok_or(Error::InvalidCookieFile)??;
51                let colon = line.find(':').ok_or(Error::InvalidCookieFile)?;
52
53                Ok((line[..colon].to_string(), line[colon + 1..].to_string()))
54            }
55        }
56    }
57}
58
59impl Client {
60    /// Creates a `simple_http` client with `url` and `auth`.
61    ///
62    /// This can fail if we are unable to read the configured [`Auth::CookieFile`].
63    pub fn new(url: &str, auth: Auth) -> Result<Self, Error> {
64        let (user, pass) = auth.get_user_pass()?;
65        Ok(Self::new_user_pass(url, user, Some(pass)))
66    }
67
68    /// Creates a `simple_http` client with `user` and `pass`.
69    pub fn new_user_pass(url: &str, user: String, pass: Option<String>) -> Self {
70        let tp = jsonrpc::simple_http::Builder::new()
71            .url(url)
72            .expect("URL check failed")
73            .timeout(std::time::Duration::from_secs(15))
74            .auth(user, pass)
75            .build();
76
77        Self {
78            inner: crate::Client::new(),
79            tp,
80        }
81    }
82
83    /// Creates a `simple_http` client with `cookie` authentication.
84    pub fn new_cookie_auth(url: &str, cookie: String) -> Self {
85        let tp = jsonrpc::simple_http::Builder::new()
86            .url(url)
87            .expect("URL check failed")
88            .timeout(std::time::Duration::from_secs(15))
89            .cookie_auth(cookie)
90            .build();
91
92        Self {
93            inner: crate::Client::new(),
94            tp,
95        }
96    }
97
98    /// Execute the RPC
99    fn call<T>(&self, rpc: Rpc, params: &[serde_json::Value]) -> Result<T, Error>
100    where
101        T: for<'de> Deserialize<'de>,
102    {
103        self.inner.call(rpc, params, |request| self.tp.send_request(request))
104    }
105}
106
107// `bitcoind` RPC methods
108impl Client {
109    /// `getblockchaininfo`.
110    pub fn get_blockchain_info(&self) -> Result<model::GetBlockchainInfo, Error> {
111        let res: v31::GetBlockchainInfo = self.call(GetBlockchainInfo, &[])?;
112        res.into_model().map_err(Error::model)
113    }
114
115    /// `getdescriptorinfo`
116    pub fn get_descriptor_info(&self, descriptor: &str) -> Result<v31::GetDescriptorInfo, Error> {
117        self.call(GetDescriptorInfo, &[json!(descriptor)])
118    }
119
120    /// `getblockcount`
121    pub fn get_block_count(&self) -> Result<u32, Error> {
122        self.call(GetBlockCount, &[])
123    }
124
125    /// `getbestblockhash`
126    pub fn get_best_block_hash(&self) -> Result<BlockHash, Error> {
127        Ok(self.call::<String>(GetBestBlockHash, &[])?.parse()?)
128    }
129
130    /// `getblockhash`
131    pub fn get_block_hash(&self, height: u32) -> Result<BlockHash, Error> {
132        let res: String = self.call(GetBlockHash, &[json!(height)])?;
133        Ok(res.parse()?)
134    }
135
136    /// `getblockheader`
137    pub fn get_block_header(&self, hash: &BlockHash) -> Result<Header, Error> {
138        let res: v31::GetBlockHeader = self.call(GetBlockHeader, &[json!(hash), json!(false)])?;
139        Ok(res.into_model().map_err(Error::model)?.0)
140    }
141
142    /// `getblockheader` (verbose)
143    pub fn get_block_header_verbose(
144        &self,
145        hash: &BlockHash,
146    ) -> Result<GetBlockHeaderVerbose, Error> {
147        let res: v31::GetBlockHeaderVerbose = self.call(GetBlockHeader, &[json!(hash)])?;
148        res.into_model().map_err(Error::model)
149    }
150
151    /// `getblockfilter`
152    pub fn get_block_filter(&self, hash: &BlockHash) -> Result<GetBlockFilter, Error> {
153        use crate::types::GetBlockFilterResponse;
154        let res: GetBlockFilterResponse = self.call(GetBlockFilter, &[json!(hash)])?;
155        res.into_model().map_err(Error::model)
156    }
157
158    /// `getblock` (raw)
159    pub fn get_block_raw(&self, hash: &BlockHash) -> Result<String, Error> {
160        let res: v31::GetBlockVerboseZero = self.call(GetBlock, &[json!(hash), json!(0)])?;
161        Ok(res.0)
162    }
163
164    /// `getblock`
165    pub fn get_block(&self, hash: &BlockHash) -> Result<Block, Error> {
166        let res: v31::GetBlockVerboseZero = self.call(GetBlock, &[json!(hash), json!(0)])?;
167        res.block().map_err(Error::model)
168    }
169
170    /// `getrawmempool`
171    pub fn get_raw_mempool(&self) -> Result<Vec<Txid>, Error> {
172        let res: v31::GetRawMempool = self.call(GetRawMempool, &[])?;
173        Ok(res.into_model().map_err(Error::model)?.0)
174    }
175
176    /// `sendtoaddress`
177    pub fn send_to_address(&self, address: &Address, amount: Amount) -> Result<Txid, Error> {
178        let res: v31::SendToAddress =
179            self.call(SendToAddress, &[json!(address), json!(amount.to_btc())])?;
180        Ok(res.txid()?)
181    }
182
183    /// `getrawtransaction`
184    pub fn get_raw_transaction(&self, txid: &Txid) -> Result<Transaction, Error> {
185        let res: v31::GetRawTransaction = self.call(GetRawTransaction, &[json!(txid)])?;
186        Ok(res.into_model().map_err(Error::model)?.0)
187    }
188
189    /// `importdescriptors`
190    pub fn import_descriptors(
191        &self,
192        requests: &[ImportDescriptorsRequest],
193    ) -> Result<Vec<ImportDescriptorsResponse>, Error> {
194        self.call(ImportDescriptors, &[json!(requests)])
195    }
196
197    /// `estimatesmartfee`
198    pub fn estimate_smart_fee(&self, blocks: u32) -> Result<FeeRate, Error> {
199        let res: v31::EstimateSmartFee = self.call(EstimateSmartFee, &[json!(blocks)])?;
200        if let Some(e) = res.errors.and_then(|v| v.first().cloned()) {
201            return Err(Error::Response(e));
202        }
203        let btc_kvb = res
204            .fee_rate
205            .ok_or(Error::Response("estimatesmartfee returned no fee_rate".to_string()))?;
206        // Reject infinite and negative values
207        if !btc_kvb.is_finite() || btc_kvb <= 0.0 {
208            return Err(Error::Response(format!("invalid feerate: {btc_kvb} BTC/kvB")));
209        }
210        // No transaction can pay more BTC/kvB as a fee than the total supply
211        if btc_kvb > Amount::MAX_MONEY.to_btc() {
212            return Err(Error::Response(format!("invalid feerate: {btc_kvb} BTC/kvB")));
213        }
214        // 1 sat/vb = 0.00001000 btc/kvb * 10^8 sat/btc * 0.25 wu/sat = 250 sat/kwu
215        let sat_kwu = (btc_kvb * 25_000_000.0).round() as u64;
216
217        Ok(FeeRate::from_sat_per_kwu(sat_kwu))
218    }
219}
220
221// --- v31 compatible APIs
222
223#[cfg(feature = "31_0")]
224impl Client {
225    /// `getblock` (verbose = 1)
226    pub fn get_block_verbose(&self, hash: &BlockHash) -> Result<GetBlockVerboseOne, Error> {
227        let res: v31::GetBlockVerboseOne = self.call(GetBlock, &[json!(hash), json!(1)])?;
228        res.into_model().map_err(Error::model)
229    }
230
231    /// `getrawmempool` (verbose = true)
232    pub fn get_raw_mempool_verbose(&self) -> Result<BTreeMap<Txid, MempoolEntry>, Error> {
233        let res: v31::GetRawMempoolVerbose = self.call(GetRawMempool, &[json!(true)])?;
234        Ok(res.into_model().map_err(Error::model)?.0)
235    }
236}
237
238// --- v30 compatible APIs
239
240#[cfg(not(feature = "31_0"))]
241use corepc_types::v30;
242
243#[cfg(not(feature = "31_0"))]
244impl Client {
245    /// `getblock` (verbose = 1)
246    pub fn get_block_verbose(&self, hash: &BlockHash) -> Result<GetBlockVerboseOne, Error> {
247        let res: v30::GetBlockVerboseOne = self.call(GetBlock, &[json!(hash), json!(1)])?;
248        res.into_model().map_err(Error::model)
249    }
250
251    /// `getrawmempool` (verbose = true)
252    pub fn get_raw_mempool_verbose(&self) -> Result<BTreeMap<Txid, MempoolEntry>, Error> {
253        let res: v30::GetRawMempoolVerbose = self.call(GetRawMempool, &[json!(true)])?;
254        Ok(res.into_model().map_err(Error::model)?.0)
255    }
256}