1use std::collections::{BTreeMap, HashSet};
2use std::io::Write;
3use std::ops::ControlFlow;
4use std::path::{Path, PathBuf};
5use std::str::FromStr;
6use std::time::{Duration, Instant};
7use std::{env, ffi};
8
9use anyhow::{Context, Result, anyhow, bail};
10use bitcoin::Txid;
11use clap::Subcommand;
12use fedimint_core::core::OperationId;
13use fedimint_core::encoding::{Decodable, Encodable};
14use fedimint_core::envs::{
15 FM_DISABLE_BASE_FEES_ENV, FM_ENABLE_MODULE_LNV1_ENV, FM_ENABLE_MODULE_LNV2_ENV,
16 FM_ENABLE_MODULE_MINT_ENV, FM_ENABLE_MODULE_MINTV2_ENV, FM_ENABLE_MODULE_WALLET_ENV,
17 FM_ENABLE_MODULE_WALLETV2_ENV, is_env_var_set,
18};
19use fedimint_core::module::registry::ModuleRegistry;
20use fedimint_core::net::api_announcement::SignedApiAnnouncement;
21use fedimint_core::task::block_in_place;
22use fedimint_core::util::backoff_util::aggressive_backoff;
23use fedimint_core::util::{retry, write_overwrite_async};
24use fedimint_core::{Amount, PeerId};
25use fedimint_ln_client::LightningPaymentOutcome;
26use fedimint_ln_client::cli::LnInvoiceResponse;
27use fedimint_ln_server::common::lightning_invoice::Bolt11Invoice;
28use fedimint_lnv2_client::FinalSendOperationState;
29use fedimint_logging::LOG_DEVIMINT;
30use fedimint_testing_core::node_type::LightningNodeType;
31use futures::future::try_join_all;
32use serde_json::json;
33use substring::Substring;
34use tokio::net::TcpStream;
35use tokio::{fs, try_join};
36use tracing::{debug, error, info};
37
38use crate::cli::{CommonArgs, cleanup_on_exit, exec_user_command, setup};
39use crate::envs::{FM_DATA_DIR_ENV, FM_DEVIMINT_RUN_DEPRECATED_TESTS_ENV};
40use crate::federation::Client;
41use crate::util::{LoadTestTool, ProcessManager, almost_equal, poll};
42use crate::version_constants::{VERSION_0_10_0_ALPHA, VERSION_0_11_0_ALPHA, VERSION_0_12_0_ALPHA};
43use crate::{DevFed, Gatewayd, LightningNode, Lnd, cmd, dev_fed};
44
45pub struct Stats {
46 pub min: Duration,
47 pub avg: Duration,
48 pub median: Duration,
49 pub p90: Duration,
50 pub max: Duration,
51 pub sum: Duration,
52}
53
54impl std::fmt::Display for Stats {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 write!(f, "min: {:.1}s", self.min.as_secs_f32())?;
57 write!(f, ", avg: {:.1}s", self.avg.as_secs_f32())?;
58 write!(f, ", median: {:.1}s", self.median.as_secs_f32())?;
59 write!(f, ", p90: {:.1}s", self.p90.as_secs_f32())?;
60 write!(f, ", max: {:.1}s", self.max.as_secs_f32())?;
61 write!(f, ", sum: {:.1}s", self.sum.as_secs_f32())?;
62 Ok(())
63 }
64}
65
66pub fn stats_for(mut v: Vec<Duration>) -> Stats {
67 assert!(!v.is_empty());
68 v.sort();
69 let n = v.len();
70 let min = v.first().unwrap().to_owned();
71 let max = v.iter().last().unwrap().to_owned();
72 let median = v[n / 2];
73 let sum: Duration = v.iter().sum();
74 let avg = sum / n as u32;
75 let p90 = v[(n as f32 * 0.9) as usize];
76 Stats {
77 min,
78 avg,
79 median,
80 p90,
81 max,
82 sum,
83 }
84}
85
86pub async fn log_binary_versions() -> Result<()> {
87 let fedimint_cli_version = cmd!(crate::util::get_fedimint_cli_path(), "--version")
88 .out_string()
89 .await?;
90 info!(?fedimint_cli_version);
91 let fedimint_cli_version_hash = cmd!(crate::util::get_fedimint_cli_path(), "version-hash")
92 .out_string()
93 .await?;
94 info!(?fedimint_cli_version_hash);
95 let gateway_cli_version = cmd!(crate::util::get_gateway_cli_path(), "--version")
96 .out_string()
97 .await?;
98 info!(?gateway_cli_version);
99 let gateway_cli_version_hash = cmd!(crate::util::get_gateway_cli_path(), "version-hash")
100 .out_string()
101 .await?;
102 info!(?gateway_cli_version_hash);
103 let fedimintd_version_hash = cmd!(crate::util::FedimintdCmd, "version-hash")
104 .out_string()
105 .await?;
106 info!(?fedimintd_version_hash);
107 let gatewayd_version_hash = cmd!(crate::util::Gatewayd, "version-hash")
108 .out_string()
109 .await?;
110 info!(?gatewayd_version_hash);
111 Ok(())
112}
113
114pub async fn latency_tests(
115 dev_fed: DevFed,
116 r#type: LatencyTest,
117 upgrade_clients: Option<&UpgradeClients>,
118 iterations: usize,
119 assert_thresholds: bool,
120) -> Result<()> {
121 log_binary_versions().await?;
122
123 let DevFed {
124 fed,
125 gw_lnd,
126 gw_ldk,
127 ..
128 } = dev_fed;
129
130 let max_p90_factor = 10.0;
131 let p90_median_factor = 10;
132
133 let client = match upgrade_clients {
134 Some(c) => match r#type {
135 LatencyTest::Reissue => c.reissue_client.clone(),
136 LatencyTest::LnSend => c.ln_send_client.clone(),
137 LatencyTest::LnReceive => c.ln_receive_client.clone(),
138 LatencyTest::FmPay => c.fm_pay_client.clone(),
139 LatencyTest::Restore => bail!("no reusable upgrade client for restore"),
140 },
141 None => fed.new_joined_client("latency-tests-client").await?,
142 };
143
144 let initial_balance_sats = 100_000_000;
145 fed.pegin_client(initial_balance_sats, &client).await?;
146
147 let lnd_gw_id = gw_lnd.gateway_id.clone();
148
149 let gw_lnd = gw_lnd.client();
150 let gw_ldk = gw_ldk.client();
151
152 match r#type {
153 LatencyTest::Reissue => {
154 info!("Testing latency of reissue");
155 let mut reissues = Vec::with_capacity(iterations);
156 let amount_per_iteration_msats =
157 ((initial_balance_sats * 1000 / iterations as u64).next_power_of_two() >> 1) - 1;
159 for _ in 0..iterations {
160 let notes = cmd!(client, "spend", amount_per_iteration_msats.to_string())
161 .out_json()
162 .await?["notes"]
163 .as_str()
164 .context("note must be a string")?
165 .to_owned();
166
167 let start_time = Instant::now();
168 cmd!(client, "reissue", notes).run().await?;
169 reissues.push(start_time.elapsed());
170 }
171 let reissue_stats = stats_for(reissues);
172 println!("### LATENCY REISSUE: {reissue_stats}");
173
174 if assert_thresholds {
175 assert!(reissue_stats.median < Duration::from_secs(10));
176 assert!(reissue_stats.p90 < reissue_stats.median * p90_median_factor);
177 assert!(
178 reissue_stats.max.as_secs_f64()
179 < reissue_stats.p90.as_secs_f64() * max_p90_factor
180 );
181 }
182 }
183 LatencyTest::LnSend => {
184 info!("Testing latency of ln send");
185 let mut ln_sends = Vec::with_capacity(iterations);
186 for _ in 0..iterations {
187 let invoice = gw_ldk.create_invoice(1_000_000).await?;
188 let start_time = Instant::now();
189 ln_pay(&client, invoice.to_string(), lnd_gw_id.clone()).await?;
190 gw_ldk
191 .wait_bolt11_invoice(invoice.payment_hash().consensus_encode_to_vec())
192 .await?;
193 ln_sends.push(start_time.elapsed());
194
195 if crate::util::supports_lnv2() {
196 let invoice = gw_lnd.create_invoice(1_000_000).await?;
197
198 let start_time = Instant::now();
199
200 lnv2_send(&client, &gw_ldk.address(), &invoice.to_string()).await?;
201
202 ln_sends.push(start_time.elapsed());
203 }
204 }
205 let ln_sends_stats = stats_for(ln_sends);
206 println!("### LATENCY LN SEND: {ln_sends_stats}");
207
208 if assert_thresholds {
209 assert!(ln_sends_stats.median < Duration::from_secs(10));
210 assert!(ln_sends_stats.p90 < ln_sends_stats.median * p90_median_factor);
211 assert!(
212 ln_sends_stats.max.as_secs_f64()
213 < ln_sends_stats.p90.as_secs_f64() * max_p90_factor
214 );
215 }
216 }
217 LatencyTest::LnReceive => {
218 info!("Testing latency of ln receive");
219 let mut ln_receives = Vec::with_capacity(iterations);
220
221 let invoice = gw_ldk.create_invoice(10_000_000).await?;
223 ln_pay(&client, invoice.to_string(), lnd_gw_id.clone()).await?;
224
225 for _ in 0..iterations {
226 let invoice = ln_invoice(
227 &client,
228 Amount::from_msats(100_000),
229 "latency-over-lnd-gw".to_string(),
230 lnd_gw_id.clone(),
231 )
232 .await?
233 .invoice;
234
235 let start_time = Instant::now();
236 gw_ldk
237 .pay_invoice(
238 Bolt11Invoice::from_str(&invoice).expect("Could not parse invoice"),
239 )
240 .await?;
241 ln_receives.push(start_time.elapsed());
242
243 if crate::util::supports_lnv2() {
244 let invoice = lnv2_receive(&client, &gw_lnd.address(), 100_000).await?.0;
245
246 let start_time = Instant::now();
247
248 gw_ldk.pay_invoice(invoice).await?;
249
250 ln_receives.push(start_time.elapsed());
251 }
252 }
253 let ln_receives_stats = stats_for(ln_receives);
254 println!("### LATENCY LN RECV: {ln_receives_stats}");
255
256 if assert_thresholds {
257 assert!(ln_receives_stats.median < Duration::from_secs(10));
258 assert!(ln_receives_stats.p90 < ln_receives_stats.median * p90_median_factor);
259 assert!(
260 ln_receives_stats.max.as_secs_f64()
261 < ln_receives_stats.p90.as_secs_f64() * max_p90_factor
262 );
263 }
264 }
265 LatencyTest::FmPay => {
266 info!("Testing latency of internal payments within a federation");
267 let mut fm_internal_pay = Vec::with_capacity(iterations);
268 let sender = fed.new_joined_client("internal-swap-sender").await?;
269 fed.pegin_client(10_000_000, &sender).await?;
270 for _ in 0..iterations {
271 let recv = cmd!(
272 client,
273 "ln-invoice",
274 "--amount=1000000msat",
275 "--description=internal-swap-invoice",
276 "--force-internal"
277 )
278 .out_json()
279 .await?;
280
281 let invoice = recv["invoice"]
282 .as_str()
283 .context("invoice must be string")?
284 .to_owned();
285 let recv_op = recv["operation_id"]
286 .as_str()
287 .context("operation id must be string")?
288 .to_owned();
289
290 let start_time = Instant::now();
291 cmd!(sender, "ln-pay", invoice, "--force-internal")
292 .run()
293 .await?;
294
295 cmd!(client, "await-invoice", recv_op).run().await?;
296 fm_internal_pay.push(start_time.elapsed());
297 }
298 let fm_pay_stats = stats_for(fm_internal_pay);
299
300 println!("### LATENCY FM PAY: {fm_pay_stats}");
301
302 if assert_thresholds {
303 assert!(fm_pay_stats.median < Duration::from_secs(15));
304 assert!(fm_pay_stats.p90 < fm_pay_stats.median * p90_median_factor);
305 assert!(
306 fm_pay_stats.max.as_secs_f64()
307 < fm_pay_stats.p90.as_secs_f64() * max_p90_factor
308 );
309 }
310 }
311 LatencyTest::Restore => {
312 info!("Testing latency of restore");
313 let backup_secret = cmd!(client, "print-secret").out_json().await?["secret"]
314 .as_str()
315 .map(ToOwned::to_owned)
316 .unwrap();
317 if !is_env_var_set(FM_DEVIMINT_RUN_DEPRECATED_TESTS_ENV) {
318 info!("Skipping tests, as in previous versions restore was very slow to test");
319 return Ok(());
320 }
321
322 let start_time = Instant::now();
323 let restore_client = Client::create("restore").await?;
324 cmd!(
325 restore_client,
326 "restore",
327 "--mnemonic",
328 &backup_secret,
329 "--invite-code",
330 fed.invite_code()?
331 )
332 .run()
333 .await?;
334 let restore_time = start_time.elapsed();
335
336 println!("### LATENCY RESTORE: {restore_time:?}");
337
338 if assert_thresholds {
339 if crate::util::is_backwards_compatibility_test() {
340 assert!(restore_time < Duration::from_secs(160));
341 } else {
342 assert!(restore_time < Duration::from_secs(30));
343 }
344 }
345 }
346 }
347
348 Ok(())
349}
350
351pub async fn lnurl_recovery_test(dev_fed: DevFed) -> Result<()> {
352 let DevFed {
353 fed,
354 gw_lnd,
355 recurringd,
356 ..
357 } = dev_fed;
358
359 const LNURL_AMOUNT: Amount = Amount::from_msats(500_000);
360 const PRE_RECOVERY_RECEIVES: u64 = 3;
361 const POST_RECOVERY_RECEIVES: u64 = 2;
362
363 let receiver = fed.new_joined_client("lnurl-recovery-receiver").await?;
364 if !client_has_module(&receiver, "ln").await? {
365 info!("ln module is not present, skipping LNv1 LNURL recovery test");
366 return Ok(());
367 }
368
369 let payer = fed.new_joined_client("lnurl-recovery-payer").await?;
370 fed.pegin_client(100_000, &payer).await?;
371 fed.pegin_gateways(100_000, vec![&gw_lnd]).await?;
372
373 let lnurl = register_lnv1_lnurl(&receiver, recurringd.api_url().as_str()).await?;
374
375 for invoice_idx in 1..=PRE_RECOVERY_RECEIVES {
376 pay_lnv1_lnurl(&payer, &lnurl, LNURL_AMOUNT, &gw_lnd.gateway_id).await?;
377 let operation_id = await_lnv1_lnurl_invoice(&receiver, invoice_idx).await?;
378 await_lnv1_lnurl_invoice_paid(&receiver, operation_id).await?;
379 }
380
381 let pre_recovery_balance = receiver.balance().await?;
382 let mnemonic = cmd!(receiver, "print-secret").out_json().await?["secret"]
383 .as_str()
384 .context("secret must be a string")?
385 .to_owned();
386
387 let restored = Client::create("lnurl-recovery-restored").await?;
388 restored
389 .restore_federation(fed.invite_code()?, mnemonic)
390 .await?;
391
392 poll(
393 "waiting for LNURL recovery client balance to be restored",
394 || async {
395 let restored_balance = restored.balance().await.map_err(ControlFlow::Break)?;
396 if almost_equal(restored_balance, pre_recovery_balance, 2_000).is_ok() {
397 return Ok(());
398 }
399
400 info!("Waiting for LNURL recovery client balance to be restored");
401 cmd!(restored, "dev", "wait", "1")
402 .out_json()
403 .await
404 .map_err(ControlFlow::Break)?;
405
406 Err(ControlFlow::Continue(anyhow!(
407 "LNURL recovery client balance is not restored yet"
408 )))
409 },
410 )
411 .await?;
412
413 assert!(
414 list_lnv1_lnurl_codes(&restored)
415 .await?
416 .as_object()
417 .context("codes must be an object")?
418 .is_empty(),
419 "LN module recovery should not restore recurring payment code registrations"
420 );
421
422 let restored_lnurl = register_lnv1_lnurl(&restored, recurringd.api_url().as_str()).await?;
423 assert_eq!(
424 restored_lnurl, lnurl,
425 "LNURL registration should be idempotent for a recovered deterministic root key"
426 );
427
428 let mut old_operation_ids = Vec::with_capacity(PRE_RECOVERY_RECEIVES as usize);
429 for invoice_idx in 1..=PRE_RECOVERY_RECEIVES {
430 old_operation_ids.push(await_lnv1_lnurl_invoice(&restored, invoice_idx).await?);
431 }
432
433 for operation_id in &old_operation_ids {
434 assert_lnv1_operation_has_no_outcome(&restored, *operation_id).await?;
435 }
436
437 let post_recovery_balance = restored.balance().await?;
438 let mut post_recovery_operation_ids = Vec::with_capacity(POST_RECOVERY_RECEIVES as usize);
439 for invoice_idx in PRE_RECOVERY_RECEIVES + 1..=PRE_RECOVERY_RECEIVES + POST_RECOVERY_RECEIVES {
440 pay_lnv1_lnurl(&payer, &restored_lnurl, LNURL_AMOUNT, &gw_lnd.gateway_id).await?;
441 let operation_id = await_lnv1_lnurl_invoice(&restored, invoice_idx).await?;
442 await_lnv1_lnurl_invoice_paid(&restored, operation_id).await?;
443 post_recovery_operation_ids.push(operation_id);
444 }
445
446 let expected_final_balance =
447 post_recovery_balance + LNURL_AMOUNT.msats * POST_RECOVERY_RECEIVES;
448 let final_balance = restored.balance().await?;
449 almost_equal(final_balance, expected_final_balance, 2_000).map_err(|error| {
450 anyhow!(
451 "restored client balance {final_balance} did not include post-recovery LNURL receives: {error}"
452 )
453 })?;
454
455 for operation_id in post_recovery_operation_ids {
456 assert_lnv1_recurring_receive_operation_logged(&restored, operation_id).await?;
457 }
458
459 Ok(())
460}
461
462async fn client_has_module(client: &Client, kind: &str) -> Result<bool> {
463 let modules = cmd!(client, "module").out_json().await?;
464 let modules = modules["list"]
465 .as_array()
466 .context("module list must be an array")?;
467
468 Ok(modules
469 .iter()
470 .any(|module| module["kind"].as_str() == Some(kind)))
471}
472
473async fn register_lnv1_lnurl(client: &Client, recurringd_api: &str) -> Result<String> {
474 cmd!(client, "module", "ln", "lnurl", "register", recurringd_api)
475 .out_json()
476 .await?["lnurl"]
477 .as_str()
478 .context("lnurl must be a string")
479 .map(ToOwned::to_owned)
480}
481
482async fn list_lnv1_lnurl_codes(client: &Client) -> Result<serde_json::Value> {
483 Ok(cmd!(client, "module", "ln", "lnurl", "list")
484 .out_json()
485 .await?["codes"]
486 .clone())
487}
488
489async fn pay_lnv1_lnurl(
490 client: &Client,
491 lnurl: &str,
492 amount: Amount,
493 gateway_id: &str,
494) -> Result<()> {
495 let value = cmd!(
496 client,
497 "module",
498 "ln",
499 "pay",
500 lnurl,
501 "--amount",
502 amount.msats,
503 "--gateway-id",
504 gateway_id,
505 )
506 .out_json()
507 .await?;
508 let outcome = serde_json::from_value::<LightningPaymentOutcome>(value)
509 .context("could not deserialize Lightning payment outcome")?;
510 match outcome {
511 LightningPaymentOutcome::Success { .. } => Ok(()),
512 LightningPaymentOutcome::Failure { error_message } => {
513 Err(anyhow!("failed to pay LNURL invoice: {error_message}"))
514 }
515 }
516}
517
518async fn await_lnv1_lnurl_invoice(client: &Client, invoice_idx: u64) -> Result<OperationId> {
519 poll("waiting for LNv1 LNURL invoice operation", || async {
520 cmd!(client, "dev", "wait", "1")
521 .out_json()
522 .await
523 .map_err(ControlFlow::Break)?;
524
525 let invoices = cmd!(client, "module", "ln", "lnurl", "invoices", "0")
526 .out_json()
527 .await
528 .map_err(ControlFlow::Break)?;
529 let Some(operation_id) = invoices["invoices"][invoice_idx.to_string()]["operation_id"]
530 .as_str()
531 .map(ToOwned::to_owned)
532 else {
533 return Err(ControlFlow::Continue(anyhow!(
534 "LNURL invoice index {invoice_idx} not found"
535 )));
536 };
537
538 serde_json::from_value::<OperationId>(json!(operation_id))
539 .map_err(anyhow::Error::from)
540 .map_err(ControlFlow::Break)
541 })
542 .await
543}
544
545async fn await_lnv1_lnurl_invoice_paid(client: &Client, operation_id: OperationId) -> Result<()> {
546 cmd!(
547 client,
548 "module",
549 "ln",
550 "lnurl",
551 "await-invoice-paid",
552 operation_id.fmt_full()
553 )
554 .run()
555 .await
556}
557
558async fn assert_lnv1_operation_has_no_outcome(
559 client: &Client,
560 operation_id: OperationId,
561) -> Result<()> {
562 let operation = get_lnv1_operation_from_log(client, operation_id).await?;
563
564 assert_eq!(
565 operation["operation_kind"].as_str(),
566 Some("ln"),
567 "replayed LNURL invoice operation must be an ln operation"
568 );
569 assert!(
570 operation.get("outcome").is_none(),
571 "replayed pre-recovery LNURL invoice operation should not have a terminal outcome"
572 );
573
574 Ok(())
575}
576
577async fn assert_lnv1_recurring_receive_operation_logged(
578 client: &Client,
579 operation_id: OperationId,
580) -> Result<()> {
581 let operation = get_lnv1_operation_from_log(client, operation_id).await?;
582
583 assert_eq!(
584 operation["operation_kind"].as_str(),
585 Some("ln"),
586 "post-recovery LNURL invoice operation must be an ln operation"
587 );
588 assert!(
589 operation["operation_meta"]["variant"]["recurring_payment_receive"].is_object(),
590 "post-recovery LNURL receive must be logged as recurring_payment_receive"
591 );
592
593 Ok(())
594}
595
596async fn get_lnv1_operation_from_log(
597 client: &Client,
598 operation_id: OperationId,
599) -> Result<serde_json::Value> {
600 let operation_id = operation_id.fmt_full().to_string();
601 let operations = cmd!(client, "list-operations", "--limit", "100")
602 .out_json()
603 .await?;
604 operations["operations"]
605 .as_array()
606 .context("operations must be an array")?
607 .iter()
608 .find(|operation| operation["id"].as_str() == Some(operation_id.as_str()))
609 .cloned()
610 .with_context(|| format!("operation {operation_id} not found"))
611}
612
613#[allow(clippy::struct_field_names)]
614pub struct UpgradeClients {
616 reissue_client: Client,
617 ln_send_client: Client,
618 ln_receive_client: Client,
619 fm_pay_client: Client,
620}
621
622async fn stress_test_fed(dev_fed: &DevFed, clients: Option<&UpgradeClients>) -> anyhow::Result<()> {
623 use futures::FutureExt;
624
625 let assert_thresholds = false;
628
629 let iterations = 1;
632
633 let restore_test = if clients.is_some() {
636 futures::future::ok(()).right_future()
637 } else {
638 latency_tests(
639 dev_fed.clone(),
640 LatencyTest::Restore,
641 clients,
642 iterations,
643 assert_thresholds,
644 )
645 .left_future()
646 };
647
648 latency_tests(
651 dev_fed.clone(),
652 LatencyTest::Reissue,
653 clients,
654 iterations,
655 assert_thresholds,
656 )
657 .await?;
658
659 latency_tests(
660 dev_fed.clone(),
661 LatencyTest::LnSend,
662 clients,
663 iterations,
664 assert_thresholds,
665 )
666 .await?;
667
668 latency_tests(
669 dev_fed.clone(),
670 LatencyTest::LnReceive,
671 clients,
672 iterations,
673 assert_thresholds,
674 )
675 .await?;
676
677 latency_tests(
678 dev_fed.clone(),
679 LatencyTest::FmPay,
680 clients,
681 iterations,
682 assert_thresholds,
683 )
684 .await?;
685
686 restore_test.await?;
687
688 Ok(())
689}
690
691pub async fn upgrade_tests(process_mgr: &ProcessManager, binary: UpgradeTest) -> Result<()> {
692 match binary {
693 UpgradeTest::Fedimintd { paths } => {
694 if let Some(oldest_fedimintd) = paths.first() {
695 unsafe { std::env::set_var("FM_FEDIMINTD_BASE_EXECUTABLE", oldest_fedimintd) };
697 } else {
698 bail!("Must provide at least 1 binary path");
699 }
700
701 let fedimintd_version = crate::util::FedimintdCmd::version_or_default().await;
702 info!(
703 "running first stress test for fedimintd version: {}",
704 fedimintd_version
705 );
706
707 let mut dev_fed = dev_fed(process_mgr).await?;
708 let client = dev_fed.fed.new_joined_client("test-client").await?;
709 try_join!(stress_test_fed(&dev_fed, None), client.wait_session())?;
710
711 for path in paths.iter().skip(1) {
712 dev_fed.fed.restart_all_with_bin(process_mgr, path).await?;
713
714 try_join!(stress_test_fed(&dev_fed, None), client.wait_session())?;
716
717 let fedimintd_version = crate::util::FedimintdCmd::version_or_default().await;
718 info!(
719 "### fedimintd passed stress test for version {}",
720 fedimintd_version
721 );
722 }
723 info!("## fedimintd upgraded all binaries successfully");
724 }
725 UpgradeTest::FedimintCli { paths } => {
726 let set_fedimint_cli_path = |path: &PathBuf| {
727 unsafe { std::env::set_var("FM_FEDIMINT_CLI_BASE_EXECUTABLE", path) };
729 let fm_mint_client: String = format!(
730 "{fedimint_cli} --data-dir {datadir}",
731 fedimint_cli = crate::util::get_fedimint_cli_path().join(" "),
732 datadir = crate::vars::utf8(&process_mgr.globals.FM_CLIENT_DIR)
733 );
734 unsafe { std::env::set_var("FM_MINT_CLIENT", fm_mint_client) };
736 };
737
738 if let Some(oldest_fedimint_cli) = paths.first() {
739 set_fedimint_cli_path(oldest_fedimint_cli);
740 } else {
741 bail!("Must provide at least 1 binary path");
742 }
743
744 let fedimint_cli_version = crate::util::FedimintCli::version_or_default().await;
745 info!(
746 "running first stress test for fedimint-cli version: {}",
747 fedimint_cli_version
748 );
749
750 let dev_fed = dev_fed(process_mgr).await?;
751
752 let wait_session_client = dev_fed.fed.new_joined_client("wait-session-client").await?;
753 let reusable_upgrade_clients = UpgradeClients {
754 reissue_client: dev_fed.fed.new_joined_client("reissue-client").await?,
755 ln_send_client: dev_fed.fed.new_joined_client("ln-send-client").await?,
756 ln_receive_client: dev_fed.fed.new_joined_client("ln-receive-client").await?,
757 fm_pay_client: dev_fed.fed.new_joined_client("fm-pay-client").await?,
758 };
759
760 try_join!(
761 stress_test_fed(&dev_fed, Some(&reusable_upgrade_clients)),
762 wait_session_client.wait_session()
763 )?;
764
765 for path in paths.iter().skip(1) {
766 set_fedimint_cli_path(path);
767 let fedimint_cli_version = crate::util::FedimintCli::version_or_default().await;
768 info!("upgraded fedimint-cli to version: {}", fedimint_cli_version);
769 try_join!(
770 stress_test_fed(&dev_fed, Some(&reusable_upgrade_clients)),
771 wait_session_client.wait_session()
772 )?;
773 info!(
774 "### fedimint-cli passed stress test for version {}",
775 fedimint_cli_version
776 );
777 }
778 info!("## fedimint-cli upgraded all binaries successfully");
779 }
780 UpgradeTest::Gatewayd {
781 gatewayd_paths,
782 gateway_cli_paths,
783 } => {
784 if let Some(oldest_gatewayd) = gatewayd_paths.first() {
785 unsafe { std::env::set_var("FM_GATEWAYD_BASE_EXECUTABLE", oldest_gatewayd) };
787 } else {
788 bail!("Must provide at least 1 gatewayd path");
789 }
790
791 if let Some(oldest_gateway_cli) = gateway_cli_paths.first() {
792 unsafe { std::env::set_var("FM_GATEWAY_CLI_BASE_EXECUTABLE", oldest_gateway_cli) };
794 } else {
795 bail!("Must provide at least 1 gateway-cli path");
796 }
797
798 let gatewayd_version = crate::util::Gatewayd::version_or_default().await;
799 let gateway_cli_version = crate::util::GatewayCli::version_or_default().await;
800 info!(
801 ?gatewayd_version,
802 ?gateway_cli_version,
803 "running first stress test for gateway",
804 );
805
806 let mut dev_fed = dev_fed(process_mgr).await?;
807 let client = dev_fed.fed.new_joined_client("test-client").await?;
808 try_join!(stress_test_fed(&dev_fed, None), client.wait_session())?;
809
810 for i in 1..gatewayd_paths.len() {
811 info!(
812 "running stress test with gatewayd path {:?}",
813 gatewayd_paths.get(i)
814 );
815 let new_gatewayd_path = gatewayd_paths.get(i).expect("Not enough gatewayd paths");
816 let new_gateway_cli_path = gateway_cli_paths
817 .get(i)
818 .expect("Not enough gateway-cli paths");
819
820 let gateways = vec![&mut dev_fed.gw_lnd];
821
822 try_join_all(gateways.into_iter().map(|gateway| {
823 gateway.restart_with_bin(process_mgr, new_gatewayd_path, new_gateway_cli_path)
824 }))
825 .await?;
826
827 dev_fed.fed.await_gateways_registered().await?;
828 try_join!(stress_test_fed(&dev_fed, None), client.wait_session())?;
829 let gatewayd_version = crate::util::Gatewayd::version_or_default().await;
830 let gateway_cli_version = crate::util::GatewayCli::version_or_default().await;
831 info!(
832 ?gatewayd_version,
833 ?gateway_cli_version,
834 "### gateway passed stress test for version",
835 );
836 }
837
838 info!("## gatewayd upgraded all binaries successfully");
839 }
840 }
841 Ok(())
842}
843
844pub async fn cli_tests(dev_fed: DevFed) -> Result<()> {
845 log_binary_versions().await?;
846 let DevFed {
847 bitcoind,
848 lnd,
849 fed,
850 gw_lnd,
851 gw_ldk,
852 ..
853 } = dev_fed;
854
855 let fedimintd_version = crate::util::FedimintdCmd::version_or_default().await;
856
857 let client = fed.new_joined_client("cli-tests-client").await?;
858 let lnd_gw_id = gw_lnd.gateway_id.clone();
859
860 fed.pegin_gateways(10_000_000, vec![&gw_lnd]).await?;
861
862 let iroh_lnd_id = gw_lnd.iroh_gateway_id.clone();
863 let gw_lnd = gw_lnd.client();
864 let gw_ldk = gw_ldk.client();
865
866 let fed_id = fed.calculate_federation_id();
867 let invite = fed.invite_code()?;
868
869 let invite_code = cmd!(client, "dev", "decode", "invite-code", invite.clone())
870 .out_json()
871 .await?;
872
873 let encode_invite_output = cmd!(
874 client,
875 "dev",
876 "encode",
877 "invite-code",
878 format!("--url={}", invite_code["url"].as_str().unwrap()),
879 "--federation_id={fed_id}",
880 "--peer=0"
881 )
882 .out_json()
883 .await?;
884
885 anyhow::ensure!(
886 encode_invite_output["invite_code"]
887 .as_str()
888 .expect("invite_code must be a string")
889 == invite,
890 "failed to decode and encode the client invite code",
891 );
892
893 info!("Testing LND can pay LDK directly");
897 let invoice = gw_ldk.create_invoice(1_200_000).await?;
898 lnd.pay_bolt11_invoice(invoice.to_string()).await?;
899 gw_ldk
900 .wait_bolt11_invoice(invoice.payment_hash().consensus_encode_to_vec())
901 .await?;
902
903 info!("Testing LDK can pay LND directly");
905 let (invoice, payment_hash) = lnd.invoice(1_000_000).await?;
906 gw_ldk
907 .pay_invoice(Bolt11Invoice::from_str(&invoice).expect("Could not parse invoice"))
908 .await?;
909 gw_lnd.wait_bolt11_invoice(payment_hash).await?;
910
911 let config = cmd!(client, "config").out_json().await?;
913 let guardian_count = config["global"]["api_endpoints"].as_object().unwrap().len();
914 let wallet_module = config["modules"]
915 .as_object()
916 .unwrap()
917 .values()
918 .find(|m| m["kind"].as_str() == Some("wallet"))
919 .expect("wallet module not found");
920 let descriptor = wallet_module["peg_in_descriptor"]
921 .as_str()
922 .unwrap()
923 .to_owned();
924
925 info!("Testing generated descriptor for {guardian_count} guardian federation");
926 if guardian_count == 1 {
927 assert!(descriptor.contains("wpkh("));
928 } else {
929 assert!(descriptor.contains("wsh(sortedmulti("));
930 }
931
932 info!("Testing Client");
934
935 if crate::util::supports_mint_v2() {
937 info!("Skipping ecash tests - MintV2 enabled, these tests are v1-specific");
938 } else {
939 info!("Testing reissuing e-cash");
941 const CLIENT_START_AMOUNT: u64 = 5_000_000_000;
942 const CLIENT_SPEND_AMOUNT: u64 = 1_100_000;
943
944 let initial_client_balance = client.balance().await?;
945 assert_eq!(initial_client_balance, 0);
946
947 fed.pegin_client(CLIENT_START_AMOUNT / 1000, &client)
948 .await?;
949
950 info!("Testing spending from client");
952 let notes = cmd!(client, "spend", CLIENT_SPEND_AMOUNT)
953 .out_json()
954 .await?
955 .get("notes")
956 .expect("Output didn't contain e-cash notes")
957 .as_str()
958 .unwrap()
959 .to_owned();
960
961 let client_post_spend_balance = client.balance().await?;
962 almost_equal(
963 client_post_spend_balance,
964 CLIENT_START_AMOUNT - CLIENT_SPEND_AMOUNT,
965 10_000,
966 )
967 .unwrap();
968
969 cmd!(client, "reissue", notes).out_json().await?;
971
972 let client_post_spend_balance = client.balance().await?;
973 almost_equal(client_post_spend_balance, CLIENT_START_AMOUNT, 10_000).unwrap();
974
975 let reissue_amount: u64 = 409_600;
976
977 info!("Testing reissuing e-cash after spending");
979 let _notes = cmd!(client, "spend", CLIENT_SPEND_AMOUNT)
980 .out_json()
981 .await?
982 .as_object()
983 .unwrap()
984 .get("notes")
985 .expect("Output didn't contain e-cash notes")
986 .as_str()
987 .unwrap();
988
989 let reissue_notes = cmd!(client, "spend", reissue_amount).out_json().await?["notes"]
990 .as_str()
991 .map(ToOwned::to_owned)
992 .unwrap();
993 let client_reissue_amt = cmd!(client, "reissue", reissue_notes)
994 .out_json()
995 .await?
996 .as_u64()
997 .unwrap();
998 assert_eq!(client_reissue_amt, reissue_amount);
999
1000 info!("Testing reissuing e-cash via module commands");
1002 let reissue_notes = cmd!(client, "spend", reissue_amount).out_json().await?["notes"]
1003 .as_str()
1004 .map(ToOwned::to_owned)
1005 .unwrap();
1006 let client_reissue_amt = cmd!(client, "module", "mint", "reissue", reissue_notes)
1007 .out_json()
1008 .await?
1009 .as_u64()
1010 .unwrap();
1011 assert_eq!(client_reissue_amt, reissue_amount);
1012 }
1013
1014 info!("Testing LND gateway");
1016
1017 if let Some(iroh_gw_id) = &iroh_lnd_id
1019 && crate::util::FedimintCli::version_or_default().await >= *VERSION_0_10_0_ALPHA
1020 {
1021 info!("Testing outgoing payment from client to LDK via IROH LND Gateway");
1022
1023 let initial_lnd_gateway_balance = gw_lnd.ecash_balance(fed_id.clone()).await?;
1024 let invoice = gw_ldk.create_invoice(2_000_000).await?;
1025 ln_pay(&client, invoice.to_string(), iroh_gw_id.clone()).await?;
1026 gw_ldk
1027 .wait_bolt11_invoice(invoice.payment_hash().consensus_encode_to_vec())
1028 .await?;
1029
1030 let final_lnd_outgoing_gateway_balance = gw_lnd.ecash_balance(fed_id.clone()).await?;
1032 info!(
1033 ?final_lnd_outgoing_gateway_balance,
1034 "Final LND ecash balance after iroh payment"
1035 );
1036 anyhow::ensure!(
1037 almost_equal(
1038 final_lnd_outgoing_gateway_balance - initial_lnd_gateway_balance,
1039 2_000_000,
1040 1_000
1041 )
1042 .is_ok(),
1043 "LND Gateway balance changed by {} on LND outgoing IROH payment, expected 2_000_000",
1044 (final_lnd_outgoing_gateway_balance - initial_lnd_gateway_balance)
1045 );
1046
1047 let recv = ln_invoice(
1049 &client,
1050 Amount::from_msats(2_000_000),
1051 "iroh receive payment".to_string(),
1052 iroh_gw_id.clone(),
1053 )
1054 .await?;
1055 gw_ldk
1056 .pay_invoice(Bolt11Invoice::from_str(&recv.invoice).expect("Could not parse invoice"))
1057 .await?;
1058 let operation_id = recv.operation_id;
1059 cmd!(client, "await-invoice", operation_id.fmt_full())
1060 .run()
1061 .await?;
1062 }
1063
1064 info!("Testing outgoing payment from client to LDK via LND gateway");
1065 let initial_lnd_gateway_balance = gw_lnd.ecash_balance(fed_id.clone()).await?;
1066 let invoice = gw_ldk.create_invoice(2_000_000).await?;
1067 ln_pay(&client, invoice.to_string(), lnd_gw_id.clone()).await?;
1068 let fed_id = fed.calculate_federation_id();
1069 gw_ldk
1070 .wait_bolt11_invoice(invoice.payment_hash().consensus_encode_to_vec())
1071 .await?;
1072
1073 let final_lnd_outgoing_gateway_balance = gw_lnd.ecash_balance(fed_id.clone()).await?;
1075 anyhow::ensure!(
1076 almost_equal(
1077 final_lnd_outgoing_gateway_balance - initial_lnd_gateway_balance,
1078 2_000_000,
1079 3_000
1080 )
1081 .is_ok(),
1082 "LND Gateway balance changed by {} on LND outgoing payment, expected 2_000_000",
1083 (final_lnd_outgoing_gateway_balance - initial_lnd_gateway_balance)
1084 );
1085
1086 info!("Testing incoming payment from LDK to client via LND gateway");
1088 let initial_lnd_incoming_client_balance = client.balance().await?;
1089 let recv = ln_invoice(
1090 &client,
1091 Amount::from_msats(1_300_000),
1092 "incoming-over-lnd-gw".to_string(),
1093 lnd_gw_id,
1094 )
1095 .await?;
1096 let invoice = recv.invoice;
1097 gw_ldk
1098 .pay_invoice(Bolt11Invoice::from_str(&invoice).expect("Could not parse invoice"))
1099 .await?;
1100
1101 info!("Testing receiving ecash notes");
1103 let operation_id = recv.operation_id;
1104 cmd!(client, "await-invoice", operation_id.fmt_full())
1105 .run()
1106 .await?;
1107
1108 let fedimint_cli_version = crate::util::FedimintCli::version_or_default().await;
1111 if fedimint_cli_version >= *VERSION_0_11_0_ALPHA {
1112 let final_lnd_incoming_client_balance = client.balance().await?;
1114 let final_lnd_incoming_gateway_balance = gw_lnd.ecash_balance(fed_id.clone()).await?;
1115 anyhow::ensure!(
1116 almost_equal(
1117 final_lnd_incoming_client_balance - initial_lnd_incoming_client_balance,
1118 1_300_000,
1119 2_000
1120 )
1121 .is_ok(),
1122 "Client balance changed by {} on LND incoming payment, expected 1_300_000",
1123 (final_lnd_incoming_client_balance - initial_lnd_incoming_client_balance)
1124 );
1125 anyhow::ensure!(
1126 almost_equal(
1127 final_lnd_outgoing_gateway_balance - final_lnd_incoming_gateway_balance,
1128 1_300_000,
1129 2_000
1130 )
1131 .is_ok(),
1132 "LND Gateway balance changed by {} on LND incoming payment, expected 1_300_000",
1133 (final_lnd_outgoing_gateway_balance - final_lnd_incoming_gateway_balance)
1134 );
1135 }
1136
1137 info!("Testing client deposit");
1140 let initial_walletng_balance = client.balance().await?;
1141
1142 fed.pegin_client(100_000, &client).await?; let post_deposit_walletng_balance = client.balance().await?;
1145
1146 almost_equal(
1147 post_deposit_walletng_balance,
1148 initial_walletng_balance + 100_000_000, 2_000,
1150 )
1151 .unwrap();
1152
1153 info!("Testing client withdraw");
1155
1156 let initial_walletng_balance = client.balance().await?;
1157
1158 let address = bitcoind.get_new_address().await?;
1159 let withdraw_res = cmd!(
1160 client,
1161 "withdraw",
1162 "--address",
1163 &address,
1164 "--amount",
1165 "50000 sat"
1166 )
1167 .out_json()
1168 .await?;
1169
1170 let txid: Txid = withdraw_res["txid"].as_str().unwrap().parse().unwrap();
1171 let fees_sat = withdraw_res["fees_sat"].as_u64().unwrap();
1172
1173 let tx_hex = bitcoind.poll_get_transaction(txid).await?;
1174
1175 let tx = bitcoin::Transaction::consensus_decode_hex(&tx_hex, &ModuleRegistry::default())?;
1176 assert!(
1177 tx.output
1178 .iter()
1179 .any(|o| o.script_pubkey == address.script_pubkey() && o.value.to_sat() == 50000)
1180 );
1181
1182 let post_withdraw_walletng_balance = client.balance().await?;
1183 let expected_wallet_balance = initial_walletng_balance - 50_000_000 - (fees_sat * 1000);
1184
1185 almost_equal(
1186 post_withdraw_walletng_balance,
1187 expected_wallet_balance,
1188 4_000,
1189 )
1190 .unwrap();
1191
1192 let peer_0_fedimintd_version = cmd!(client, "dev", "peer-version", "--peer-id", "0")
1194 .out_json()
1195 .await?
1196 .get("version")
1197 .expect("Output didn't contain version")
1198 .as_str()
1199 .unwrap()
1200 .to_owned();
1201
1202 assert_eq!(
1203 semver::Version::parse(&peer_0_fedimintd_version)?,
1204 fedimintd_version
1205 );
1206
1207 info!("Checking initial announcements...");
1208
1209 retry(
1210 "Check initial announcements",
1211 aggressive_backoff(),
1212 || async {
1213 cmd!(client, "dev", "wait", "1").run().await?;
1215
1216 let initial_announcements =
1218 serde_json::from_value::<BTreeMap<PeerId, SignedApiAnnouncement>>(
1219 cmd!(client, "dev", "api-announcements",).out_json().await?,
1220 )
1221 .expect("failed to parse API announcements");
1222
1223 if initial_announcements.len() < fed.members.len() {
1224 bail!(
1225 "Not all announcements ready; got: {}, expected: {}",
1226 initial_announcements.len(),
1227 fed.members.len()
1228 )
1229 }
1230
1231 if !initial_announcements
1232 .values()
1233 .all(|announcement| announcement.api_announcement.nonce == 0)
1234 {
1235 bail!("Not all announcements have their initial value");
1236 }
1237 Ok(())
1238 },
1239 )
1240 .await?;
1241
1242 const NEW_API_URL: &str = "ws://127.0.0.1:4242";
1243 let new_announcement = serde_json::from_value::<SignedApiAnnouncement>(
1244 cmd!(
1245 client,
1246 "--our-id",
1247 "0",
1248 "--password",
1249 "pass",
1250 "admin",
1251 "sign-api-announcement",
1252 NEW_API_URL
1253 )
1254 .out_json()
1255 .await?,
1256 )
1257 .expect("Couldn't parse signed announcement");
1258
1259 assert_eq!(
1260 new_announcement.api_announcement.nonce, 1,
1261 "Nonce did not increment correctly"
1262 );
1263
1264 info!("Testing if the client syncs the announcement");
1265 let announcement = poll("Waiting for the announcement to propagate", || async {
1266 cmd!(client, "dev", "wait", "1")
1267 .run()
1268 .await
1269 .map_err(ControlFlow::Break)?;
1270
1271 let new_announcements_peer2 =
1272 serde_json::from_value::<BTreeMap<PeerId, SignedApiAnnouncement>>(
1273 cmd!(client, "dev", "api-announcements",)
1274 .out_json()
1275 .await
1276 .map_err(ControlFlow::Break)?,
1277 )
1278 .expect("failed to parse API announcements");
1279
1280 let announcement = new_announcements_peer2[&PeerId::from(0)]
1281 .api_announcement
1282 .clone();
1283 if announcement.nonce == 1 {
1284 Ok(announcement)
1285 } else {
1286 Err(ControlFlow::Continue(anyhow!(
1287 "Haven't received updated announcement yet; nonce: {}",
1288 announcement.nonce
1289 )))
1290 }
1291 })
1292 .await?;
1293
1294 assert_eq!(
1295 announcement.api_url,
1296 NEW_API_URL.parse().expect("valid URL")
1297 );
1298
1299 Ok(())
1300}
1301
1302pub async fn guardian_metadata_tests(dev_fed: DevFed) -> Result<()> {
1303 use fedimint_api_client::api::{DynGlobalApi, FederationApiExt};
1304 use fedimint_connectors::ConnectorRegistry;
1305 use fedimint_core::PeerId;
1306 use fedimint_core::endpoint_constants::INVITE_CODE_ENDPOINT;
1307 use fedimint_core::invite_code::InviteCode;
1308 use fedimint_core::module::ApiRequestErased;
1309 use fedimint_core::net::guardian_metadata::SignedGuardianMetadata;
1310 use fedimint_core::util::SafeUrl;
1311
1312 log_binary_versions().await?;
1313
1314 let fedimintd_version = crate::util::FedimintdCmd::version_or_default().await;
1315 let fedimint_cli_version = crate::util::FedimintCli::version_or_default().await;
1316
1317 if fedimintd_version < *VERSION_0_11_0_ALPHA || fedimint_cli_version < *VERSION_0_11_0_ALPHA {
1318 info!("Skipping test for too old versions");
1319 return Ok(());
1320 }
1321
1322 let DevFed { fed, .. } = dev_fed;
1323
1324 let client = fed.internal_client().await?;
1325
1326 info!("Checking initial guardian metadata...");
1327
1328 retry(
1329 "Check initial guardian metadata",
1330 aggressive_backoff(),
1331 || async {
1332 cmd!(client, "dev", "wait", "1").run().await?;
1334
1335 let initial_metadata =
1336 serde_json::from_value::<BTreeMap<PeerId, SignedGuardianMetadata>>(
1337 cmd!(client, "dev", "guardian-metadata",).out_json().await?,
1338 )
1339 .expect("failed to parse guardian metadata");
1340
1341 if initial_metadata.len() < fed.members.len() {
1342 bail!(
1343 "Not all guardian metadata ready; got: {}, expected: {}",
1344 initial_metadata.len(),
1345 fed.members.len()
1346 )
1347 }
1348
1349 Ok(())
1350 },
1351 )
1352 .await?;
1353
1354 const TEST_API_URL: &str = "ws://127.0.0.1:5000/";
1355 const TEST_PKARR_ID: &str = "test_pkarr_id_z32";
1356
1357 let new_metadata = serde_json::from_value::<SignedGuardianMetadata>(
1358 cmd!(
1359 client,
1360 "--our-id",
1361 "0",
1362 "--password",
1363 "pass",
1364 "admin",
1365 "sign-guardian-metadata",
1366 "--api-urls",
1367 TEST_API_URL,
1368 "--pkarr-id",
1369 TEST_PKARR_ID
1370 )
1371 .out_json()
1372 .await?,
1373 )
1374 .expect("Couldn't parse signed guardian metadata");
1375
1376 let parsed_metadata = new_metadata.guardian_metadata();
1377
1378 assert_eq!(
1379 parsed_metadata.api_urls.first().unwrap().to_string(),
1380 TEST_API_URL,
1381 "API URL did not match"
1382 );
1383
1384 assert_eq!(
1385 parsed_metadata.pkarr_id_z32, TEST_PKARR_ID,
1386 "Pkarr ID did not match"
1387 );
1388
1389 info!("Testing if the client syncs the guardian metadata");
1390 let metadata = poll("Waiting for the guardian metadata to propagate", || async {
1391 cmd!(client, "dev", "wait", "1")
1392 .run()
1393 .await
1394 .map_err(ControlFlow::Break)?;
1395
1396 let new_metadata_peer0 =
1397 serde_json::from_value::<BTreeMap<PeerId, SignedGuardianMetadata>>(
1398 cmd!(client, "dev", "guardian-metadata",)
1399 .out_json()
1400 .await
1401 .map_err(ControlFlow::Break)?,
1402 )
1403 .expect("failed to parse guardian metadata");
1404
1405 let metadata = new_metadata_peer0[&PeerId::from(0)].guardian_metadata();
1406
1407 if metadata.api_urls.first().unwrap().to_string() == TEST_API_URL {
1408 Ok(metadata.clone())
1409 } else {
1410 Err(ControlFlow::Continue(anyhow!(
1411 "Haven't received updated guardian metadata yet"
1412 )))
1413 }
1414 })
1415 .await?;
1416
1417 assert_eq!(
1418 metadata.pkarr_id_z32, TEST_PKARR_ID,
1419 "Pkarr ID did not propagate correctly"
1420 );
1421
1422 if fedimintd_version < *VERSION_0_12_0_ALPHA {
1425 info!("Skipping invite_code endpoint assertion: fedimintd < 0.12.0-alpha");
1426 return Ok(());
1427 }
1428
1429 info!("Checking invite_code endpoint reflects overridden guardian metadata URL...");
1430 let peer_id = PeerId::from(0);
1433 let peer0_real_url: SafeUrl = fed
1434 .vars
1435 .get(&0)
1436 .expect("peer 0 vars must exist")
1437 .FM_API_URL
1438 .parse()
1439 .expect("FM_API_URL must be a valid SafeUrl");
1440 let connectors = ConnectorRegistry::build_from_testing_env()?.bind().await?;
1441 let admin_api = DynGlobalApi::new_admin(connectors, peer_id, peer0_real_url, None)?;
1442 let invite: InviteCode = admin_api
1443 .request_single_peer(
1444 INVITE_CODE_ENDPOINT.to_string(),
1445 ApiRequestErased::default(),
1446 peer_id,
1447 )
1448 .await
1449 .expect("invite_code RPC request should succeed");
1450 assert_eq!(
1451 invite.url().to_string(),
1452 TEST_API_URL,
1453 "invite_code endpoint did not reflect overridden guardian metadata URL"
1454 );
1455
1456 Ok(())
1457}
1458
1459pub async fn cli_load_test_tool_test(dev_fed: DevFed) -> Result<()> {
1460 log_binary_versions().await?;
1461 let data_dir = env::var(FM_DATA_DIR_ENV)?;
1462 let load_test_temp = PathBuf::from(data_dir).join("load-test-temp");
1463 dev_fed
1464 .fed
1465 .pegin_client(10_000, dev_fed.fed.internal_client().await?)
1466 .await?;
1467 let invite_code = dev_fed.fed.invite_code()?;
1468 dev_fed
1469 .gw_lnd
1470 .client()
1471 .set_federation_routing_fee(dev_fed.fed.calculate_federation_id(), 0, 0)
1472 .await?;
1473 run_standard_load_test(&load_test_temp, &invite_code).await?;
1474 run_ln_circular_load_test(&load_test_temp, &invite_code).await?;
1475 Ok(())
1476}
1477
1478pub async fn run_standard_load_test(
1479 load_test_temp: &Path,
1480 invite_code: &str,
1481) -> anyhow::Result<()> {
1482 let output = cmd!(
1483 LoadTestTool,
1484 "--archive-dir",
1485 load_test_temp.display(),
1486 "--users",
1487 "1",
1488 "load-test",
1489 "--notes-per-user",
1490 "1",
1491 "--generate-invoice-with",
1492 "ldk-lightning-cli",
1493 "--invite-code",
1494 invite_code
1495 )
1496 .out_string()
1497 .await?;
1498 println!("{output}");
1499 anyhow::ensure!(
1500 output.contains("2 reissue_notes"),
1501 "reissued different number notes than expected"
1502 );
1503 anyhow::ensure!(
1504 output.contains("1 gateway_pay_invoice"),
1505 "paid different number of invoices than expected"
1506 );
1507 Ok(())
1508}
1509
1510pub async fn run_ln_circular_load_test(
1511 load_test_temp: &Path,
1512 invite_code: &str,
1513) -> anyhow::Result<()> {
1514 info!("Testing ln-circular-load-test with 'two-gateways' strategy");
1515 let output = cmd!(
1516 LoadTestTool,
1517 "--archive-dir",
1518 load_test_temp.display(),
1519 "--users",
1520 "1",
1521 "ln-circular-load-test",
1522 "--strategy",
1523 "two-gateways",
1524 "--test-duration-secs",
1525 "2",
1526 "--invite-code",
1527 invite_code
1528 )
1529 .out_string()
1530 .await?;
1531 println!("{output}");
1532 anyhow::ensure!(
1533 output.contains("gateway_create_invoice"),
1534 "missing invoice creation"
1535 );
1536 anyhow::ensure!(
1537 output.contains("gateway_pay_invoice_success"),
1538 "missing invoice payment"
1539 );
1540 anyhow::ensure!(
1541 output.contains("gateway_payment_received_success"),
1542 "missing received payment"
1543 );
1544
1545 info!("Testing ln-circular-load-test with 'partner-ping-pong' strategy");
1546 let output = cmd!(
1550 LoadTestTool,
1551 "--archive-dir",
1552 load_test_temp.display(),
1553 "--users",
1554 "1",
1555 "ln-circular-load-test",
1556 "--strategy",
1557 "partner-ping-pong",
1558 "--test-duration-secs",
1559 "6",
1560 "--invite-code",
1561 invite_code
1562 )
1563 .out_string()
1564 .await?;
1565 println!("{output}");
1566 anyhow::ensure!(
1567 output.contains("gateway_create_invoice"),
1568 "missing invoice creation"
1569 );
1570 anyhow::ensure!(
1571 output.contains("gateway_payment_received_success"),
1572 "missing received payment"
1573 );
1574
1575 info!("Testing ln-circular-load-test with 'self-payment' strategy");
1576 let output = cmd!(
1578 LoadTestTool,
1579 "--archive-dir",
1580 load_test_temp.display(),
1581 "--users",
1582 "1",
1583 "ln-circular-load-test",
1584 "--strategy",
1585 "self-payment",
1586 "--test-duration-secs",
1587 "2",
1588 "--invite-code",
1589 invite_code
1590 )
1591 .out_string()
1592 .await?;
1593 println!("{output}");
1594 anyhow::ensure!(
1595 output.contains("gateway_create_invoice"),
1596 "missing invoice creation"
1597 );
1598 anyhow::ensure!(
1599 output.contains("gateway_payment_received_success"),
1600 "missing received payment"
1601 );
1602 Ok(())
1603}
1604
1605pub async fn lightning_gw_reconnect_test(
1606 dev_fed: DevFed,
1607 process_mgr: &ProcessManager,
1608) -> Result<()> {
1609 log_binary_versions().await?;
1610
1611 let DevFed {
1612 bitcoind,
1613 lnd,
1614 fed,
1615 mut gw_lnd,
1616 gw_ldk,
1617 ..
1618 } = dev_fed;
1619
1620 let client = fed
1621 .new_joined_client("lightning-gw-reconnect-test-client")
1622 .await?;
1623
1624 info!("Pegging-in both gateways");
1625 fed.pegin_gateways(99_999, vec![&gw_lnd]).await?;
1626
1627 drop(lnd);
1629
1630 tracing::info!("Stopping LND");
1631 assert!(gw_lnd.client().get_info().await.is_ok());
1633
1634 let ln_type = gw_lnd.ln.ln_type().to_string();
1637 gw_lnd.stop_lightning_node().await?;
1638 let lightning_info = gw_lnd.client().get_info().await?;
1639 if gw_lnd.gatewayd_version < *VERSION_0_10_0_ALPHA {
1640 let lightning_pub_key: Option<String> =
1641 serde_json::from_value(lightning_info["lightning_pub_key"].clone())?;
1642
1643 assert!(lightning_pub_key.is_none());
1644 } else {
1645 let not_connected = lightning_info["lightning_info"].clone();
1646 assert!(not_connected.as_str().expect("ln info is not a string") == "not_connected");
1647 }
1648
1649 tracing::info!("Restarting LND...");
1651 let new_lnd = Lnd::new(process_mgr, bitcoind.clone()).await?;
1652 gw_lnd.set_lightning_node(LightningNode::Lnd(new_lnd.clone()));
1653
1654 tracing::info!("Retrying info...");
1655 const MAX_RETRIES: usize = 30;
1656 const RETRY_INTERVAL: Duration = Duration::from_secs(1);
1657
1658 for i in 0..MAX_RETRIES {
1659 match do_try_create_and_pay_invoice(&gw_lnd, &client, &gw_ldk).await {
1660 Ok(()) => break,
1661 Err(e) => {
1662 if i == MAX_RETRIES - 1 {
1663 return Err(e);
1664 }
1665 tracing::debug!(
1666 "Pay invoice for gateway {} failed with {e:?}, retrying in {} seconds (try {}/{MAX_RETRIES})",
1667 ln_type,
1668 RETRY_INTERVAL.as_secs(),
1669 i + 1,
1670 );
1671 fedimint_core::task::sleep_in_test(
1672 "paying invoice for gateway failed",
1673 RETRY_INTERVAL,
1674 )
1675 .await;
1676 }
1677 }
1678 }
1679
1680 info!(target: LOG_DEVIMINT, "lightning_reconnect_test: success");
1681 Ok(())
1682}
1683
1684pub async fn gw_reboot_test(dev_fed: DevFed, process_mgr: &ProcessManager) -> Result<()> {
1685 log_binary_versions().await?;
1686
1687 let DevFed {
1688 bitcoind,
1689 lnd,
1690 fed,
1691 gw_lnd,
1692 gw_ldk,
1693 gw_ldk_second,
1694 ..
1695 } = dev_fed;
1696
1697 let client = fed.new_joined_client("gw-reboot-test-client").await?;
1698 fed.pegin_client(10_000, &client).await?;
1699
1700 let block_height = bitcoind.get_block_count().await? - 1;
1702 try_join!(
1703 async { gw_lnd.client().wait_for_block_height(block_height).await },
1704 async { gw_ldk.client().wait_for_block_height(block_height).await },
1705 )?;
1706
1707 let lnd_gateway_id = gw_lnd.gateway_id.clone();
1709 let ldk_gateway_id = gw_ldk.gateway_id.clone();
1710 let gw_ldk_name = gw_ldk.gw_name.clone();
1711 let gw_ldk_port = gw_ldk.gw_port;
1712 let gw_lightning_port = gw_ldk.ldk_port;
1713 let gw_ldk_metrics_port = gw_ldk.metrics_port;
1714 drop(gw_lnd);
1715 drop(gw_ldk);
1716
1717 info!("Making payment while gateway is down");
1720 let initial_client_balance = client.balance().await?;
1721 let invoice = gw_ldk_second.client().create_invoice(3000).await?;
1722 ln_pay(&client, invoice.to_string(), lnd_gateway_id.clone())
1723 .await
1724 .expect_err("Expected ln-pay to return error because the gateway is not online");
1725 let new_client_balance = client.balance().await?;
1726 anyhow::ensure!(initial_client_balance == new_client_balance);
1727
1728 info!("Rebooting gateways...");
1730 let (new_gw_lnd, new_gw_ldk) = try_join!(
1731 Gatewayd::new(process_mgr, LightningNode::Lnd(lnd.clone()), 0),
1732 Gatewayd::new(
1733 process_mgr,
1734 LightningNode::Ldk {
1735 name: gw_ldk_name,
1736 gw_port: gw_ldk_port,
1737 ldk_port: gw_lightning_port,
1738 metrics_port: gw_ldk_metrics_port,
1739 },
1740 1,
1741 )
1742 )?;
1743
1744 let lnd_gateway_id = fedimint_core::secp256k1::PublicKey::from_str(&lnd_gateway_id)?;
1745
1746 poll(
1747 "Waiting for LND Gateway Running state after reboot",
1748 || async {
1749 let lnd_value = new_gw_lnd.client().get_info().await.map_err(ControlFlow::Continue)?;
1750 let reboot_gateway_state: String = serde_json::from_value(lnd_value["gateway_state"].clone()).context("invalid gateway state").map_err(ControlFlow::Break)?;
1751 let reboot_gateway_id = fedimint_core::secp256k1::PublicKey::from_str(&new_gw_lnd.gateway_id).expect("Could not convert public key");
1752
1753 if reboot_gateway_state == "Running" {
1754 info!(target: LOG_DEVIMINT, "LND Gateway restarted, with auto-rejoin to federation");
1755 assert_eq!(lnd_gateway_id, reboot_gateway_id);
1757 return Ok(());
1758 }
1759 Err(ControlFlow::Continue(anyhow!("gateway not running")))
1760 },
1761 )
1762 .await?;
1763
1764 let ldk_gateway_id = fedimint_core::secp256k1::PublicKey::from_str(&ldk_gateway_id)?;
1765 poll(
1766 "Waiting for LDK Gateway Running state after reboot",
1767 || async {
1768 let ldk_value = new_gw_ldk.client().get_info().await.map_err(ControlFlow::Continue)?;
1769 let reboot_gateway_state: String = serde_json::from_value(ldk_value["gateway_state"].clone()).context("invalid gateway state").map_err(ControlFlow::Break)?;
1770 let reboot_gateway_id = fedimint_core::secp256k1::PublicKey::from_str(&new_gw_ldk.gateway_id).expect("Could not convert public key");
1771
1772 if reboot_gateway_state == "Running" {
1773 info!(target: LOG_DEVIMINT, "LDK Gateway restarted, with auto-rejoin to federation");
1774 assert_eq!(ldk_gateway_id, reboot_gateway_id);
1776 return Ok(());
1777 }
1778 Err(ControlFlow::Continue(anyhow!("gateway not running")))
1779 },
1780 )
1781 .await?;
1782
1783 info!(LOG_DEVIMINT, "gateway_reboot_test: success");
1784 Ok(())
1785}
1786
1787pub async fn do_try_create_and_pay_invoice(
1788 gw_lnd: &Gatewayd,
1789 client: &Client,
1790 gw_ldk: &Gatewayd,
1791) -> anyhow::Result<()> {
1792 poll("Waiting for info to succeed after restart", || async {
1796 gw_lnd
1797 .client()
1798 .lightning_pubkey()
1799 .await
1800 .map_err(ControlFlow::Continue)?;
1801 Ok(())
1802 })
1803 .await?;
1804
1805 tracing::info!("Creating invoice....");
1806 let invoice = ln_invoice(
1807 client,
1808 Amount::from_msats(1000),
1809 "incoming-over-lnd-gw".to_string(),
1810 gw_lnd.gateway_id.clone(),
1811 )
1812 .await?
1813 .invoice;
1814
1815 match &gw_lnd.ln.ln_type() {
1816 LightningNodeType::Lnd => {
1817 gw_ldk
1819 .client()
1820 .pay_invoice(Bolt11Invoice::from_str(&invoice).expect("Could not parse invoice"))
1821 .await?;
1822 }
1823 LightningNodeType::Ldk => {
1824 unimplemented!("do_try_create_and_pay_invoice not implemented for LDK yet");
1825 }
1826 }
1827 Ok(())
1828}
1829
1830async fn ln_pay(client: &Client, invoice: String, gw_id: String) -> anyhow::Result<String> {
1831 let value = cmd!(client, "ln-pay", invoice, "--gateway-id", gw_id,)
1832 .out_json()
1833 .await?;
1834 let outcome = serde_json::from_value::<LightningPaymentOutcome>(value)
1835 .expect("Could not deserialize Lightning payment outcome");
1836 match outcome {
1837 LightningPaymentOutcome::Success { preimage } => Ok(preimage),
1838 LightningPaymentOutcome::Failure { error_message } => {
1839 Err(anyhow!("Failed to pay lightning invoice: {error_message}"))
1840 }
1841 }
1842}
1843
1844async fn ln_invoice(
1845 client: &Client,
1846 amount: Amount,
1847 description: String,
1848 gw_id: String,
1849) -> anyhow::Result<LnInvoiceResponse> {
1850 let ln_response_val = cmd!(
1851 client,
1852 "ln-invoice",
1853 "--amount",
1854 amount.msats,
1855 format!("--description='{description}'"),
1856 "--gateway-id",
1857 gw_id,
1858 )
1859 .out_json()
1860 .await?;
1861
1862 let ln_invoice_response: LnInvoiceResponse = serde_json::from_value(ln_response_val)?;
1863
1864 Ok(ln_invoice_response)
1865}
1866
1867async fn lnv2_receive(
1868 client: &Client,
1869 gateway: &str,
1870 amount: u64,
1871) -> anyhow::Result<(Bolt11Invoice, OperationId)> {
1872 Ok(serde_json::from_value::<(Bolt11Invoice, OperationId)>(
1873 cmd!(
1874 client,
1875 "module",
1876 "lnv2",
1877 "receive",
1878 amount,
1879 "--gateway",
1880 gateway
1881 )
1882 .out_json()
1883 .await?,
1884 )?)
1885}
1886
1887async fn lnv2_send(client: &Client, gateway: &String, invoice: &String) -> anyhow::Result<()> {
1888 let send_op = serde_json::from_value::<OperationId>(
1889 cmd!(
1890 client,
1891 "module",
1892 "lnv2",
1893 "send",
1894 invoice,
1895 "--gateway",
1896 gateway
1897 )
1898 .out_json()
1899 .await?,
1900 )?;
1901
1902 let send_state = lnv2_await_send(client, send_op).await?;
1903 assert!(
1904 matches!(send_state, FinalSendOperationState::Success(_)),
1905 "unexpected send state: {send_state:?}"
1906 );
1907
1908 Ok(())
1909}
1910
1911async fn lnv2_await_send(
1920 client: &Client,
1921 send_op: OperationId,
1922) -> anyhow::Result<FinalSendOperationState> {
1923 let raw = cmd!(
1924 client,
1925 "module",
1926 "lnv2",
1927 "await-send",
1928 serde_json::to_string(&send_op)?.substring(1, 65)
1929 )
1930 .out_json()
1931 .await?;
1932
1933 Ok(if raw.as_str() == Some("Success") {
1934 FinalSendOperationState::Success([0; 32])
1935 } else {
1936 serde_json::from_value(raw)?
1937 })
1938}
1939
1940pub async fn reconnect_test(dev_fed: DevFed, process_mgr: &ProcessManager) -> Result<()> {
1941 log_binary_versions().await?;
1942
1943 let DevFed {
1944 bitcoind, mut fed, ..
1945 } = dev_fed;
1946
1947 bitcoind.mine_blocks(110).await?;
1948 fed.await_block_sync().await?;
1949 fed.await_all_peers().await?;
1950
1951 fed.terminate_server(0).await?;
1953 fed.mine_then_wait_blocks_sync(100).await?;
1954
1955 fed.start_server(process_mgr, 0).await?;
1956 fed.mine_then_wait_blocks_sync(100).await?;
1957 fed.await_all_peers().await?;
1958 info!(target: LOG_DEVIMINT, "Server 0 successfully rejoined!");
1959 fed.mine_then_wait_blocks_sync(100).await?;
1960
1961 fed.terminate_server(1).await?;
1963 fed.mine_then_wait_blocks_sync(100).await?;
1964 fed.terminate_server(2).await?;
1965 fed.terminate_server(3).await?;
1966
1967 fed.start_server(process_mgr, 1).await?;
1968 fed.start_server(process_mgr, 2).await?;
1969 fed.start_server(process_mgr, 3).await?;
1970
1971 fed.await_all_peers().await?;
1972
1973 info!(target: LOG_DEVIMINT, "fm success: reconnect-test");
1974 Ok(())
1975}
1976
1977pub async fn recoverytool_test(dev_fed: DevFed) -> Result<()> {
1978 log_binary_versions().await?;
1979
1980 let DevFed { bitcoind, fed, .. } = dev_fed;
1981
1982 let data_dir = env::var(FM_DATA_DIR_ENV)?;
1983 let client = fed.new_joined_client("recoverytool-test-client").await?;
1984
1985 let mut fed_utxos_sats = HashSet::from([12_345_000, 23_456_000, 34_567_000]);
1986 let deposit_fees = fed.deposit_fees()?.msats / 1000;
1987 for sats in &fed_utxos_sats {
1988 fed.pegin_client(*sats - deposit_fees, &client).await?;
1990 }
1991
1992 async fn withdraw(
1993 client: &Client,
1994 bitcoind: &crate::external::Bitcoind,
1995 fed_utxos_sats: &mut HashSet<u64>,
1996 ) -> Result<()> {
1997 let withdrawal_address = bitcoind.get_new_address().await?;
1998 let withdraw_res = cmd!(
1999 client,
2000 "withdraw",
2001 "--address",
2002 &withdrawal_address,
2003 "--amount",
2004 "5000 sat"
2005 )
2006 .out_json()
2007 .await?;
2008
2009 let fees_sat = withdraw_res["fees_sat"]
2010 .as_u64()
2011 .expect("withdrawal should contain fees");
2012 let txid: Txid = withdraw_res["txid"]
2013 .as_str()
2014 .expect("withdrawal should contain txid string")
2015 .parse()
2016 .expect("txid should be parsable");
2017 let tx_hex = bitcoind.poll_get_transaction(txid).await?;
2018
2019 let tx = bitcoin::Transaction::consensus_decode_hex(&tx_hex, &ModuleRegistry::default())?;
2020 assert_eq!(tx.input.len(), 1);
2021 assert_eq!(tx.output.len(), 2);
2022
2023 let change_output = tx
2024 .output
2025 .iter()
2026 .find(|o| o.to_owned().script_pubkey != withdrawal_address.script_pubkey())
2027 .expect("withdrawal must have change output");
2028 assert!(fed_utxos_sats.insert(change_output.value.to_sat()));
2029
2030 let total_output_sats = tx.output.iter().map(|o| o.value.to_sat()).sum::<u64>();
2032 let input_sats = total_output_sats + fees_sat;
2033 assert!(fed_utxos_sats.remove(&input_sats));
2034
2035 Ok(())
2036 }
2037
2038 for _ in 0..2 {
2041 withdraw(&client, &bitcoind, &mut fed_utxos_sats).await?;
2042 }
2043
2044 let total_fed_sats = fed_utxos_sats.iter().sum::<u64>();
2045 fed.finalize_mempool_tx().await?;
2046
2047 let last_tx_session = client.get_session_count().await?;
2051
2052 info!("Recovering using utxos method");
2053 let output = cmd!(
2054 crate::util::Recoverytool,
2055 "--cfg",
2056 "{data_dir}/fedimintd-default-0",
2057 "utxos",
2058 "--db",
2059 "{data_dir}/fedimintd-default-0/database"
2060 )
2061 .out_json()
2062 .await?;
2063 let outputs = output.as_array().context("expected an array")?;
2064 assert_eq!(outputs.len(), fed_utxos_sats.len());
2065
2066 assert_eq!(
2067 outputs
2068 .iter()
2069 .map(|o| o["amount_sat"].as_u64().unwrap())
2070 .collect::<HashSet<_>>(),
2071 fed_utxos_sats
2072 );
2073 let utxos_descriptors = outputs
2074 .iter()
2075 .map(|o| o["descriptor"].as_str().unwrap())
2076 .collect::<HashSet<_>>();
2077
2078 debug!(target: LOG_DEVIMINT, ?utxos_descriptors, "recoverytool descriptors using UTXOs method");
2079
2080 let descriptors_json = serde_json::value::to_raw_value(&serde_json::Value::Array(vec![
2081 serde_json::Value::Array(
2082 utxos_descriptors
2083 .iter()
2084 .map(|d| {
2085 json!({
2086 "desc": d,
2087 "timestamp": 0,
2088 })
2089 })
2090 .collect(),
2091 ),
2092 ]))?;
2093 info!("Getting wallet balances before import");
2094 let bitcoin_client = bitcoind.wallet_client().await?;
2095 let balances_before = bitcoin_client.get_balances().await?;
2096 info!("Importing descriptors into bitcoin wallet");
2097 let request = bitcoin_client
2098 .get_jsonrpc_client()
2099 .build_request("importdescriptors", Some(&descriptors_json));
2100 let response = block_in_place(|| bitcoin_client.get_jsonrpc_client().send_request(request))?;
2101 response.check_error()?;
2102 info!("Getting wallet balances after import");
2103 let balances_after = bitcoin_client.get_balances().await?;
2104 let diff = balances_after.mine.immature + balances_after.mine.trusted
2105 - balances_before.mine.immature
2106 - balances_before.mine.trusted;
2107
2108 client.wait_session_outcome(last_tx_session).await?;
2113
2114 assert_eq!(diff.to_sat(), total_fed_sats);
2116 info!("Recovering using epochs method");
2117
2118 let outputs = cmd!(
2119 crate::util::Recoverytool,
2120 "--cfg",
2121 "{data_dir}/fedimintd-default-0",
2122 "epochs",
2123 "--db",
2124 "{data_dir}/fedimintd-default-0/database"
2125 )
2126 .out_json()
2127 .await?
2128 .as_array()
2129 .context("expected an array")?
2130 .clone();
2131
2132 let epochs_descriptors = outputs
2133 .iter()
2134 .map(|o| o["descriptor"].as_str().unwrap())
2135 .collect::<HashSet<_>>();
2136
2137 debug!(target: LOG_DEVIMINT, ?epochs_descriptors, "recoverytool descriptors using epochs method");
2139
2140 for utxo_descriptor in utxos_descriptors {
2143 assert!(epochs_descriptors.contains(utxo_descriptor));
2144 }
2145 Ok(())
2146}
2147
2148pub async fn guardian_backup_test(dev_fed: DevFed, process_mgr: &ProcessManager) -> Result<()> {
2149 const PEER_TO_TEST: u16 = 0;
2150
2151 log_binary_versions().await?;
2152
2153 let DevFed { mut fed, .. } = dev_fed;
2154
2155 fed.await_all_peers()
2156 .await
2157 .expect("Awaiting federation coming online failed");
2158
2159 let client = fed.new_joined_client("guardian-client").await?;
2160 let old_block_count = cmd!(
2161 client,
2162 "dev",
2163 "api",
2164 "--peer-id",
2165 PEER_TO_TEST.to_string(),
2166 "--module",
2167 "wallet",
2168 "block_count",
2169 )
2170 .out_json()
2171 .await?["value"]
2172 .as_u64()
2173 .expect("No block height returned");
2174
2175 let backup_res = cmd!(
2176 client,
2177 "--our-id",
2178 PEER_TO_TEST.to_string(),
2179 "--password",
2180 "pass",
2181 "admin",
2182 "guardian-config-backup"
2183 )
2184 .out_json()
2185 .await?;
2186 let backup_hex = backup_res["tar_archive_bytes"]
2187 .as_str()
2188 .expect("expected hex string");
2189 let backup_tar = hex::decode(backup_hex).expect("invalid hex");
2190
2191 let data_dir = fed
2192 .vars
2193 .get(&PEER_TO_TEST.into())
2194 .expect("peer not found")
2195 .FM_DATA_DIR
2196 .clone();
2197
2198 fed.terminate_server(PEER_TO_TEST.into())
2199 .await
2200 .expect("could not terminate fedimintd");
2201
2202 std::fs::remove_dir_all(&data_dir).expect("error deleting old datadir");
2203 std::fs::create_dir(&data_dir).expect("error creating new datadir");
2204
2205 let write_file = |name: &str, data: &[u8]| {
2206 let mut file = std::fs::File::options()
2207 .write(true)
2208 .create(true)
2209 .truncate(true)
2210 .open(data_dir.join(name))
2211 .expect("could not open file");
2212 file.write_all(data).expect("could not write file");
2213 file.flush().expect("could not flush file");
2214 };
2215
2216 write_file("backup.tar", &backup_tar);
2217
2218 assert_eq!(
2219 std::process::Command::new("tar")
2220 .arg("-xf")
2221 .arg("backup.tar")
2222 .current_dir(&data_dir)
2223 .spawn()
2224 .expect("error spawning tar")
2225 .wait()
2226 .expect("error extracting archive")
2227 .code(),
2228 Some(0),
2229 "tar failed"
2230 );
2231
2232 if data_dir.join("private.encrypt").exists() {
2235 write_file("password.private", "pass".as_bytes());
2236 }
2237
2238 fed.start_server(process_mgr, PEER_TO_TEST.into())
2239 .await
2240 .expect("could not restart fedimintd");
2241
2242 poll("Peer catches up again", || async {
2243 let block_counts = all_peer_block_count(&client, fed.member_ids())
2244 .await
2245 .map_err(ControlFlow::Continue)?;
2246 let block_count = block_counts[&PeerId::from(PEER_TO_TEST)];
2247
2248 info!("Caught up to block {block_count} of at least {old_block_count} (counts={block_counts:?})");
2249
2250 if block_count < old_block_count {
2251 return Err(ControlFlow::Continue(anyhow!("Block count still behind")));
2252 }
2253
2254 Ok(())
2255 })
2256 .await
2257 .expect("Peer didn't rejoin federation");
2258
2259 Ok(())
2260}
2261
2262async fn peer_block_count(client: &Client, peer: PeerId) -> Result<u64> {
2263 cmd!(
2264 client,
2265 "dev",
2266 "api",
2267 "--peer-id",
2268 peer.to_string(),
2269 "--module",
2270 "wallet",
2271 "block_count",
2272 )
2273 .out_json()
2274 .await?["value"]
2275 .as_u64()
2276 .context("No block height returned")
2277}
2278
2279async fn all_peer_block_count(
2280 client: &Client,
2281 peers: impl Iterator<Item = PeerId>,
2282) -> Result<BTreeMap<PeerId, u64>> {
2283 let mut peer_heights = BTreeMap::new();
2284 for peer in peers {
2285 peer_heights.insert(peer, peer_block_count(client, peer).await?);
2286 }
2287 Ok(peer_heights)
2288}
2289
2290pub async fn cannot_replay_tx_test(dev_fed: DevFed) -> Result<()> {
2291 log_binary_versions().await?;
2292
2293 let DevFed { fed, .. } = dev_fed;
2294
2295 let client = fed.new_joined_client("cannot-replay-client").await?;
2296
2297 const CLIENT_START_AMOUNT: u64 = 10_000_000_000;
2298 const CLIENT_SPEND_AMOUNT: u64 = 5_000_000_000;
2299
2300 let initial_client_balance = client.balance().await?;
2301 assert_eq!(initial_client_balance, 0);
2302
2303 fed.pegin_client(CLIENT_START_AMOUNT / 1000, &client)
2304 .await?;
2305
2306 let double_spend_client = client.new_forked("double-spender").await?;
2308
2309 let notes = cmd!(client, "spend", CLIENT_SPEND_AMOUNT)
2311 .out_json()
2312 .await?
2313 .get("notes")
2314 .expect("Output didn't contain e-cash notes")
2315 .as_str()
2316 .unwrap()
2317 .to_owned();
2318
2319 let client_post_spend_balance = client.balance().await?;
2320 crate::util::almost_equal(
2321 client_post_spend_balance,
2322 CLIENT_START_AMOUNT - CLIENT_SPEND_AMOUNT,
2323 10_000,
2324 )
2325 .unwrap();
2326
2327 cmd!(client, "reissue", notes).out_json().await?;
2328 let client_post_reissue_balance = client.balance().await?;
2329 crate::util::almost_equal(client_post_reissue_balance, CLIENT_START_AMOUNT, 20_000).unwrap();
2330
2331 let double_spend_notes = cmd!(double_spend_client, "spend", CLIENT_SPEND_AMOUNT)
2333 .out_json()
2334 .await?
2335 .get("notes")
2336 .expect("Output didn't contain e-cash notes")
2337 .as_str()
2338 .unwrap()
2339 .to_owned();
2340
2341 let double_spend_client_post_spend_balance = double_spend_client.balance().await?;
2342 crate::util::almost_equal(
2343 double_spend_client_post_spend_balance,
2344 CLIENT_START_AMOUNT - CLIENT_SPEND_AMOUNT,
2345 10_000,
2346 )
2347 .unwrap();
2348
2349 cmd!(double_spend_client, "reissue", double_spend_notes)
2350 .assert_error_contains("The transaction had an invalid input")
2351 .await?;
2352
2353 let double_spend_client_post_spend_balance = double_spend_client.balance().await?;
2354 crate::util::almost_equal(
2355 double_spend_client_post_spend_balance,
2356 CLIENT_START_AMOUNT - CLIENT_SPEND_AMOUNT,
2357 10_000,
2358 )
2359 .unwrap();
2360
2361 Ok(())
2362}
2363
2364pub async fn test_offline_client_initialization(
2368 dev_fed: DevFed,
2369 _process_mgr: &ProcessManager,
2370) -> Result<()> {
2371 log_binary_versions().await?;
2372
2373 let DevFed { mut fed, .. } = dev_fed;
2374
2375 fed.await_all_peers().await?;
2377
2378 let client = fed.new_joined_client("offline-test-client").await?;
2380
2381 const INFO_COMMAND_TIMEOUT: Duration = Duration::from_secs(5);
2383 let online_info =
2384 fedimint_core::runtime::timeout(INFO_COMMAND_TIMEOUT, cmd!(client, "info").out_json())
2385 .await
2386 .context("Client info command timed out while federation was online")?
2387 .context("Client info command failed while federation was online")?;
2388 info!(target: LOG_DEVIMINT, "Client info while federation online: {:?}", online_info);
2389
2390 info!(target: LOG_DEVIMINT, "Shutting down all federation servers...");
2392 fed.terminate_all_servers().await?;
2393
2394 fedimint_core::task::sleep_in_test("wait for federation shutdown", Duration::from_secs(2))
2396 .await;
2397
2398 info!(target: LOG_DEVIMINT, "Testing client info command with all servers offline...");
2402 let offline_info =
2403 fedimint_core::runtime::timeout(INFO_COMMAND_TIMEOUT, cmd!(client, "info").out_json())
2404 .await
2405 .context("Client info command timed out while federation was offline")?
2406 .context("Client info command failed while federation was offline")?;
2407
2408 info!(target: LOG_DEVIMINT, "Client info while federation offline: {:?}", offline_info);
2409
2410 Ok(())
2411}
2412
2413pub async fn test_client_config_change_detection(
2420 dev_fed: DevFed,
2421 process_mgr: &ProcessManager,
2422) -> Result<()> {
2423 log_binary_versions().await?;
2424
2425 let fedimintd_version = crate::util::FedimintdCmd::version_or_default().await;
2430 if fedimintd_version < *VERSION_0_12_0_ALPHA {
2431 info!(
2432 "Skipping test_client_config_change_detection - requires fedimintd v0.12.0-alpha or later"
2433 );
2434 return Ok(());
2435 }
2436
2437 let DevFed { mut fed, .. } = dev_fed;
2438 let peer_ids: Vec<_> = fed.member_ids().collect();
2439
2440 fed.await_all_peers().await?;
2441
2442 let client = fed.new_joined_client("config-change-test-client").await?;
2443
2444 info!(target: LOG_DEVIMINT, "Getting initial client configuration...");
2445 let initial_config = cmd!(client, "config")
2446 .out_json()
2447 .await
2448 .context("Failed to get initial client config")?;
2449
2450 info!(target: LOG_DEVIMINT, "Initial config modules: {:?}", initial_config["modules"].as_object().unwrap().keys().collect::<Vec<_>>());
2451
2452 let data_dir = env::var(FM_DATA_DIR_ENV)?;
2453 let config_dir = PathBuf::from(&data_dir);
2454
2455 info!(target: LOG_DEVIMINT, "Shutting down all federation servers...");
2462 fed.terminate_all_servers().await?;
2463
2464 fedimint_core::task::sleep_in_test("wait for federation shutdown", Duration::from_secs(2))
2466 .await;
2467
2468 info!(target: LOG_DEVIMINT, "Modifying server configurations to add new meta module...");
2469 modify_server_configs(&config_dir, &peer_ids).await?;
2470
2471 info!(target: LOG_DEVIMINT, "Restarting all servers with modified configurations...");
2473 for peer_id in peer_ids {
2474 fed.start_server(process_mgr, peer_id.to_usize()).await?;
2475 }
2476
2477 info!(target: LOG_DEVIMINT, "Wait for peers to get back up");
2479 fed.await_all_peers().await?;
2480
2481 info!(target: LOG_DEVIMINT, "Waiting for client to fetch updated configuration...");
2483 cmd!(client, "dev", "wait", "3")
2484 .run()
2485 .await
2486 .context("Failed to wait for client config update")?;
2487
2488 info!(target: LOG_DEVIMINT, "Testing client detection of configuration changes...");
2490 let updated_config = cmd!(client, "config")
2491 .out_json()
2492 .await
2493 .context("Failed to get updated client config")?;
2494
2495 info!(target: LOG_DEVIMINT, "Updated config modules: {:?}", updated_config["modules"].as_object().unwrap().keys().collect::<Vec<_>>());
2496
2497 let initial_modules = initial_config["modules"].as_object().unwrap();
2499 let updated_modules = updated_config["modules"].as_object().unwrap();
2500
2501 anyhow::ensure!(
2502 updated_modules.len() > initial_modules.len(),
2503 "Expected more modules in updated config. Initial: {}, Updated: {}",
2504 initial_modules.len(),
2505 updated_modules.len()
2506 );
2507
2508 let new_meta_module = updated_modules.iter().find(|(module_id, module_config)| {
2510 module_config["kind"].as_str() == Some("meta") && !initial_modules.contains_key(*module_id)
2511 });
2512
2513 let new_meta_module_id = new_meta_module
2514 .map(|(id, _)| id)
2515 .with_context(|| "Expected to find new meta module in updated configuration")?;
2516
2517 info!(target: LOG_DEVIMINT, "Found new meta module with id: {}", new_meta_module_id);
2518
2519 info!(target: LOG_DEVIMINT, "Verifying client operations work with new configuration...");
2521 let final_info = cmd!(client, "info")
2522 .out_json()
2523 .await
2524 .context("Client info command failed with updated configuration")?;
2525
2526 info!(target: LOG_DEVIMINT, "Client successfully adapted to configuration changes: {:?}", final_info["federation_id"]);
2527
2528 Ok(())
2529}
2530
2531async fn modify_server_configs(config_dir: &Path, peer_ids: &[PeerId]) -> Result<()> {
2533 for &peer_id in peer_ids {
2534 modify_single_peer_config(config_dir, peer_id).await?;
2535 }
2536 Ok(())
2537}
2538
2539async fn modify_single_peer_config(config_dir: &Path, peer_id: PeerId) -> Result<()> {
2542 use fedimint_core::core::ModuleInstanceId;
2543 use fedimint_server::config::io::read_server_config;
2544 use serde_json::Value;
2545
2546 info!(target: LOG_DEVIMINT, %peer_id, "Modifying config for peer");
2547 let peer_dir = config_dir.join(format!("fedimintd-default-{}", peer_id.to_usize()));
2548
2549 let consensus_config_path = peer_dir.join("consensus.json");
2551 let consensus_config_content = fs::read_to_string(&consensus_config_path)
2552 .await
2553 .with_context(|| format!("Failed to read consensus config for peer {peer_id}"))?;
2554
2555 let mut consensus_config: Value = serde_json::from_str(&consensus_config_content)
2556 .with_context(|| format!("Failed to parse consensus config for peer {peer_id}"))?;
2557
2558 let server_config = read_server_config(&peer_dir)
2560 .with_context(|| format!("Failed to read server config for peer {peer_id}"))?;
2561
2562 let consensus_config_modules = consensus_config["modules"]
2564 .as_object()
2565 .with_context(|| format!("No modules found in consensus config for peer {peer_id}"))?;
2566
2567 let existing_meta_consensus = consensus_config_modules
2569 .values()
2570 .find(|module_config| module_config["kind"].as_str() == Some("meta"));
2571
2572 let existing_meta_consensus = existing_meta_consensus
2573 .with_context(|| {
2574 format!("No existing meta module found in consensus config for peer {peer_id}")
2575 })?
2576 .clone();
2577
2578 let existing_meta_instance_id = server_config
2580 .consensus
2581 .modules
2582 .iter()
2583 .find(|(_, config)| config.kind.as_str() == "meta")
2584 .map(|(id, _)| *id)
2585 .with_context(|| {
2586 format!("No existing meta module found in private config for peer {peer_id}")
2587 })?;
2588
2589 let existing_meta_private = server_config
2590 .private
2591 .modules
2592 .get(&existing_meta_instance_id)
2593 .with_context(|| format!("Failed to get existing meta private config for peer {peer_id}"))?
2594 .clone();
2595
2596 let last_existing_module_id = consensus_config_modules
2598 .keys()
2599 .filter_map(|id| id.parse::<u32>().ok())
2600 .max()
2601 .unwrap_or(0);
2602
2603 let new_module_id = (last_existing_module_id + 1).to_string();
2604 let new_module_instance_id = ModuleInstanceId::from((last_existing_module_id + 1) as u16);
2605
2606 info!(
2607 "Adding new meta module with id {} for peer {} (copying existing meta module config)",
2608 new_module_id, peer_id
2609 );
2610
2611 if let Some(modules) = consensus_config["modules"].as_object_mut() {
2613 modules.insert(new_module_id.clone(), existing_meta_consensus);
2614 }
2615
2616 let mut updated_private_config = server_config.private.clone();
2618 updated_private_config
2619 .modules
2620 .insert(new_module_instance_id, existing_meta_private);
2621
2622 let updated_consensus_content = serde_json::to_string_pretty(&consensus_config)
2624 .with_context(|| format!("Failed to serialize consensus config for peer {peer_id}"))?;
2625
2626 write_overwrite_async(&consensus_config_path, updated_consensus_content)
2627 .await
2628 .with_context(|| format!("Failed to write consensus config for peer {peer_id}"))?;
2629
2630 let private_json_path = peer_dir.join("private.json");
2632 let private_config_content = serde_json::to_string_pretty(&updated_private_config)
2633 .with_context(|| format!("Failed to serialize private config for peer {peer_id}"))?;
2634
2635 write_overwrite_async(&private_json_path, private_config_content)
2636 .await
2637 .with_context(|| format!("Failed to write private config for peer {peer_id}"))?;
2638
2639 info!("Successfully modified configs for peer {}", peer_id);
2640 Ok(())
2641}
2642
2643pub async fn admin_auth_tests(dev_fed: DevFed) -> Result<()> {
2647 log_binary_versions().await?;
2648
2649 let DevFed { fed, .. } = dev_fed;
2650
2651 fed.await_all_peers().await?;
2654
2655 let client = fed.new_joined_client("admin-auth-test-client").await?;
2656
2657 let peer_id = 0;
2658
2659 info!(target: LOG_DEVIMINT, "Testing admin auth command stores credentials");
2660
2661 let auth_result = cmd!(
2664 client,
2665 "--our-id",
2666 &peer_id.to_string(),
2667 "--password",
2668 "pass",
2669 "admin",
2670 "auth",
2671 "--peer-id",
2672 &peer_id.to_string(),
2673 "--password",
2674 "pass",
2675 "--no-verify",
2676 "--force"
2677 )
2678 .out_json()
2679 .await
2680 .context("Admin auth command failed")?;
2681
2682 info!(target: LOG_DEVIMINT, ?auth_result, "Admin auth command completed");
2683
2684 assert_eq!(
2686 auth_result
2687 .get("peer_id")
2688 .and_then(serde_json::Value::as_u64),
2689 Some(peer_id as u64),
2690 "peer_id in response should match"
2691 );
2692 assert_eq!(
2693 auth_result
2694 .get("status")
2695 .and_then(serde_json::Value::as_str),
2696 Some("saved"),
2697 "status should be 'saved'"
2698 );
2699
2700 info!(target: LOG_DEVIMINT, "Testing that stored credentials are used automatically");
2701
2702 let status_result = cmd!(client, "admin", "status")
2705 .out_json()
2706 .await
2707 .context("Admin status command should succeed with stored credentials")?;
2708
2709 info!(target: LOG_DEVIMINT, ?status_result, "Admin status with stored credentials succeeded");
2710
2711 info!(target: LOG_DEVIMINT, "Testing that --force overwrites existing credentials");
2712
2713 let auth_result_force = cmd!(
2715 client,
2716 "--our-id",
2717 &peer_id.to_string(),
2718 "--password",
2719 "pass",
2720 "admin",
2721 "auth",
2722 "--peer-id",
2723 &peer_id.to_string(),
2724 "--password",
2725 "pass",
2726 "--no-verify",
2727 "--force"
2728 )
2729 .out_json()
2730 .await
2731 .context("Admin auth force overwrite failed")?;
2732
2733 assert_eq!(
2734 auth_result_force.get("status").and_then(|v| v.as_str()),
2735 Some("saved"),
2736 "Force overwrite should succeed"
2737 );
2738
2739 info!(target: LOG_DEVIMINT, "admin_auth_tests completed successfully");
2740
2741 Ok(())
2742}
2743
2744#[derive(Subcommand)]
2745pub enum LatencyTest {
2746 Reissue,
2747 LnSend,
2748 LnReceive,
2749 FmPay,
2750 Restore,
2751}
2752
2753#[derive(Subcommand)]
2754pub enum UpgradeTest {
2755 Fedimintd {
2756 #[arg(long, trailing_var_arg = true, num_args=1..)]
2757 paths: Vec<PathBuf>,
2758 },
2759 FedimintCli {
2760 #[arg(long, trailing_var_arg = true, num_args=1..)]
2761 paths: Vec<PathBuf>,
2762 },
2763 Gatewayd {
2764 #[arg(long, trailing_var_arg = true, num_args=1..)]
2765 gatewayd_paths: Vec<PathBuf>,
2766 #[arg(long, trailing_var_arg = true, num_args=1..)]
2767 gateway_cli_paths: Vec<PathBuf>,
2768 },
2769}
2770
2771#[derive(Subcommand)]
2772pub enum TestCmd {
2773 LatencyTests {
2776 #[clap(subcommand)]
2777 r#type: LatencyTest,
2778
2779 #[arg(long, default_value = "10")]
2780 iterations: usize,
2781 },
2782 ReconnectTest,
2785 CliTests,
2787 GuardianMetadataTests,
2789 LoadTestToolTest,
2792 LightningReconnectTest,
2795 GatewayRebootTest,
2798 RecoverytoolTests,
2800 LnurlRecoveryTest,
2802 WasmTestSetup {
2804 #[arg(long, trailing_var_arg = true, allow_hyphen_values = true, num_args=1..)]
2805 exec: Option<Vec<ffi::OsString>>,
2806 },
2807 GuardianBackup,
2809 CannotReplayTransaction,
2811 TestOfflineClientInitialization,
2814 TestClientConfigChangeDetection,
2817 TestAdminAuth,
2819 UpgradeTests {
2821 #[clap(subcommand)]
2822 binary: UpgradeTest,
2823 #[arg(long)]
2824 lnv2: String,
2825 },
2826}
2827
2828pub async fn handle_command(cmd: TestCmd, common_args: CommonArgs) -> Result<()> {
2829 match cmd {
2830 TestCmd::WasmTestSetup { exec } => {
2831 let (process_mgr, task_group) = setup(common_args).await?;
2832 let main = {
2833 let task_group = task_group.clone();
2834 async move {
2835 let dev_fed = dev_fed(&process_mgr).await?;
2836 let gw_lnd = dev_fed.gw_lnd.clone();
2837 let fed = dev_fed.fed.clone();
2838 gw_lnd
2839 .client()
2840 .set_federation_routing_fee(dev_fed.fed.calculate_federation_id(), 0, 0)
2841 .await?;
2842 task_group.spawn_cancellable("faucet", async move {
2843 if let Err(err) = crate::faucet::run(
2844 &dev_fed,
2845 format!("0.0.0.0:{}", process_mgr.globals.FM_PORT_FAUCET),
2846 process_mgr.globals.FM_PORT_GW_LND,
2847 )
2848 .await
2849 {
2850 error!("Error spawning faucet: {err}");
2851 }
2852 });
2853 try_join!(fed.pegin_gateways(30_000, vec![&gw_lnd]), async {
2854 poll("waiting for faucet startup", || async {
2855 TcpStream::connect(format!(
2856 "127.0.0.1:{}",
2857 process_mgr.globals.FM_PORT_FAUCET
2858 ))
2859 .await
2860 .context("connect to faucet")
2861 .map_err(ControlFlow::Continue)
2862 })
2863 .await?;
2864 Ok(())
2865 },)?;
2866 if let Some(exec) = exec {
2867 exec_user_command(exec).await?;
2868 task_group.shutdown();
2869 }
2870 Ok::<_, anyhow::Error>(())
2871 }
2872 };
2873 cleanup_on_exit(main, task_group).await?;
2874 }
2875 TestCmd::LatencyTests { r#type, iterations } => {
2876 let (process_mgr, _) = setup(common_args).await?;
2877 let dev_fed = dev_fed(&process_mgr).await?;
2878 latency_tests(dev_fed, r#type, None, iterations, true).await?;
2879 }
2880 TestCmd::ReconnectTest => {
2881 let (process_mgr, _) = setup(common_args).await?;
2882 let dev_fed = dev_fed(&process_mgr).await?;
2883 reconnect_test(dev_fed, &process_mgr).await?;
2884 }
2885 TestCmd::CliTests => {
2886 let (process_mgr, _) = setup(common_args).await?;
2887 let dev_fed = dev_fed(&process_mgr).await?;
2888 cli_tests(dev_fed).await?;
2889 }
2890 TestCmd::GuardianMetadataTests => {
2891 let (process_mgr, _) = setup(common_args).await?;
2892 let dev_fed = dev_fed(&process_mgr).await?;
2893 guardian_metadata_tests(dev_fed).await?;
2894 }
2895 TestCmd::LoadTestToolTest => {
2896 unsafe { std::env::set_var(FM_DISABLE_BASE_FEES_ENV, "1") };
2898
2899 let (process_mgr, _) = setup(common_args).await?;
2900 let dev_fed = dev_fed(&process_mgr).await?;
2901 cli_load_test_tool_test(dev_fed).await?;
2902 }
2903 TestCmd::LightningReconnectTest => {
2904 let (process_mgr, _) = setup(common_args).await?;
2905 let dev_fed = dev_fed(&process_mgr).await?;
2906 lightning_gw_reconnect_test(dev_fed, &process_mgr).await?;
2907 }
2908 TestCmd::GatewayRebootTest => {
2909 let (process_mgr, _) = setup(common_args).await?;
2910 let dev_fed = dev_fed(&process_mgr).await?;
2911 gw_reboot_test(dev_fed, &process_mgr).await?;
2912 }
2913 TestCmd::RecoverytoolTests => {
2914 let (process_mgr, _) = setup(common_args).await?;
2915 let dev_fed = dev_fed(&process_mgr).await?;
2916 recoverytool_test(dev_fed).await?;
2917 }
2918 TestCmd::LnurlRecoveryTest => {
2919 let (process_mgr, _) = setup(common_args).await?;
2920 let dev_fed = dev_fed(&process_mgr).await?;
2921 lnurl_recovery_test(dev_fed).await?;
2922 }
2923 TestCmd::GuardianBackup => {
2924 let (process_mgr, _) = setup(common_args).await?;
2925 let dev_fed = dev_fed(&process_mgr).await?;
2926 guardian_backup_test(dev_fed, &process_mgr).await?;
2927 }
2928 TestCmd::CannotReplayTransaction => {
2929 let (process_mgr, _) = setup(common_args).await?;
2930 let dev_fed = dev_fed(&process_mgr).await?;
2931 cannot_replay_tx_test(dev_fed).await?;
2932 }
2933 TestCmd::TestOfflineClientInitialization => {
2934 let (process_mgr, _) = setup(common_args).await?;
2935 let dev_fed = dev_fed(&process_mgr).await?;
2936 test_offline_client_initialization(dev_fed, &process_mgr).await?;
2937 }
2938 TestCmd::TestClientConfigChangeDetection => {
2939 let (process_mgr, _) = setup(common_args).await?;
2940 let dev_fed = dev_fed(&process_mgr).await?;
2941 test_client_config_change_detection(dev_fed, &process_mgr).await?;
2942 }
2943 TestCmd::TestAdminAuth => {
2944 let fedimint_cli_version = crate::util::FedimintCli::version_or_default().await;
2946 let fedimintd_version = crate::util::FedimintdCmd::version_or_default().await;
2947
2948 if fedimint_cli_version < *VERSION_0_11_0_ALPHA
2949 || fedimintd_version < *VERSION_0_11_0_ALPHA
2950 {
2951 info!(target: LOG_DEVIMINT, "Skipping admin_auth_tests - requires v0.11.0-alpha or later");
2952 return Ok(());
2953 }
2954
2955 let (process_mgr, _) = setup(common_args).await?;
2956 let dev_fed = dev_fed(&process_mgr).await?;
2957 admin_auth_tests(dev_fed).await?;
2958 }
2959 TestCmd::UpgradeTests { binary, lnv2 } => {
2960 unsafe {
2962 std::env::set_var(FM_ENABLE_MODULE_LNV2_ENV, lnv2);
2963 std::env::set_var(FM_ENABLE_MODULE_LNV1_ENV, "1");
2967 std::env::set_var(FM_ENABLE_MODULE_MINT_ENV, "1");
2968 std::env::set_var(FM_ENABLE_MODULE_MINTV2_ENV, "0");
2969 std::env::set_var(FM_ENABLE_MODULE_WALLET_ENV, "1");
2970 std::env::set_var(FM_ENABLE_MODULE_WALLETV2_ENV, "0");
2971 }
2972 let (process_mgr, _) = setup(common_args).await?;
2973 Box::pin(upgrade_tests(&process_mgr, binary)).await?;
2974 }
2975 }
2976 Ok(())
2977}