gl_plugin/
messages.rs

1use anyhow::{anyhow, Error};
2use hex::{self, FromHex};
3use serde::de::{self, Deserializer};
4use serde::ser::{self, Serializer};
5use serde::{Deserialize, Serialize};
6use serde_json::{json, Value};
7use std::collections::HashMap;
8
9#[derive(Serialize, Deserialize, Debug)]
10#[serde(tag = "method", content = "params")]
11#[serde(rename_all = "snake_case")]
12enum JsonRpcCall {
13    //HtlcAccepted(HtlcAcceptedCall),
14}
15
16#[derive(Debug)]
17pub struct ParserError {
18    reason: String,
19}
20
21impl std::fmt::Display for ParserError {
22    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
23        f.write_fmt(format_args!("ParserError {}", self.reason))
24    }
25}
26impl std::error::Error for ParserError {}
27
28#[derive(Serialize, Deserialize, Debug)]
29struct JsonRpcRequest {
30    id: Option<Value>,
31    jsonrpc: String,
32    method: String,
33    params: JsonRpcCall,
34}
35
36// "Inspired" by https://github.com/serde-rs/serde/issues/1028#issuecomment-325434041
37#[derive(Serialize, Deserialize, Debug)]
38#[serde(tag = "method", content = "params")]
39#[serde(rename_all = "snake_case")]
40pub enum MyRequests {
41    HtlcAccepted(HtlcAcceptedCall),
42    Getmanifest(GetManifestCall),
43    Init(InitCall),
44    InvoicePayment(InvoicePaymentCall),
45    CommitmentRevocation(CommitmentRevocationCall),
46}
47
48#[derive(Serialize, Deserialize, Debug)]
49#[serde(rename_all = "snake_case")]
50pub struct HtlcAcceptedCall {
51    pub onion: HtlcAcceptedCallOnion,
52    pub htlc: HtlcAcceptedCallHtlc,
53}
54
55#[derive(Serialize, Deserialize, Debug)]
56#[serde(rename_all = "snake_case")]
57pub struct InvoicePaymentCall {
58    pub payment: InvoicePaymentCallPayment,
59}
60
61#[derive(Serialize, Deserialize, Debug)]
62#[serde(rename_all = "snake_case")]
63pub struct Custommsg {
64    pub peer_id: String,
65    pub payload: String,
66}
67
68#[derive(Serialize, Deserialize, Debug)]
69#[serde(rename_all = "snake_case")]
70pub struct CommitmentRevocationCall {
71    pub commitment_txid: String,
72    pub penalty_tx: String,
73    pub channel_id: Option<String>,
74    pub commitnum: Option<u64>,
75}
76
77fn amt_from_str_or_int<'de, D>(deserializer: D) -> Result<u64, D::Error>
78where
79    D: de::Deserializer<'de>,
80{
81    struct JsonStringVisitor;
82
83    impl<'de> de::Visitor<'de> for JsonStringVisitor {
84        type Value = u64;
85
86        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
87            formatter.write_str("a string containing json data")
88        }
89
90        fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
91        where
92            E: de::Error,
93        {
94            Ok(v)
95        }
96
97        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
98        where
99            E: de::Error,
100        {
101            let (nums, exts): (Vec<char>, Vec<char>) = v.chars().partition(|c| c.is_digit(10));
102
103            let mut num = String::new();
104            num.extend(nums);
105            let mut ext = String::new();
106            ext.extend(exts);
107
108            // Conversion table from the unit to `msat`, since msat is
109            // the unit of account internally for off-chain payments.
110            let mult = match ext.as_str() {
111                "msat" => 1,
112                "sat" => 1000,
113                "btc" => 100_000_000_000,
114                _ => return Err(E::custom("unable to parse unit")),
115            };
116
117            let num: u64 = num.parse::<u64>().expect("converting chars to u64");
118            Ok(num * mult)
119        }
120    }
121
122    // use our visitor to deserialize an `ActualValue`
123    deserializer.deserialize_any(JsonStringVisitor)
124}
125
126#[derive(Serialize, Deserialize, Debug)]
127pub struct InvoicePaymentCallPayment {
128    pub label: String,
129    pub preimage: String,
130    #[serde(rename = "msat", deserialize_with = "amt_from_str_or_int")]
131    pub amount: u64,
132    pub extratlvs: Option<Vec<TlvField>>,
133}
134
135#[derive(Serialize, Deserialize, Debug)]
136pub struct TlvField {
137    #[serde(rename = "type")]
138    pub typ: u64,
139    pub value: String,
140}
141
142#[derive(Serialize, Deserialize, Debug)]
143#[serde(rename_all = "snake_case")]
144pub struct GetManifestCall {}
145
146#[derive(Serialize, Deserialize, Debug)]
147#[serde(rename_all = "snake_case")]
148pub struct GetManifestResult {
149    pub subscriptions: Vec<String>,
150    pub hooks: Vec<String>,
151    pub dynamic: bool,
152    pub options: Vec<PluginOption>,
153    pub rpcmethods: Vec<PluginRpcMethod>,
154}
155
156#[derive(Serialize, Deserialize, Debug)]
157pub struct PluginOption {
158    name: String,
159    default: String,
160    description: String,
161}
162
163#[derive(Serialize, Deserialize, Debug)]
164#[serde(rename_all = "snake_case")]
165pub struct PluginRpcMethod {
166    name: String,
167    usage: String,
168    description: String,
169}
170
171#[derive(Serialize, Deserialize, Debug)]
172#[serde(rename_all = "snake_case")]
173pub struct HtlcAcceptedCallOnion {
174    #[serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")]
175    pub payload: Vec<u8>,
176    short_channel_id: Option<String>,
177    forward_amount: String,
178    outgoing_cltv_value: u64,
179
180    #[serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")]
181    next_onion: Vec<u8>,
182
183    #[serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")]
184    pub shared_secret: Vec<u8>,
185}
186
187#[derive(Serialize, Deserialize, Debug)]
188#[serde(rename_all = "snake_case")]
189pub struct HtlcAcceptedCallHtlc {
190    pub amount: String,
191    cltv_expiry: u64,
192    cltv_expiry_relative: u64,
193
194    #[serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")]
195    pub payment_hash: Vec<u8>,
196}
197
198#[derive(Serialize, Deserialize, Debug)]
199#[serde(rename_all = "snake_case")]
200pub struct HtlcAcceptedResponse {
201    pub result: String,
202    #[serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")]
203    pub payment_key: Vec<u8>,
204}
205
206#[derive(Serialize, Deserialize, Debug)]
207pub struct InitCall {
208    pub options: Value,
209    pub configuration: HashMap<String, Value>,
210}
211
212#[derive(Serialize, Deserialize, Debug)]
213#[serde(tag = "method", content = "params")]
214#[serde(rename_all = "snake_case")]
215pub enum MyNotifications {
216    Disconnect(DisconnectNotification),
217}
218
219#[derive(Serialize, Deserialize, Debug)]
220pub struct DisconnectNotification {
221    pub id: String,
222}
223
224#[derive(Debug)]
225pub enum JsonRpc<N, R> {
226    Request(usize, R),
227    Notification(N),
228}
229
230impl<N, R> Serialize for JsonRpc<N, R>
231where
232    N: Serialize,
233    R: Serialize,
234{
235    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
236    where
237        S: Serializer,
238    {
239        match *self {
240            JsonRpc::Request(id, ref r) => {
241                let mut v = serde_json::to_value(r).map_err(ser::Error::custom)?;
242                v["id"] = json!(id);
243                v.serialize(serializer)
244            }
245            JsonRpc::Notification(ref n) => n.serialize(serializer),
246        }
247    }
248}
249
250impl<'de, N, R> Deserialize<'de> for JsonRpc<N, R>
251where
252    N: Deserialize<'de>,
253    R: Deserialize<'de>,
254{
255    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
256    where
257        D: Deserializer<'de>,
258    {
259        #[derive(Deserialize)]
260        struct IdHelper {
261            id: Option<usize>,
262        }
263
264        let v = Value::deserialize(deserializer)?;
265        let helper = IdHelper::deserialize(&v).map_err(de::Error::custom)?;
266        match helper.id {
267            Some(id) => {
268                let r = R::deserialize(v).map_err(de::Error::custom)?;
269                Ok(JsonRpc::Request(id, r))
270            }
271            None => {
272                let n = N::deserialize(v).map_err(de::Error::custom)?;
273                Ok(JsonRpc::Notification(n))
274            }
275        }
276    }
277}
278/// Serializes `buffer` to a lowercase hex string.
279pub fn buffer_to_hex<T, S>(buffer: &T, serializer: S) -> Result<S::Ok, S::Error>
280where
281    T: AsRef<[u8]>,
282    S: Serializer,
283{
284    serializer.serialize_str(&hex::encode(buffer))
285}
286
287/// Deserializes a lowercase hex string to a `Vec<u8>`.
288pub fn hex_to_buffer<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
289where
290    D: Deserializer<'de>,
291{
292    use serde::de::Error;
293    String::deserialize(deserializer)
294        .and_then(|string| Vec::from_hex(&string).map_err(|err| Error::custom(err.to_string())))
295}
296
297#[derive(Serialize, Deserialize, Debug)]
298pub struct Amount {
299    pub msatoshi: i64,
300}
301
302impl Amount {
303    pub fn from_string(s: &str) -> Result<Amount, Error> {
304        if !s.ends_with("msat") {
305            return Err(anyhow!("Amount string does not end with msat."));
306        }
307
308        let amount_string: &str = s[0..s.len() - 4].into();
309
310        let amount: i64 = match amount_string.parse::<i64>() {
311            Ok(v) => v,
312            Err(e) => return Err(anyhow!(e)),
313        };
314
315        Ok(Amount { msatoshi: amount })
316    }
317}
318
319fn _string_to_amount<'de, D>(deserializer: D) -> Result<Amount, D::Error>
320where
321    D: Deserializer<'de>,
322{
323    use serde::de::Error;
324    String::deserialize(deserializer).and_then(|string| {
325        Amount::from_string(&string).map_err(|_| Error::custom("could not parse amount"))
326    })
327}
328
329fn _amount_to_string<S>(amount: &Amount, serializer: S) -> Result<S::Ok, S::Error>
330where
331    S: Serializer,
332{
333    let s = format!("{}msat", amount.msatoshi);
334    serializer.serialize_str(&s)
335}
336
337/// PeerConnectedCall is the the message that is returned by the
338/// `peer_connected` hook.
339#[derive(Serialize, Deserialize, Debug)]
340pub struct PeerConnectedCall {
341    pub peer: Peer
342}
343
344#[derive(Serialize, Deserialize, Debug)]
345pub struct Peer {
346    pub id: String,
347    pub direction: Direction,
348    pub addr: String,
349    pub features: String,
350}
351
352#[derive(Serialize, Deserialize, Debug, PartialEq)]
353#[serde(rename_all = "snake_case")]
354pub enum Direction {
355    In,
356    Out
357}
358
359
360#[cfg(test)]
361mod test {
362    use super::*;
363
364    #[test]
365    fn test_peer_connected_call() {
366        let msg = json!({
367            "peer": {
368                "id": "03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f",
369                "direction": "in",
370                "addr": "34.239.230.56:9735",
371                "features": ""
372            }
373        });
374
375        let call = serde_json::from_str::<PeerConnectedCall>(&msg.to_string()).unwrap();
376        assert_eq!(call.peer.id, "03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f");
377        assert_eq!(call.peer.direction, Direction::In);
378        assert_eq!(call.peer.addr, "34.239.230.56:9735");
379        assert_eq!(call.peer.features, "");
380    }
381
382    #[test]
383    fn test_htlc_accepted_call() {
384        let req = json!({"id": 1, "jsonrpc": "2.0", "method": "htlc_accepted", "params": {
385            "onion": {
386              "payload": "",
387              "type": "legacy",
388        "short_channel_id": "1x2x3",
389              "forward_amount": "42msat",
390        "outgoing_cltv_value": 500014,
391        "shared_secret": "0000000000000000000000000000000000000000000000000000000000000000",
392        "next_onion": "00DEADBEEF00",
393            },
394            "htlc": {
395        "amount": "43msat",
396        "cltv_expiry": 500028,
397        "cltv_expiry_relative": 10,
398        "payment_hash": "0000000000000000000000000000000000000000000000000000000000000000"
399            }
400        }
401        });
402
403        type T = JsonRpc<MyNotifications, MyRequests>;
404        let req = serde_json::from_str::<T>(&req.to_string()).unwrap();
405        match req {
406            T::Request(id, c) => {
407                assert_eq!(id, 1);
408                match c {
409                    MyRequests::HtlcAccepted(c) => {
410                        //assert_eq!(c.onion.payload, "");
411                        assert_eq!(c.onion.forward_amount, "42msat");
412                        assert_eq!(c.onion.outgoing_cltv_value, 500014);
413                        //assert_eq!(c.onion.next_onion, "[1365bytes of serialized onion]");
414                        //assert_eq!(
415                        //    c.onion.shared_secret,
416                        //    "0000000000000000000000000000000000000000000000000000000000000000"
417                        //);
418                        //assert_eq!(
419                        //    c.htlc.payment_hash,
420                        //    "0000000000000000000000000000000000000000000000000000000000000000"
421                        //);
422                    }
423                    _ => panic!("This was supposed to be an htlc_accepted call"),
424                }
425            }
426            _ => panic!("This was supposed to be a request"),
427        }
428    }
429
430    /// We have a bit of trouble parsing some invoice payment hook
431    /// calls in 2024/06/03.
432    #[test]
433    fn test_invoice_payment_payload() {
434        let s = "{\"payment\": {\"extratlvs\": [], \"label\": \"{\\\"unix_milli\\\":1717422773673,\\\"payer_amount_msat\\\":null}\", \"msat\": \"42btc\", \"preimage\": \"243adf90767a5c3a8f6118e003c89b3e1ab5a2fd318d49cb41f4d42e92d3de41\"}}";
435        let v = serde_json::from_str(&s).expect("parsing generic value");
436        let _c: InvoicePaymentCall = serde_json::from_value(v).expect("parsing into struct");
437    }
438}