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::LEGACY_HARDCODED_INSTANCE_ID_WALLET;
13use fedimint_core::encoding::{Decodable, Encodable};
14use fedimint_core::envs::{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_logging::LOG_DEVIMINT;
25use fedimint_testing_core::node_type::LightningNodeType;
26use futures::future::try_join_all;
27use serde_json::json;
28use tokio::net::TcpStream;
29use tokio::{fs, try_join};
30use tracing::{debug, error, info};
31
32use crate::cli::{CommonArgs, cleanup_on_exit, exec_user_command, setup};
33use crate::envs::{FM_DATA_DIR_ENV, FM_DEVIMINT_RUN_DEPRECATED_TESTS_ENV, FM_PASSWORD_ENV};
34use crate::federation::Client;
35use crate::gatewayd::LdkChainSource;
36use crate::util::{LoadTestTool, ProcessManager, poll};
37use crate::version_constants::{VERSION_0_7_0_ALPHA, VERSION_0_9_0_ALPHA};
38use crate::{DevFed, Gatewayd, LightningNode, Lnd, cmd, dev_fed, poll_eq};
39
40pub struct Stats {
41 pub min: Duration,
42 pub avg: Duration,
43 pub median: Duration,
44 pub p90: Duration,
45 pub max: Duration,
46 pub sum: Duration,
47}
48
49impl std::fmt::Display for Stats {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 write!(f, "min: {:.1}s", self.min.as_secs_f32())?;
52 write!(f, ", avg: {:.1}s", self.avg.as_secs_f32())?;
53 write!(f, ", median: {:.1}s", self.median.as_secs_f32())?;
54 write!(f, ", p90: {:.1}s", self.p90.as_secs_f32())?;
55 write!(f, ", max: {:.1}s", self.max.as_secs_f32())?;
56 write!(f, ", sum: {:.1}s", self.sum.as_secs_f32())?;
57 Ok(())
58 }
59}
60
61pub fn stats_for(mut v: Vec<Duration>) -> Stats {
62 assert!(!v.is_empty());
63 v.sort();
64 let n = v.len();
65 let min = v.first().unwrap().to_owned();
66 let max = v.iter().last().unwrap().to_owned();
67 let median = v[n / 2];
68 let sum: Duration = v.iter().sum();
69 let avg = sum / n as u32;
70 let p90 = v[(n as f32 * 0.9) as usize];
71 Stats {
72 min,
73 avg,
74 median,
75 p90,
76 max,
77 sum,
78 }
79}
80
81pub async fn log_binary_versions() -> Result<()> {
82 let fedimint_cli_version = cmd!(crate::util::get_fedimint_cli_path(), "--version")
83 .out_string()
84 .await?;
85 info!(?fedimint_cli_version);
86 let fedimint_cli_version_hash = cmd!(crate::util::get_fedimint_cli_path(), "version-hash")
87 .out_string()
88 .await?;
89 info!(?fedimint_cli_version_hash);
90 let gateway_cli_version = cmd!(crate::util::get_gateway_cli_path(), "--version")
91 .out_string()
92 .await?;
93 info!(?gateway_cli_version);
94 let gateway_cli_version_hash = cmd!(crate::util::get_gateway_cli_path(), "version-hash")
95 .out_string()
96 .await?;
97 info!(?gateway_cli_version_hash);
98 let fedimintd_version_hash = cmd!(crate::util::FedimintdCmd, "version-hash")
99 .out_string()
100 .await?;
101 info!(?fedimintd_version_hash);
102 let gatewayd_version_hash = cmd!(crate::util::Gatewayd, "version-hash")
103 .out_string()
104 .await?;
105 info!(?gatewayd_version_hash);
106 Ok(())
107}
108
109pub async fn latency_tests(
110 dev_fed: DevFed,
111 r#type: LatencyTest,
112 upgrade_clients: Option<&UpgradeClients>,
113 iterations: usize,
114 assert_thresholds: bool,
115) -> Result<()> {
116 log_binary_versions().await?;
117
118 let DevFed {
119 fed,
120 gw_lnd,
121 gw_ldk,
122 ..
123 } = dev_fed;
124
125 let max_p90_factor = 5.0;
126 let p90_median_factor = 10;
127
128 let client = match upgrade_clients {
129 Some(c) => match r#type {
130 LatencyTest::Reissue => c.reissue_client.clone(),
131 LatencyTest::LnSend => c.ln_send_client.clone(),
132 LatencyTest::LnReceive => c.ln_receive_client.clone(),
133 LatencyTest::FmPay => c.fm_pay_client.clone(),
134 LatencyTest::Restore => bail!("no reusable upgrade client for restore"),
135 },
136 None => fed.new_joined_client("latency-tests-client").await?,
137 };
138
139 let initial_balance_sats = 100_000_000;
140 fed.pegin_client(initial_balance_sats, &client).await?;
141
142 let lnd_gw_id = gw_lnd.gateway_id().await?;
143
144 match r#type {
145 LatencyTest::Reissue => {
146 info!("Testing latency of reissue");
147 let mut reissues = Vec::with_capacity(iterations);
148 let amount_per_iteration_msats =
149 ((initial_balance_sats * 1000 / iterations as u64).next_power_of_two() >> 1) - 1;
151 for _ in 0..iterations {
152 let notes = cmd!(client, "spend", amount_per_iteration_msats.to_string())
153 .out_json()
154 .await?["notes"]
155 .as_str()
156 .context("note must be a string")?
157 .to_owned();
158
159 let start_time = Instant::now();
160 cmd!(client, "reissue", notes).run().await?;
161 reissues.push(start_time.elapsed());
162 }
163 let reissue_stats = stats_for(reissues);
164 println!("### LATENCY REISSUE: {reissue_stats}");
165
166 if assert_thresholds {
167 assert!(reissue_stats.median < Duration::from_secs(10));
168 assert!(reissue_stats.p90 < reissue_stats.median * p90_median_factor);
169 assert!(
170 reissue_stats.max.as_secs_f64()
171 < reissue_stats.p90.as_secs_f64() * max_p90_factor
172 );
173 }
174 }
175 LatencyTest::LnSend => {
176 info!("Testing latency of ln send");
177 let mut ln_sends = Vec::with_capacity(iterations);
178 for _ in 0..iterations {
179 let invoice = gw_ldk.create_invoice(1_000_000).await?;
180 let start_time = Instant::now();
181 ln_pay(&client, invoice.to_string(), lnd_gw_id.clone()).await?;
182 gw_ldk
183 .wait_bolt11_invoice(invoice.payment_hash().consensus_encode_to_vec())
184 .await?;
185 ln_sends.push(start_time.elapsed());
186 }
187 let ln_sends_stats = stats_for(ln_sends);
188 println!("### LATENCY LN SEND: {ln_sends_stats}");
189
190 if assert_thresholds {
191 assert!(ln_sends_stats.median < Duration::from_secs(10));
192 assert!(ln_sends_stats.p90 < ln_sends_stats.median * p90_median_factor);
193 assert!(
194 ln_sends_stats.max.as_secs_f64()
195 < ln_sends_stats.p90.as_secs_f64() * max_p90_factor
196 );
197 }
198 }
199 LatencyTest::LnReceive => {
200 info!("Testing latency of ln receive");
201 let mut ln_receives = Vec::with_capacity(iterations);
202
203 let invoice = gw_ldk.create_invoice(10_000_000).await?;
205 ln_pay(&client, invoice.to_string(), lnd_gw_id.clone()).await?;
206
207 for _ in 0..iterations {
208 let invoice = ln_invoice(
209 &client,
210 Amount::from_msats(100_000),
211 "latency-over-lnd-gw".to_string(),
212 lnd_gw_id.clone(),
213 )
214 .await?
215 .invoice;
216
217 let start_time = Instant::now();
218 gw_ldk
219 .pay_invoice(
220 Bolt11Invoice::from_str(&invoice).expect("Could not parse invoice"),
221 )
222 .await?;
223 ln_receives.push(start_time.elapsed());
224 }
225 let ln_receives_stats = stats_for(ln_receives);
226 println!("### LATENCY LN RECV: {ln_receives_stats}");
227
228 if assert_thresholds {
229 assert!(ln_receives_stats.median < Duration::from_secs(10));
230 assert!(ln_receives_stats.p90 < ln_receives_stats.median * p90_median_factor);
231 assert!(
232 ln_receives_stats.max.as_secs_f64()
233 < ln_receives_stats.p90.as_secs_f64() * max_p90_factor
234 );
235 }
236 }
237 LatencyTest::FmPay => {
238 info!("Testing latency of internal payments within a federation");
239 let mut fm_internal_pay = Vec::with_capacity(iterations);
240 let sender = fed.new_joined_client("internal-swap-sender").await?;
241 fed.pegin_client(10_000_000, &sender).await?;
242 for _ in 0..iterations {
243 let recv = cmd!(
244 client,
245 "ln-invoice",
246 "--amount=1000000msat",
247 "--description=internal-swap-invoice",
248 "--force-internal"
249 )
250 .out_json()
251 .await?;
252
253 let invoice = recv["invoice"]
254 .as_str()
255 .context("invoice must be string")?
256 .to_owned();
257 let recv_op = recv["operation_id"]
258 .as_str()
259 .context("operation id must be string")?
260 .to_owned();
261
262 let start_time = Instant::now();
263 cmd!(sender, "ln-pay", invoice, "--force-internal")
264 .run()
265 .await?;
266
267 cmd!(client, "await-invoice", recv_op).run().await?;
268 fm_internal_pay.push(start_time.elapsed());
269 }
270 let fm_pay_stats = stats_for(fm_internal_pay);
271
272 println!("### LATENCY FM PAY: {fm_pay_stats}");
273
274 if assert_thresholds {
275 assert!(fm_pay_stats.median < Duration::from_secs(15));
276 assert!(fm_pay_stats.p90 < fm_pay_stats.median * p90_median_factor);
277 assert!(
278 fm_pay_stats.max.as_secs_f64()
279 < fm_pay_stats.p90.as_secs_f64() * max_p90_factor
280 );
281 }
282 }
283 LatencyTest::Restore => {
284 info!("Testing latency of restore");
285 let backup_secret = cmd!(client, "print-secret").out_json().await?["secret"]
286 .as_str()
287 .map(ToOwned::to_owned)
288 .unwrap();
289 if !is_env_var_set(FM_DEVIMINT_RUN_DEPRECATED_TESTS_ENV) {
290 info!("Skipping tests, as in previous versions restore was very slow to test");
291 return Ok(());
292 }
293
294 let start_time = Instant::now();
295 let restore_client = Client::create("restore").await?;
296 cmd!(
297 restore_client,
298 "restore",
299 "--mnemonic",
300 &backup_secret,
301 "--invite-code",
302 fed.invite_code()?
303 )
304 .run()
305 .await?;
306 let restore_time = start_time.elapsed();
307
308 println!("### LATENCY RESTORE: {restore_time:?}");
309
310 if assert_thresholds {
311 if crate::util::is_backwards_compatibility_test() {
312 assert!(restore_time < Duration::from_secs(160));
313 } else {
314 assert!(restore_time < Duration::from_secs(30));
315 }
316 }
317 }
318 }
319
320 Ok(())
321}
322
323#[allow(clippy::struct_field_names)]
324pub struct UpgradeClients {
326 reissue_client: Client,
327 ln_send_client: Client,
328 ln_receive_client: Client,
329 fm_pay_client: Client,
330}
331
332async fn stress_test_fed(dev_fed: &DevFed, clients: Option<&UpgradeClients>) -> anyhow::Result<()> {
333 use futures::FutureExt;
334
335 let assert_thresholds = false;
338
339 let iterations = 1;
342
343 let restore_test = if clients.is_some() {
346 futures::future::ok(()).right_future()
347 } else {
348 latency_tests(
349 dev_fed.clone(),
350 LatencyTest::Restore,
351 clients,
352 iterations,
353 assert_thresholds,
354 )
355 .left_future()
356 };
357
358 latency_tests(
361 dev_fed.clone(),
362 LatencyTest::Reissue,
363 clients,
364 iterations,
365 assert_thresholds,
366 )
367 .await?;
368
369 latency_tests(
370 dev_fed.clone(),
371 LatencyTest::LnSend,
372 clients,
373 iterations,
374 assert_thresholds,
375 )
376 .await?;
377
378 latency_tests(
379 dev_fed.clone(),
380 LatencyTest::LnReceive,
381 clients,
382 iterations,
383 assert_thresholds,
384 )
385 .await?;
386
387 latency_tests(
388 dev_fed.clone(),
389 LatencyTest::FmPay,
390 clients,
391 iterations,
392 assert_thresholds,
393 )
394 .await?;
395
396 restore_test.await?;
397
398 Ok(())
399}
400
401pub async fn upgrade_tests(process_mgr: &ProcessManager, binary: UpgradeTest) -> Result<()> {
402 match binary {
403 UpgradeTest::Fedimintd { paths } => {
404 if let Some(oldest_fedimintd) = paths.first() {
405 unsafe { std::env::set_var("FM_FEDIMINTD_BASE_EXECUTABLE", oldest_fedimintd) };
407 } else {
408 bail!("Must provide at least 1 binary path");
409 }
410
411 let fedimintd_version = crate::util::FedimintdCmd::version_or_default().await;
412 info!(
413 "running first stress test for fedimintd version: {}",
414 fedimintd_version
415 );
416
417 let mut dev_fed = dev_fed(process_mgr).await?;
418 let client = dev_fed.fed.new_joined_client("test-client").await?;
419 try_join!(stress_test_fed(&dev_fed, None), client.wait_session())?;
420
421 for path in paths.iter().skip(1) {
422 dev_fed.fed.restart_all_with_bin(process_mgr, path).await?;
423
424 try_join!(stress_test_fed(&dev_fed, None), client.wait_session())?;
426
427 let fedimintd_version = crate::util::FedimintdCmd::version_or_default().await;
428 info!(
429 "### fedimintd passed stress test for version {}",
430 fedimintd_version
431 );
432 }
433 info!("## fedimintd upgraded all binaries successfully");
434 }
435 UpgradeTest::FedimintCli { paths } => {
436 let set_fedimint_cli_path = |path: &PathBuf| {
437 unsafe { std::env::set_var("FM_FEDIMINT_CLI_BASE_EXECUTABLE", path) };
439 let fm_mint_client: String = format!(
440 "{fedimint_cli} --data-dir {datadir}",
441 fedimint_cli = crate::util::get_fedimint_cli_path().join(" "),
442 datadir = crate::vars::utf8(&process_mgr.globals.FM_CLIENT_DIR)
443 );
444 unsafe { std::env::set_var("FM_MINT_CLIENT", fm_mint_client) };
446 };
447
448 if let Some(oldest_fedimint_cli) = paths.first() {
449 set_fedimint_cli_path(oldest_fedimint_cli);
450 } else {
451 bail!("Must provide at least 1 binary path");
452 }
453
454 let fedimint_cli_version = crate::util::FedimintCli::version_or_default().await;
455 info!(
456 "running first stress test for fedimint-cli version: {}",
457 fedimint_cli_version
458 );
459
460 let dev_fed = dev_fed(process_mgr).await?;
461
462 let wait_session_client = dev_fed.fed.new_joined_client("wait-session-client").await?;
463 let reusable_upgrade_clients = UpgradeClients {
464 reissue_client: dev_fed.fed.new_joined_client("reissue-client").await?,
465 ln_send_client: dev_fed.fed.new_joined_client("ln-send-client").await?,
466 ln_receive_client: dev_fed.fed.new_joined_client("ln-receive-client").await?,
467 fm_pay_client: dev_fed.fed.new_joined_client("fm-pay-client").await?,
468 };
469
470 try_join!(
471 stress_test_fed(&dev_fed, Some(&reusable_upgrade_clients)),
472 wait_session_client.wait_session()
473 )?;
474
475 for path in paths.iter().skip(1) {
476 set_fedimint_cli_path(path);
477 let fedimint_cli_version = crate::util::FedimintCli::version_or_default().await;
478 info!("upgraded fedimint-cli to version: {}", fedimint_cli_version);
479 try_join!(
480 stress_test_fed(&dev_fed, Some(&reusable_upgrade_clients)),
481 wait_session_client.wait_session()
482 )?;
483 info!(
484 "### fedimint-cli passed stress test for version {}",
485 fedimint_cli_version
486 );
487 }
488 info!("## fedimint-cli upgraded all binaries successfully");
489 }
490 UpgradeTest::Gatewayd {
491 gatewayd_paths,
492 gateway_cli_paths,
493 } => {
494 if let Some(oldest_gatewayd) = gatewayd_paths.first() {
495 unsafe { std::env::set_var("FM_GATEWAYD_BASE_EXECUTABLE", oldest_gatewayd) };
497 } else {
498 bail!("Must provide at least 1 gatewayd path");
499 }
500
501 if let Some(oldest_gateway_cli) = gateway_cli_paths.first() {
502 unsafe { std::env::set_var("FM_GATEWAY_CLI_BASE_EXECUTABLE", oldest_gateway_cli) };
504 } else {
505 bail!("Must provide at least 1 gateway-cli path");
506 }
507
508 let gatewayd_version = crate::util::Gatewayd::version_or_default().await;
509 let gateway_cli_version = crate::util::GatewayCli::version_or_default().await;
510 info!(
511 ?gatewayd_version,
512 ?gateway_cli_version,
513 "running first stress test for gateway",
514 );
515
516 let mut dev_fed = dev_fed(process_mgr).await?;
517 let client = dev_fed.fed.new_joined_client("test-client").await?;
518 try_join!(stress_test_fed(&dev_fed, None), client.wait_session())?;
519
520 for i in 1..gatewayd_paths.len() {
521 info!(
522 "running stress test with gatewayd path {:?}",
523 gatewayd_paths.get(i)
524 );
525 let new_gatewayd_path = gatewayd_paths.get(i).expect("Not enough gatewayd paths");
526 let new_gateway_cli_path = gateway_cli_paths
527 .get(i)
528 .expect("Not enough gateway-cli paths");
529
530 let gateways = vec![&mut dev_fed.gw_lnd];
531
532 try_join_all(gateways.into_iter().map(|gateway| {
533 gateway.restart_with_bin(process_mgr, new_gatewayd_path, new_gateway_cli_path)
534 }))
535 .await?;
536
537 dev_fed.fed.await_gateways_registered().await?;
538 try_join!(stress_test_fed(&dev_fed, None), client.wait_session())?;
539 let gatewayd_version = crate::util::Gatewayd::version_or_default().await;
540 let gateway_cli_version = crate::util::GatewayCli::version_or_default().await;
541 info!(
542 ?gatewayd_version,
543 ?gateway_cli_version,
544 "### gateway passed stress test for version",
545 );
546 }
547
548 info!("## gatewayd upgraded all binaries successfully");
549 }
550 }
551 Ok(())
552}
553
554pub async fn cli_tests(dev_fed: DevFed) -> Result<()> {
555 log_binary_versions().await?;
556 let data_dir = env::var(FM_DATA_DIR_ENV)?;
557
558 let DevFed {
559 bitcoind,
560 lnd,
561 fed,
562 gw_lnd,
563 gw_ldk,
564 ..
565 } = dev_fed;
566
567 let fedimintd_version = crate::util::FedimintdCmd::version_or_default().await;
568
569 let client = fed.new_joined_client("cli-tests-client").await?;
570 let lnd_gw_id = gw_lnd.gateway_id().await?;
571
572 cmd!(
573 client,
574 "dev",
575 "config-decrypt",
576 "--in-file={data_dir}/fedimintd-default-0/private.encrypt",
577 "--out-file={data_dir}/fedimintd-default-0/config-plaintext.json"
578 )
579 .env(FM_PASSWORD_ENV, "pass")
580 .run()
581 .await?;
582
583 cmd!(
584 client,
585 "dev",
586 "config-encrypt",
587 "--in-file={data_dir}/fedimintd-default-0/config-plaintext.json",
588 "--out-file={data_dir}/fedimintd-default-0/config-2"
589 )
590 .env(FM_PASSWORD_ENV, "pass-foo")
591 .run()
592 .await?;
593
594 cmd!(
595 client,
596 "dev",
597 "config-decrypt",
598 "--in-file={data_dir}/fedimintd-default-0/config-2",
599 "--out-file={data_dir}/fedimintd-default-0/config-plaintext-2.json"
600 )
601 .env(FM_PASSWORD_ENV, "pass-foo")
602 .run()
603 .await?;
604
605 let plaintext_one = fs::read_to_string(format!(
606 "{data_dir}/fedimintd-default-0/config-plaintext.json"
607 ))
608 .await?;
609 let plaintext_two = fs::read_to_string(format!(
610 "{data_dir}/fedimintd-default-0/config-plaintext-2.json"
611 ))
612 .await?;
613 anyhow::ensure!(
614 plaintext_one == plaintext_two,
615 "config-decrypt/encrypt failed"
616 );
617
618 fed.pegin_gateways(10_000_000, vec![&gw_lnd]).await?;
619
620 let fed_id = fed.calculate_federation_id();
621 let invite = fed.invite_code()?;
622
623 gw_lnd
625 .set_federation_routing_fee(fed_id.clone(), 0, 0)
626 .await?;
627 cmd!(client, "list-gateways").run().await?;
628
629 let invite_code = cmd!(client, "dev", "decode", "invite-code", invite.clone())
630 .out_json()
631 .await?;
632
633 let encode_invite_output = cmd!(
634 client,
635 "dev",
636 "encode",
637 "invite-code",
638 format!("--url={}", invite_code["url"].as_str().unwrap()),
639 "--federation_id={fed_id}",
640 "--peer=0"
641 )
642 .out_json()
643 .await?;
644
645 anyhow::ensure!(
646 encode_invite_output["invite_code"]
647 .as_str()
648 .expect("invite_code must be a string")
649 == invite,
650 "failed to decode and encode the client invite code",
651 );
652
653 info!("Testing LND can pay LDK directly");
657 let invoice = gw_ldk.create_invoice(1_200_000).await?;
658 lnd.pay_bolt11_invoice(invoice.to_string()).await?;
659 gw_ldk
660 .wait_bolt11_invoice(invoice.payment_hash().consensus_encode_to_vec())
661 .await?;
662
663 info!("Testing LDK can pay LND directly");
665 let (invoice, payment_hash) = lnd.invoice(1_000_000).await?;
666 gw_ldk
667 .pay_invoice(Bolt11Invoice::from_str(&invoice).expect("Could not parse invoice"))
668 .await?;
669 gw_lnd.wait_bolt11_invoice(payment_hash).await?;
670
671 let config = cmd!(client, "config").out_json().await?;
673 let guardian_count = config["global"]["api_endpoints"].as_object().unwrap().len();
674 let descriptor = config["modules"]["2"]["peg_in_descriptor"]
675 .as_str()
676 .unwrap()
677 .to_owned();
678
679 info!("Testing generated descriptor for {guardian_count} guardian federation");
680 if guardian_count == 1 {
681 assert!(descriptor.contains("wpkh("));
682 } else {
683 assert!(descriptor.contains("wsh(sortedmulti("));
684 }
685
686 info!("Testing Client");
688 info!("Testing reissuing e-cash");
690 const CLIENT_START_AMOUNT: u64 = 5_000_000_000;
691 const CLIENT_SPEND_AMOUNT: u64 = 1_100_000;
692
693 let initial_client_balance = client.balance().await?;
694 assert_eq!(initial_client_balance, 0);
695
696 fed.pegin_client(CLIENT_START_AMOUNT / 1000, &client)
697 .await?;
698
699 info!("Testing spending from client");
701 let notes = cmd!(client, "spend", CLIENT_SPEND_AMOUNT)
702 .out_json()
703 .await?
704 .get("notes")
705 .expect("Output didn't contain e-cash notes")
706 .as_str()
707 .unwrap()
708 .to_owned();
709
710 let client_post_spend_balance = client.balance().await?;
711 assert_eq!(
712 client_post_spend_balance,
713 CLIENT_START_AMOUNT - CLIENT_SPEND_AMOUNT
714 );
715
716 cmd!(client, "reissue", notes).out_json().await?;
718
719 let client_post_spend_balance = client.balance().await?;
720 assert_eq!(client_post_spend_balance, CLIENT_START_AMOUNT);
721
722 let reissue_amount: u64 = 409_600;
723
724 info!("Testing reissuing e-cash after spending");
726 let _notes = cmd!(client, "spend", CLIENT_SPEND_AMOUNT)
727 .out_json()
728 .await?
729 .as_object()
730 .unwrap()
731 .get("notes")
732 .expect("Output didn't contain e-cash notes")
733 .as_str()
734 .unwrap();
735
736 let reissue_notes = cmd!(client, "spend", reissue_amount).out_json().await?["notes"]
737 .as_str()
738 .map(ToOwned::to_owned)
739 .unwrap();
740 let client_reissue_amt = cmd!(client, "reissue", reissue_notes)
741 .out_json()
742 .await?
743 .as_u64()
744 .unwrap();
745 assert_eq!(client_reissue_amt, reissue_amount);
746
747 info!("Testing reissuing e-cash via module commands");
749 let reissue_notes = cmd!(client, "spend", reissue_amount).out_json().await?["notes"]
750 .as_str()
751 .map(ToOwned::to_owned)
752 .unwrap();
753 let client_reissue_amt = cmd!(client, "module", "mint", "reissue", reissue_notes)
754 .out_json()
755 .await?
756 .as_u64()
757 .unwrap();
758 assert_eq!(client_reissue_amt, reissue_amount);
759
760 info!("Testing LND gateway");
762
763 info!("Testing outgoing payment from client to LDK via LND gateway");
765 let initial_lnd_gateway_balance = gw_lnd.ecash_balance(fed_id.clone()).await?;
766 let invoice = gw_ldk.create_invoice(2_000_000).await?;
767 ln_pay(&client, invoice.to_string(), lnd_gw_id.clone()).await?;
768 let fed_id = fed.calculate_federation_id();
769 gw_ldk
770 .wait_bolt11_invoice(invoice.payment_hash().consensus_encode_to_vec())
771 .await?;
772
773 let final_lnd_outgoing_client_balance = client.balance().await?;
775 let final_lnd_outgoing_gateway_balance = gw_lnd.ecash_balance(fed_id.clone()).await?;
776 anyhow::ensure!(
777 final_lnd_outgoing_gateway_balance - initial_lnd_gateway_balance == 2_000_000,
778 "LND Gateway balance changed by {} on LND outgoing payment, expected 2_000_000",
779 (final_lnd_outgoing_gateway_balance - initial_lnd_gateway_balance)
780 );
781
782 info!("Testing incoming payment from LDK to client via LND gateway");
784 let recv = ln_invoice(
785 &client,
786 Amount::from_msats(1_300_000),
787 "incoming-over-lnd-gw".to_string(),
788 lnd_gw_id,
789 )
790 .await?;
791 let invoice = recv.invoice;
792 gw_ldk
793 .pay_invoice(Bolt11Invoice::from_str(&invoice).expect("Could not parse invoice"))
794 .await?;
795
796 info!("Testing receiving ecash notes");
798 let operation_id = recv.operation_id;
799 cmd!(client, "await-invoice", operation_id.fmt_full())
800 .run()
801 .await?;
802
803 let final_lnd_incoming_client_balance = client.balance().await?;
805 let final_lnd_incoming_gateway_balance = gw_lnd.ecash_balance(fed_id.clone()).await?;
806 anyhow::ensure!(
807 final_lnd_incoming_client_balance - final_lnd_outgoing_client_balance == 1_300_000,
808 "Client balance changed by {} on LND incoming payment, expected 1_300_000",
809 (final_lnd_incoming_client_balance - final_lnd_outgoing_client_balance)
810 );
811 anyhow::ensure!(
812 final_lnd_outgoing_gateway_balance - final_lnd_incoming_gateway_balance == 1_300_000,
813 "LND Gateway balance changed by {} on LND incoming payment, expected 1_300_000",
814 (final_lnd_outgoing_gateway_balance - final_lnd_incoming_gateway_balance)
815 );
816
817 info!("Testing client deposit");
822 let initial_walletng_balance = client.balance().await?;
823
824 fed.pegin_client(100_000, &client).await?; let post_deposit_walletng_balance = client.balance().await?;
827
828 assert_eq!(
829 post_deposit_walletng_balance,
830 initial_walletng_balance + 100_000_000 );
832
833 info!("Testing client withdraw");
835
836 let initial_walletng_balance = client.balance().await?;
837
838 let address = bitcoind.get_new_address().await?;
839 let withdraw_res = cmd!(
840 client,
841 "withdraw",
842 "--address",
843 &address,
844 "--amount",
845 "50000 sat"
846 )
847 .out_json()
848 .await?;
849
850 let txid: Txid = withdraw_res["txid"].as_str().unwrap().parse().unwrap();
851 let fees_sat = withdraw_res["fees_sat"].as_u64().unwrap();
852
853 let tx_hex = bitcoind.poll_get_transaction(txid).await?;
854
855 let tx = bitcoin::Transaction::consensus_decode_hex(&tx_hex, &ModuleRegistry::default())?;
856 assert!(
857 tx.output
858 .iter()
859 .any(|o| o.script_pubkey == address.script_pubkey() && o.value.to_sat() == 50000)
860 );
861
862 let post_withdraw_walletng_balance = client.balance().await?;
863 let expected_wallet_balance = initial_walletng_balance - 50_000_000 - (fees_sat * 1000);
864
865 assert_eq!(post_withdraw_walletng_balance, expected_wallet_balance);
866
867 let peer_0_fedimintd_version = cmd!(client, "dev", "peer-version", "--peer-id", "0")
869 .out_json()
870 .await?
871 .get("version")
872 .expect("Output didn't contain version")
873 .as_str()
874 .unwrap()
875 .to_owned();
876
877 assert_eq!(
878 semver::Version::parse(&peer_0_fedimintd_version)?,
879 fedimintd_version
880 );
881
882 info!("Checking initial announcements...");
883
884 retry(
885 "Check initial announcements",
886 aggressive_backoff(),
887 || async {
888 let initial_announcements =
890 serde_json::from_value::<BTreeMap<PeerId, SignedApiAnnouncement>>(
891 cmd!(client, "dev", "api-announcements",).out_json().await?,
892 )
893 .expect("failed to parse API announcements");
894
895 if fed.members.len() != initial_announcements.len() {
896 bail!(
897 "Not all announcements ready: {}",
898 initial_announcements.len()
899 )
900 }
901
902 cmd!(client, "dev", "wait", "3").run().await?;
904
905 if !initial_announcements
906 .values()
907 .all(|announcement| announcement.api_announcement.nonce == 0)
908 {
909 bail!("Not all announcements have their initial value");
910 }
911 Ok(())
912 },
913 )
914 .await?;
915
916 const NEW_API_URL: &str = "ws://127.0.0.1:4242";
917 let new_announcement = serde_json::from_value::<SignedApiAnnouncement>(
918 cmd!(
919 client,
920 "--our-id",
921 "0",
922 "--password",
923 "pass",
924 "admin",
925 "sign-api-announcement",
926 NEW_API_URL
927 )
928 .out_json()
929 .await?,
930 )
931 .expect("Couldn't parse signed announcement");
932
933 assert_eq!(
934 new_announcement.api_announcement.nonce, 1,
935 "Nonce did not increment correctly"
936 );
937
938 info!("Testing if the client syncs the announcement");
939 let announcement = poll("Waiting for the announcement to propagate", || async {
940 cmd!(client, "dev", "wait", "1")
941 .run()
942 .await
943 .map_err(ControlFlow::Break)?;
944
945 let new_announcements_peer2 =
946 serde_json::from_value::<BTreeMap<PeerId, SignedApiAnnouncement>>(
947 cmd!(client, "dev", "api-announcements",)
948 .out_json()
949 .await
950 .map_err(ControlFlow::Break)?,
951 )
952 .expect("failed to parse API announcements");
953
954 let announcement = new_announcements_peer2[&PeerId::from(0)]
955 .api_announcement
956 .clone();
957 if announcement.nonce == 1 {
958 Ok(announcement)
959 } else {
960 Err(ControlFlow::Continue(anyhow!(
961 "Haven't received updated announcement yet"
962 )))
963 }
964 })
965 .await?;
966
967 assert_eq!(
968 announcement.api_url,
969 NEW_API_URL.parse().expect("valid URL")
970 );
971
972 Ok(())
973}
974
975pub async fn cli_load_test_tool_test(dev_fed: DevFed) -> Result<()> {
976 log_binary_versions().await?;
977 let data_dir = env::var(FM_DATA_DIR_ENV)?;
978 let load_test_temp = PathBuf::from(data_dir).join("load-test-temp");
979 dev_fed
980 .fed
981 .pegin_client(10_000, dev_fed.fed.internal_client().await?)
982 .await?;
983 let invite_code = dev_fed.fed.invite_code()?;
984 dev_fed
985 .gw_lnd
986 .set_federation_routing_fee(dev_fed.fed.calculate_federation_id(), 0, 0)
987 .await?;
988 run_standard_load_test(&load_test_temp, &invite_code).await?;
989 run_ln_circular_load_test(&load_test_temp, &invite_code).await?;
990 Ok(())
991}
992
993pub async fn run_standard_load_test(
994 load_test_temp: &Path,
995 invite_code: &str,
996) -> anyhow::Result<()> {
997 let output = cmd!(
998 LoadTestTool,
999 "--archive-dir",
1000 load_test_temp.display(),
1001 "--users",
1002 "1",
1003 "load-test",
1004 "--notes-per-user",
1005 "1",
1006 "--generate-invoice-with",
1007 "ldk-lightning-cli",
1008 "--invite-code",
1009 invite_code
1010 )
1011 .out_string()
1012 .await?;
1013 println!("{output}");
1014 anyhow::ensure!(
1015 output.contains("2 reissue_notes"),
1016 "reissued different number notes than expected"
1017 );
1018 anyhow::ensure!(
1019 output.contains("1 gateway_pay_invoice"),
1020 "paid different number of invoices than expected"
1021 );
1022 Ok(())
1023}
1024
1025pub async fn run_ln_circular_load_test(
1026 load_test_temp: &Path,
1027 invite_code: &str,
1028) -> anyhow::Result<()> {
1029 info!("Testing ln-circular-load-test with 'two-gateways' strategy");
1030 let output = cmd!(
1031 LoadTestTool,
1032 "--archive-dir",
1033 load_test_temp.display(),
1034 "--users",
1035 "1",
1036 "ln-circular-load-test",
1037 "--strategy",
1038 "two-gateways",
1039 "--test-duration-secs",
1040 "2",
1041 "--invite-code",
1042 invite_code
1043 )
1044 .out_string()
1045 .await?;
1046 println!("{output}");
1047 anyhow::ensure!(
1048 output.contains("gateway_create_invoice"),
1049 "missing invoice creation"
1050 );
1051 anyhow::ensure!(
1052 output.contains("gateway_pay_invoice_success"),
1053 "missing invoice payment"
1054 );
1055 anyhow::ensure!(
1056 output.contains("gateway_payment_received_success"),
1057 "missing received payment"
1058 );
1059
1060 info!("Testing ln-circular-load-test with 'partner-ping-pong' strategy");
1061 let output = cmd!(
1065 LoadTestTool,
1066 "--archive-dir",
1067 load_test_temp.display(),
1068 "--users",
1069 "1",
1070 "ln-circular-load-test",
1071 "--strategy",
1072 "partner-ping-pong",
1073 "--test-duration-secs",
1074 "6",
1075 "--invite-code",
1076 invite_code
1077 )
1078 .out_string()
1079 .await?;
1080 println!("{output}");
1081 anyhow::ensure!(
1082 output.contains("gateway_create_invoice"),
1083 "missing invoice creation"
1084 );
1085 anyhow::ensure!(
1086 output.contains("gateway_payment_received_success"),
1087 "missing received payment"
1088 );
1089
1090 info!("Testing ln-circular-load-test with 'self-payment' strategy");
1091 let output = cmd!(
1093 LoadTestTool,
1094 "--archive-dir",
1095 load_test_temp.display(),
1096 "--users",
1097 "1",
1098 "ln-circular-load-test",
1099 "--strategy",
1100 "self-payment",
1101 "--test-duration-secs",
1102 "2",
1103 "--invite-code",
1104 invite_code
1105 )
1106 .out_string()
1107 .await?;
1108 println!("{output}");
1109 anyhow::ensure!(
1110 output.contains("gateway_create_invoice"),
1111 "missing invoice creation"
1112 );
1113 anyhow::ensure!(
1114 output.contains("gateway_payment_received_success"),
1115 "missing received payment"
1116 );
1117 Ok(())
1118}
1119
1120pub async fn lightning_gw_reconnect_test(
1121 dev_fed: DevFed,
1122 process_mgr: &ProcessManager,
1123) -> Result<()> {
1124 log_binary_versions().await?;
1125
1126 let DevFed {
1127 bitcoind,
1128 lnd,
1129 fed,
1130 mut gw_lnd,
1131 gw_ldk,
1132 ..
1133 } = dev_fed;
1134
1135 let client = fed
1136 .new_joined_client("lightning-gw-reconnect-test-client")
1137 .await?;
1138
1139 info!("Pegging-in both gateways");
1140 fed.pegin_gateways(99_999, vec![&gw_lnd]).await?;
1141
1142 drop(lnd);
1144
1145 tracing::info!("Stopping LND");
1146 let mut info_cmd = cmd!(gw_lnd, "info");
1148 assert!(info_cmd.run().await.is_ok());
1149
1150 let ln_type = gw_lnd.ln.ln_type().to_string();
1153 gw_lnd.stop_lightning_node().await?;
1154 let lightning_info = info_cmd.out_json().await?;
1155 let lightning_pub_key: Option<String> =
1156 serde_json::from_value(lightning_info["lightning_pub_key"].clone())?;
1157
1158 assert!(lightning_pub_key.is_none());
1159
1160 tracing::info!("Restarting LND...");
1162 let new_lnd = Lnd::new(process_mgr, bitcoind.clone()).await?;
1163 gw_lnd.set_lightning_node(LightningNode::Lnd(new_lnd.clone()));
1164
1165 tracing::info!("Retrying info...");
1166 const MAX_RETRIES: usize = 30;
1167 const RETRY_INTERVAL: Duration = Duration::from_secs(1);
1168
1169 for i in 0..MAX_RETRIES {
1170 match do_try_create_and_pay_invoice(&gw_lnd, &client, &gw_ldk).await {
1171 Ok(()) => break,
1172 Err(e) => {
1173 if i == MAX_RETRIES - 1 {
1174 return Err(e);
1175 }
1176 tracing::debug!(
1177 "Pay invoice for gateway {} failed with {e:?}, retrying in {} seconds (try {}/{MAX_RETRIES})",
1178 ln_type,
1179 RETRY_INTERVAL.as_secs(),
1180 i + 1,
1181 );
1182 fedimint_core::task::sleep_in_test(
1183 "paying invoice for gateway failed",
1184 RETRY_INTERVAL,
1185 )
1186 .await;
1187 }
1188 }
1189 }
1190
1191 info!(target: LOG_DEVIMINT, "lightning_reconnect_test: success");
1192 Ok(())
1193}
1194
1195pub async fn gw_reboot_test(dev_fed: DevFed, process_mgr: &ProcessManager) -> Result<()> {
1196 log_binary_versions().await?;
1197
1198 let DevFed {
1199 bitcoind,
1200 lnd,
1201 fed,
1202 gw_lnd,
1203 gw_ldk,
1204 gw_ldk_second,
1205 ..
1206 } = dev_fed;
1207
1208 let client = fed.new_joined_client("gw-reboot-test-client").await?;
1209 fed.pegin_client(10_000, &client).await?;
1210
1211 let block_height = bitcoind.get_block_count().await? - 1;
1213 try_join!(
1214 gw_lnd.wait_for_block_height(block_height),
1215 gw_ldk.wait_for_block_height(block_height),
1216 )?;
1217
1218 let (lnd_value, ldk_value) = try_join!(gw_lnd.get_info(), gw_ldk.get_info())?;
1220
1221 let lnd_gateway_id = gw_lnd.gateway_id().await?;
1223 let gw_ldk_name = gw_ldk.gw_name.clone();
1224 let gw_ldk_port = gw_ldk.gw_port;
1225 let gw_lightning_port = gw_ldk.ldk_port;
1226 drop(gw_lnd);
1227 drop(gw_ldk);
1228
1229 info!("Making payment while gateway is down");
1232 let initial_client_balance = client.balance().await?;
1233 let invoice = gw_ldk_second.create_invoice(3000).await?;
1234 ln_pay(&client, invoice.to_string(), lnd_gateway_id)
1235 .await
1236 .expect_err("Expected ln-pay to return error because the gateway is not online");
1237 let new_client_balance = client.balance().await?;
1238 anyhow::ensure!(initial_client_balance == new_client_balance);
1239
1240 info!("Rebooting gateways...");
1242 let chain_source = if crate::util::Gatewayd::version_or_default().await < *VERSION_0_7_0_ALPHA {
1243 LdkChainSource::Bitcoind
1244 } else {
1245 LdkChainSource::Esplora
1247 };
1248 let (new_gw_lnd, new_gw_ldk) = try_join!(
1249 Gatewayd::new(process_mgr, LightningNode::Lnd(lnd.clone())),
1250 Gatewayd::new(
1251 process_mgr,
1252 LightningNode::Ldk {
1253 name: gw_ldk_name,
1254 gw_port: gw_ldk_port,
1255 ldk_port: gw_lightning_port,
1256 chain_source,
1257 }
1258 )
1259 )?;
1260
1261 let lnd_gateway_id: fedimint_core::secp256k1::PublicKey =
1262 serde_json::from_value(lnd_value["gateway_id"].clone())?;
1263
1264 poll(
1265 "Waiting for LND Gateway Running state after reboot",
1266 || async {
1267 let mut new_lnd_cmd = cmd!(new_gw_lnd, "info");
1268 let lnd_value = new_lnd_cmd.out_json().await.map_err(ControlFlow::Continue)?;
1269 let reboot_gateway_state: String = serde_json::from_value(lnd_value["gateway_state"].clone()).context("invalid gateway state").map_err(ControlFlow::Break)?;
1270 let reboot_gateway_id: fedimint_core::secp256k1::PublicKey =
1271 serde_json::from_value(lnd_value["gateway_id"].clone()).context("invalid gateway id").map_err(ControlFlow::Break)?;
1272
1273 if reboot_gateway_state == "Running" {
1274 info!(target: LOG_DEVIMINT, "LND Gateway restarted, with auto-rejoin to federation");
1275 assert_eq!(lnd_gateway_id, reboot_gateway_id);
1277 return Ok(());
1278 }
1279 Err(ControlFlow::Continue(anyhow!("gateway not running")))
1280 },
1281 )
1282 .await?;
1283
1284 let ldk_gateway_id: fedimint_core::secp256k1::PublicKey =
1285 serde_json::from_value(ldk_value["gateway_id"].clone())?;
1286 poll(
1287 "Waiting for LDK Gateway Running state after reboot",
1288 || async {
1289 let mut new_ldk_cmd = cmd!(new_gw_ldk, "info");
1290 let ldk_value = new_ldk_cmd.out_json().await.map_err(ControlFlow::Continue)?;
1291 let reboot_gateway_state: String = serde_json::from_value(ldk_value["gateway_state"].clone()).context("invalid gateway state").map_err(ControlFlow::Break)?;
1292 let reboot_gateway_id: fedimint_core::secp256k1::PublicKey =
1293 serde_json::from_value(ldk_value["gateway_id"].clone()).context("invalid gateway id").map_err(ControlFlow::Break)?;
1294
1295 if reboot_gateway_state == "Running" {
1296 info!(target: LOG_DEVIMINT, "LDK Gateway restarted, with auto-rejoin to federation");
1297 assert_eq!(ldk_gateway_id, reboot_gateway_id);
1299 return Ok(());
1300 }
1301 Err(ControlFlow::Continue(anyhow!("gateway not running")))
1302 },
1303 )
1304 .await?;
1305
1306 info!(LOG_DEVIMINT, "gateway_reboot_test: success");
1307 Ok(())
1308}
1309
1310pub async fn do_try_create_and_pay_invoice(
1311 gw_lnd: &Gatewayd,
1312 client: &Client,
1313 gw_ldk: &Gatewayd,
1314) -> anyhow::Result<()> {
1315 poll("Waiting for info to succeed after restart", || async {
1319 let lightning_pub_key = cmd!(gw_lnd, "info")
1320 .out_json()
1321 .await
1322 .map_err(ControlFlow::Continue)?
1323 .get("lightning_pub_key")
1324 .map(|ln_pk| {
1325 serde_json::from_value::<Option<String>>(ln_pk.clone())
1326 .expect("could not parse lightning_pub_key")
1327 })
1328 .expect("missing lightning_pub_key");
1329
1330 poll_eq!(lightning_pub_key.is_some(), true)
1331 })
1332 .await?;
1333
1334 tracing::info!("Creating invoice....");
1335 let invoice = ln_invoice(
1336 client,
1337 Amount::from_msats(1000),
1338 "incoming-over-lnd-gw".to_string(),
1339 gw_lnd.gateway_id().await?,
1340 )
1341 .await?
1342 .invoice;
1343
1344 match &gw_lnd.ln.ln_type() {
1345 LightningNodeType::Lnd => {
1346 gw_ldk
1348 .pay_invoice(Bolt11Invoice::from_str(&invoice).expect("Could not parse invoice"))
1349 .await?;
1350 }
1351 LightningNodeType::Ldk => {
1352 unimplemented!("do_try_create_and_pay_invoice not implemented for LDK yet");
1353 }
1354 }
1355 Ok(())
1356}
1357
1358async fn ln_pay(client: &Client, invoice: String, gw_id: String) -> anyhow::Result<String> {
1359 let value = cmd!(client, "ln-pay", invoice, "--gateway-id", gw_id,)
1360 .out_json()
1361 .await?;
1362 let fedimint_cli_version = crate::util::FedimintCli::version_or_default().await;
1363 if fedimint_cli_version >= *VERSION_0_9_0_ALPHA {
1364 let outcome = serde_json::from_value::<LightningPaymentOutcome>(value)
1365 .expect("Could not deserialize Lightning payment outcome");
1366 match outcome {
1367 LightningPaymentOutcome::Success { preimage } => Ok(preimage),
1368 LightningPaymentOutcome::Failure { error_message } => {
1369 Err(anyhow!("Failed to pay lightning invoice: {error_message}"))
1370 }
1371 }
1372 } else {
1373 let operation_id = value["operation_id"]
1374 .as_str()
1375 .ok_or(anyhow!("Failed to pay invoice"))?
1376 .to_string();
1377 Ok(operation_id)
1378 }
1379}
1380
1381async fn ln_invoice(
1382 client: &Client,
1383 amount: Amount,
1384 description: String,
1385 gw_id: String,
1386) -> anyhow::Result<LnInvoiceResponse> {
1387 let ln_response_val = cmd!(
1388 client,
1389 "ln-invoice",
1390 "--amount",
1391 amount.msats,
1392 format!("--description='{description}'"),
1393 "--gateway-id",
1394 gw_id,
1395 )
1396 .out_json()
1397 .await?;
1398
1399 let ln_invoice_response: LnInvoiceResponse = serde_json::from_value(ln_response_val)?;
1400
1401 Ok(ln_invoice_response)
1402}
1403
1404pub async fn reconnect_test(dev_fed: DevFed, process_mgr: &ProcessManager) -> Result<()> {
1405 log_binary_versions().await?;
1406
1407 let DevFed {
1408 bitcoind, mut fed, ..
1409 } = dev_fed;
1410
1411 bitcoind.mine_blocks(110).await?;
1412 fed.await_block_sync().await?;
1413 fed.await_all_peers().await?;
1414
1415 fed.terminate_server(0).await?;
1417 fed.mine_then_wait_blocks_sync(100).await?;
1418
1419 fed.start_server(process_mgr, 0).await?;
1420 fed.mine_then_wait_blocks_sync(100).await?;
1421 fed.await_all_peers().await?;
1422 info!(target: LOG_DEVIMINT, "Server 0 successfully rejoined!");
1423 fed.mine_then_wait_blocks_sync(100).await?;
1424
1425 fed.terminate_server(1).await?;
1427 fed.mine_then_wait_blocks_sync(100).await?;
1428 fed.terminate_server(2).await?;
1429 fed.terminate_server(3).await?;
1430
1431 fed.start_server(process_mgr, 1).await?;
1432 fed.start_server(process_mgr, 2).await?;
1433 fed.start_server(process_mgr, 3).await?;
1434
1435 fed.await_all_peers().await?;
1436
1437 info!(target: LOG_DEVIMINT, "fm success: reconnect-test");
1438 Ok(())
1439}
1440
1441pub async fn recoverytool_test(dev_fed: DevFed) -> Result<()> {
1442 log_binary_versions().await?;
1443
1444 let DevFed { bitcoind, fed, .. } = dev_fed;
1445
1446 let data_dir = env::var(FM_DATA_DIR_ENV)?;
1447 let client = fed.new_joined_client("recoverytool-test-client").await?;
1448
1449 let mut fed_utxos_sats = HashSet::from([12_345_000, 23_456_000, 34_567_000]);
1450 let deposit_fees = fed.deposit_fees()?.msats / 1000;
1451 for sats in &fed_utxos_sats {
1452 fed.pegin_client(*sats - deposit_fees, &client).await?;
1454 }
1455
1456 async fn withdraw(
1457 client: &Client,
1458 bitcoind: &crate::external::Bitcoind,
1459 fed_utxos_sats: &mut HashSet<u64>,
1460 ) -> Result<()> {
1461 let withdrawal_address = bitcoind.get_new_address().await?;
1462 let withdraw_res = cmd!(
1463 client,
1464 "withdraw",
1465 "--address",
1466 &withdrawal_address,
1467 "--amount",
1468 "5000 sat"
1469 )
1470 .out_json()
1471 .await?;
1472
1473 let fees_sat = withdraw_res["fees_sat"]
1474 .as_u64()
1475 .expect("withdrawal should contain fees");
1476 let txid: Txid = withdraw_res["txid"]
1477 .as_str()
1478 .expect("withdrawal should contain txid string")
1479 .parse()
1480 .expect("txid should be parsable");
1481 let tx_hex = bitcoind.poll_get_transaction(txid).await?;
1482
1483 let tx = bitcoin::Transaction::consensus_decode_hex(&tx_hex, &ModuleRegistry::default())?;
1484 assert_eq!(tx.input.len(), 1);
1485 assert_eq!(tx.output.len(), 2);
1486
1487 let change_output = tx
1488 .output
1489 .iter()
1490 .find(|o| o.to_owned().script_pubkey != withdrawal_address.script_pubkey())
1491 .expect("withdrawal must have change output");
1492 assert!(fed_utxos_sats.insert(change_output.value.to_sat()));
1493
1494 let total_output_sats = tx.output.iter().map(|o| o.value.to_sat()).sum::<u64>();
1496 let input_sats = total_output_sats + fees_sat;
1497 assert!(fed_utxos_sats.remove(&input_sats));
1498
1499 Ok(())
1500 }
1501
1502 for _ in 0..2 {
1505 withdraw(&client, &bitcoind, &mut fed_utxos_sats).await?;
1506 }
1507
1508 let total_fed_sats = fed_utxos_sats.iter().sum::<u64>();
1509 fed.finalize_mempool_tx().await?;
1510
1511 let last_tx_session = client.get_session_count().await?;
1515
1516 info!("Recovering using utxos method");
1517 let output = cmd!(
1518 crate::util::Recoverytool,
1519 "--cfg",
1520 "{data_dir}/fedimintd-default-0",
1521 "utxos",
1522 "--db",
1523 "{data_dir}/fedimintd-default-0/database"
1524 )
1525 .env(FM_PASSWORD_ENV, "pass")
1526 .out_json()
1527 .await?;
1528 let outputs = output.as_array().context("expected an array")?;
1529 assert_eq!(outputs.len(), fed_utxos_sats.len());
1530
1531 assert_eq!(
1532 outputs
1533 .iter()
1534 .map(|o| o["amount_sat"].as_u64().unwrap())
1535 .collect::<HashSet<_>>(),
1536 fed_utxos_sats
1537 );
1538 let utxos_descriptors = outputs
1539 .iter()
1540 .map(|o| o["descriptor"].as_str().unwrap())
1541 .collect::<HashSet<_>>();
1542
1543 debug!(target: LOG_DEVIMINT, ?utxos_descriptors, "recoverytool descriptors using UTXOs method");
1544
1545 let descriptors_json = serde_json::value::to_raw_value(&serde_json::Value::Array(vec![
1546 serde_json::Value::Array(
1547 utxos_descriptors
1548 .iter()
1549 .map(|d| {
1550 json!({
1551 "desc": d,
1552 "timestamp": 0,
1553 })
1554 })
1555 .collect(),
1556 ),
1557 ]))?;
1558 info!("Getting wallet balances before import");
1559 let bitcoin_client = bitcoind.wallet_client().await?;
1560 let balances_before = bitcoin_client.get_balances().await?;
1561 info!("Importing descriptors into bitcoin wallet");
1562 let request = bitcoin_client
1563 .get_jsonrpc_client()
1564 .build_request("importdescriptors", Some(&descriptors_json));
1565 let response = block_in_place(|| bitcoin_client.get_jsonrpc_client().send_request(request))?;
1566 response.check_error()?;
1567 info!("Getting wallet balances after import");
1568 let balances_after = bitcoin_client.get_balances().await?;
1569 let diff = balances_after.mine.immature + balances_after.mine.trusted
1570 - balances_before.mine.immature
1571 - balances_before.mine.trusted;
1572
1573 client.wait_session_outcome(last_tx_session).await?;
1578
1579 assert_eq!(diff.to_sat(), total_fed_sats);
1581 info!("Recovering using epochs method");
1582
1583 let outputs = cmd!(
1584 crate::util::Recoverytool,
1585 "--cfg",
1586 "{data_dir}/fedimintd-default-0",
1587 "epochs",
1588 "--db",
1589 "{data_dir}/fedimintd-default-0/database"
1590 )
1591 .env(FM_PASSWORD_ENV, "pass")
1592 .out_json()
1593 .await?
1594 .as_array()
1595 .context("expected an array")?
1596 .clone();
1597
1598 let epochs_descriptors = outputs
1599 .iter()
1600 .map(|o| o["descriptor"].as_str().unwrap())
1601 .collect::<HashSet<_>>();
1602
1603 debug!(target: LOG_DEVIMINT, ?epochs_descriptors, "recoverytool descriptors using epochs method");
1604
1605 for utxo_descriptor in utxos_descriptors {
1608 assert!(epochs_descriptors.contains(utxo_descriptor));
1609 }
1610 Ok(())
1611}
1612
1613pub async fn guardian_backup_test(dev_fed: DevFed, process_mgr: &ProcessManager) -> Result<()> {
1614 const PEER_TO_TEST: u16 = 0;
1615
1616 log_binary_versions().await?;
1617
1618 let DevFed { mut fed, .. } = dev_fed;
1619
1620 fed.await_all_peers()
1621 .await
1622 .expect("Awaiting federation coming online failed");
1623
1624 let client = fed.new_joined_client("guardian-client").await?;
1625 let old_block_count = cmd!(
1626 client,
1627 "dev",
1628 "api",
1629 "--peer-id",
1630 PEER_TO_TEST.to_string(),
1631 "--module",
1632 "wallet",
1633 "block_count",
1634 )
1635 .out_json()
1636 .await?["value"]
1637 .as_u64()
1638 .expect("No block height returned");
1639
1640 let backup_res = cmd!(
1641 client,
1642 "--our-id",
1643 PEER_TO_TEST.to_string(),
1644 "--password",
1645 "pass",
1646 "admin",
1647 "guardian-config-backup"
1648 )
1649 .out_json()
1650 .await?;
1651 let backup_hex = backup_res["tar_archive_bytes"]
1652 .as_str()
1653 .expect("expected hex string");
1654 let backup_tar = hex::decode(backup_hex).expect("invalid hex");
1655
1656 let data_dir = fed
1657 .vars
1658 .get(&PEER_TO_TEST.into())
1659 .expect("peer not found")
1660 .FM_DATA_DIR
1661 .clone();
1662
1663 fed.terminate_server(PEER_TO_TEST.into())
1664 .await
1665 .expect("could not terminate fedimintd");
1666
1667 std::fs::remove_dir_all(&data_dir).expect("error deleting old datadir");
1668 std::fs::create_dir(&data_dir).expect("error creating new datadir");
1669
1670 let write_file = |name: &str, data: &[u8]| {
1671 let mut file = std::fs::File::options()
1672 .write(true)
1673 .create(true)
1674 .truncate(true)
1675 .open(data_dir.join(name))
1676 .expect("could not open file");
1677 file.write_all(data).expect("could not write file");
1678 file.flush().expect("could not flush file");
1679 };
1680
1681 write_file("backup.tar", &backup_tar);
1682 write_file(
1683 fedimint_server::config::io::PLAINTEXT_PASSWORD,
1684 "pass".as_bytes(),
1685 );
1686
1687 assert_eq!(
1688 std::process::Command::new("tar")
1689 .arg("-xf")
1690 .arg("backup.tar")
1691 .current_dir(data_dir)
1692 .spawn()
1693 .expect("error spawning tar")
1694 .wait()
1695 .expect("error extracting archive")
1696 .code(),
1697 Some(0),
1698 "tar failed"
1699 );
1700
1701 fed.start_server(process_mgr, PEER_TO_TEST.into())
1702 .await
1703 .expect("could not restart fedimintd");
1704
1705 poll("Peer catches up again", || async {
1706 let block_counts = all_peer_block_count(&client, fed.member_ids())
1707 .await
1708 .map_err(ControlFlow::Continue)?;
1709 let block_count = block_counts[&PeerId::from(PEER_TO_TEST)];
1710
1711 info!("Caught up to block {block_count} of at least {old_block_count} (counts={block_counts:?})");
1712
1713 if block_count < old_block_count {
1714 return Err(ControlFlow::Continue(anyhow!("Block count still behind")));
1715 }
1716
1717 Ok(())
1718 })
1719 .await
1720 .expect("Peer didn't rejoin federation");
1721
1722 Ok(())
1723}
1724
1725async fn peer_block_count(client: &Client, peer: PeerId) -> Result<u64> {
1726 cmd!(
1727 client,
1728 "dev",
1729 "api",
1730 "--peer-id",
1731 peer.to_string(),
1732 "module_{LEGACY_HARDCODED_INSTANCE_ID_WALLET}_block_count",
1733 )
1734 .out_json()
1735 .await?["value"]
1736 .as_u64()
1737 .context("No block height returned")
1738}
1739
1740async fn all_peer_block_count(
1741 client: &Client,
1742 peers: impl Iterator<Item = PeerId>,
1743) -> Result<BTreeMap<PeerId, u64>> {
1744 let mut peer_heights = BTreeMap::new();
1745 for peer in peers {
1746 peer_heights.insert(peer, peer_block_count(client, peer).await?);
1747 }
1748 Ok(peer_heights)
1749}
1750
1751pub async fn cannot_replay_tx_test(dev_fed: DevFed) -> Result<()> {
1752 log_binary_versions().await?;
1753
1754 let DevFed { fed, .. } = dev_fed;
1755
1756 let client = fed.new_joined_client("cannot-replay-client").await?;
1757
1758 const CLIENT_START_AMOUNT: u64 = 5_000_000_000;
1760 const CLIENT_SPEND_AMOUNT: u64 = 5_000_000_000;
1761
1762 let initial_client_balance = client.balance().await?;
1763 assert_eq!(initial_client_balance, 0);
1764
1765 fed.pegin_client(CLIENT_START_AMOUNT / 1000, &client)
1766 .await?;
1767
1768 let double_spend_client = client.new_forked("double-spender").await?;
1770
1771 let notes = cmd!(client, "spend", CLIENT_SPEND_AMOUNT)
1773 .out_json()
1774 .await?
1775 .get("notes")
1776 .expect("Output didn't contain e-cash notes")
1777 .as_str()
1778 .unwrap()
1779 .to_owned();
1780
1781 let client_post_spend_balance = client.balance().await?;
1782 assert_eq!(
1783 client_post_spend_balance,
1784 CLIENT_START_AMOUNT - CLIENT_SPEND_AMOUNT
1785 );
1786
1787 cmd!(client, "reissue", notes).out_json().await?;
1788 let client_post_reissue_balance = client.balance().await?;
1789 assert_eq!(client_post_reissue_balance, CLIENT_START_AMOUNT);
1790
1791 let double_spend_notes = cmd!(double_spend_client, "spend", CLIENT_SPEND_AMOUNT)
1793 .out_json()
1794 .await?
1795 .get("notes")
1796 .expect("Output didn't contain e-cash notes")
1797 .as_str()
1798 .unwrap()
1799 .to_owned();
1800
1801 let double_spend_client_post_spend_balance = double_spend_client.balance().await?;
1802 assert_eq!(
1803 double_spend_client_post_spend_balance,
1804 CLIENT_START_AMOUNT - CLIENT_SPEND_AMOUNT
1805 );
1806
1807 cmd!(double_spend_client, "reissue", double_spend_notes)
1808 .assert_error_contains("The transaction had an invalid input")
1809 .await?;
1810
1811 let double_spend_client_post_spend_balance = double_spend_client.balance().await?;
1812 assert_eq!(
1813 double_spend_client_post_spend_balance,
1814 CLIENT_START_AMOUNT - CLIENT_SPEND_AMOUNT
1815 );
1816
1817 Ok(())
1818}
1819
1820pub async fn test_offline_client_initialization(
1824 dev_fed: DevFed,
1825 _process_mgr: &ProcessManager,
1826) -> Result<()> {
1827 log_binary_versions().await?;
1828
1829 let DevFed { mut fed, .. } = dev_fed;
1830
1831 fed.await_all_peers().await?;
1833
1834 let client = fed.new_joined_client("offline-test-client").await?;
1836
1837 const INFO_COMMAND_TIMEOUT: Duration = Duration::from_secs(5);
1839 let online_info =
1840 fedimint_core::runtime::timeout(INFO_COMMAND_TIMEOUT, cmd!(client, "info").out_json())
1841 .await
1842 .context("Client info command timed out while federation was online")?
1843 .context("Client info command failed while federation was online")?;
1844 info!(target: LOG_DEVIMINT, "Client info while federation online: {:?}", online_info);
1845
1846 info!(target: LOG_DEVIMINT, "Shutting down all federation servers...");
1848 fed.terminate_all_servers().await?;
1849
1850 fedimint_core::task::sleep_in_test("wait for federation shutdown", Duration::from_secs(2))
1852 .await;
1853
1854 info!(target: LOG_DEVIMINT, "Testing client info command with all servers offline...");
1858 let offline_info =
1859 fedimint_core::runtime::timeout(INFO_COMMAND_TIMEOUT, cmd!(client, "info").out_json())
1860 .await
1861 .context("Client info command timed out while federation was offline")?
1862 .context("Client info command failed while federation was offline")?;
1863
1864 info!(target: LOG_DEVIMINT, "Client info while federation offline: {:?}", offline_info);
1865
1866 Ok(())
1867}
1868
1869pub async fn test_client_config_change_detection(
1876 dev_fed: DevFed,
1877 process_mgr: &ProcessManager,
1878) -> Result<()> {
1879 log_binary_versions().await?;
1880
1881 let fedimint_cli_version = crate::util::FedimintCli::version_or_default().await;
1882 let fedimintd_version = crate::util::FedimintdCmd::version_or_default().await;
1883
1884 if fedimint_cli_version < *VERSION_0_9_0_ALPHA {
1885 info!(target: LOG_DEVIMINT, "Skipping the test - fedimint-cli too old");
1886 return Ok(());
1887 }
1888
1889 if fedimintd_version < *VERSION_0_9_0_ALPHA {
1890 info!(target: LOG_DEVIMINT, "Skipping the test - fedimintd too old");
1891 return Ok(());
1892 }
1893
1894 let DevFed { mut fed, .. } = dev_fed;
1895 let peer_ids: Vec<_> = fed.member_ids().collect();
1896
1897 fed.await_all_peers().await?;
1898
1899 let client = fed.new_joined_client("config-change-test-client").await?;
1900
1901 info!(target: LOG_DEVIMINT, "Getting initial client configuration...");
1902 let initial_config = cmd!(client, "config")
1903 .out_json()
1904 .await
1905 .context("Failed to get initial client config")?;
1906
1907 info!(target: LOG_DEVIMINT, "Initial config modules: {:?}", initial_config["modules"].as_object().unwrap().keys().collect::<Vec<_>>());
1908
1909 let data_dir = env::var(FM_DATA_DIR_ENV)?;
1910 let config_dir = PathBuf::from(&data_dir);
1911
1912 info!(target: LOG_DEVIMINT, "Shutting down all federation servers...");
1919 fed.terminate_all_servers().await?;
1920
1921 fedimint_core::task::sleep_in_test("wait for federation shutdown", Duration::from_secs(2))
1923 .await;
1924
1925 info!(target: LOG_DEVIMINT, "Modifying server configurations to add new meta module...");
1926 modify_server_configs(&config_dir, &peer_ids).await?;
1927
1928 info!(target: LOG_DEVIMINT, "Restarting all servers with modified configurations...");
1930 for peer_id in peer_ids {
1931 fed.start_server(process_mgr, peer_id.to_usize()).await?;
1932 }
1933
1934 info!(target: LOG_DEVIMINT, "Wait for peers to get back up");
1936 fed.await_all_peers().await?;
1937
1938 info!(target: LOG_DEVIMINT, "Waiting for client to fetch updated configuration...");
1940 cmd!(client, "dev", "wait", "3")
1941 .run()
1942 .await
1943 .context("Failed to wait for client config update")?;
1944
1945 info!(target: LOG_DEVIMINT, "Testing client detection of configuration changes...");
1947 let updated_config = cmd!(client, "config")
1948 .out_json()
1949 .await
1950 .context("Failed to get updated client config")?;
1951
1952 info!(target: LOG_DEVIMINT, "Updated config modules: {:?}", updated_config["modules"].as_object().unwrap().keys().collect::<Vec<_>>());
1953
1954 let initial_modules = initial_config["modules"].as_object().unwrap();
1956 let updated_modules = updated_config["modules"].as_object().unwrap();
1957
1958 anyhow::ensure!(
1959 updated_modules.len() > initial_modules.len(),
1960 "Expected more modules in updated config. Initial: {}, Updated: {}",
1961 initial_modules.len(),
1962 updated_modules.len()
1963 );
1964
1965 let new_meta_module = updated_modules.iter().find(|(module_id, module_config)| {
1967 module_config["kind"].as_str() == Some("meta") && !initial_modules.contains_key(*module_id)
1968 });
1969
1970 let new_meta_module_id = new_meta_module
1971 .map(|(id, _)| id)
1972 .with_context(|| "Expected to find new meta module in updated configuration")?;
1973
1974 info!(target: LOG_DEVIMINT, "Found new meta module with id: {}", new_meta_module_id);
1975
1976 info!(target: LOG_DEVIMINT, "Verifying client operations work with new configuration...");
1978 let final_info = cmd!(client, "info")
1979 .out_json()
1980 .await
1981 .context("Client info command failed with updated configuration")?;
1982
1983 info!(target: LOG_DEVIMINT, "Client successfully adapted to configuration changes: {:?}", final_info["federation_id"]);
1984
1985 Ok(())
1986}
1987
1988async fn modify_server_configs(config_dir: &Path, peer_ids: &[PeerId]) -> Result<()> {
1990 for &peer_id in peer_ids {
1991 modify_single_peer_config(config_dir, peer_id).await?;
1992 }
1993 Ok(())
1994}
1995
1996async fn modify_single_peer_config(config_dir: &Path, peer_id: PeerId) -> Result<()> {
1999 use fedimint_aead::{encrypted_write, get_encryption_key};
2000 use fedimint_core::core::ModuleInstanceId;
2001 use fedimint_server::config::io::read_server_config;
2002 use serde_json::Value;
2003
2004 info!(target: LOG_DEVIMINT, %peer_id, "Modifying config for peer");
2005 let peer_dir = config_dir.join(format!("fedimintd-default-{}", peer_id.to_usize()));
2006
2007 let consensus_config_path = peer_dir.join("consensus.json");
2009 let consensus_config_content = fs::read_to_string(&consensus_config_path)
2010 .await
2011 .with_context(|| format!("Failed to read consensus config for peer {peer_id}"))?;
2012
2013 let mut consensus_config: Value = serde_json::from_str(&consensus_config_content)
2014 .with_context(|| format!("Failed to parse consensus config for peer {peer_id}"))?;
2015
2016 let password = "pass"; let server_config = read_server_config(password, &peer_dir)
2019 .with_context(|| format!("Failed to read server config for peer {peer_id}"))?;
2020
2021 let consensus_config_modules = consensus_config["modules"]
2023 .as_object()
2024 .with_context(|| format!("No modules found in consensus config for peer {peer_id}"))?;
2025
2026 let existing_meta_consensus = consensus_config_modules
2028 .values()
2029 .find(|module_config| module_config["kind"].as_str() == Some("meta"));
2030
2031 let existing_meta_consensus = existing_meta_consensus
2032 .with_context(|| {
2033 format!("No existing meta module found in consensus config for peer {peer_id}")
2034 })?
2035 .clone();
2036
2037 let existing_meta_instance_id = server_config
2039 .consensus
2040 .modules
2041 .iter()
2042 .find(|(_, config)| config.kind.as_str() == "meta")
2043 .map(|(id, _)| *id)
2044 .with_context(|| {
2045 format!("No existing meta module found in private config for peer {peer_id}")
2046 })?;
2047
2048 let existing_meta_private = server_config
2049 .private
2050 .modules
2051 .get(&existing_meta_instance_id)
2052 .with_context(|| format!("Failed to get existing meta private config for peer {peer_id}"))?
2053 .clone();
2054
2055 let last_existing_module_id = consensus_config_modules
2057 .keys()
2058 .filter_map(|id| id.parse::<u32>().ok())
2059 .max()
2060 .unwrap_or(0);
2061
2062 let new_module_id = (last_existing_module_id + 1).to_string();
2063 let new_module_instance_id = ModuleInstanceId::from((last_existing_module_id + 1) as u16);
2064
2065 info!(
2066 "Adding new meta module with id {} for peer {} (copying existing meta module config)",
2067 new_module_id, peer_id
2068 );
2069
2070 if let Some(modules) = consensus_config["modules"].as_object_mut() {
2072 modules.insert(new_module_id.clone(), existing_meta_consensus);
2073 }
2074
2075 let mut updated_private_config = server_config.private.clone();
2077 updated_private_config
2078 .modules
2079 .insert(new_module_instance_id, existing_meta_private);
2080
2081 let updated_consensus_content = serde_json::to_string_pretty(&consensus_config)
2083 .with_context(|| format!("Failed to serialize consensus config for peer {peer_id}"))?;
2084
2085 write_overwrite_async(&consensus_config_path, updated_consensus_content)
2086 .await
2087 .with_context(|| format!("Failed to write consensus config for peer {peer_id}"))?;
2088
2089 let salt = std::fs::read_to_string(peer_dir.join("private.salt"))
2091 .with_context(|| format!("Failed to read salt file for peer {peer_id}"))?;
2092 let key = get_encryption_key(password, &salt)
2093 .with_context(|| format!("Failed to get encryption key for peer {peer_id}"))?;
2094
2095 let private_config_bytes = serde_json::to_string(&updated_private_config)
2096 .with_context(|| format!("Failed to serialize private config for peer {peer_id}"))?
2097 .into_bytes();
2098
2099 let encrypted_private_path = peer_dir.join("private.encrypt");
2101 if encrypted_private_path.exists() {
2102 std::fs::remove_file(&encrypted_private_path)
2103 .with_context(|| format!("Failed to remove old private config for peer {peer_id}"))?;
2104 }
2105
2106 encrypted_write(private_config_bytes, &key, encrypted_private_path)
2107 .with_context(|| format!("Failed to write encrypted private config for peer {peer_id}"))?;
2108
2109 info!("Successfully modified configs for peer {}", peer_id);
2110 Ok(())
2111}
2112
2113#[derive(Subcommand)]
2114pub enum LatencyTest {
2115 Reissue,
2116 LnSend,
2117 LnReceive,
2118 FmPay,
2119 Restore,
2120}
2121
2122#[derive(Subcommand)]
2123pub enum UpgradeTest {
2124 Fedimintd {
2125 #[arg(long, trailing_var_arg = true, num_args=1..)]
2126 paths: Vec<PathBuf>,
2127 },
2128 FedimintCli {
2129 #[arg(long, trailing_var_arg = true, num_args=1..)]
2130 paths: Vec<PathBuf>,
2131 },
2132 Gatewayd {
2133 #[arg(long, trailing_var_arg = true, num_args=1..)]
2134 gatewayd_paths: Vec<PathBuf>,
2135 #[arg(long, trailing_var_arg = true, num_args=1..)]
2136 gateway_cli_paths: Vec<PathBuf>,
2137 },
2138}
2139
2140#[derive(Subcommand)]
2141pub enum TestCmd {
2142 LatencyTests {
2145 #[clap(subcommand)]
2146 r#type: LatencyTest,
2147
2148 #[arg(long, default_value = "10")]
2149 iterations: usize,
2150 },
2151 ReconnectTest,
2154 CliTests,
2156 LoadTestToolTest,
2159 LightningReconnectTest,
2162 GatewayRebootTest,
2165 RecoverytoolTests,
2167 WasmTestSetup {
2169 #[arg(long, trailing_var_arg = true, allow_hyphen_values = true, num_args=1..)]
2170 exec: Option<Vec<ffi::OsString>>,
2171 },
2172 GuardianBackup,
2174 CannotReplayTransaction,
2176 TestOfflineClientInitialization,
2179 TestClientConfigChangeDetection,
2182 UpgradeTests {
2184 #[clap(subcommand)]
2185 binary: UpgradeTest,
2186 #[arg(long)]
2187 lnv2: String,
2188 },
2189}
2190
2191pub async fn handle_command(cmd: TestCmd, common_args: CommonArgs) -> Result<()> {
2192 match cmd {
2193 TestCmd::WasmTestSetup { exec } => {
2194 let (process_mgr, task_group) = setup(common_args).await?;
2195 let main = {
2196 let task_group = task_group.clone();
2197 async move {
2198 let dev_fed = dev_fed(&process_mgr).await?;
2199 let gw_lnd = dev_fed.gw_lnd.clone();
2200 let fed = dev_fed.fed.clone();
2201 gw_lnd
2202 .set_federation_routing_fee(dev_fed.fed.calculate_federation_id(), 0, 0)
2203 .await?;
2204 task_group.spawn_cancellable("faucet", async move {
2205 if let Err(err) = crate::faucet::run(
2206 &dev_fed,
2207 format!("0.0.0.0:{}", process_mgr.globals.FM_PORT_FAUCET),
2208 process_mgr.globals.FM_PORT_GW_LND,
2209 )
2210 .await
2211 {
2212 error!("Error spawning faucet: {err}");
2213 }
2214 });
2215 try_join!(fed.pegin_gateways(30_000, vec![&gw_lnd]), async {
2216 poll("waiting for faucet startup", || async {
2217 TcpStream::connect(format!(
2218 "127.0.0.1:{}",
2219 process_mgr.globals.FM_PORT_FAUCET
2220 ))
2221 .await
2222 .context("connect to faucet")
2223 .map_err(ControlFlow::Continue)
2224 })
2225 .await?;
2226 Ok(())
2227 },)?;
2228 if let Some(exec) = exec {
2229 exec_user_command(exec).await?;
2230 task_group.shutdown();
2231 }
2232 Ok::<_, anyhow::Error>(())
2233 }
2234 };
2235 cleanup_on_exit(main, task_group).await?;
2236 }
2237 TestCmd::LatencyTests { r#type, iterations } => {
2238 let (process_mgr, _) = setup(common_args).await?;
2239 let dev_fed = dev_fed(&process_mgr).await?;
2240 latency_tests(dev_fed, r#type, None, iterations, true).await?;
2241 }
2242 TestCmd::ReconnectTest => {
2243 let (process_mgr, _) = setup(common_args).await?;
2244 let dev_fed = dev_fed(&process_mgr).await?;
2245 reconnect_test(dev_fed, &process_mgr).await?;
2246 }
2247 TestCmd::CliTests => {
2248 let (process_mgr, _) = setup(common_args).await?;
2249 let dev_fed = dev_fed(&process_mgr).await?;
2250 cli_tests(dev_fed).await?;
2251 }
2252 TestCmd::LoadTestToolTest => {
2253 let (process_mgr, _) = setup(common_args).await?;
2254 let dev_fed = dev_fed(&process_mgr).await?;
2255 cli_load_test_tool_test(dev_fed).await?;
2256 }
2257 TestCmd::LightningReconnectTest => {
2258 let (process_mgr, _) = setup(common_args).await?;
2259 let dev_fed = dev_fed(&process_mgr).await?;
2260 lightning_gw_reconnect_test(dev_fed, &process_mgr).await?;
2261 }
2262 TestCmd::GatewayRebootTest => {
2263 let (process_mgr, _) = setup(common_args).await?;
2264 let dev_fed = dev_fed(&process_mgr).await?;
2265 gw_reboot_test(dev_fed, &process_mgr).await?;
2266 }
2267 TestCmd::RecoverytoolTests => {
2268 let (process_mgr, _) = setup(common_args).await?;
2269 let dev_fed = dev_fed(&process_mgr).await?;
2270 recoverytool_test(dev_fed).await?;
2271 }
2272 TestCmd::GuardianBackup => {
2273 let (process_mgr, _) = setup(common_args).await?;
2274 let dev_fed = dev_fed(&process_mgr).await?;
2275 guardian_backup_test(dev_fed, &process_mgr).await?;
2276 }
2277 TestCmd::CannotReplayTransaction => {
2278 let (process_mgr, _) = setup(common_args).await?;
2279 let dev_fed = dev_fed(&process_mgr).await?;
2280 cannot_replay_tx_test(dev_fed).await?;
2281 }
2282 TestCmd::TestOfflineClientInitialization => {
2283 let (process_mgr, _) = setup(common_args).await?;
2284 let dev_fed = dev_fed(&process_mgr).await?;
2285 test_offline_client_initialization(dev_fed, &process_mgr).await?;
2286 }
2287 TestCmd::TestClientConfigChangeDetection => {
2288 let (process_mgr, _) = setup(common_args).await?;
2289 let dev_fed = dev_fed(&process_mgr).await?;
2290 test_client_config_change_detection(dev_fed, &process_mgr).await?;
2291 }
2292 TestCmd::UpgradeTests { binary, lnv2 } => {
2293 unsafe { std::env::set_var(FM_ENABLE_MODULE_LNV2_ENV, lnv2) };
2295 let (process_mgr, _) = setup(common_args).await?;
2296 Box::pin(upgrade_tests(&process_mgr, binary)).await?;
2297 }
2298 }
2299 Ok(())
2300}