lnv2_module_tests/
tests.rs

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
275    let fed_id = federation.calculate_federation_id();
276
277    gw_lnd
278        .set_federation_routing_fee(fed_id.clone(), 0, 0)
279        .await?;
280
281    gw_lnd
282        .set_federation_transaction_fee(fed_id.clone(), 0, 0)
283        .await?;
284
285    // Gateway pays: 1_000 msat LNv2 federation base fee, 100 msat LNv2 federation
286    // relative fee. Gateway receives: 1_000_000 payment.
287    test_fees(fed_id, &client, gw_lnd, gw_ldk, 1_000_000 - 1_000 - 100).await?;
288
289    let gatewayd_version = util::Gatewayd::version_or_default().await;
290
291    if gatewayd_version >= *VERSION_0_7_0_ALPHA {
292        info!("Testing payment summary...");
293
294        let lnd_payment_summary = gw_lnd.payment_summary().await?;
295
296        assert_eq!(lnd_payment_summary.outgoing.total_success, 4);
297        assert_eq!(lnd_payment_summary.outgoing.total_failure, 2);
298        assert_eq!(lnd_payment_summary.incoming.total_success, 3);
299        assert_eq!(lnd_payment_summary.incoming.total_failure, 0);
300
301        assert!(lnd_payment_summary.outgoing.median_latency.is_some());
302        assert!(lnd_payment_summary.outgoing.average_latency.is_some());
303        assert!(lnd_payment_summary.incoming.median_latency.is_some());
304        assert!(lnd_payment_summary.incoming.average_latency.is_some());
305
306        let ldk_payment_summary = gw_ldk.payment_summary().await?;
307
308        assert_eq!(ldk_payment_summary.outgoing.total_success, 4);
309        assert_eq!(ldk_payment_summary.outgoing.total_failure, 2);
310        assert_eq!(ldk_payment_summary.incoming.total_success, 4);
311        assert_eq!(ldk_payment_summary.incoming.total_failure, 0);
312
313        assert!(ldk_payment_summary.outgoing.median_latency.is_some());
314        assert!(ldk_payment_summary.outgoing.average_latency.is_some());
315        assert!(ldk_payment_summary.incoming.median_latency.is_some());
316        assert!(ldk_payment_summary.incoming.average_latency.is_some());
317    }
318
319    Ok(())
320}
321
322async fn test_fees(
323    fed_id: String,
324    client: &Client,
325    gw_lnd: &Gatewayd,
326    gw_ldk: &Gatewayd,
327    expected_addition: u64,
328) -> anyhow::Result<()> {
329    let gw_lnd_ecash_prev = gw_lnd.ecash_balance(fed_id.clone()).await?;
330
331    let (invoice, receive_op) = receive(client, &gw_ldk.addr, 1_000_000).await?;
332
333    test_send(
334        client,
335        &gw_lnd.addr,
336        &invoice.to_string(),
337        FinalSendOperationState::Success,
338    )
339    .await?;
340
341    await_receive_claimed(client, receive_op).await?;
342
343    let gw_lnd_ecash_after = gw_lnd.ecash_balance(fed_id.clone()).await?;
344
345    assert_eq!(gw_lnd_ecash_prev + expected_addition, gw_lnd_ecash_after);
346
347    Ok(())
348}
349
350async fn add_gateway(client: &Client, peer: usize, gateway: &String) -> anyhow::Result<bool> {
351    cmd!(
352        client,
353        "--our-id",
354        peer.to_string(),
355        "--password",
356        "pass",
357        "module",
358        "lnv2",
359        "gateways",
360        "add",
361        gateway
362    )
363    .out_json()
364    .await?
365    .as_bool()
366    .ok_or(anyhow::anyhow!("JSON Value is not a boolean"))
367}
368
369async fn remove_gateway(client: &Client, peer: usize, gateway: &String) -> anyhow::Result<bool> {
370    cmd!(
371        client,
372        "--our-id",
373        peer.to_string(),
374        "--password",
375        "pass",
376        "module",
377        "lnv2",
378        "gateways",
379        "remove",
380        gateway
381    )
382    .out_json()
383    .await?
384    .as_bool()
385    .ok_or(anyhow::anyhow!("JSON Value is not a boolean"))
386}
387
388async fn receive(
389    client: &Client,
390    gateway: &str,
391    amount: u64,
392) -> anyhow::Result<(Bolt11Invoice, OperationId)> {
393    Ok(serde_json::from_value::<(Bolt11Invoice, OperationId)>(
394        cmd!(
395            client,
396            "module",
397            "lnv2",
398            "receive",
399            amount,
400            "--gateway",
401            gateway
402        )
403        .out_json()
404        .await?,
405    )?)
406}
407
408async fn test_send(
409    client: &Client,
410    gateway: &String,
411    invoice: &String,
412    final_state: FinalSendOperationState,
413) -> anyhow::Result<()> {
414    let send_op = serde_json::from_value::<OperationId>(
415        cmd!(
416            client,
417            "module",
418            "lnv2",
419            "send",
420            invoice,
421            "--gateway",
422            gateway
423        )
424        .out_json()
425        .await?,
426    )?;
427
428    assert_eq!(
429        cmd!(
430            client,
431            "module",
432            "lnv2",
433            "await-send",
434            serde_json::to_string(&send_op)?.substring(1, 65)
435        )
436        .out_json()
437        .await?,
438        serde_json::to_value(final_state).expect("JSON serialization failed"),
439    );
440
441    Ok(())
442}
443
444async fn await_receive_claimed(client: &Client, operation_id: OperationId) -> anyhow::Result<()> {
445    assert_eq!(
446        cmd!(
447            client,
448            "module",
449            "lnv2",
450            "await-receive",
451            serde_json::to_string(&operation_id)?.substring(1, 65)
452        )
453        .out_json()
454        .await?,
455        serde_json::to_value(FinalReceiveOperationState::Claimed)
456            .expect("JSON serialization failed"),
457    );
458
459    Ok(())
460}