switchgear_testing/
credentials.rs

1use crate::services::{IntegrationTestServices, LightningIntegrationTestServices};
2use anyhow::Context;
3use flate2::read::GzDecoder;
4use secp256k1::PublicKey;
5use std::fs;
6use std::path::{Path, PathBuf};
7use tar::Archive;
8use tempfile::TempDir;
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub struct ClnRegTestLnNode {
12    pub public_key: PublicKey,
13    pub address: String,
14    pub ca_cert_path: PathBuf,
15    pub client_cert_path: PathBuf,
16    pub client_key_path: PathBuf,
17    pub sni: String,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq, Hash)]
21pub struct LndRegTestLnNode {
22    pub public_key: PublicKey,
23    pub address: String,
24    pub tls_cert_path: PathBuf,
25    pub macaroon_path: PathBuf,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Hash)]
29pub enum RegTestLnNode {
30    Cln(ClnRegTestLnNode),
31    Lnd(LndRegTestLnNode),
32}
33
34impl RegTestLnNode {
35    pub fn public_key(&self) -> &PublicKey {
36        match self {
37            RegTestLnNode::Cln(cln) => &cln.public_key,
38            RegTestLnNode::Lnd(lnd) => &lnd.public_key,
39        }
40    }
41
42    pub fn address(&self) -> &str {
43        match self {
44            RegTestLnNode::Cln(cln) => &cln.address,
45            RegTestLnNode::Lnd(lnd) => &lnd.address,
46        }
47    }
48
49    pub fn kind(&self) -> &'static str {
50        match self {
51            RegTestLnNode::Cln(_) => "cln",
52            RegTestLnNode::Lnd(_) => "lnd",
53        }
54    }
55}
56
57#[derive(Copy, Clone, Debug)]
58pub enum RegTestLnNodeType {
59    Cln,
60    Lnd,
61}
62
63pub struct LnCredentials {
64    inner: Option<LnCredentialsInner>,
65}
66
67struct LnCredentialsInner {
68    credentials_dir: TempDir,
69    lightning: LightningIntegrationTestServices,
70}
71
72impl LnCredentials {
73    pub fn create() -> anyhow::Result<Self> {
74        let services = IntegrationTestServices::create()?;
75        let inner = match services.lightning() {
76            None => None,
77            Some(lightning) => {
78                let credentials_dir = TempDir::new()?;
79                Self::download_credentials(credentials_dir.path(), &lightning.credentials)?;
80                Some(LnCredentialsInner {
81                    credentials_dir,
82                    lightning: lightning.clone(),
83                })
84            }
85        };
86        Ok(Self { inner })
87    }
88
89    fn download_credentials(credentials_dir: &Path, credentials_url: &str) -> anyhow::Result<()> {
90        let download_path = credentials_dir.join("credentials.tar.gz");
91        let response = ureq::get(credentials_url)
92            .call()
93            .with_context(|| format!("Downloading credentials from {}", credentials_url))?;
94
95        let bytes = response
96            .into_body()
97            .read_to_vec()
98            .with_context(|| format!("Downloading credentials from {}", credentials_url))?;
99
100        fs::write(&download_path, &bytes)
101            .with_context(|| format!("Downloading credentials from {}", credentials_url))?;
102
103        let tar_gz = fs::File::open(&download_path)
104            .with_context(|| format!("Downloading credentials from {}", credentials_url))?;
105
106        let tar = GzDecoder::new(tar_gz);
107        let mut archive = Archive::new(tar);
108        archive
109            .unpack(credentials_dir)
110            .with_context(|| format!("Downloading credentials from {}", credentials_url))?;
111        Ok(())
112    }
113
114    pub fn get_backends(&self) -> anyhow::Result<Vec<RegTestLnNode>> {
115        let inner = match &self.inner {
116            None => return Ok(vec![]),
117            Some(inner) => inner,
118        };
119
120        let credentials = inner.credentials_dir.path().join("credentials");
121        let base_path = credentials.as_path();
122
123        let entries = fs::read_dir(base_path)
124            .with_context(|| format!("reading directory {}", base_path.display()))?;
125
126        let mut backends = Vec::new();
127
128        for entry in entries {
129            let entry = entry
130                .with_context(|| format!("reading directory entry in {}", base_path.display(),))?;
131
132            let path = entry.path();
133
134            if !path.is_dir() {
135                continue;
136            }
137
138            let dir_name = match path.file_name() {
139                Some(name) => match name.to_str() {
140                    Some(s) => s,
141                    None => continue,
142                },
143                None => continue,
144            };
145
146            let node_type = if dir_name.starts_with("cln") {
147                RegTestLnNodeType::Cln
148            } else if dir_name.starts_with("lnd") {
149                RegTestLnNodeType::Lnd
150            } else {
151                continue;
152            };
153
154            let node_id_path = path.join("node_id");
155            let node_id_str = fs::read_to_string(&node_id_path)
156                .with_context(|| format!("reading node ID from {}", node_id_path.display(),))?;
157
158            let node_id_hex = node_id_str.trim();
159            let node_id_bytes = hex::decode(node_id_hex)
160                .with_context(|| format!("decoding {} node ID to hex", node_id_path.display(),))?;
161
162            let public_key = PublicKey::from_slice(&node_id_bytes).with_context(|| {
163                format!("parsing {} public key from bytes", node_id_path.display(),)
164            })?;
165
166            let node = match node_type {
167                RegTestLnNodeType::Cln => RegTestLnNode::Cln(Self::build_cln_node(
168                    public_key,
169                    &inner.lightning.cln,
170                    &path,
171                )?),
172                RegTestLnNodeType::Lnd => RegTestLnNode::Lnd(Self::build_lnd_node(
173                    public_key,
174                    &inner.lightning.lnd,
175                    &path,
176                )?),
177            };
178
179            backends.push(node);
180        }
181
182        Ok(backends)
183    }
184
185    fn build_cln_node(
186        public_key: PublicKey,
187        address: &str,
188        node_path: &Path,
189    ) -> anyhow::Result<ClnRegTestLnNode> {
190        let ca_cert_path = node_path.join("ca.pem");
191        let ca_cert_path = ca_cert_path.canonicalize().with_context(|| {
192            format!("canonicalizing CLN CA cert path {}", ca_cert_path.display(),)
193        })?;
194        let client_cert_path = node_path.join("client.pem");
195        let client_cert_path = client_cert_path.canonicalize().with_context(|| {
196            format!(
197                "canonicalizing CLN client cert path {}",
198                client_cert_path.display(),
199            )
200        })?;
201        let client_key_path = node_path.join("client-key.pem");
202        let client_key_path = client_key_path.canonicalize().with_context(|| {
203            format!(
204                "canonicalizing CLN client key path {}",
205                client_key_path.display(),
206            )
207        })?;
208
209        Ok(ClnRegTestLnNode {
210            public_key,
211            address: address.to_string(),
212            ca_cert_path,
213            client_cert_path,
214            client_key_path,
215            sni: "localhost".to_string(),
216        })
217    }
218
219    fn build_lnd_node(
220        public_key: PublicKey,
221        address: &str,
222        node_path: &Path,
223    ) -> anyhow::Result<LndRegTestLnNode> {
224        let tls_cert_path = node_path.join("tls.cert");
225        let tls_cert_path = tls_cert_path.canonicalize().with_context(|| {
226            format!(
227                "canonicalizing LND TLS cert path {}",
228                tls_cert_path.display(),
229            )
230        })?;
231        let macaroon_path = node_path.join("admin.macaroon");
232        let macaroon_path = macaroon_path.canonicalize().with_context(|| {
233            format!(
234                "canonicalizing LND macaroon path {}",
235                macaroon_path.display(),
236            )
237        })?;
238
239        Ok(LndRegTestLnNode {
240            public_key,
241            address: address.to_string(),
242            tls_cert_path,
243            macaroon_path,
244        })
245    }
246}