1#![deny(clippy::pedantic)]
2
3use std::collections::BTreeMap;
4use std::env;
5use std::fs::remove_dir_all;
6use std::ops::ControlFlow;
7use std::path::PathBuf;
8use std::str::FromStr;
9use std::time::Duration;
10
11use anyhow::ensure;
12use clap::{Parser, Subcommand};
13use devimint::envs::FM_DATA_DIR_ENV;
14use devimint::federation::Federation;
15use devimint::util::{ProcessManager, poll_with_timeout};
16use devimint::version_constants::{VERSION_0_5_0_ALPHA, VERSION_0_6_0_ALPHA, VERSION_0_7_0_ALPHA};
17use devimint::{Gatewayd, LightningNode, cmd, util};
18use fedimint_core::config::FederationId;
19use fedimint_core::time::now;
20use fedimint_core::util::backoff_util::aggressive_backoff_long;
21use fedimint_core::util::retry;
22use fedimint_core::{Amount, BitcoinAmountOrAll};
23use fedimint_gateway_common::{
24 FederationInfo, GatewayBalances, GatewayFedConfig, PaymentDetails, PaymentKind, PaymentStatus,
25};
26use fedimint_logging::LOG_TEST;
27use fedimint_testing_core::node_type::LightningNodeType;
28use itertools::Itertools;
29use tracing::{debug, info, warn};
30
31#[derive(Parser)]
32struct GatewayTestOpts {
33 #[clap(subcommand)]
34 test: GatewayTest,
35}
36
37#[derive(Debug, Clone, Subcommand)]
38enum GatewayTest {
39 ConfigTest {
40 #[arg(long = "gw-type")]
41 gateway_type: LightningNodeType,
42 },
43 GatewaydMnemonic {
44 #[arg(long)]
45 old_gatewayd_path: PathBuf,
46 #[arg(long)]
47 new_gatewayd_path: PathBuf,
48 #[arg(long)]
49 old_gateway_cli_path: PathBuf,
50 #[arg(long)]
51 new_gateway_cli_path: PathBuf,
52 },
53 BackupRestoreTest,
54 LiquidityTest,
55}
56
57#[tokio::main]
58async fn main() -> anyhow::Result<()> {
59 let opts = GatewayTestOpts::parse();
60 match opts.test {
61 GatewayTest::ConfigTest { gateway_type } => Box::pin(config_test(gateway_type)).await,
62 GatewayTest::GatewaydMnemonic {
63 old_gatewayd_path,
64 new_gatewayd_path,
65 old_gateway_cli_path,
66 new_gateway_cli_path,
67 } => {
68 mnemonic_upgrade_test(
69 old_gatewayd_path,
70 new_gatewayd_path,
71 old_gateway_cli_path,
72 new_gateway_cli_path,
73 )
74 .await
75 }
76 GatewayTest::BackupRestoreTest => Box::pin(backup_restore_test()).await,
77 GatewayTest::LiquidityTest => Box::pin(liquidity_test()).await,
78 }
79}
80
81async fn backup_restore_test() -> anyhow::Result<()> {
82 Box::pin(
83 devimint::run_devfed_test().call(|dev_fed, process_mgr| async move {
84 let gatewayd_version = util::Gatewayd::version_or_default().await;
85 if gatewayd_version < *VERSION_0_5_0_ALPHA {
86 warn!(target: LOG_TEST, "Gateway backup-restore is not supported below v0.5.0");
87 return Ok(());
88 }
89
90 let gw = if devimint::util::supports_lnv2() {
91 dev_fed.gw_ldk_connected().await?
92 } else {
93 dev_fed.gw_lnd_registered().await?
94 };
95
96 let fed = dev_fed.fed().await?;
97 fed.pegin_gateways(10_000_000, vec![gw]).await?;
98
99 let mnemonic = gw.get_mnemonic().await?.mnemonic;
100
101 info!(target: LOG_TEST, "Wiping gateway and recovering without a backup...");
103 let ln = gw.ln.clone();
104 let new_gw = stop_and_recover_gateway(
105 process_mgr.clone(),
106 mnemonic.clone(),
107 gw.to_owned(),
108 ln.clone(),
109 fed,
110 )
111 .await?;
112
113 info!(target: LOG_TEST, "Wiping gateway and recovering with a backup...");
115 info!(target: LOG_TEST, "Creating backup...");
116 new_gw.backup_to_fed(fed).await?;
117 stop_and_recover_gateway(process_mgr, mnemonic, new_gw, ln, fed).await?;
118
119 info!(target: LOG_TEST, "backup_restore_test successful");
120 Ok(())
121 }),
122 )
123 .await
124}
125
126async fn stop_and_recover_gateway(
127 process_mgr: ProcessManager,
128 mnemonic: Vec<String>,
129 old_gw: Gatewayd,
130 new_ln: LightningNode,
131 fed: &Federation,
132) -> anyhow::Result<Gatewayd> {
133 let gateway_balances =
134 serde_json::from_value::<GatewayBalances>(cmd!(old_gw, "get-balances").out_json().await?)?;
135 let before_onchain_balance = gateway_balances.onchain_balance_sats;
136
137 let gw_type = old_gw.ln.ln_type();
139 let gw_name = old_gw.gw_name.clone();
140 old_gw.terminate().await?;
141 info!(target: LOG_TEST, "Terminated Gateway");
142
143 let data_dir: PathBuf = env::var(FM_DATA_DIR_ENV)
145 .expect("Data dir is not set")
146 .parse()
147 .expect("Could not parse data dir");
148 let gw_db = data_dir.join(gw_name.clone()).join("gatewayd.db");
149 remove_dir_all(gw_db)?;
150 info!(target: LOG_TEST, "Deleted the Gateway's database");
151
152 if gw_type == LightningNodeType::Ldk {
153 let ldk_data_dir = data_dir.join(gw_name).join("ldk_node");
155 remove_dir_all(ldk_data_dir)?;
156 info!(target: LOG_TEST, "Deleted LDK's database");
157 }
158
159 let seed = mnemonic.join(" ");
160 unsafe { std::env::set_var("FM_GATEWAY_MNEMONIC", seed) };
162 let new_gw = Gatewayd::new(&process_mgr, new_ln).await?;
163 let new_mnemonic = new_gw.get_mnemonic().await?.mnemonic;
164 assert_eq!(mnemonic, new_mnemonic);
165 info!(target: LOG_TEST, "Verified mnemonic is the same after creating new Gateway");
166
167 let federations = serde_json::from_value::<Vec<FederationInfo>>(
168 new_gw.get_info().await?["federations"].clone(),
169 )?;
170 assert_eq!(0, federations.len());
171 info!(target: LOG_TEST, "Verified new Gateway has no federations");
172
173 new_gw.recover_fed(fed).await?;
174
175 let gateway_balances =
176 serde_json::from_value::<GatewayBalances>(cmd!(new_gw, "get-balances").out_json().await?)?;
177 let ecash_balance = gateway_balances
178 .ecash_balances
179 .first()
180 .expect("Should have one joined federation");
181 assert_eq!(
182 10_000_000,
183 ecash_balance.ecash_balance_msats.sats_round_down()
184 );
185 let after_onchain_balance = gateway_balances.onchain_balance_sats;
186 assert_eq!(before_onchain_balance, after_onchain_balance);
187 info!(target: LOG_TEST, "Verified balances after recovery");
188
189 Ok(new_gw)
190}
191
192async fn mnemonic_upgrade_test(
196 old_gatewayd_path: PathBuf,
197 new_gatewayd_path: PathBuf,
198 old_gateway_cli_path: PathBuf,
199 new_gateway_cli_path: PathBuf,
200) -> anyhow::Result<()> {
201 unsafe { std::env::set_var("FM_GATEWAYD_BASE_EXECUTABLE", old_gatewayd_path) };
203 unsafe { std::env::set_var("FM_GATEWAY_CLI_BASE_EXECUTABLE", old_gateway_cli_path) };
205 unsafe { std::env::set_var("FM_ENABLE_MODULE_LNV2", "0") };
207
208 devimint::run_devfed_test()
209 .call(|dev_fed, process_mgr| async move {
210 let gatewayd_version = util::Gatewayd::version_or_default().await;
211 let gateway_cli_version = util::GatewayCli::version_or_default().await;
212 info!(
213 target: LOG_TEST,
214 gatewayd_version = %gatewayd_version,
215 gateway_cli_version = %gateway_cli_version,
216 "Running gatewayd mnemonic test"
217 );
218
219 let mut gw_lnd = dev_fed.gw_lnd_registered().await?.to_owned();
220 let fed = dev_fed.fed().await?;
221 let federation_id = FederationId::from_str(fed.calculate_federation_id().as_str())?;
222
223 gw_lnd
224 .restart_with_bin(&process_mgr, &new_gatewayd_path, &new_gateway_cli_path)
225 .await?;
226
227 let new_gatewayd_version = util::Gatewayd::version_or_default().await;
229 if new_gatewayd_version < *VERSION_0_5_0_ALPHA {
230 warn!(target: LOG_TEST, "Gateway mnemonic test is not supported below v0.5.0");
231 return Ok(());
232 }
233
234 let mnemonic_response = gw_lnd.get_mnemonic().await?;
236 assert!(
237 mnemonic_response
238 .legacy_federations
239 .contains(&federation_id)
240 );
241
242 info!(target: LOG_TEST, "Verified a legacy federation exists");
243
244 gw_lnd.leave_federation(federation_id).await?;
246
247 gw_lnd.connect_fed(fed).await?;
249
250 let mnemonic_response = gw_lnd.get_mnemonic().await?;
252 assert!(
253 mnemonic_response
254 .legacy_federations
255 .contains(&federation_id)
256 );
257 assert_eq!(mnemonic_response.legacy_federations.len(), 1);
258
259 info!(target: LOG_TEST, "Verified leaving and re-joining preservers legacy federation");
260
261 gw_lnd.leave_federation(federation_id).await?;
263
264 let data_dir: PathBuf = env::var(FM_DATA_DIR_ENV)
265 .expect("Data dir is not set")
266 .parse()
267 .expect("Could not parse data dir");
268 let gw_fed_db = data_dir
269 .join(gw_lnd.gw_name.clone())
270 .join(format!("{federation_id}.db"));
271 remove_dir_all(gw_fed_db)?;
272
273 gw_lnd.connect_fed(fed).await?;
274
275 let mnemonic_response = gw_lnd.get_mnemonic().await?;
277 assert!(
278 !mnemonic_response
279 .legacy_federations
280 .contains(&federation_id)
281 );
282 assert_eq!(mnemonic_response.legacy_federations.len(), 0);
283
284 info!(target: LOG_TEST, "Verified deleting database will migrate the federation to use mnemonic");
285
286 info!(target: LOG_TEST, "Successfully completed mnemonic upgrade test");
287
288 Ok(())
289 })
290 .await
291}
292
293#[allow(clippy::too_many_lines)]
295async fn config_test(gw_type: LightningNodeType) -> anyhow::Result<()> {
296 Box::pin(
297 devimint::run_devfed_test()
298 .num_feds(2)
299 .call(|dev_fed, process_mgr| async move {
300 let gatewayd_version = util::Gatewayd::version_or_default().await;
301 if gatewayd_version < *VERSION_0_5_0_ALPHA && gw_type == LightningNodeType::Ldk {
302 return Ok(());
303 }
304
305 let gw = match gw_type {
306 LightningNodeType::Lnd => dev_fed.gw_lnd_registered().await?,
307 LightningNodeType::Ldk => dev_fed.gw_ldk_connected().await?,
308 };
309
310 let invite_code = dev_fed.fed().await?.invite_code()?;
312 let output = cmd!(gw, "connect-fed", invite_code.clone())
313 .out_json()
314 .await;
315 assert!(
316 output.is_err(),
317 "Connecting to the same federation succeeded"
318 );
319 info!(target: LOG_TEST, "Verified that gateway couldn't connect to already connected federation");
320
321 let gatewayd_version = util::Gatewayd::version_or_default().await;
322
323 let fed_id = dev_fed.fed().await?.calculate_federation_id();
325 gw.set_federation_routing_fee(fed_id.clone(), 20, 20000)
326 .await?;
327
328 let lightning_fee = gw.get_lightning_fee(fed_id.clone()).await?;
329 assert_eq!(
330 lightning_fee.base.msats, 20,
331 "Federation base msat is not 20"
332 );
333 assert_eq!(
334 lightning_fee.parts_per_million, 20000,
335 "Federation proportional millionths is not 20000"
336 );
337 info!(target: LOG_TEST, "Verified per-federation routing fees changed");
338
339 let info_value = cmd!(gw, "info").out_json().await?;
340 let federations = info_value["federations"]
341 .as_array()
342 .expect("federations is an array");
343 assert_eq!(
344 federations.len(),
345 1,
346 "Gateway did not have one connected federation"
347 );
348
349 if gatewayd_version >= *VERSION_0_5_0_ALPHA
352 && gatewayd_version < *VERSION_0_6_0_ALPHA
353 {
354 let config_val = cmd!(gw, "config", "--federation-id", fed_id)
356 .out_json()
357 .await?;
358 serde_json::from_value::<GatewayFedConfig>(config_val)?;
359 } else if gatewayd_version >= *VERSION_0_6_0_ALPHA {
360 let config_val = cmd!(gw, "cfg", "client-config", "--federation-id", fed_id)
362 .out_json()
363 .await?;
364 serde_json::from_value::<GatewayFedConfig>(config_val)?;
365 }
366
367 let bitcoind = dev_fed.bitcoind().await?;
369 let new_fed = Federation::new(
370 &process_mgr,
371 bitcoind.clone(),
372 false,
373 false,
374 1,
375 "config-test".to_string(),
376 )
377 .await?;
378 let new_fed_id = new_fed.calculate_federation_id();
379 info!(target: LOG_TEST, "Successfully spawned new federation");
380
381 let new_invite_code = new_fed.invite_code()?;
382 cmd!(gw, "connect-fed", new_invite_code.clone())
383 .out_json()
384 .await?;
385
386 let (default_base, default_ppm) = if gatewayd_version >= *VERSION_0_6_0_ALPHA {
387 (50000, 5000)
388 } else {
389 (0, 10000)
390 };
391
392 let lightning_fee = gw.get_lightning_fee(new_fed_id.clone()).await?;
393 assert_eq!(
394 lightning_fee.base.msats, default_base,
395 "Default Base msat for new federation was not correct"
396 );
397 assert_eq!(
398 lightning_fee.parts_per_million, default_ppm,
399 "Default Base msat for new federation was not correct"
400 );
401
402 info!(target: LOG_TEST, federation_id = %new_fed_id, "Verified new federation");
403
404 let pegin_amount = Amount::from_msats(10_000_000);
406 new_fed
407 .pegin_gateways(pegin_amount.sats_round_down(), vec![gw])
408 .await?;
409
410 let info_value = cmd!(gw, "info").out_json().await?;
412 let federations = info_value["federations"]
413 .as_array()
414 .expect("federations is an array");
415
416 assert_eq!(
417 federations.len(),
418 2,
419 "Gateway did not have two connected federations"
420 );
421
422 let federation_fake_scids =
423 serde_json::from_value::<Option<BTreeMap<u64, FederationId>>>(
424 info_value
425 .get("channels")
426 .or_else(|| info_value.get("federation_fake_scids"))
427 .expect("field exists")
428 .to_owned(),
429 )
430 .expect("cannot parse")
431 .expect("should have scids");
432
433 assert_eq!(
434 federation_fake_scids.keys().copied().collect::<Vec<u64>>(),
435 vec![1, 2]
436 );
437
438 let first_fed_info = federations
439 .iter()
440 .find(|i| {
441 *i["federation_id"]
442 .as_str()
443 .expect("should parse as str")
444 .to_string()
445 == fed_id
446 })
447 .expect("Could not find federation");
448
449 let second_fed_info = federations
450 .iter()
451 .find(|i| {
452 *i["federation_id"]
453 .as_str()
454 .expect("should parse as str")
455 .to_string()
456 == new_fed_id
457 })
458 .expect("Could not find federation");
459
460 let first_fed_balance_msat =
461 serde_json::from_value::<Amount>(first_fed_info["balance_msat"].clone())
462 .expect("fed should have balance");
463
464 let second_fed_balance_msat =
465 serde_json::from_value::<Amount>(second_fed_info["balance_msat"].clone())
466 .expect("fed should have balance");
467
468 assert_eq!(first_fed_balance_msat, Amount::ZERO);
469 assert_eq!(second_fed_balance_msat, pegin_amount);
470
471 leave_federation(gw, fed_id, 1).await?;
472 leave_federation(gw, new_fed_id, 2).await?;
473
474 let output = cmd!(gw, "connect-fed", new_invite_code.clone())
476 .out_json()
477 .await?;
478 let rejoined_federation_balance_msat =
479 serde_json::from_value::<Amount>(output["balance_msat"].clone())
480 .expect("fed has balance");
481
482 assert_eq!(second_fed_balance_msat, rejoined_federation_balance_msat);
483
484 info!(target: LOG_TEST, "Gateway configuration test successful");
485 Ok(())
486 }),
487 )
488 .await
489}
490
491#[allow(clippy::too_many_lines)]
494async fn liquidity_test() -> anyhow::Result<()> {
495 devimint::run_devfed_test()
496 .call(|dev_fed, _process_mgr| async move {
497 let federation = dev_fed.fed().await?;
498
499 if !devimint::util::supports_lnv2() {
500 info!(target: LOG_TEST, "LNv2 is not supported, which is necessary for LDK GW and liquidity test");
501 return Ok(());
502 }
503
504 let gw_lnd = dev_fed.gw_lnd_registered().await?;
505 let gw_ldk = dev_fed.gw_ldk_connected().await?;
506 let gw_ldk_second = dev_fed.gw_ldk_second_connected().await?;
507 let gateways = [gw_lnd, gw_ldk].to_vec();
508
509 let gateway_matrix = gateways
510 .iter()
511 .cartesian_product(gateways.iter())
512 .filter(|(a, b)| a.ln.ln_type() != b.ln.ln_type());
513
514 info!(target: LOG_TEST, "Pegging-in gateways...");
515 federation
516 .pegin_gateways(1_000_000, gateways.clone())
517 .await?;
518
519 info!(target: LOG_TEST, "Testing ecash payments between gateways...");
520 for (gw_send, gw_receive) in gateway_matrix.clone() {
521 info!(
522 target: LOG_TEST,
523 gw_send = %gw_send.ln.ln_type(),
524 gw_receive = %gw_receive.ln.ln_type(),
525 "Testing ecash payment",
526 );
527
528 let fed_id = federation.calculate_federation_id();
529 let prev_send_ecash_balance = gw_send.ecash_balance(fed_id.clone()).await?;
530 let prev_receive_ecash_balance = gw_receive.ecash_balance(fed_id.clone()).await?;
531 let ecash = gw_send.send_ecash(fed_id.clone(), 500_000).await?;
532 gw_receive.receive_ecash(ecash).await?;
533 let after_send_ecash_balance = gw_send.ecash_balance(fed_id.clone()).await?;
534 let after_receive_ecash_balance = gw_receive.ecash_balance(fed_id.clone()).await?;
535 assert_eq!(prev_send_ecash_balance - 500_000, after_send_ecash_balance);
536 assert_eq!(
537 prev_receive_ecash_balance + 500_000,
538 after_receive_ecash_balance
539 );
540 }
541
542 info!(target: LOG_TEST, "Testing payments between gateways...");
543 for (gw_send, gw_receive) in gateway_matrix.clone() {
544 info!(
545 target: LOG_TEST,
546 gw_send = %gw_send.ln.ln_type(),
547 gw_receive = %gw_receive.ln.ln_type(),
548 "Testing lightning payment",
549 );
550
551 let invoice = gw_receive.create_invoice(1_000_000).await?;
552 gw_send.pay_invoice(invoice).await?;
553 }
554
555 if devimint::util::Gatewayd::version_or_default().await >= *VERSION_0_7_0_ALPHA {
556 let start = now() - Duration::from_secs(5 * 60);
557 let end = now() + Duration::from_secs(5 * 60);
558 info!(target: LOG_TEST, "Verifying list of transactions");
559 let lnd_transactions = gw_lnd.list_transactions(start, end).await?;
560 assert_eq!(lnd_transactions.len(), 2);
562
563 let ldk_transactions = gw_ldk.list_transactions(start, end).await?;
564 assert_eq!(ldk_transactions.len(), 2);
565
566 let start = now() - Duration::from_secs(10 * 60);
568 let end = now() - Duration::from_secs(5 * 60);
569 let lnd_transactions = gw_lnd.list_transactions(start, end).await?;
570 assert_eq!(lnd_transactions.len(), 0);
571 }
572
573 info!(target: LOG_TEST, "Testing paying through LND Gateway...");
574 poll_with_timeout("LDK2 pay LDK", Duration::from_secs(180), || async {
578 debug!(target: LOG_TEST, "Trying LDK2 -> LND -> LDK...");
579 let invoice = gw_ldk
580 .create_invoice(1_550_000)
581 .await
582 .map_err(ControlFlow::Continue)?;
583 gw_ldk_second
584 .pay_invoice(invoice.clone())
585 .await
586 .map_err(ControlFlow::Continue)?;
587 Ok(())
588 })
589 .await?;
590
591 if devimint::util::Gatewayd::version_or_default().await >= *VERSION_0_7_0_ALPHA {
592 info!(target: LOG_TEST, "Testing paying Bolt12 Offers...");
593 poll_with_timeout("First BOLT12 payment", Duration::from_secs(30), || async {
595 let offer_with_amount = gw_ldk_second.create_offer(Some(Amount::from_msats(10_000_000))).await.map_err(ControlFlow::Continue)?;
596 gw_ldk.pay_offer(offer_with_amount, None).await.map_err(ControlFlow::Continue)?;
597 assert!(get_transaction(gw_ldk_second, PaymentKind::Bolt12Offer, Amount::from_msats(10_000_000), PaymentStatus::Succeeded).await.is_some());
598 Ok(())
599 }).await?;
600
601 let offer_without_amount = gw_ldk.create_offer(None).await?;
602 gw_ldk_second.pay_offer(offer_without_amount.clone(), Some(Amount::from_msats(5_000_000))).await?;
603 assert!(get_transaction(gw_ldk, PaymentKind::Bolt12Offer, Amount::from_msats(5_000_000), PaymentStatus::Succeeded).await.is_some());
604
605 gw_ldk_second.pay_offer(offer_without_amount.clone(), None).await.expect_err("Cannot pay amountless offer without specifying an amount");
607
608 gw_ldk_second.pay_offer(offer_without_amount, Some(Amount::from_msats(3_000_000))).await?;
610 assert!(get_transaction(gw_ldk, PaymentKind::Bolt12Offer, Amount::from_msats(3_000_000), PaymentStatus::Succeeded).await.is_some());
611 }
612
613 info!(target: LOG_TEST, "Pegging-out gateways...");
614 federation
615 .pegout_gateways(500_000_000, gateways.clone())
616 .await?;
617
618 info!(target: LOG_TEST, "Testing closing all channels...");
619 gw_ldk_second.close_all_channels().await?;
620 gw_ldk.close_all_channels().await?;
621 let bitcoind = dev_fed.bitcoind().await?;
622 bitcoind.mine_blocks(2016).await?;
624 let block_height = bitcoind.get_block_count().await? - 1;
625 gw_ldk_second.wait_for_block_height(block_height).await?;
626 check_empty_lightning_balance(gw_ldk_second).await?;
627 gw_ldk.wait_for_block_height(block_height).await?;
628 check_empty_lightning_balance(gw_ldk).await?;
629 check_empty_lightning_balance(gw_lnd).await?;
630
631 info!(target: LOG_TEST, "Testing sending onchain...");
632 gw_ldk
635 .send_onchain(dev_fed.bitcoind().await?, BitcoinAmountOrAll::All, 10)
636 .await?;
637 gw_ldk_second
638 .send_onchain(dev_fed.bitcoind().await?, BitcoinAmountOrAll::All, 10)
639 .await?;
640 check_empty_onchain_balance(gw_ldk).await?;
641 check_empty_onchain_balance(gw_ldk_second).await?;
642
643 Ok(())
644 })
645 .await
646}
647
648async fn get_transaction(
649 gateway: &Gatewayd,
650 kind: PaymentKind,
651 amount: Amount,
652 status: PaymentStatus,
653) -> Option<PaymentDetails> {
654 let transactions = gateway
655 .list_transactions(
656 now() - Duration::from_secs(5 * 60),
657 now() + Duration::from_secs(5 * 60),
658 )
659 .await
660 .ok()?;
661 transactions.into_iter().find(|details| {
662 details.payment_kind == kind && details.amount == amount && details.status == status
663 })
664}
665
666async fn check_empty_onchain_balance(gw: &Gatewayd) -> anyhow::Result<()> {
667 retry(
668 "Wait for onchain balance update",
669 aggressive_backoff_long(),
670 || async {
671 let curr_balance = gw.get_balances().await?.onchain_balance_sats;
672 let gw_name = gw.gw_name.clone();
673 ensure!(
674 curr_balance == 0,
675 "Gateway onchain balance is not empty: {curr_balance} gw_name: {gw_name}"
676 );
677 Ok(())
678 },
679 )
680 .await
681}
682
683async fn check_empty_lightning_balance(gw: &Gatewayd) -> anyhow::Result<()> {
684 let balances = gw.get_balances().await?;
685 let curr_lightning_balance = balances.lightning_balance_msats;
686 ensure!(
687 curr_lightning_balance == 0,
688 "Close channels did not sweep all lightning funds"
689 );
690 let inbound_lightning_balance = balances.inbound_lightning_liquidity_msats;
691 ensure!(
692 inbound_lightning_balance == 0,
693 "Close channels did not sweep all lightning funds"
694 );
695 Ok(())
696}
697
698async fn leave_federation(gw: &Gatewayd, fed_id: String, expected_scid: u64) -> anyhow::Result<()> {
701 let gatewayd_version = util::Gatewayd::version_or_default().await;
702 let leave_fed = cmd!(gw, "leave-fed", "--federation-id", fed_id.clone())
703 .out_json()
704 .await
705 .expect("Leaving the federation failed");
706
707 let federation_id: FederationId = serde_json::from_value(leave_fed["federation_id"].clone())?;
708 assert_eq!(federation_id.to_string(), fed_id);
709
710 let scid = if gatewayd_version < *VERSION_0_5_0_ALPHA {
713 let channel_id: Option<u64> = serde_json::from_value(leave_fed["channel_id"].clone())?;
714 channel_id.expect("must have channel id")
715 } else if gatewayd_version >= *VERSION_0_5_0_ALPHA && gatewayd_version < *VERSION_0_6_0_ALPHA {
716 serde_json::from_value::<u64>(leave_fed["federation_index"].clone())?
717 } else {
718 serde_json::from_value::<u64>(leave_fed["config"]["federation_index"].clone())?
719 };
720
721 assert_eq!(scid, expected_scid);
722
723 info!(target: LOG_TEST, federation_id = %fed_id, "Verified gateway left federation");
724 Ok(())
725}