cln_plugin/
messages.rs

1use crate::options::UntypedConfigOption;
2use serde::de::{self, Deserializer};
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::collections::HashMap;
6use std::fmt::Debug;
7
8#[derive(Deserialize, Debug)]
9#[serde(tag = "method", content = "params")]
10#[serde(rename_all = "snake_case")]
11pub(crate) enum Request {
12    // Builtin
13    Getmanifest(GetManifestCall),
14    Init(InitCall),
15    // Hooks
16    //     PeerConnected,
17    //     CommitmentRevocation,
18    //     DbWrite,
19    //     InvoicePayment,
20    //     Openchannel,
21    //     Openchannel2,
22    //     Openchannel2Changed,
23    //     Openchannel2Sign,
24    //     RbfChannel,
25    //     HtlcAccepted,
26    //     RpcCommand,
27    //     Custommsg,
28    //     OnionMessage,
29    //     OnionMessageBlinded,
30    //     OnionMessageOurpath,
31
32    // Bitcoin backend
33    //     Getchaininfo,
34    //     Estimatefees,
35    //     Getrawblockbyheight,
36    //     Getutxout,
37    //     Sendrawtransaction,
38}
39
40#[derive(Deserialize, Debug)]
41#[serde(tag = "method", content = "params")]
42#[serde(rename_all = "snake_case")]
43pub(crate) enum Notification {
44    //     ChannelOpened,
45    //     ChannelOpenFailed,
46    //     ChannelStateChanged,
47    //     Connect,
48    //     Disconnect,
49    //     InvoicePayment,
50    //     InvoiceCreation,
51    //     Warning,
52    //     ForwardEvent,
53    //     SendpaySuccess,
54    //     SendpayFailure,
55    //     CoinMovement,
56    //     OpenchannelPeerSigs,
57    //     Shutdown,
58}
59
60#[derive(Deserialize, Debug)]
61pub(crate) struct GetManifestCall {}
62
63#[derive(Deserialize, Debug)]
64pub(crate) struct InitCall {
65    pub(crate) options: HashMap<String, Value>,
66    pub configuration: Configuration,
67}
68
69#[derive(Clone, Deserialize, Debug)]
70pub struct Configuration {
71    #[serde(rename = "lightning-dir")]
72    pub lightning_dir: String,
73    #[serde(rename = "rpc-file")]
74    pub rpc_file: String,
75    pub startup: bool,
76    pub network: String,
77    pub feature_set: HashMap<String, String>,
78
79    // The proxy related options are only populated if a proxy was
80    // configured.
81    pub proxy: Option<ProxyInfo>,
82    #[serde(rename = "torv3-enabled")]
83    pub torv3_enabled: Option<bool>,
84    pub always_use_proxy: Option<bool>,
85}
86
87#[derive(Clone, Debug, Deserialize)]
88pub struct ProxyInfo {
89    #[serde(alias = "type")]
90    pub typ: String,
91    pub address: String,
92    pub port: i64,
93}
94
95#[derive(Debug)]
96pub(crate) enum JsonRpc<N, R> {
97    Request(serde_json::Value, R),
98    Notification(N),
99    CustomRequest(serde_json::Value, Value),
100    CustomNotification(Value),
101}
102
103/// This function disentangles the various cases:
104///
105///   1) If we have an `id` then it is a request
106///
107///   2) Otherwise it's a notification that doesn't require a
108///   response.
109///
110/// Furthermore we distinguish between the built-in types and the
111/// custom user notifications/methods:
112///
113///   1) We either match a built-in type above,
114///
115///   2) Or it's a custom one, so we pass it around just as a
116///   `serde_json::Value`
117impl<'de, N, R> Deserialize<'de> for JsonRpc<N, R>
118where
119    N: Deserialize<'de> + Debug,
120    R: Deserialize<'de> + Debug,
121{
122    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
123    where
124        D: Deserializer<'de>,
125    {
126        #[derive(Deserialize, Debug)]
127        struct IdHelper {
128            id: Option<serde_json::Value>,
129        }
130
131        let v = Value::deserialize(deserializer)?;
132        let helper = IdHelper::deserialize(&v).map_err(de::Error::custom)?;
133        match helper.id {
134            Some(id) => match R::deserialize(v.clone()) {
135                Ok(r) => Ok(JsonRpc::Request(id, r)),
136                Err(_) => Ok(JsonRpc::CustomRequest(id, v)),
137            },
138            None => match N::deserialize(v.clone()) {
139                Ok(n) => Ok(JsonRpc::Notification(n)),
140                Err(_) => Ok(JsonRpc::CustomNotification(v)),
141            },
142        }
143    }
144}
145
146#[derive(Serialize, Default, Debug)]
147pub(crate) struct RpcMethod {
148    pub(crate) name: String,
149    pub(crate) description: String,
150    pub(crate) usage: String,
151}
152
153#[derive(Serialize, Default, Debug, Clone)]
154pub struct NotificationTopic {
155    pub method: String,
156}
157
158impl NotificationTopic {
159    pub fn method(&self) -> &str {
160        &self.method
161    }
162}
163
164impl NotificationTopic {
165    pub fn new(method: &str) -> Self {
166        Self {
167            method: method.to_string(),
168        }
169    }
170}
171
172#[derive(Serialize, Default, Debug)]
173pub(crate) struct GetManifestResponse {
174    pub(crate) options: Vec<UntypedConfigOption>,
175    pub(crate) rpcmethods: Vec<RpcMethod>,
176    pub(crate) subscriptions: Vec<String>,
177    pub(crate) notifications: Vec<NotificationTopic>,
178    pub(crate) hooks: Vec<String>,
179    pub(crate) dynamic: bool,
180    pub(crate) featurebits: FeatureBits,
181    pub(crate) nonnumericids: bool,
182    #[serde(skip_serializing_if = "Vec::is_empty")]
183    pub(crate) custommessages: Vec<u16>,
184}
185
186#[derive(Serialize, Default, Debug, Clone)]
187pub(crate) struct FeatureBits {
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub node: Option<String>,
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub channel: Option<String>,
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub init: Option<String>,
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub invoice: Option<String>,
196}
197
198#[derive(Serialize, Default, Debug)]
199pub struct InitResponse {
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub disable: Option<String>,
202}
203
204pub trait Response: Serialize + Debug {}
205
206#[cfg(test)]
207mod test {
208    use super::*;
209    use crate::messages;
210    use serde_json::json;
211
212    #[test]
213    fn test_init_message_parsing() {
214        let value = json!({
215            "jsonrpc": "2.0",
216            "method": "init",
217            "params": {
218                "options": {
219                    "greeting": "World",
220                    "number": [0]
221                },
222                "configuration": {
223                    "lightning-dir": "/home/user/.lightning/testnet",
224                    "rpc-file": "lightning-rpc",
225                    "startup": true,
226                    "network": "testnet",
227                    "feature_set": {
228                        "init": "02aaa2",
229                        "node": "8000000002aaa2",
230                        "channel": "",
231                        "invoice": "028200"
232                    },
233                    "proxy": {
234                        "type": "ipv4",
235                        "address": "127.0.0.1",
236                        "port": 9050
237                    },
238                    "torv3-enabled": true,
239                    "always_use_proxy": false
240                }
241            },
242            "id": "10",
243        });
244        let req: JsonRpc<Notification, Request> = serde_json::from_value(value).unwrap();
245        match req {
246            messages::JsonRpc::Request(_, messages::Request::Init(init)) => {
247                assert_eq!(init.options["greeting"], "World");
248                assert_eq!(
249                    init.configuration.lightning_dir,
250                    String::from("/home/user/.lightning/testnet")
251                );
252            }
253            _ => panic!("Couldn't parse init message"),
254        }
255    }
256}