fedimint_lnv2_devimint_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    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    // Gateway pays: 1_000 msat LNv2 federation base fee, 1_000 msat LNv2 federation
282    // relative fee. Gateway receives: 1_000_000 payment.
283    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}