Skip to main content

cln_plugin/
messages.rs

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