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