bwk_electrum/electrum/
params.rs

1use super::types::ScriptHash;
2use miniscript::bitcoin::Txid;
3use miniscript::serde::{ser::SerializeSeq, Deserialize, Serialize, Serializer};
4
5#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
6#[serde(untagged)]
7pub enum VersionKind {
8    Single(String),
9    MinMax(String, String),
10}
11
12#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
13#[serde(untagged)]
14pub enum TxGetArgs {
15    Txid((Txid,)),
16    TxidVerbose(Txid, bool),
17}
18
19impl From<&TxGetArgs> for (Txid, bool) {
20    fn from(args: &TxGetArgs) -> Self {
21        match args {
22            TxGetArgs::Txid((txid,)) => (*txid, false),
23            TxGetArgs::TxidVerbose(txid, verbose) => (*txid, *verbose),
24        }
25    }
26}
27
28#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
29#[serde(untagged)]
30pub enum Params {
31    #[serde(serialize_with = "default")]
32    None,
33    // NOTE: electrs does not support `cp_height` even if
34    // it's in the 1.4 version spec. ...
35    // https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-block-header
36    // BlockHeader((usize /* height*/, usize /* cp_height */)),
37    BlockHeader((usize /* height*/,)),
38    // NOTE: idem
39    BlockHeaders(
40        (
41            usize, /* start */
42            usize, /* count */
43                   // usize, /* cp_height */
44        ),
45    ),
46    TransactionBroadcast((String,)),
47    EstimateFee((u16,)),
48    ScriptHashGetBalance((ScriptHash,)),
49    ScriptHashGetHistory((ScriptHash,)),
50    ScriptHashGetMempool((ScriptHash,)),
51    ScriptHashListUnspent((ScriptHash,)),
52    ScriptHashSubscribe((ScriptHash,)),
53    ScriptHashUnsubscribe((ScriptHash,)),
54    TransactionGet(TxGetArgs),
55    TransactionGetMerkle((Txid, usize)),
56    TransactionFromPosition(
57        (
58            usize, /*height*/
59            usize, /*tx_pos*/
60            bool,  /*merkle*/
61        ),
62    ),
63    Version((String, VersionKind)),
64}
65
66fn default<S>(serializer: S) -> Result<S::Ok, S::Error>
67where
68    S: Serializer,
69{
70    // Serialize unit type as an empty array "[]"
71    let sequence = serializer.serialize_seq(Some(0))?;
72    sequence.end()
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use miniscript::bitcoin::{hex::FromHex, OutPoint, Script};
79    use std::str::FromStr;
80
81    macro_rules! json {
82        ($value:expr, $str:expr) => {{
83            use serde_json::to_string;
84
85            let json_str = to_string(&$value).unwrap();
86            // let json_str = json_str.replace('"', "");
87
88            assert_eq!(
89                json_str, $str,
90                "Debug and JSON representations do not match"
91            );
92        }};
93    }
94
95    #[test]
96    fn params() {
97        assert_eq!(serde_json::to_string(&Params::None).unwrap(), "[]");
98        assert_eq!(
99            serde_json::to_string(&Params::BlockHeader((0,))).unwrap(),
100            "[0]"
101        );
102    }
103
104    #[test]
105    fn tx_get_args() {
106        let outpoint = OutPoint::from_str(
107            "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:42",
108        )
109        .unwrap();
110        let arg1 = TxGetArgs::Txid((outpoint.txid,));
111
112        let arg2 = TxGetArgs::TxidVerbose(outpoint.txid, true);
113
114        assert_eq!(
115            arg1,
116            serde_json::from_str(
117                r#"["5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456"]"#
118            )
119            .unwrap()
120        );
121        assert_eq!(
122            arg2,
123            serde_json::from_str(
124                r#"["5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456",true]"#
125            )
126            .unwrap()
127        );
128    }
129
130    #[test]
131    fn from_tx_get_arg() {
132        let outpoint = OutPoint::from_str(
133            "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:42",
134        )
135        .unwrap();
136        let arg1 = TxGetArgs::Txid((outpoint.txid,));
137
138        let arg2 = TxGetArgs::TxidVerbose(outpoint.txid, true);
139
140        let (txid, verbose): (Txid, bool) = (&arg1).into();
141        assert_eq!(txid, outpoint.txid);
142        assert!(!verbose);
143
144        let (txid, verbose): (Txid, bool) = (&arg2).into();
145        assert_eq!(txid, outpoint.txid);
146        assert!(verbose);
147    }
148
149    #[test]
150    fn version_kind() {
151        let version1 = VersionKind::Single("1.4".into());
152        let version2 = VersionKind::MinMax("1.1".into(), "1.4".into());
153
154        assert_eq!(version1, serde_json::from_str(r#""1.4""#).unwrap());
155        assert_eq!(version2, serde_json::from_str(r#"["1.1","1.4"]"#).unwrap());
156    }
157
158    #[test]
159    fn params_() {
160        json!(Params::None, "[]");
161        json!(Params::BlockHeader((12,)), "[12]");
162        json!(Params::BlockHeaders((12, 34)), "[12,34]");
163        json!(Params::TransactionBroadcast(("toto".into(),)), "[\"toto\"]");
164        json!(Params::EstimateFee((2,)), "[2]");
165
166        let raw_script = Vec::from_hex("0014992f8cc4f6d284acac5f603e233592b566c04b2a").unwrap();
167        let script = Script::from_bytes(raw_script.as_slice());
168        let sh = ScriptHash::new(script);
169        json!(
170            Params::ScriptHashGetBalance((sh,)),
171            "[\"8b2154ad6733677e53c2b9fd12d527bf292ace4df41281755ce1ecabe456fce5\"]"
172        );
173        json!(
174            Params::ScriptHashGetHistory((sh,)),
175            "[\"8b2154ad6733677e53c2b9fd12d527bf292ace4df41281755ce1ecabe456fce5\"]"
176        );
177        json!(
178            Params::ScriptHashGetMempool((sh,)),
179            "[\"8b2154ad6733677e53c2b9fd12d527bf292ace4df41281755ce1ecabe456fce5\"]"
180        );
181        json!(
182            Params::ScriptHashListUnspent((sh,)),
183            "[\"8b2154ad6733677e53c2b9fd12d527bf292ace4df41281755ce1ecabe456fce5\"]"
184        );
185        json!(
186            Params::ScriptHashSubscribe((sh,)),
187            "[\"8b2154ad6733677e53c2b9fd12d527bf292ace4df41281755ce1ecabe456fce5\"]"
188        );
189        json!(
190            Params::ScriptHashUnsubscribe((sh,)),
191            "[\"8b2154ad6733677e53c2b9fd12d527bf292ace4df41281755ce1ecabe456fce5\"]"
192        );
193
194        let outpoint = OutPoint::from_str(
195            "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:42",
196        )
197        .unwrap();
198        let arg1 = TxGetArgs::Txid((outpoint.txid,));
199
200        let arg2 = TxGetArgs::TxidVerbose(outpoint.txid, true);
201
202        json!(
203            Params::TransactionGet(arg1),
204            "[\"5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456\"]"
205        );
206        json!(
207            Params::TransactionGet(arg2),
208            "[\"5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456\",true]"
209        );
210        json!(
211            Params::TransactionGetMerkle((outpoint.txid, 3)),
212            "[\"5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456\",3]"
213        );
214        json!(
215            Params::TransactionFromPosition((1, 2, false)),
216            "[1,2,false]"
217        );
218        json!(
219            Params::Version(("last".into(), VersionKind::Single("1.4.into())".into()))),
220            "[\"last\",\"1.4.into())\"]"
221        );
222    }
223}