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