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}