corepc_client/client_sync/
mod.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! JSON-RPC clients for testing against specific versions of Bitcoin Core.
4
5mod error;
6pub mod v17;
7pub mod v18;
8pub mod v19;
9pub mod v20;
10pub mod v21;
11pub mod v22;
12pub mod v23;
13pub mod v24;
14pub mod v25;
15pub mod v26;
16pub mod v27;
17pub mod v28;
18
19use std::fs::File;
20use std::io::{BufRead, BufReader};
21use std::path::PathBuf;
22
23use bitcoin::Txid;
24use serde::{Deserialize, Serialize};
25
26pub use crate::client_sync::error::Error;
27
28/// Crate-specific Result type.
29///
30/// Shorthand for `std::result::Result` with our crate-specific [`Error`] type.
31pub type Result<T> = std::result::Result<T, Error>;
32
33/// The different authentication methods for the client.
34#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
35pub enum Auth {
36    None,
37    UserPass(String, String),
38    CookieFile(PathBuf),
39}
40
41impl Auth {
42    /// Convert into the arguments that jsonrpc::Client needs.
43    pub fn get_user_pass(self) -> Result<(Option<String>, Option<String>)> {
44        match self {
45            Auth::None => Ok((None, None)),
46            Auth::UserPass(u, p) => Ok((Some(u), Some(p))),
47            Auth::CookieFile(path) => {
48                let line = BufReader::new(File::open(path)?)
49                    .lines()
50                    .next()
51                    .ok_or(Error::InvalidCookieFile)??;
52                let colon = line.find(':').ok_or(Error::InvalidCookieFile)?;
53                Ok((Some(line[..colon].into()), Some(line[colon + 1..].into())))
54            }
55        }
56    }
57}
58
59/// Defines a `jsonrpc::Client` using `minreq`.
60#[macro_export]
61macro_rules! define_jsonrpc_minreq_client {
62    ($version:literal) => {
63        use std::fmt;
64
65        use $crate::client_sync::{log_response, Auth, Result};
66        use $crate::client_sync::error::Error;
67
68        /// Client implements a JSON-RPC client for the Bitcoin Core daemon or compatible APIs.
69        pub struct Client {
70            inner: jsonrpc::client::Client,
71        }
72
73        impl fmt::Debug for Client {
74            fn fmt(&self, f: &mut fmt::Formatter) -> core::fmt::Result {
75                write!(
76                    f,
77                    "corepc_client::client_sync::{}::Client({:?})", $version, self.inner
78                )
79            }
80        }
81
82        impl Client {
83            /// Creates a client to a bitcoind JSON-RPC server without authentication.
84            pub fn new(url: &str) -> Self {
85                let transport = jsonrpc::http::minreq_http::Builder::new()
86                    .url(url)
87                    .expect("jsonrpc v0.18, this function does not error")
88                    .build();
89                let inner = jsonrpc::client::Client::with_transport(transport);
90
91                Self { inner }
92            }
93
94            /// Creates a client to a bitcoind JSON-RPC server with authentication.
95            pub fn new_with_auth(url: &str, auth: Auth) -> Result<Self> {
96                if matches!(auth, Auth::None) {
97                    return Err(Error::MissingUserPassword);
98                }
99                let (user, pass) = auth.get_user_pass()?;
100
101                let transport = jsonrpc::http::minreq_http::Builder::new()
102                    .url(url)
103                    .expect("jsonrpc v0.18, this function does not error")
104                    .basic_auth(user.unwrap(), pass)
105                    .build();
106                let inner = jsonrpc::client::Client::with_transport(transport);
107
108                Ok(Self { inner })
109            }
110
111            /// Call an RPC `method` with given `args` list.
112            pub fn call<T: for<'a> serde::de::Deserialize<'a>>(
113                &self,
114                method: &str,
115                args: &[serde_json::Value],
116            ) -> Result<T> {
117                let raw = serde_json::value::to_raw_value(args)?;
118                let req = self.inner.build_request(&method, Some(&*raw));
119                if log::log_enabled!(log::Level::Debug) {
120                    log::debug!(target: "corepc", "request: {} {}", method, serde_json::Value::from(args));
121                }
122
123                let resp = self.inner.send_request(req).map_err(Error::from);
124                log_response(method, &resp);
125                Ok(resp?.result()?)
126            }
127        }
128    }
129}
130
131/// Implements the `check_expected_server_version()` on `Client`.
132///
133/// Requires `Client` to be in scope and implement `server_version()`.
134/// See and/or use `impl_client_v17__getnetworkinfo`.
135///
136/// # Parameters
137///
138/// - `$expected_versions`: An vector of expected server versions e.g., `[230100, 230200]`.
139#[macro_export]
140macro_rules! impl_client_check_expected_server_version {
141    ($expected_versions:expr) => {
142        impl Client {
143            /// Checks that the JSON-RPC endpoint is for a `bitcoind` instance with the expected version.
144            pub fn check_expected_server_version(&self) -> Result<()> {
145                let server_version = self.server_version()?;
146                if !$expected_versions.contains(&server_version) {
147                    return Err($crate::client_sync::error::UnexpectedServerVersionError {
148                        got: server_version,
149                        expected: $expected_versions.to_vec(),
150                    })?;
151                }
152                Ok(())
153            }
154        }
155    };
156}
157
158/// Shorthand for converting a variable into a `serde_json::Value`.
159fn into_json<T>(val: T) -> Result<serde_json::Value>
160where
161    T: serde::ser::Serialize,
162{
163    Ok(serde_json::to_value(val)?)
164}
165
166/// Shorthand for converting an `Option` into an `Option<serde_json::Value>`.
167#[allow(dead_code)] // TODO: Remove this if unused still when we are done.
168fn opt_into_json<T>(opt: Option<T>) -> Result<serde_json::Value>
169where
170    T: serde::ser::Serialize,
171{
172    match opt {
173        Some(val) => Ok(into_json(val)?),
174        None => Ok(serde_json::Value::Null),
175    }
176}
177
178/// Shorthand for `serde_json::Value::Null`.
179#[allow(dead_code)] // TODO: Remove this if unused still when we are done.
180fn null() -> serde_json::Value { serde_json::Value::Null }
181
182/// Shorthand for an empty `serde_json::Value` array.
183#[allow(dead_code)] // TODO: Remove this if unused still when we are done.
184fn empty_arr() -> serde_json::Value { serde_json::Value::Array(vec![]) }
185
186/// Shorthand for an empty `serde_json` object.
187#[allow(dead_code)] // TODO: Remove this if unused still when we are done.
188fn empty_obj() -> serde_json::Value { serde_json::Value::Object(Default::default()) }
189
190/// Convert a possible-null result into an `Option`.
191#[allow(dead_code)] // TODO: Remove this if unused still when we are done.
192fn opt_result<T: for<'a> serde::de::Deserialize<'a>>(
193    result: serde_json::Value,
194) -> Result<Option<T>> {
195    if result == serde_json::Value::Null {
196        Ok(None)
197    } else {
198        Ok(serde_json::from_value(result)?)
199    }
200}
201
202/// Helper to log an RPC response.
203fn log_response(method: &str, resp: &Result<jsonrpc::Response>) {
204    use log::Level::{Debug, Trace, Warn};
205
206    if log::log_enabled!(Warn) || log::log_enabled!(Debug) || log::log_enabled!(Trace) {
207        match resp {
208            Err(ref e) =>
209                if log::log_enabled!(Debug) {
210                    log::debug!(target: "corepc", "error: {}: {:?}", method, e);
211                },
212            Ok(ref resp) =>
213                if let Some(ref e) = resp.error {
214                    if log::log_enabled!(Debug) {
215                        log::debug!(target: "corepc", "response error for {}: {:?}", method, e);
216                    }
217                } else if log::log_enabled!(Trace) {
218                    let def =
219                        serde_json::value::to_raw_value(&serde_json::value::Value::Null).unwrap();
220                    let result = resp.result.as_ref().unwrap_or(&def);
221                    log::trace!(target: "corepc", "response for {}: {}", method, result);
222                },
223        }
224    }
225}
226
227/// Input used as parameter to `create_raw_transaction`.
228#[derive(Debug, Serialize)]
229pub struct Input {
230    /// The txid of the transaction that contains the UTXO.
231    pub txid: bitcoin::Txid,
232    /// The vout for the UTXO.
233    pub vout: u64,
234    /// Sequence number if needed.
235    pub sequence: Option<bitcoin::Sequence>,
236}
237
238/// An element in the `inputs` argument of method `walletcreatefundedpsbt`.
239#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
240pub struct WalletCreateFundedPsbtInput {
241    txid: Txid,
242    vout: u32,
243}
244
245/// Arg for the `getblocktemplate` method.
246#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
247pub struct TemplateRequest {
248    /// A list of strings.
249    pub rules: Vec<TemplateRules>,
250}
251
252/// Client side supported softfork deployment.
253#[derive(Copy, Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
254#[serde(rename_all = "lowercase")]
255pub enum TemplateRules {
256    /// SegWit v0 supported.
257    Segwit,
258    /// Signet supported.
259    Signet,
260    /// CSV supported.
261    Csv,
262    /// Taproot supported.
263    Taproot,
264}