bwk_electrum/electrum/
response.rs

1use std::{collections::HashMap, fmt::Display, str::FromStr};
2
3use super::{method::Method, params::VersionKind, request::Request, types::ScriptHash, Error};
4use miniscript::bitcoin::Txid;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8#[derive(Debug, PartialEq, Clone)]
9pub enum Response {
10    HeaderNotif(HeaderNotification),
11    BatchHeaderNotif(BatchHeaderNotif),
12    SHNotification(SHNotification),
13    Ping(PingResponse),
14    Banner(BannerResponse),
15    Header(HeaderResponse),
16    Headers(HeadersResponse),
17    Version(VersionResponse),
18    TxGet(TxGetResponse),
19    SHSubscribe(SHSubscribeResponse),
20    SHUnsubscribe(SHUnsubscribeResponse),
21    SHGetBalance(SHGetBalanceResponse),
22    SHGetHistory(SHGetHistoryResponse),
23    SHGetMempool(SHGetMempoolResponse),
24    SHListUnspent(SHListUnspentResponse),
25    Error(ErrorResponse),
26    Features(FeaturesResponse),
27    TxBroadcast(TxBroadcastResponse),
28    Donation(DonationResponse),
29    EstimateFee(EstimateFeeResponse),
30    FeeHistogram(FeeHistogramResponse),
31    RelayFee(RelayFeeResponse),
32    TxGetMerkle(TxGetMerkleResponse),
33    TxFromposition(TxFromPositionResponse),
34    ListPeers(ListPeersResponse),
35}
36
37impl From<Response> for Vec<Response> {
38    fn from(val: Response) -> Self {
39        vec![val]
40    }
41}
42
43impl Response {
44    pub fn id(&self) -> Option<usize> {
45        match self {
46            Response::Ping(PingResponse { id, .. }) => Some(*id),
47            Response::Banner(BannerResponse { id, .. }) => Some(*id),
48            Response::Header(HeaderResponse { id, .. }) => Some(*id),
49            Response::Headers(HeadersResponse { id, .. }) => Some(*id),
50            Response::Version(VersionResponse { id, .. }) => Some(*id),
51            Response::TxGet(TxGetResponse { id, .. }) => Some(*id),
52            Response::SHSubscribe(SHSubscribeResponse { id, .. }) => Some(*id),
53            Response::SHUnsubscribe(SHUnsubscribeResponse { id, .. }) => Some(*id),
54            Response::SHGetBalance(SHGetBalanceResponse { id, .. }) => Some(*id),
55            Response::SHGetHistory(SHGetHistoryResponse { id, .. }) => Some(*id),
56            Response::SHGetMempool(SHGetMempoolResponse { id, .. }) => Some(*id),
57            Response::SHListUnspent(SHListUnspentResponse { id, .. }) => Some(*id),
58            Response::Error(ErrorResponse { id, .. }) => Some(*id),
59            Response::Features(FeaturesResponse { id, .. }) => Some(*id),
60            Response::TxBroadcast(TxBroadcastResponse { id, .. }) => Some(*id),
61            Response::Donation(DonationResponse { id, .. }) => Some(*id),
62            Response::EstimateFee(EstimateFeeResponse { id, .. }) => Some(*id),
63            Response::FeeHistogram(FeeHistogramResponse { id, .. }) => Some(*id),
64            Response::RelayFee(RelayFeeResponse { id, .. }) => Some(*id),
65            Response::TxGetMerkle(TxGetMerkleResponse { id, .. }) => Some(*id),
66            Response::TxFromposition(TxFromPositionResponse { id, .. }) => Some(*id),
67            Response::ListPeers(ListPeersResponse { id, .. }) => Some(*id),
68            _ => None,
69        }
70    }
71}
72
73pub struct ResponseBatch {
74    pub batch: Vec<Response>,
75}
76
77pub fn parse_str_response(
78    raw: &str,
79    index: &HashMap<usize, Request>,
80) -> Result<Vec<Response>, Error> {
81    // first we check if it's a batch
82    let batch = ResponseBatch::from_str(raw, index)?;
83    if let Some(b) = batch {
84        return Ok(b.batch);
85    }
86    // then try to parse a single Response
87    Ok(Response::try_parse(raw, index)?.into())
88}
89
90impl ResponseBatch {
91    pub fn from_str(s: &str, index: &HashMap<usize, Request>) -> Result<Option<Self>, Error> {
92        let parsed: Result<Vec<Value>, _> = serde_json::from_str(s);
93        if let Ok(parsed) = parsed {
94            let mut batch = Vec::<Response>::new();
95            for response in parsed {
96                let raw =
97                    serde_json::to_string(&response).expect("parsing Value to string do not fail!");
98                batch.push(Response::try_parse(&raw, index)?);
99            }
100            Ok(Some(ResponseBatch { batch }))
101        } else {
102            Ok(None)
103        }
104    }
105}
106
107macro_rules! parse {
108    ($method:ident, $response_type:ty, $raw:expr) => {{
109        let r: $response_type =
110            serde_json::from_str($raw).map_err(|_| Error::ResponseParsing($raw.into()))?;
111        Ok(Self::$method(r))
112    }};
113}
114
115impl Response {
116    pub fn parse(raw: &str, index: &HashMap<usize, Request>) -> Response {
117        Self::try_parse(raw, index).unwrap()
118    }
119
120    pub fn try_parse(raw: &str, index: &HashMap<usize, Request>) -> Result<Response, Error> {
121        log::debug!("Response::try_parse() {raw}");
122        // first we handle the case of a single error
123        let error: Result<ErrorResponse, _> = serde_json::from_str(raw);
124        if let Ok(e) = error {
125            return Ok(Response::Error(e));
126        }
127
128        // then we handle the Batch Header Notification case
129        let header_notif: Result<BatchHeaderNotif, _> = serde_json::from_str(raw);
130        if let Ok(n) = header_notif {
131            return Ok(Response::BatchHeaderNotif(n));
132        }
133
134        // then we handle the ScriptHash Notification case
135        let sh_notif: Result<SHNotification, _> = serde_json::from_str(raw);
136        if let Ok(n) = sh_notif {
137            return Ok(Response::SHNotification(n));
138        }
139
140        // the we handle the case we need to match request/response id
141        let rr: RawResponse = serde_json::from_str(raw)
142            .map_err(|e| Error::RawResponseParsing(format!("Fail to parse `{}`: {:?}", raw, e)))?;
143        let request = index.get(&rr.id).ok_or(Error::ResponseId(rr.id))?;
144        match request.method {
145            Method::Ping => parse!(Ping, PingResponse, raw),
146            Method::Banner => parse!(Banner, BannerResponse, raw),
147            Method::HeadersSubscribe => parse!(HeaderNotif, HeaderNotification, raw),
148            Method::BlockHeader => parse!(Header, HeaderResponse, raw),
149            Method::BlockHeaders => parse!(Headers, HeadersResponse, raw),
150            Method::Version => parse!(Version, VersionResponse, raw),
151            Method::TransactionGet => parse!(TxGet, TxGetResponse, raw),
152            Method::ScriptHashSubscribe => parse!(SHSubscribe, SHSubscribeResponse, raw),
153            Method::ScriptHashUnsubscribe => parse!(SHUnsubscribe, SHUnsubscribeResponse, raw),
154            Method::ScriptHashGetBalance => parse!(SHGetBalance, SHGetBalanceResponse, raw),
155            Method::ScriptHashGetHistory => parse!(SHGetHistory, SHGetHistoryResponse, raw),
156            Method::ScriptHashListUnspent => parse!(SHListUnspent, SHListUnspentResponse, raw),
157            // NOTE: not supported by electrs
158            // Method::ScriptHashGetMempool => parse!(SHGetMempool, SHGetMempoolResponse, raw),
159            Method::Features => parse!(Features, FeaturesResponse, raw),
160            Method::Donation => parse!(Donation, DonationResponse, raw),
161            Method::EstimateFee => parse!(EstimateFee, EstimateFeeResponse, raw),
162            Method::FeeHistogram => parse!(FeeHistogram, FeeHistogramResponse, raw),
163            Method::RelayFee => parse!(RelayFee, RelayFeeResponse, raw),
164            Method::TransactionGetMerkle => parse!(TxGetMerkle, TxGetMerkleResponse, raw),
165            Method::TransactionFromPosition => parse!(TxFromposition, TxFromPositionResponse, raw),
166            Method::TransactionBroadcast => parse!(TxBroadcast, TxBroadcastResponse, raw),
167            Method::ListPeers => todo!(),
168        }
169    }
170}
171
172#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
173pub struct ErrorResult {
174    pub code: usize,
175    pub message: String,
176}
177
178impl Display for ErrorResponse {
179    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180        write!(
181            f,
182            "Electrum error {}: {}",
183            self.error.code, self.error.message
184        )
185    }
186}
187
188#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
189pub struct ErrorResponse {
190    pub id: usize,
191    pub error: ErrorResult,
192}
193
194#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
195pub struct SHNotification {
196    pub method: Method,
197    #[serde(rename = "params")]
198    pub status: (ScriptHash, Option<String>),
199}
200
201impl FromStr for SHNotification {
202    type Err = Error;
203    fn from_str(value: &str) -> Result<Self, Error> {
204        let notif: Self =
205            serde_json::from_str(value).map_err(|_| Error::ResponseParsing(value.into()))?;
206        if let Method::ScriptHashSubscribe = notif.method {
207            Ok(notif)
208        } else {
209            Err(Error::WrongMethod)
210        }
211    }
212}
213
214#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
215pub struct RawResponse {
216    jsonrpc: String,
217    pub id: usize,
218}
219
220#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
221pub struct BannerResponse {
222    pub id: usize,
223    pub result: String,
224}
225
226#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
227pub struct Header {
228    pub height: usize,
229    #[serde(rename = "hex")]
230    pub raw_header: String,
231}
232
233#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
234pub struct SingleHeaderNotif {
235    pub id: usize,
236    #[serde(rename = "result")]
237    pub header: Header,
238}
239
240#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
241pub struct BatchHeaderNotif {
242    pub method: Method,
243    #[serde(rename = "params")]
244    pub headers: Vec<Header>,
245}
246
247#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
248#[serde(untagged)]
249pub enum HeaderNotification {
250    Single(SingleHeaderNotif),
251    Batch(BatchHeaderNotif),
252}
253
254#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
255pub struct HeaderResponse {
256    pub id: usize,
257    #[serde(rename = "result")]
258    pub raw_header: String,
259}
260
261#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
262pub struct Headers {
263    pub count: usize,
264    #[serde(rename = "hex")]
265    pub raw_headers: String,
266    pub max: usize,
267}
268
269#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
270pub struct HeadersResponse {
271    pub id: usize,
272    #[serde(rename = "result")]
273    pub headers: Headers,
274}
275
276#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
277pub struct TxBroadcastResponse {
278    pub id: usize,
279    #[serde(rename = "result")]
280    pub txid: Txid,
281}
282
283#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
284pub struct DonationResponse {
285    pub id: usize,
286    #[serde(rename = "result")]
287    pub address: Option<String>,
288}
289
290#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
291pub struct EstimateFeeResponse {
292    pub id: usize,
293    #[serde(rename = "result")]
294    pub fee: OptionalFee,
295}
296
297#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
298#[serde(untagged)]
299pub enum Port {
300    String(String),
301    U16(u16),
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
305pub struct Host {
306    #[serde(skip_serializing_if = "Option::is_none")]
307    pub tcp_port: Option<Port>,
308    #[serde(skip_serializing_if = "Option::is_none")]
309    pub ssl_port: Option<Port>,
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
313#[serde(untagged)]
314pub enum Hosts {
315    Single(Host),
316    Map(HashMap<String, Host>),
317}
318
319#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
320pub struct FeaturesResult {
321    #[serde(rename = "genesis_hash")]
322    pub genesis: String,
323    pub hosts: Hosts,
324    pub protocol_max: String,
325    pub protocol_min: String,
326    pub pruning: Option<usize>,
327    pub server_version: String,
328    pub hash_function: String,
329    #[serde(skip_serializing_if = "Option::is_none")]
330    pub services: Option<Vec<String>>,
331}
332
333#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
334pub struct FeaturesResponse {
335    pub id: usize,
336    #[serde(rename = "result")]
337    pub features: FeaturesResult,
338}
339
340#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
341pub struct FeeHistogramResponse {
342    pub id: usize,
343    #[serde(rename = "result")]
344    pub histogram: Vec<(usize, usize)>,
345}
346
347#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
348pub struct PingResponse {
349    pub id: usize,
350    // result should always be `null`
351    pub result: Option<String>,
352}
353
354#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
355#[serde(untagged)]
356pub enum OptionalFee {
357    Fee(f64),
358    None(i64),
359}
360
361#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
362pub struct RelayFeeResponse {
363    pub id: usize,
364    #[serde(rename = "result")]
365    // TODO: handle
366    pub fee: OptionalFee,
367}
368
369#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
370pub struct SHSubscribeResponse {
371    pub id: usize,
372    pub result: Option<String>,
373}
374
375#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
376pub struct SHUnsubscribeResponse {
377    pub id: usize,
378    pub result: bool,
379}
380
381#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
382pub struct BalanceResult {
383    pub confirmed: i64,
384    pub unconfirmed: i64,
385}
386
387#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
388pub struct SHGetBalanceResponse {
389    pub id: usize,
390    #[serde(rename = "result")]
391    pub balance: BalanceResult,
392}
393
394#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
395pub struct HistoryResult {
396    // NOTE: as mempool txs are appended at the end of the response:
397    //  height for mempool txs => 0 if all inputs are confirmed, and -1 otherwise
398    //  height for confirmed txs => block height
399    pub height: i128,
400    #[serde(rename = "tx_hash")]
401    pub txid: Txid,
402    pub fee: Option<usize>,
403}
404
405#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
406pub struct SHGetHistoryResponse {
407    pub id: usize,
408    #[serde(rename = "result")]
409    pub history: Vec<HistoryResult>,
410}
411
412#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
413pub struct SHGetMempoolResponse {
414    pub id: usize,
415    #[serde(rename = "result")]
416    pub mempool: Vec<HistoryResult>,
417}
418
419#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
420pub struct UtxoResult {
421    pub height: usize,
422    #[serde(rename = "tx_hash")]
423    pub txid: Txid,
424    #[serde(rename = "tx_pos")]
425    pub vout: usize,
426    pub value: usize,
427}
428
429#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
430pub struct SHListUnspentResponse {
431    pub id: usize,
432    #[serde(rename = "result")]
433    pub unspent: Vec<UtxoResult>,
434}
435
436#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
437pub struct VerboseTx {
438    pub blockhash: String,
439    pub blocktime: usize,
440    pub confirmations: usize,
441    pub locktime: usize,
442    pub size: usize,
443    pub time: usize,
444    pub version: usize,
445    pub txid: String,
446    #[serde(rename = "hex")]
447    pub raw_tx: String,
448    // TODO: better parsing of vin/vout
449    pub vin: Value,
450    pub vout: Value,
451}
452
453#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
454#[serde(untagged)]
455pub enum TxGetResult {
456    Raw(String),
457    Verbose(Box<VerboseTx>),
458}
459#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
460pub struct TxGetResponse {
461    pub id: usize,
462    pub result: TxGetResult,
463}
464
465#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
466pub struct GetMerkleResult {
467    merkle: Vec<String>,
468    block_height: usize,
469    #[serde(rename = "pos")]
470    tx_pos: usize,
471}
472
473#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
474pub struct TxGetMerkleResponse {
475    pub id: usize,
476    pub result: GetMerkleResult,
477}
478
479#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
480#[serde(untagged)]
481pub enum TxfromPosResult {
482    Simple(Txid),
483    WithMerkle {
484        #[serde(rename = "tx_hash")]
485        txid: Txid,
486        merkle: Vec<String>,
487    },
488}
489
490#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
491pub struct TxFromPositionResponse {
492    pub id: usize,
493    #[serde(rename = "result")]
494    pub tx: TxfromPosResult,
495}
496
497#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
498pub struct Peer(
499    (
500        String,      /* ip address */
501        String,      /* domain */
502        Vec<String>, /* features */
503    ),
504);
505
506#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
507pub struct ListPeersResponse {
508    pub id: usize,
509    #[serde(rename = "result")]
510    pub peers: Vec<Peer>,
511}
512
513#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
514pub struct ResultVersion((String, VersionKind));
515
516#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
517pub struct VersionResponse {
518    pub id: usize,
519    #[serde(rename = "result")]
520    pub version: ResultVersion,
521}
522
523#[cfg(test)]
524mod tests {
525    use miniscript::bitcoin::{OutPoint, Script};
526
527    use super::*;
528
529    #[test]
530    fn parse_block_header_subscribe_response_a() {
531        let response = r#"{"id":3,"jsonrpc":"2.0","result":{"height":119367,"hex":"00000020835fdbdeeadd23463fad98b4e21aaa8519afde89eecd0eb224001317421cbb5f5e636df02303e51280b586bc596ee9326bc849bbb5993e121a8cab7e6b60e8ab593fe166ffff7f2000000000"}}"#;
532
533        let parsed: HeaderNotification = serde_json::from_str(response).unwrap();
534        let expected = HeaderNotification::Single(SingleHeaderNotif { id: 3, header: Header { height: 119367, raw_header: "00000020835fdbdeeadd23463fad98b4e21aaa8519afde89eecd0eb224001317421cbb5f5e636df02303e51280b586bc596ee9326bc849bbb5993e121a8cab7e6b60e8ab593fe166ffff7f2000000000".into() }});
535        assert_eq!(parsed, expected)
536    }
537
538    #[test]
539    fn parse_header_response() {
540        let response = r#"{"id":0,"jsonrpc":"2.0","result":"000000206e59d4b0d8d5b9daa4d3ad3093975b0f2a18a6909533350cbfb4b7a04adc6f5f380884ecf7425e488e7f2b249de516e839a5b2d48bcc9b65d45387ce5081c1e8563fe166ffff7f2001000000"}"#;
541
542        let parsed: HeaderResponse = serde_json::from_str(response).unwrap();
543        assert_eq!(
544            parsed,
545            HeaderResponse {
546                id: 0,
547                raw_header: "000000206e59d4b0d8d5b9daa4d3ad3093975b0f2a18a6909533350cbfb4b7a04adc6f5f380884ecf7425e488e7f2b249de516e839a5b2d48bcc9b65d45387ce5081c1e8563fe166ffff7f2001000000".into()
548            }
549        )
550    }
551
552    #[test]
553    fn parse_headers_response() {
554        let response = r#"{"id":0,"jsonrpc":"2.0","result":{"count":5,"hex":"000000206e59d4b0d8d5b9daa4d3ad3093975b0f2a18a6909533350cbfb4b7a04adc6f5f380884ecf7425e488e7f2b249de516e839a5b2d48bcc9b65d45387ce5081c1e8563fe166ffff7f200100000000000020e4a9efb184a77e3b3d75c374823a808f437c5d04fc322f6585c1682ea859a379874002727ca2397cbf8b45bffbd0463c1a8e4f52c23af48b3d8e30c0c4556bd1563fe166ffff7f200100000000000020d02dd6842a2be3611748c75b423d0199f86599a7f565de283ee09ffe3527cf49d2e107eae3f796827fb71fc950ee32f5c45c58704cd0f6de8c5125dfe18d0005573fe166ffff7f20000000000000002007e28823c56f2b29644eaa8060f1e62e622733fbb796a429119963f6318e4d012833a1ec146ca836cbd22f3be596ee73f00134c1edafaeb1178623cf480e554c573fe166ffff7f200600000000000020a7cc866c5522c258d4d08cf78aaf6dec40df9cba90c51b4fb63577dab6000b4805c639b49ecb0ddb0d6e922047310faefc6d69316e137084386a24238d1152ba573fe166ffff7f2000000000","max":2016}}"#;
555
556        let parsed: HeadersResponse = serde_json::from_str(response).unwrap();
557        assert_eq!(
558            parsed,
559            HeadersResponse {
560                id: 0,
561                headers: Headers {
562                    count: 5,
563                    raw_headers: "000000206e59d4b0d8d5b9daa4d3ad3093975b0f2a18a6909533350cbfb4b7a04adc6f5f380884ecf7425e488e7f2b249de516e839a5b2d48bcc9b65d45387ce5081c1e8563fe166ffff7f200100000000000020e4a9efb184a77e3b3d75c374823a808f437c5d04fc322f6585c1682ea859a379874002727ca2397cbf8b45bffbd0463c1a8e4f52c23af48b3d8e30c0c4556bd1563fe166ffff7f200100000000000020d02dd6842a2be3611748c75b423d0199f86599a7f565de283ee09ffe3527cf49d2e107eae3f796827fb71fc950ee32f5c45c58704cd0f6de8c5125dfe18d0005573fe166ffff7f20000000000000002007e28823c56f2b29644eaa8060f1e62e622733fbb796a429119963f6318e4d012833a1ec146ca836cbd22f3be596ee73f00134c1edafaeb1178623cf480e554c573fe166ffff7f200600000000000020a7cc866c5522c258d4d08cf78aaf6dec40df9cba90c51b4fb63577dab6000b4805c639b49ecb0ddb0d6e922047310faefc6d69316e137084386a24238d1152ba573fe166ffff7f2000000000".into(),
564                    max: 2016
565                }
566            }
567        )
568    }
569
570    #[test]
571    fn version() {
572        let response = r#"{"id":0,"jsonrpc":"2.0","result":["electrs/0.10.5","1.4"]}"#;
573        let response: VersionResponse = serde_json::from_str(response).unwrap();
574        if let VersionResponse {
575            id,
576            version: ResultVersion((server_name, VersionKind::Single(version))),
577        } = response
578        {
579            assert_eq!(id, 0);
580            assert_eq!(server_name, "electrs/0.10.5");
581            assert_eq!(version, "1.4");
582        } else {
583            panic!("wrong response")
584        }
585
586        let response = r#"{"id":0,"jsonrpc":"2.0","result":["electrs/0.10.5",["1.1","1.4"]]}"#;
587        let response: VersionResponse = serde_json::from_str(response).unwrap();
588        if let VersionResponse {
589            id,
590            version: ResultVersion((server_name, VersionKind::MinMax(min, max))),
591        } = response
592        {
593            assert_eq!(id, 0);
594            assert_eq!(server_name, "electrs/0.10.5");
595            assert_eq!(min, "1.1");
596            assert_eq!(max, "1.4");
597        } else {
598            panic!("wrong response")
599        }
600    }
601
602    #[test]
603    fn hash_subscribe_response() {
604        let response = r#"{"id":14,"jsonrpc":"2.0","result":"1c8606707de065bef7474d719b76fb41cdff0090fffb78ca6b640c66ba9a9542"}"#;
605
606        let response: SHSubscribeResponse = serde_json::from_str(response).unwrap();
607        assert_eq!(response.id, 14);
608        assert_eq!(
609            response.result,
610            Some("1c8606707de065bef7474d719b76fb41cdff0090fffb78ca6b640c66ba9a9542".to_string())
611        )
612    }
613
614    #[test]
615    fn batch_sh_subscribe_response() {
616        let script = Script::from_bytes(&[0x00]);
617        let req = Request::subscribe_sh(script);
618
619        // populate index w/ requests
620        let mut index = HashMap::new();
621        for i in 14..35usize {
622            let mut r = req.clone();
623            r.id = i;
624            index.insert(i, r);
625        }
626
627        let response = r#"[{"id":14,"jsonrpc":"2.0","result":"1c8606707de065bef7474d719b76fb41cdff0090fffb78ca6b640c66ba9a9542"},{"id":15,"jsonrpc":"2.0","result":null},{"id":16,"jsonrpc":"2.0","result":null},{"id":17,"jsonrpc":"2.0","result":null},{"id":18,"jsonrpc":"2.0","result":null},{"id":19,"jsonrpc":"2.0","result":null},{"id":20,"jsonrpc":"2.0","result":null},{"id":21,"jsonrpc":"2.0","result":null},{"id":22,"jsonrpc":"2.0","result":null},{"id":23,"jsonrpc":"2.0","result":null},{"id":24,"jsonrpc":"2.0","result":null},{"id":25,"jsonrpc":"2.0","result":null},{"id":26,"jsonrpc":"2.0","result":null},{"id":27,"jsonrpc":"2.0","result":null},{"id":28,"jsonrpc":"2.0","result":null},{"id":29,"jsonrpc":"2.0","result":null},{"id":30,"jsonrpc":"2.0","result":null},{"id":31,"jsonrpc":"2.0","result":null},{"id":32,"jsonrpc":"2.0","result":null},{"id":33,"jsonrpc":"2.0","result":null},{"id":34,"jsonrpc":"2.0","result":null}]"#;
628
629        let batch = ResponseBatch::from_str(response, &index).unwrap().unwrap();
630        assert_eq!(batch.batch.len(), 21);
631        let resp = &batch.batch[5];
632        if let Response::SHSubscribe(SHSubscribeResponse { id, result }) = resp {
633            assert_eq!(*id, 19);
634            assert_eq!(*result, None);
635        } else {
636            panic!("wrong response");
637        }
638    }
639
640    #[test]
641    fn error_response() {
642        let response = r#"{"error":{"code":1,"message":"unsupported request Single(\"0.4\") by smart"},"id":0,"jsonrpc":"2.0"}"#;
643
644        let response: ErrorResponse = serde_json::from_str(response).unwrap();
645        assert_eq!(response.error.code, 1);
646        assert_eq!(response.id, 0);
647        assert_eq!(
648            response.error.message,
649            r#"unsupported request Single("0.4") by smart"#
650        );
651    }
652
653    #[test]
654    fn sh_unsubscribe_response() {
655        let response = r#"{"id":0,"jsonrpc":"2.0","result":false}"#;
656        let response: SHUnsubscribeResponse = serde_json::from_str(response).unwrap();
657        assert_eq!(response.id, 0);
658        assert!(!response.result);
659    }
660
661    #[test]
662    fn sh_subscribe_response() {
663        let response = r#"{"id":1,"jsonrpc":"2.0","result":null}"#;
664        let response: SHSubscribeResponse = serde_json::from_str(response).unwrap();
665        assert_eq!(response.id, 1);
666        assert_eq!(response.result, None);
667
668        let response = r#"{"id":1,"jsonrpc":"2.0","result":"some_garbage_string"}"#;
669        let response: SHSubscribeResponse = serde_json::from_str(response).unwrap();
670        assert_eq!(response.id, 1);
671        assert_eq!(response.result, Some("some_garbage_string".into()));
672    }
673
674    #[test]
675    fn sh_notification() {
676        let response = r#" {"jsonrpc":"2.0","method":"blockchain.scripthash.subscribe","params":["95ebd95e7c0763b785d12b1d20d9f548fa5bb809f120afb0dd11276fa1ce8352","9bf1d98ff899eafd048290199144aed63e3d7ccbc8925e8351a4c1e8af2137f4"]}"#;
677
678        let _: SHNotification = serde_json::from_str(response).unwrap();
679        let response = SHNotification::from_str(response).unwrap();
680
681        assert_eq!(response.method, Method::ScriptHashSubscribe);
682        assert!(response.status.1.is_some());
683        assert_eq!(
684            response.status.1,
685            Some("9bf1d98ff899eafd048290199144aed63e3d7ccbc8925e8351a4c1e8af2137f4".into())
686        );
687    }
688
689    #[test]
690    fn sh_list_unspent() {
691        let response = r#"{"jsonrpc": "2.0", "result": [{"tx_hash": "b14edd61d6902890932be0d4386c79ca64a8dea345e9b9c95b2e8a825316cfc0", "tx_pos": 1, "height": 861250, "value": 566888}], "id": 0}"#;
692        let response: SHListUnspentResponse = serde_json::from_str(response).unwrap();
693        assert_eq!(response.id, 0);
694        assert_eq!(response.unspent.len(), 1);
695        assert_eq!(
696            response.unspent[0].txid,
697            Txid::from_str("b14edd61d6902890932be0d4386c79ca64a8dea345e9b9c95b2e8a825316cfc0")
698                .unwrap()
699        );
700        assert_eq!(response.unspent[0].vout, 1);
701        assert_eq!(response.unspent[0].height, 861250);
702        assert_eq!(response.unspent[0].value, 566888);
703    }
704
705    #[test]
706    fn sh_get_balance() {
707        let response =
708            r#"{"jsonrpc": "2.0", "result": {"confirmed": 566888, "unconfirmed": 0}, "id": 0}"#;
709        let response: SHGetBalanceResponse = serde_json::from_str(response).unwrap();
710        assert_eq!(response.id, 0);
711        assert_eq!(response.balance.confirmed, 566888);
712        assert_eq!(response.balance.unconfirmed, 0);
713    }
714
715    #[test]
716    fn sh_get_history() {
717        let response = r#"{"jsonrpc": "2.0", "result": [{"tx_hash": "b14edd61d6902890932be0d4386c79ca64a8dea345e9b9c95b2e8a825316cfc0", "height": 861250}], "id": 0}"#;
718        let response: SHGetHistoryResponse = serde_json::from_str(response).unwrap();
719        assert_eq!(response.id, 0);
720        assert_eq!(response.history.len(), 1);
721        assert_eq!(
722            response.history[0].txid,
723            Txid::from_str("b14edd61d6902890932be0d4386c79ca64a8dea345e9b9c95b2e8a825316cfc0")
724                .unwrap()
725        );
726        assert_eq!(response.history[0].height, 861250);
727    }
728
729    #[test]
730    fn features() {
731        let response = r#"{"jsonrpc": "2.0", "result": {"hosts": {}, "pruning": null, "server_version": "ElectrumX 1.15.0", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", "hash_function": "sha256", "services": []}, "id": 0}"#;
732
733        let response: FeaturesResponse = serde_json::from_str(response).unwrap();
734        assert_eq!(response.id, 0);
735        assert!(response.features.pruning.is_none());
736        assert_eq!(response.features.server_version, "ElectrumX 1.15.0");
737        assert_eq!(response.features.protocol_min, "1.4");
738        assert_eq!(response.features.protocol_max, "1.4.2");
739        assert_eq!(
740            response.features.genesis,
741            "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
742        );
743        assert_eq!(response.features.hash_function, "sha256");
744        assert!(response.features.services.is_some());
745        assert!(response.features.services.unwrap().is_empty());
746
747        let response = r#"{
748              "id": 0,
749              "jsonrpc": "2.0",
750              "result": {
751                "genesis_hash": "abc",
752                "hash_function": "sha256",
753                "hosts": {
754                  "tcp_port": 46771
755                },
756                "protocol_max": "1.4",
757                "protocol_min": "1.4",
758                "pruning": null,
759                "server_version": "toto"
760              }
761            }"#;
762
763        let response: FeaturesResponse = serde_json::from_str(response).unwrap();
764
765        let expected = FeaturesResponse {
766            id: 0,
767            features: FeaturesResult {
768                genesis: "abc".into(),
769                hosts: Hosts::Single(Host {
770                    tcp_port: Some(Port::U16(46771)),
771                    ssl_port: None,
772                }),
773                protocol_max: "1.4".into(),
774                protocol_min: "1.4".into(),
775                pruning: None,
776                server_version: "toto".into(),
777                hash_function: "sha256".into(),
778                services: None,
779            },
780        };
781        assert_eq!(response, expected);
782    }
783
784    #[test]
785    fn donation() {
786        let response = r#"{"jsonrpc": "2.0", "result": "make_me_rich", "id": 0}"#;
787
788        let response: DonationResponse = serde_json::from_str(response).unwrap();
789        assert_eq!(response.id, 0);
790        assert_eq!(response.address, Some("make_me_rich".into()));
791    }
792
793    #[test]
794    fn estimate_fee() {
795        let response = r#"{"jsonrpc": "2.0", "result": 3.006e-05, "id": 0}"#;
796
797        let response: EstimateFeeResponse = serde_json::from_str(response).unwrap();
798        assert_eq!(response.id, 0);
799        assert_eq!(response.fee, OptionalFee::Fee(0.00003006));
800    }
801
802    #[test]
803    fn get_fee_histogram() {
804        let response = r#"{"jsonrpc": "2.0", "result": [[5, 103673], [3, 238053], [2, 12058673], [1, 34188435]], "id": 0}"#;
805
806        let response: FeeHistogramResponse = serde_json::from_str(response).unwrap();
807        assert_eq!(response.id, 0);
808        assert_eq!(response.histogram.len(), 4);
809        assert_eq!(response.histogram[1].0, 3);
810        assert_eq!(response.histogram[2].1, 12058673);
811        let expected = FeeHistogramResponse {
812            id: 0,
813            histogram: vec![(5, 103673), (3, 238053), (2, 12058673), (1, 34188435)],
814        };
815        assert_eq!(response, expected);
816    }
817
818    #[test]
819    fn relay_fee() {
820        let response = r#"{"jsonrpc": "2.0", "result": 1e-05, "id": 0}"#;
821
822        let response: RelayFeeResponse = serde_json::from_str(response).unwrap();
823        assert_eq!(response.id, 0);
824        assert_eq!(response.fee, OptionalFee::Fee(0.00001));
825    }
826
827    #[test]
828    fn tx_get_merkle() {
829        let response = r#"{"jsonrpc": "2.0", "result": {"block_height": 200000, "merkle": ["ffa0267c8f2af736858894d6f3e5081a05e2ec16dc98f78a80f376ce35077491", "d0039b6be844e631698f57fa02bbfbfb5e8b680f3ebb17646631e6ec9f91f6e6", "bbe3063ce3d04c2e3f18e494a287867f81ad1182b62a1ecb3e1ea2686edcea20", "1d15a2423f52d4aa281a2ac389c0a5a601ed08bdf814494ddf7697196860b801", "b63e58ec9f5ee2e268f1540af8bb0e5b8fd0ce7cd6877a174e6178c676d6b574", "7407724b98c77cdbf070f3fe297839de2bef50fead98b452883f0f3a4643cde2", "d029f17725e71e3c025bd7d0505006dc859af5450d0b6dd092ee88c0d98f9a25", "e4df974d81ab4fdf35f635024a01f20aa88af9f520215708b339dbc5bceddf63", "20f4202f18666483306f175e1c9c521741845afcf2710f0b0d42602ac72c5fd6"], "pos": 2}, "id": 0}"#;
830
831        let response: TxGetMerkleResponse = serde_json::from_str(response).unwrap();
832        assert_eq!(response.id, 0);
833        let expected = TxGetMerkleResponse {
834            id: 0,
835            result: GetMerkleResult {
836                merkle: vec![
837                    "ffa0267c8f2af736858894d6f3e5081a05e2ec16dc98f78a80f376ce35077491".into(),
838                    "d0039b6be844e631698f57fa02bbfbfb5e8b680f3ebb17646631e6ec9f91f6e6".into(),
839                    "bbe3063ce3d04c2e3f18e494a287867f81ad1182b62a1ecb3e1ea2686edcea20".into(),
840                    "1d15a2423f52d4aa281a2ac389c0a5a601ed08bdf814494ddf7697196860b801".into(),
841                    "b63e58ec9f5ee2e268f1540af8bb0e5b8fd0ce7cd6877a174e6178c676d6b574".into(),
842                    "7407724b98c77cdbf070f3fe297839de2bef50fead98b452883f0f3a4643cde2".into(),
843                    "d029f17725e71e3c025bd7d0505006dc859af5450d0b6dd092ee88c0d98f9a25".into(),
844                    "e4df974d81ab4fdf35f635024a01f20aa88af9f520215708b339dbc5bceddf63".into(),
845                    "20f4202f18666483306f175e1c9c521741845afcf2710f0b0d42602ac72c5fd6".into(),
846                ],
847                block_height: 200_000,
848                tx_pos: 2,
849            },
850        };
851        assert_eq!(expected, response);
852    }
853
854    #[test]
855    fn tx_from_pos() {
856        let response = r#"{"jsonrpc": "2.0", "result": "ffa0267c8f2af736858894d6f3e5081a05e2ec16dc98f78a80f376ce35077491", "id": 0}"#;
857
858        let outpoint = OutPoint::from_str(
859            "ffa0267c8f2af736858894d6f3e5081a05e2ec16dc98f78a80f376ce35077491:0",
860        )
861        .unwrap();
862
863        let response: TxFromPositionResponse = serde_json::from_str(response).unwrap();
864        let expected = TxFromPositionResponse {
865            id: 0,
866            tx: TxfromPosResult::Simple(outpoint.txid),
867        };
868        assert_eq!(response, expected);
869
870        let response = r#"{"jsonrpc": "2.0", "result": {"tx_hash": "9cc064bbce74a2c56ce12b0b59fc7267a2618a35e1d8c66f642efd6d033a9681", "merkle": ["e48b08df0afa01a7339335fb6b6964100d11985765cbc6afcde990fd65856a9b", "12a6c68b6c033d6704bda3437370b3e7d65bec81b2e3f4eafb17632197f0b6c7", "c0dbecba7c7990f3bfbe727dd9a7371225852600dc0a0f07e68b3ec7c4fd629e", "8e351c5bac49e6dbf08bc67cc1f57fb4dbea0383336d0ee2c38fefc8736b18eb", "aa7171ca4f639d14050101ac602f3f526abec753414b3b4648071b252434e38e", "583b92abff3481905c686d3ff594c4a1d6a00bab25deb3397369b9e49adf11ae", "6a4d797a4d3e162a951ccd142fe6ca86e12006145f1670c5d1aa5e7bfcc05fa3", "c6dd553f393d1b7694ae168e8f5efeba8db4c3b000c2d9bf5205dd19f96c08a8"]}, "id": 1}"#;
871
872        let response: TxFromPositionResponse = serde_json::from_str(response).unwrap();
873        let outpoint = OutPoint::from_str(
874            "9cc064bbce74a2c56ce12b0b59fc7267a2618a35e1d8c66f642efd6d033a9681:0",
875        )
876        .unwrap();
877
878        let expected = TxFromPositionResponse {
879            id: 1,
880            tx: TxfromPosResult::WithMerkle {
881                txid: outpoint.txid,
882                merkle: vec![
883                    "e48b08df0afa01a7339335fb6b6964100d11985765cbc6afcde990fd65856a9b".into(),
884                    "12a6c68b6c033d6704bda3437370b3e7d65bec81b2e3f4eafb17632197f0b6c7".into(),
885                    "c0dbecba7c7990f3bfbe727dd9a7371225852600dc0a0f07e68b3ec7c4fd629e".into(),
886                    "8e351c5bac49e6dbf08bc67cc1f57fb4dbea0383336d0ee2c38fefc8736b18eb".into(),
887                    "aa7171ca4f639d14050101ac602f3f526abec753414b3b4648071b252434e38e".into(),
888                    "583b92abff3481905c686d3ff594c4a1d6a00bab25deb3397369b9e49adf11ae".into(),
889                    "6a4d797a4d3e162a951ccd142fe6ca86e12006145f1670c5d1aa5e7bfcc05fa3".into(),
890                    "c6dd553f393d1b7694ae168e8f5efeba8db4c3b000c2d9bf5205dd19f96c08a8".into(),
891                ],
892            },
893        };
894        assert_eq!(response, expected);
895    }
896}