electrum/
types.rs

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