1use anyhow::ensure;
2use devimint::devfed::DevJitFed;
3use devimint::federation::Client;
4use devimint::version_constants::VERSION_0_7_0_ALPHA;
5use devimint::{Gatewayd, cmd, util};
6use fedimint_core::core::OperationId;
7use fedimint_core::util::{backoff_util, retry};
8use fedimint_lnv2_client::{FinalReceiveOperationState, FinalSendOperationState};
9use lightning_invoice::Bolt11Invoice;
10use substring::Substring;
11use tokio::try_join;
12use tracing::info;
13
14#[tokio::main]
15async fn main() -> anyhow::Result<()> {
16 devimint::run_devfed_test()
17 .call(|dev_fed, _process_mgr| async move {
18 if !devimint::util::supports_lnv2() {
19 info!("lnv2 is disabled, skipping");
20 return Ok(());
21 }
22
23 test_gateway_registration(&dev_fed).await?;
24 test_payments(&dev_fed).await?;
25
26 Ok(())
27 })
28 .await
29}
30
31async fn test_gateway_registration(dev_fed: &DevJitFed) -> anyhow::Result<()> {
32 let client = dev_fed
33 .fed()
34 .await?
35 .new_joined_client("lnv2-test-gateway-registration-client")
36 .await?;
37
38 let gw_lnd = dev_fed.gw_lnd().await?;
39 let gw_ldk = dev_fed.gw_ldk_connected().await?;
40
41 let gateways = [gw_lnd.addr.clone(), gw_ldk.addr.clone()];
42
43 info!("Testing registration of gateways...");
44
45 for gateway in &gateways {
46 for peer in 0..dev_fed.fed().await?.members.len() {
47 assert!(add_gateway(&client, peer, gateway).await?);
48 }
49 }
50
51 assert_eq!(
52 cmd!(client, "module", "lnv2", "gateways", "list")
53 .out_json()
54 .await?
55 .as_array()
56 .expect("JSON Value is not an array")
57 .len(),
58 2
59 );
60
61 assert_eq!(
62 cmd!(client, "module", "lnv2", "gateways", "list", "--peer", "0")
63 .out_json()
64 .await?
65 .as_array()
66 .expect("JSON Value is not an array")
67 .len(),
68 2
69 );
70
71 info!("Testing selection of gateways...");
72
73 assert!(
74 gateways.contains(
75 &cmd!(client, "module", "lnv2", "gateways", "select")
76 .out_json()
77 .await?
78 .as_str()
79 .expect("JSON Value is not a string")
80 .to_string()
81 )
82 );
83
84 cmd!(client, "module", "lnv2", "gateways", "map")
85 .out_json()
86 .await?;
87
88 for _ in 0..10 {
89 for gateway in &gateways {
90 let invoice = receive(&client, gateway, 1_000_000).await?.0;
91
92 assert_eq!(
93 cmd!(
94 client,
95 "module",
96 "lnv2",
97 "gateways",
98 "select",
99 "--invoice",
100 invoice.to_string()
101 )
102 .out_json()
103 .await?
104 .as_str()
105 .expect("JSON Value is not a string"),
106 gateway
107 )
108 }
109 }
110
111 info!("Testing deregistration of gateways...");
112
113 for gateway in &gateways {
114 for peer in 0..dev_fed.fed().await?.members.len() {
115 assert!(remove_gateway(&client, peer, gateway).await?);
116 }
117 }
118
119 assert!(
120 cmd!(client, "module", "lnv2", "gateways", "list")
121 .out_json()
122 .await?
123 .as_array()
124 .expect("JSON Value is not an array")
125 .is_empty(),
126 );
127
128 assert!(
129 cmd!(client, "module", "lnv2", "gateways", "list", "--peer", "0")
130 .out_json()
131 .await?
132 .as_array()
133 .expect("JSON Value is not an array")
134 .is_empty()
135 );
136
137 Ok(())
138}
139
140async fn test_payments(dev_fed: &DevJitFed) -> anyhow::Result<()> {
141 let federation = dev_fed.fed().await?;
142
143 let client = federation
144 .new_joined_client("lnv2-test-payments-client")
145 .await?;
146
147 federation.pegin_client(10_000, &client).await?;
148
149 assert_eq!(client.balance().await?, 10_000 * 1000);
150
151 let gw_lnd = dev_fed.gw_lnd().await?;
152 let gw_ldk = dev_fed.gw_ldk().await?;
153 let lnd = dev_fed.lnd().await?;
154
155 let (hold_preimage, hold_invoice, hold_payment_hash) = lnd.create_hold_invoice(60000).await?;
156
157 let gateway_pairs = [(gw_lnd, gw_ldk), (gw_ldk, gw_lnd)];
158
159 let gateway_matrix = [
160 (gw_lnd, gw_lnd),
161 (gw_lnd, gw_ldk),
162 (gw_ldk, gw_lnd),
163 (gw_ldk, gw_ldk),
164 ];
165
166 info!("Testing refund of circular payments...");
167
168 for (gw_send, gw_receive) in gateway_matrix {
169 info!(
170 "Testing refund of payment: client -> {} -> {} -> client",
171 gw_send.ln.ln_type(),
172 gw_receive.ln.ln_type()
173 );
174
175 let invoice = receive(&client, &gw_receive.addr, 1_000_000).await?.0;
176
177 test_send(
178 &client,
179 &gw_send.addr,
180 &invoice.to_string(),
181 FinalSendOperationState::Refunded,
182 )
183 .await?;
184 }
185
186 info!("Pegging-in gateways...");
187
188 federation
189 .pegin_gateways(1_000_000, vec![gw_lnd, gw_ldk])
190 .await?;
191
192 info!("Testing circular payments...");
193
194 for (gw_send, gw_receive) in gateway_matrix {
195 info!(
196 "Testing payment: client -> {} -> {} -> client",
197 gw_send.ln.ln_type(),
198 gw_receive.ln.ln_type()
199 );
200
201 let (invoice, receive_op) = receive(&client, &gw_receive.addr, 1_000_000).await?;
202
203 test_send(
204 &client,
205 &gw_send.addr,
206 &invoice.to_string(),
207 FinalSendOperationState::Success,
208 )
209 .await?;
210
211 await_receive_claimed(&client, receive_op).await?;
212 }
213
214 info!("Testing payments from client to gateways...");
215
216 for (gw_send, gw_receive) in gateway_pairs {
217 info!(
218 "Testing payment: client -> {} -> {}",
219 gw_send.ln.ln_type(),
220 gw_receive.ln.ln_type()
221 );
222
223 let invoice = gw_receive.create_invoice(1_000_000).await?;
224
225 test_send(
226 &client,
227 &gw_send.addr,
228 &invoice.to_string(),
229 FinalSendOperationState::Success,
230 )
231 .await?;
232 }
233
234 info!("Testing payments from gateways to client...");
235
236 for (gw_send, gw_receive) in gateway_pairs {
237 info!(
238 "Testing payment: {} -> {} -> client",
239 gw_send.ln.ln_type(),
240 gw_receive.ln.ln_type()
241 );
242
243 let (invoice, receive_op) = receive(&client, &gw_receive.addr, 1_000_000).await?;
244
245 gw_send.pay_invoice(invoice).await?;
246
247 await_receive_claimed(&client, receive_op).await?;
248 }
249
250 retry(
251 "Waiting for the full balance to become available to the client".to_string(),
252 backoff_util::background_backoff(),
253 || async {
254 ensure!(client.balance().await? >= 9000 * 1000);
255
256 Ok(())
257 },
258 )
259 .await?;
260
261 info!("Testing Client can pay LND HOLD invoice via LDK Gateway...");
262
263 try_join!(
264 test_send(
265 &client,
266 &gw_ldk.addr,
267 &hold_invoice,
268 FinalSendOperationState::Success
269 ),
270 lnd.settle_hold_invoice(hold_preimage, hold_payment_hash),
271 )?;
272
273 info!("Testing LNv2 lightning fees...");
274 let fed_id = federation.calculate_federation_id();
275 gw_lnd
276 .set_federation_routing_fee(fed_id.clone(), 0, 0)
277 .await?;
278 gw_lnd
279 .set_federation_transaction_fee(fed_id.clone(), 0, 0)
280 .await?;
281 test_fees(fed_id, &client, gw_lnd, gw_ldk, 1_000_000 - 1_000 - 1_000).await?;
284
285 let gatewayd_version = util::Gatewayd::version_or_default().await;
286 if gatewayd_version >= *VERSION_0_7_0_ALPHA {
287 info!("Testing payment summary...");
288 let lnd_payment_summary = gw_lnd.payment_summary().await?;
289 assert_eq!(lnd_payment_summary.outgoing.total_success, 4);
290 assert_eq!(lnd_payment_summary.outgoing.total_failure, 2);
291 assert_eq!(lnd_payment_summary.incoming.total_success, 3);
292 assert_eq!(lnd_payment_summary.incoming.total_failure, 0);
293 assert!(lnd_payment_summary.outgoing.median_latency.is_some());
294 assert!(lnd_payment_summary.outgoing.average_latency.is_some());
295 assert!(lnd_payment_summary.incoming.median_latency.is_some());
296 assert!(lnd_payment_summary.incoming.average_latency.is_some());
297
298 let ldk_payment_summary = gw_ldk.payment_summary().await?;
299 assert_eq!(ldk_payment_summary.outgoing.total_success, 4);
300 assert_eq!(ldk_payment_summary.outgoing.total_failure, 2);
301 assert_eq!(ldk_payment_summary.incoming.total_success, 4);
302 assert_eq!(ldk_payment_summary.incoming.total_failure, 0);
303 assert!(ldk_payment_summary.outgoing.median_latency.is_some());
304 assert!(ldk_payment_summary.outgoing.average_latency.is_some());
305 assert!(ldk_payment_summary.incoming.median_latency.is_some());
306 assert!(ldk_payment_summary.incoming.average_latency.is_some());
307 }
308
309 Ok(())
310}
311
312async fn test_fees(
313 fed_id: String,
314 client: &Client,
315 gw_lnd: &Gatewayd,
316 gw_ldk: &Gatewayd,
317 expected_addition: u64,
318) -> anyhow::Result<()> {
319 let gw_lnd_ecash_prev = gw_lnd.ecash_balance(fed_id.clone()).await?;
320 let (invoice, receive_op) = receive(client, &gw_ldk.addr, 1_000_000).await?;
321 test_send(
322 client,
323 &gw_lnd.addr,
324 &invoice.to_string(),
325 FinalSendOperationState::Success,
326 )
327 .await?;
328 await_receive_claimed(client, receive_op).await?;
329 let gw_lnd_ecash_after = gw_lnd.ecash_balance(fed_id.clone()).await?;
330 assert_eq!(gw_lnd_ecash_prev + expected_addition, gw_lnd_ecash_after);
331
332 Ok(())
333}
334
335async fn add_gateway(client: &Client, peer: usize, gateway: &String) -> anyhow::Result<bool> {
336 cmd!(
337 client,
338 "--our-id",
339 peer.to_string(),
340 "--password",
341 "pass",
342 "module",
343 "lnv2",
344 "gateways",
345 "add",
346 gateway
347 )
348 .out_json()
349 .await?
350 .as_bool()
351 .ok_or(anyhow::anyhow!("JSON Value is not a boolean"))
352}
353
354async fn remove_gateway(client: &Client, peer: usize, gateway: &String) -> anyhow::Result<bool> {
355 cmd!(
356 client,
357 "--our-id",
358 peer.to_string(),
359 "--password",
360 "pass",
361 "module",
362 "lnv2",
363 "gateways",
364 "remove",
365 gateway
366 )
367 .out_json()
368 .await?
369 .as_bool()
370 .ok_or(anyhow::anyhow!("JSON Value is not a boolean"))
371}
372
373async fn receive(
374 client: &Client,
375 gateway: &str,
376 amount: u64,
377) -> anyhow::Result<(Bolt11Invoice, OperationId)> {
378 Ok(serde_json::from_value::<(Bolt11Invoice, OperationId)>(
379 cmd!(
380 client,
381 "module",
382 "lnv2",
383 "receive",
384 amount,
385 "--gateway",
386 gateway
387 )
388 .out_json()
389 .await?,
390 )?)
391}
392
393async fn test_send(
394 client: &Client,
395 gateway: &String,
396 invoice: &String,
397 final_state: FinalSendOperationState,
398) -> anyhow::Result<()> {
399 let send_op = serde_json::from_value::<OperationId>(
400 cmd!(
401 client,
402 "module",
403 "lnv2",
404 "send",
405 invoice,
406 "--gateway",
407 gateway
408 )
409 .out_json()
410 .await?,
411 )?;
412
413 assert_eq!(
414 cmd!(
415 client,
416 "module",
417 "lnv2",
418 "await-send",
419 serde_json::to_string(&send_op)?.substring(1, 65)
420 )
421 .out_json()
422 .await?,
423 serde_json::to_value(final_state).expect("JSON serialization failed"),
424 );
425
426 Ok(())
427}
428
429async fn await_receive_claimed(client: &Client, operation_id: OperationId) -> anyhow::Result<()> {
430 assert_eq!(
431 cmd!(
432 client,
433 "module",
434 "lnv2",
435 "await-receive",
436 serde_json::to_string(&operation_id)?.substring(1, 65)
437 )
438 .out_json()
439 .await?,
440 serde_json::to_value(FinalReceiveOperationState::Claimed)
441 .expect("JSON serialization failed"),
442 );
443
444 Ok(())
445}