electrum_client/
types.rs

1//! Return types
2//!
3//! This module contains definitions of all the complex data structures that are returned by calls
4
5use std::convert::TryFrom;
6use std::fmt::{self, Display, Formatter};
7use std::ops::Deref;
8use std::sync::Arc;
9
10use bitcoin::blockdata::block;
11use bitcoin::consensus::encode::deserialize;
12use bitcoin::hashes::{sha256, Hash};
13use bitcoin::hex::{DisplayHex, FromHex};
14use bitcoin::{Script, Txid};
15
16use serde::{de, Deserialize, Serialize};
17
18static JSONRPC_2_0: &str = "2.0";
19
20pub(crate) type Call = (String, Vec<Param>);
21
22#[derive(Serialize, Clone)]
23#[serde(untagged)]
24/// A single parameter of a [`Request`](struct.Request.html)
25pub enum Param {
26    /// Integer parameter
27    U32(u32),
28    /// Integer parameter
29    Usize(usize),
30    /// String parameter
31    String(String),
32    /// Boolean parameter
33    Bool(bool),
34    /// Bytes array parameter
35    Bytes(Vec<u8>),
36}
37
38#[derive(Serialize, Clone)]
39/// A request that can be sent to the server
40pub struct Request<'a> {
41    jsonrpc: &'static str,
42
43    /// The JSON-RPC request id
44    pub id: usize,
45    /// The request method
46    pub method: &'a str,
47    /// The request parameters
48    pub params: Vec<Param>,
49}
50
51impl<'a> Request<'a> {
52    /// Creates a new request with a default id
53    fn new(method: &'a str, params: Vec<Param>) -> Self {
54        Self {
55            id: 0,
56            jsonrpc: JSONRPC_2_0,
57            method,
58            params,
59        }
60    }
61
62    /// Creates a new request with a user-specified id
63    pub fn new_id(id: usize, method: &'a str, params: Vec<Param>) -> Self {
64        let mut instance = Self::new(method, params);
65        instance.id = id;
66
67        instance
68    }
69}
70
71#[doc(hidden)]
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
73pub struct Hex32Bytes(#[serde(deserialize_with = "from_hex", serialize_with = "to_hex")] [u8; 32]);
74
75impl Deref for Hex32Bytes {
76    type Target = [u8; 32];
77
78    fn deref(&self) -> &Self::Target {
79        &self.0
80    }
81}
82
83impl From<[u8; 32]> for Hex32Bytes {
84    fn from(other: [u8; 32]) -> Hex32Bytes {
85        Hex32Bytes(other)
86    }
87}
88
89impl Hex32Bytes {
90    pub(crate) fn to_hex(self) -> String {
91        self.0.to_lower_hex_string()
92    }
93}
94
95/// Format used by the Electrum server to identify an address. The reverse sha256 hash of the
96/// scriptPubKey. Documented [here](https://electrumx.readthedocs.io/en/latest/protocol-basics.html#script-hashes).
97pub type ScriptHash = Hex32Bytes;
98
99/// Binary blob that condenses all the activity of an address. Used to detect changes without
100/// having to compare potentially long lists of transactions.
101pub type ScriptStatus = Hex32Bytes;
102
103/// Trait used to convert a struct into the Electrum representation of an address
104pub trait ToElectrumScriptHash {
105    /// Transforms the current struct into a `ScriptHash`
106    fn to_electrum_scripthash(&self) -> ScriptHash;
107}
108
109impl ToElectrumScriptHash for Script {
110    fn to_electrum_scripthash(&self) -> ScriptHash {
111        let mut result = sha256::Hash::hash(self.as_bytes()).to_byte_array();
112        result.reverse();
113
114        result.into()
115    }
116}
117
118fn from_hex<'de, T, D>(deserializer: D) -> Result<T, D::Error>
119where
120    T: FromHex,
121    D: de::Deserializer<'de>,
122{
123    let s = String::deserialize(deserializer)?;
124    T::from_hex(&s).map_err(de::Error::custom)
125}
126
127fn to_hex<S>(bytes: &[u8], serializer: S) -> std::result::Result<S::Ok, S::Error>
128where
129    S: serde::ser::Serializer,
130{
131    serializer.serialize_str(&bytes.to_lower_hex_string())
132}
133
134fn from_hex_array<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
135where
136    T: FromHex + std::fmt::Debug,
137    D: de::Deserializer<'de>,
138{
139    let arr = Vec::<String>::deserialize(deserializer)?;
140
141    let results: Vec<Result<T, _>> = arr
142        .into_iter()
143        .map(|s| T::from_hex(&s).map_err(de::Error::custom))
144        .collect();
145
146    let mut answer = Vec::new();
147    for x in results.into_iter() {
148        answer.push(x?);
149    }
150
151    Ok(answer)
152}
153
154fn from_hex_header<'de, D>(deserializer: D) -> Result<block::Header, D::Error>
155where
156    D: de::Deserializer<'de>,
157{
158    let vec: Vec<u8> = from_hex(deserializer)?;
159    deserialize(&vec).map_err(de::Error::custom)
160}
161
162/// Response to a [`script_get_history`](../client/struct.Client.html#method.script_get_history) request.
163#[derive(Clone, Debug, Deserialize)]
164pub struct GetHistoryRes {
165    /// Confirmation height of the transaction. 0 if unconfirmed, -1 if unconfirmed while some of
166    /// its inputs are unconfirmed too.
167    pub height: i32,
168    /// Txid of the transaction.
169    pub tx_hash: Txid,
170    /// Fee of the transaction.
171    pub fee: Option<u64>,
172}
173
174/// Response to a [`script_list_unspent`](../client/struct.Client.html#method.script_list_unspent) request.
175#[derive(Clone, Debug, Deserialize)]
176pub struct ListUnspentRes {
177    /// Confirmation height of the transaction that created this output.
178    pub height: usize,
179    /// Txid of the transaction
180    pub tx_hash: Txid,
181    /// Index of the output in the transaction.
182    pub tx_pos: usize,
183    /// Value of the output.
184    pub value: u64,
185}
186
187/// Response to a [`server_features`](../client/struct.Client.html#method.server_features) request.
188#[derive(Clone, Debug, Deserialize)]
189pub struct ServerFeaturesRes {
190    /// Server version reported.
191    pub server_version: String,
192    /// Hash of the genesis block.
193    #[serde(deserialize_with = "from_hex")]
194    pub genesis_hash: [u8; 32],
195    /// Minimum supported version of the protocol.
196    pub protocol_min: String,
197    /// Maximum supported version of the protocol.
198    pub protocol_max: String,
199    /// Hash function used to create the [`ScriptHash`](type.ScriptHash.html).
200    pub hash_function: Option<String>,
201    /// Pruned height of the server.
202    pub pruning: Option<i64>,
203}
204
205/// Response to a [`server_features`](../client/struct.Client.html#method.server_features) request.
206#[derive(Clone, Debug, Deserialize)]
207pub struct GetHeadersRes {
208    /// Maximum number of headers returned in a single response.
209    pub max: usize,
210    /// Number of headers in this response.
211    pub count: usize,
212    /// Raw headers concatenated. Normally cleared before returning.
213    #[serde(rename(deserialize = "hex"), deserialize_with = "from_hex")]
214    pub raw_headers: Vec<u8>,
215    /// Array of block headers.
216    #[serde(skip)]
217    pub headers: Vec<block::Header>,
218}
219
220/// Response to a [`script_get_balance`](../client/struct.Client.html#method.script_get_balance) request.
221#[derive(Clone, Debug, Deserialize)]
222pub struct GetBalanceRes {
223    /// Confirmed balance in Satoshis for the address.
224    pub confirmed: u64,
225    /// Unconfirmed balance in Satoshis for the address.
226    ///
227    /// Some servers (e.g. `electrs`) return this as a negative value.
228    pub unconfirmed: i64,
229}
230
231/// Response to a [`transaction_get_merkle`](../client/struct.Client.html#method.transaction_get_merkle) request.
232#[derive(Clone, Debug, Deserialize)]
233pub struct GetMerkleRes {
234    /// Height of the block that confirmed the transaction
235    pub block_height: usize,
236    /// Position in the block of the transaction.
237    pub pos: usize,
238    /// The merkle path of the transaction.
239    #[serde(deserialize_with = "from_hex_array")]
240    pub merkle: Vec<[u8; 32]>,
241}
242
243/// Response to a [`txid_from_pos_with_merkle`](../client/struct.Client.html#method.txid_from_pos_with_merkle)
244/// request.
245#[derive(Clone, Debug, Deserialize)]
246pub struct TxidFromPosRes {
247    /// Txid of the transaction.
248    pub tx_hash: Txid,
249    /// The merkle path of the transaction.
250    #[serde(deserialize_with = "from_hex_array")]
251    pub merkle: Vec<[u8; 32]>,
252}
253
254/// Notification of a new block header
255#[derive(Clone, Debug, Deserialize)]
256pub struct HeaderNotification {
257    /// New block height.
258    pub height: usize,
259    /// Newly added header.
260    #[serde(rename = "hex", deserialize_with = "from_hex_header")]
261    pub header: block::Header,
262}
263
264/// Notification of a new block header with the header encoded as raw bytes
265#[derive(Clone, Debug, Deserialize)]
266pub struct RawHeaderNotification {
267    /// New block height.
268    pub height: usize,
269    /// Newly added header.
270    #[serde(rename = "hex", deserialize_with = "from_hex")]
271    pub header: Vec<u8>,
272}
273
274impl TryFrom<RawHeaderNotification> for HeaderNotification {
275    type Error = Error;
276
277    fn try_from(raw: RawHeaderNotification) -> Result<Self, Self::Error> {
278        Ok(HeaderNotification {
279            height: raw.height,
280            header: deserialize(&raw.header)?,
281        })
282    }
283}
284
285/// Notification of the new status of a script
286#[derive(Clone, Debug, Deserialize)]
287pub struct ScriptNotification {
288    /// Address that generated this notification.
289    pub scripthash: ScriptHash,
290    /// The new status of the address.
291    pub status: ScriptStatus,
292}
293
294/// Errors
295#[derive(Debug)]
296pub enum Error {
297    /// Wraps `std::io::Error`
298    IOError(std::io::Error),
299    /// Wraps `serde_json::error::Error`
300    JSON(serde_json::error::Error),
301    /// Wraps `bitcoin::hex::HexToBytesError`
302    Hex(bitcoin::hex::HexToBytesError),
303    /// Error returned by the Electrum server
304    Protocol(serde_json::Value),
305    /// Error during the deserialization of a Bitcoin data structure
306    Bitcoin(bitcoin::consensus::encode::Error),
307    /// Already subscribed to the notifications of an address
308    AlreadySubscribed(ScriptHash),
309    /// Not subscribed to the notifications of an address
310    NotSubscribed(ScriptHash),
311    /// Error during the deserialization of a response from the server
312    InvalidResponse(serde_json::Value),
313    /// Generic error with a message
314    Message(String),
315    /// Invalid domain name for an SSL certificate
316    InvalidDNSNameError(String),
317    /// Missing domain while it was explicitly asked to validate it
318    MissingDomain,
319    /// Made one or multiple attempts, always in Error
320    AllAttemptsErrored(Vec<Error>),
321    /// There was an io error reading the socket, to be shared between threads
322    SharedIOError(Arc<std::io::Error>),
323
324    /// Couldn't take a lock on the reader mutex. This means that there's already another reader
325    /// thread running
326    CouldntLockReader,
327    /// Broken IPC communication channel: the other thread probably has exited
328    Mpsc,
329    #[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))]
330    /// Could not create a rustls client connection
331    CouldNotCreateConnection(rustls::Error),
332
333    #[cfg(feature = "use-openssl")]
334    /// Invalid OpenSSL method used
335    InvalidSslMethod(openssl::error::ErrorStack),
336    #[cfg(feature = "use-openssl")]
337    /// SSL Handshake failed with the server
338    SslHandshakeError(openssl::ssl::HandshakeError<std::net::TcpStream>),
339}
340
341impl Display for Error {
342    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
343        match self {
344            Error::IOError(e) => Display::fmt(e, f),
345            Error::JSON(e) => Display::fmt(e, f),
346            Error::Hex(e) => Display::fmt(e, f),
347            Error::Bitcoin(e) => Display::fmt(e, f),
348            Error::SharedIOError(e) => Display::fmt(e, f),
349            #[cfg(feature = "use-openssl")]
350            Error::SslHandshakeError(e) => Display::fmt(e, f),
351            #[cfg(feature = "use-openssl")]
352            Error::InvalidSslMethod(e) => Display::fmt(e, f),
353            #[cfg(any(
354                feature = "use-rustls",
355                feature = "use-rustls-ring",
356            ))]
357            Error::CouldNotCreateConnection(e) => Display::fmt(e, f),
358
359            Error::Message(e) => f.write_str(e),
360            Error::InvalidDNSNameError(domain) => write!(f, "Invalid domain name {} not matching SSL certificate", domain),
361            Error::AllAttemptsErrored(errors) => {
362                f.write_str("Made one or multiple attempts, all errored:\n")?;
363                for err in errors {
364                    writeln!(f, "\t- {}", err)?;
365                }
366                Ok(())
367            }
368
369            Error::Protocol(e) => write!(f, "Electrum server error: {}", e.clone().take()),
370            Error::InvalidResponse(e) => write!(f, "Error during the deserialization of a response from the server: {}", e.clone().take()),
371
372            // TODO: Print out addresses once `ScriptHash` will implement `Display`
373            Error::AlreadySubscribed(_) => write!(f, "Already subscribed to the notifications of an address"),
374            Error::NotSubscribed(_) => write!(f, "Not subscribed to the notifications of an address"),
375
376            Error::MissingDomain => f.write_str("Missing domain while it was explicitly asked to validate it"),
377            Error::CouldntLockReader => f.write_str("Couldn't take a lock on the reader mutex. This means that there's already another reader thread is running"),
378            Error::Mpsc => f.write_str("Broken IPC communication channel: the other thread probably has exited"),
379        }
380    }
381}
382
383impl std::error::Error for Error {}
384
385macro_rules! impl_error {
386    ( $from:ty, $to:ident ) => {
387        impl std::convert::From<$from> for Error {
388            fn from(err: $from) -> Self {
389                Error::$to(err.into())
390            }
391        }
392    };
393}
394
395impl_error!(std::io::Error, IOError);
396impl_error!(serde_json::Error, JSON);
397impl_error!(bitcoin::hex::HexToBytesError, Hex);
398impl_error!(bitcoin::consensus::encode::Error, Bitcoin);
399
400impl<T> From<std::sync::PoisonError<T>> for Error {
401    fn from(_: std::sync::PoisonError<T>) -> Self {
402        Error::IOError(std::io::Error::from(std::io::ErrorKind::BrokenPipe))
403    }
404}
405
406impl<T> From<std::sync::mpsc::SendError<T>> for Error {
407    fn from(_: std::sync::mpsc::SendError<T>) -> Self {
408        Error::Mpsc
409    }
410}
411
412impl From<std::sync::mpsc::RecvError> for Error {
413    fn from(_: std::sync::mpsc::RecvError) -> Self {
414        Error::Mpsc
415    }
416}
417
418#[cfg(test)]
419mod tests {
420    use crate::ScriptStatus;
421
422    #[test]
423    fn script_status_roundtrip() {
424        let script_status: ScriptStatus = [1u8; 32].into();
425        let script_status_json = serde_json::to_string(&script_status).unwrap();
426        let script_status_back = serde_json::from_str(&script_status_json).unwrap();
427        assert_eq!(script_status, script_status_back);
428    }
429}