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