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