/ gateway / ln-gateway / tests / integration_tests.rs
integration_tests.rs
   1  //! Gateway integration test suite
   2  //!
   3  //! This crate contains integration tests for the gateway API
   4  //! and business logic.
   5  use std::str::FromStr;
   6  use std::sync::Arc;
   7  use std::time::Duration;
   8  
   9  use assert_matches::assert_matches;
  10  use bitcoin::Network;
  11  use bitcoin_hashes::{sha256, Hash};
  12  use fedimint_client::transaction::{ClientInput, ClientOutput, TransactionBuilder};
  13  use fedimint_client::ClientHandleArc;
  14  use fedimint_core::config::FederationId;
  15  use fedimint_core::core::{IntoDynInstance, OperationId};
  16  use fedimint_core::secp256k1::PublicKey;
  17  use fedimint_core::task::sleep_in_test;
  18  use fedimint_core::util::NextOrPending;
  19  use fedimint_core::{msats, sats, Amount, OutPoint, TransactionId};
  20  use fedimint_dummy_client::{DummyClientInit, DummyClientModule};
  21  use fedimint_dummy_common::config::DummyGenParams;
  22  use fedimint_dummy_server::DummyInit;
  23  use fedimint_ln_client::api::LnFederationApi;
  24  use fedimint_ln_client::pay::{PayInvoicePayload, PaymentData};
  25  use fedimint_ln_client::{
  26      LightningClientInit, LightningClientModule, LightningClientStateMachines,
  27      LightningOperationMeta, LightningOperationMetaVariant, LnPayState, LnReceiveState,
  28      MockGatewayConnection, OutgoingLightningPayment, PayType,
  29  };
  30  use fedimint_ln_common::config::{FeeToAmount, GatewayFee, LightningGenParams};
  31  use fedimint_ln_common::contracts::incoming::IncomingContractOffer;
  32  use fedimint_ln_common::contracts::outgoing::OutgoingContractAccount;
  33  use fedimint_ln_common::contracts::{EncryptedPreimage, FundedContract, Preimage, PreimageKey};
  34  use fedimint_ln_common::{LightningGateway, LightningInput, LightningOutput, PrunedInvoice};
  35  use fedimint_ln_server::LightningInit;
  36  use fedimint_logging::LOG_TEST;
  37  use fedimint_testing::btc::BitcoinTest;
  38  use fedimint_testing::db::BYTE_33;
  39  use fedimint_testing::federation::FederationTest;
  40  use fedimint_testing::fixtures::Fixtures;
  41  use fedimint_testing::gateway::{GatewayTest, DEFAULT_GATEWAY_PASSWORD};
  42  use fedimint_testing::ln::FakeLightningTest;
  43  use fedimint_unknown_common::config::UnknownGenParams;
  44  use fedimint_unknown_server::UnknownInit;
  45  use futures::Future;
  46  use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description, RoutingFees};
  47  use ln_gateway::rpc::rpc_client::{GatewayRpcClient, GatewayRpcError, GatewayRpcResult};
  48  use ln_gateway::rpc::rpc_server::hash_password;
  49  use ln_gateway::rpc::{
  50      BalancePayload, ConnectFedPayload, FederationRoutingFees, LeaveFedPayload,
  51      SetConfigurationPayload,
  52  };
  53  use ln_gateway::state_machine::pay::{
  54      OutgoingContractError, OutgoingPaymentError, OutgoingPaymentErrorType,
  55  };
  56  use ln_gateway::state_machine::{
  57      GatewayClientModule, GatewayClientStateMachines, GatewayExtPayStates, GatewayExtReceiveStates,
  58      GatewayMeta, Htlc,
  59  };
  60  use ln_gateway::{DEFAULT_FEES, DEFAULT_NETWORK};
  61  use reqwest::StatusCode;
  62  use tracing::info;
  63  
  64  async fn user_pay_invoice(
  65      ln_module: &LightningClientModule,
  66      invoice: Bolt11Invoice,
  67      gateway_id: &PublicKey,
  68  ) -> anyhow::Result<OutgoingLightningPayment> {
  69      let gateway = ln_module.select_gateway(gateway_id).await;
  70      ln_module.pay_bolt11_invoice(gateway, invoice, ()).await
  71  }
  72  
  73  fn fixtures() -> Fixtures {
  74      info!(target: LOG_TEST, "Setting up fixtures");
  75      let fixtures = Fixtures::new_primary(DummyClientInit, DummyInit, DummyGenParams::default())
  76          .with_server_only_module(UnknownInit, UnknownGenParams::default());
  77      let ln_params = LightningGenParams::regtest(fixtures.bitcoin_server());
  78      fixtures.with_module(
  79          LightningClientInit {
  80              gateway_conn: Arc::new(MockGatewayConnection),
  81          },
  82          LightningInit,
  83          ln_params,
  84      )
  85  }
  86  
  87  async fn single_federation_test<B>(
  88      f: impl FnOnce(
  89              GatewayTest,
  90              FakeLightningTest,
  91              FederationTest,
  92              ClientHandleArc, // User Client
  93              Arc<dyn BitcoinTest>,
  94          ) -> B
  95          + Copy,
  96  ) -> anyhow::Result<()>
  97  where
  98      B: Future<Output = anyhow::Result<()>>,
  99  {
 100      let fixtures = fixtures();
 101      let other_ln = FakeLightningTest::new();
 102  
 103      let fed = fixtures.new_default_fed().await;
 104      let mut gateway = fixtures
 105          .new_gateway(0, Some(DEFAULT_GATEWAY_PASSWORD.to_string()))
 106          .await;
 107      gateway.connect_fed(&fed).await;
 108      let user_client = fed.new_client().await;
 109      let bitcoin = fixtures.bitcoin();
 110      f(gateway, other_ln, fed, user_client, bitcoin).await?;
 111  
 112      Ok(())
 113  }
 114  
 115  async fn multi_federation_test<B>(
 116      f: impl FnOnce(
 117              GatewayTest,
 118              GatewayRpcClient,
 119              FederationTest,
 120              FederationTest,
 121              Arc<dyn BitcoinTest>,
 122          ) -> B
 123          + Copy,
 124  ) -> anyhow::Result<()>
 125  where
 126      B: Future<Output = anyhow::Result<()>>,
 127  {
 128      let fixtures = fixtures();
 129      let fed1 = fixtures.new_default_fed().await;
 130      let fed2 = fixtures.new_default_fed().await;
 131  
 132      let gateway = fixtures
 133          .new_gateway(0, Some(DEFAULT_GATEWAY_PASSWORD.to_string()))
 134          .await;
 135      let client = gateway
 136          .get_rpc()
 137          .await
 138          .with_password(Some(DEFAULT_GATEWAY_PASSWORD.to_string()));
 139  
 140      f(gateway, client, fed1, fed2, fixtures.bitcoin()).await?;
 141      Ok(())
 142  }
 143  
 144  fn sha256(data: &[u8]) -> sha256::Hash {
 145      sha256::Hash::hash(data)
 146  }
 147  
 148  /// Helper function for constructing the `PaymentData` that the gateway uses to
 149  /// pay the invoice. LND supports "private" payments where the description is
 150  /// stripped from the invoice.
 151  fn get_payment_data(gateway: Option<LightningGateway>, invoice: Bolt11Invoice) -> PaymentData {
 152      match gateway {
 153          Some(g) if g.supports_private_payments => {
 154              let pruned_invoice: PrunedInvoice = invoice.try_into().expect("Invoice has amount");
 155              PaymentData::PrunedInvoice(pruned_invoice)
 156          }
 157          _ => PaymentData::Invoice(invoice),
 158      }
 159  }
 160  
 161  /// Test helper function for paying a valid BOLT11 invoice with a gateway
 162  /// specified by `gateway_id`.
 163  async fn gateway_pay_valid_invoice(
 164      invoice: Bolt11Invoice,
 165      user_client: &ClientHandleArc,
 166      gateway_client: &ClientHandleArc,
 167      gateway_id: &PublicKey,
 168  ) -> anyhow::Result<()> {
 169      let user_lightning_module = &user_client.get_first_module::<LightningClientModule>();
 170      let gateway = user_lightning_module.select_gateway(gateway_id).await;
 171  
 172      // User client pays test invoice
 173      let OutgoingLightningPayment {
 174          payment_type,
 175          contract_id,
 176          fee: _,
 177      } = user_pay_invoice(user_lightning_module, invoice.clone(), gateway_id).await?;
 178      match payment_type {
 179          PayType::Lightning(pay_op) => {
 180              let mut pay_sub = user_lightning_module
 181                  .subscribe_ln_pay(pay_op)
 182                  .await?
 183                  .into_stream();
 184              assert_eq!(pay_sub.ok().await?, LnPayState::Created);
 185              let funded = pay_sub.ok().await?;
 186              assert_matches!(funded, LnPayState::Funded);
 187  
 188              let payload = PayInvoicePayload {
 189                  federation_id: user_client.federation_id(),
 190                  contract_id,
 191                  payment_data: get_payment_data(gateway, invoice),
 192                  preimage_auth: Hash::hash(&[0; 32]),
 193              };
 194  
 195              let gw_pay_op = gateway_client
 196                  .get_first_module::<GatewayClientModule>()
 197                  .gateway_pay_bolt11_invoice(payload)
 198                  .await?;
 199              let mut gw_pay_sub = gateway_client
 200                  .get_first_module::<GatewayClientModule>()
 201                  .gateway_subscribe_ln_pay(gw_pay_op)
 202                  .await?
 203                  .into_stream();
 204              assert_eq!(gw_pay_sub.ok().await?, GatewayExtPayStates::Created);
 205              assert_matches!(gw_pay_sub.ok().await?, GatewayExtPayStates::Preimage { .. });
 206  
 207              let dummy_module = gateway_client.get_first_module::<DummyClientModule>();
 208              if let GatewayExtPayStates::Success { out_points, .. } = gw_pay_sub.ok().await? {
 209                  for outpoint in out_points {
 210                      dummy_module.receive_money(outpoint).await?;
 211                  }
 212              } else {
 213                  panic!("Gateway pay state machine was not successful");
 214              }
 215          }
 216          _ => panic!("Expected Lightning payment!"),
 217      }
 218      Ok(())
 219  }
 220  
 221  #[tokio::test(flavor = "multi_thread")]
 222  async fn test_gateway_client_pay_valid_invoice() -> anyhow::Result<()> {
 223      single_federation_test(
 224          |gateway, other_lightning_client, fed, user_client, _| async move {
 225              let gateway_client = gateway.select_client(fed.id()).await;
 226              // Print money for user_client
 227              let dummy_module = user_client.get_first_module::<DummyClientModule>();
 228              let (_, outpoint) = dummy_module.print_money(sats(1000)).await?;
 229              dummy_module.receive_money(outpoint).await?;
 230              assert_eq!(user_client.get_balance().await, sats(1000));
 231  
 232              // Create test invoice
 233              let invoice = other_lightning_client.invoice(sats(250), None).await?;
 234  
 235              gateway_pay_valid_invoice(
 236                  invoice,
 237                  &user_client,
 238                  &gateway_client,
 239                  &gateway.gateway.gateway_id,
 240              )
 241              .await?;
 242  
 243              assert_eq!(user_client.get_balance().await, sats(1000 - 250));
 244              assert_eq!(gateway_client.get_balance().await, sats(250));
 245  
 246              Ok(())
 247          },
 248      )
 249      .await
 250  }
 251  
 252  #[tokio::test(flavor = "multi_thread")]
 253  async fn test_can_change_default_routing_fees() -> anyhow::Result<()> {
 254      single_federation_test(
 255          |gateway, other_lightning_client, fed, user_client, _| async move {
 256              let rpc_client = gateway
 257                  .get_rpc()
 258                  .await
 259                  .with_password(Some(DEFAULT_GATEWAY_PASSWORD.to_string()));
 260              // Print money for user_client
 261              let dummy_module = user_client.get_first_module::<DummyClientModule>();
 262              let (_, outpoint) = dummy_module.print_money(sats(1000)).await?;
 263              dummy_module.receive_money(outpoint).await?;
 264              assert_eq!(user_client.get_balance().await, sats(1000));
 265  
 266              let fee = "10,10000".to_string();
 267              let federation_fee = FederationRoutingFees::from_str(&fee)?;
 268              let set_configuration_payload = SetConfigurationPayload {
 269                  password: None,
 270                  num_route_hints: None,
 271                  routing_fees: Some(federation_fee.clone()),
 272                  network: None,
 273                  per_federation_routing_fees: None,
 274              };
 275              verify_gateway_rpc_success("set_configuration", || {
 276                  rpc_client.set_configuration(set_configuration_payload.clone())
 277              })
 278              .await;
 279  
 280              // we need to reconnect to set the fees as defaults from gateway
 281              reconnect_federation(&rpc_client, &fed).await;
 282  
 283              // Update the gateway cache since the fees have changed
 284              let ln_module = user_client.get_first_module::<LightningClientModule>();
 285              ln_module.update_gateway_cache().await?;
 286  
 287              // Create test invoice
 288              let invoice_amount = sats(250);
 289              let invoice = other_lightning_client.invoice(invoice_amount, None).await?;
 290  
 291              let gateway_client = gateway.select_client(fed.id()).await;
 292              gateway_pay_valid_invoice(
 293                  invoice,
 294                  &user_client,
 295                  &gateway_client,
 296                  &gateway.gateway.gateway_id,
 297              )
 298              .await?;
 299  
 300              let fee: RoutingFees = federation_fee.into();
 301              let fee_amount = fee.to_amount(&invoice_amount);
 302              assert_eq!(
 303                  user_client.get_balance().await,
 304                  sats(1000 - 250) - fee_amount
 305              );
 306              assert_eq!(gateway_client.get_balance().await, sats(250) + fee_amount);
 307  
 308              Ok(())
 309          },
 310      )
 311      .await
 312  }
 313  
 314  #[tokio::test(flavor = "multi_thread")]
 315  async fn test_can_change_federation_routing_fees() -> anyhow::Result<()> {
 316      single_federation_test(
 317          |gateway, other_lightning_client, fed, user_client, _| async move {
 318              let rpc_client = gateway
 319                  .get_rpc()
 320                  .await
 321                  .with_password(Some(DEFAULT_GATEWAY_PASSWORD.to_string()));
 322              // Print money for user_client
 323              let dummy_module = user_client.get_first_module::<DummyClientModule>();
 324              let (_, outpoint) = dummy_module.print_money(sats(1000)).await?;
 325              dummy_module.receive_money(outpoint).await?;
 326              assert_eq!(user_client.get_balance().await, sats(1000));
 327  
 328              let fee = "10,10000".to_string();
 329              let federation_fee = FederationRoutingFees::from_str(&fee)?;
 330              let set_configuration_payload = SetConfigurationPayload {
 331                  password: None,
 332                  num_route_hints: None,
 333                  routing_fees: None,
 334                  network: None,
 335                  per_federation_routing_fees: Some(vec![(fed.id(), federation_fee.clone())]),
 336              };
 337              verify_gateway_rpc_success("set_configuration", || {
 338                  rpc_client.set_configuration(set_configuration_payload.clone())
 339              })
 340              .await;
 341  
 342              // Update the gateway cache since the fees have changed
 343              let ln_module = user_client.get_first_module::<LightningClientModule>();
 344              ln_module.update_gateway_cache().await?;
 345  
 346              // Create test invoice
 347              let invoice_amount = sats(250);
 348              let invoice = other_lightning_client.invoice(invoice_amount, None).await?;
 349  
 350              let gateway_client = gateway.select_client(fed.id()).await;
 351              gateway_pay_valid_invoice(
 352                  invoice,
 353                  &user_client,
 354                  &gateway_client,
 355                  &gateway.gateway.gateway_id,
 356              )
 357              .await?;
 358  
 359              let fee: RoutingFees = federation_fee.into();
 360              let fee_amount = fee.to_amount(&invoice_amount);
 361              assert_eq!(
 362                  user_client.get_balance().await,
 363                  sats(1000 - 250) - fee_amount
 364              );
 365              assert_eq!(gateway_client.get_balance().await, sats(250) + fee_amount);
 366  
 367              Ok(())
 368          },
 369      )
 370      .await
 371  }
 372  
 373  #[tokio::test(flavor = "multi_thread")]
 374  async fn test_gateway_enforces_fees() -> anyhow::Result<()> {
 375      single_federation_test(
 376          |gateway_test, other_lightning_client, fed, user_client, _| async move {
 377              let rpc_client = gateway_test
 378                  .get_rpc()
 379                  .await
 380                  .with_password(Some(DEFAULT_GATEWAY_PASSWORD.to_string()));
 381              // Print money for user_client
 382              let dummy_module = user_client.get_first_module::<DummyClientModule>();
 383              let (_, outpoint) = dummy_module.print_money(sats(1000)).await?;
 384              dummy_module.receive_money(outpoint).await?;
 385              assert_eq!(user_client.get_balance().await, sats(1000));
 386  
 387              // Change the fees of the gateway
 388              let fee = "10,10000".to_string();
 389              let federation_fee = FederationRoutingFees::from_str(&fee)?;
 390              let set_configuration_payload = SetConfigurationPayload {
 391                  password: None,
 392                  num_route_hints: None,
 393                  routing_fees: Some(federation_fee),
 394                  network: None,
 395                  per_federation_routing_fees: None,
 396              };
 397              verify_gateway_rpc_success("set_configuration", || {
 398                  rpc_client.set_configuration(set_configuration_payload.clone())
 399              })
 400              .await;
 401  
 402              // we need to reconnect to set the fees as defaults from gateway
 403              reconnect_federation(&rpc_client, &fed).await;
 404  
 405              info!("### Changed gateway routing fees");
 406  
 407              let user_lightning_module = user_client.get_first_module::<LightningClientModule>();
 408              let gateway_id = gateway_test.gateway.gateway_id;
 409              let gateway = user_lightning_module.select_gateway(&gateway_id).await;
 410              let gateway_client = gateway_test.select_client(fed.id()).await;
 411  
 412              let invoice_amount = sats(250);
 413              let invoice = other_lightning_client.invoice(invoice_amount, None).await?;
 414  
 415              // Try to pay an invoice, this should fail since the client will not set the
 416              // gateway's fees.
 417              info!("### User client paying invoice");
 418              let OutgoingLightningPayment {
 419                  payment_type,
 420                  contract_id,
 421                  fee: _,
 422              } = user_lightning_module
 423                  .pay_bolt11_invoice(gateway.clone(), invoice.clone(), ())
 424                  .await
 425                  .expect("No Lightning Payment was started");
 426              match payment_type {
 427                  PayType::Lightning(pay_op) => {
 428                      let mut pay_sub = user_lightning_module
 429                          .subscribe_ln_pay(pay_op)
 430                          .await?
 431                          .into_stream();
 432                      assert_eq!(pay_sub.ok().await?, LnPayState::Created);
 433                      let funded = pay_sub.ok().await?;
 434                      assert_matches!(funded, LnPayState::Funded);
 435                      info!("### User client funded contract");
 436  
 437                      let payload = PayInvoicePayload {
 438                          federation_id: user_client.federation_id(),
 439                          contract_id,
 440                          payment_data: get_payment_data(gateway, invoice),
 441                          preimage_auth: Hash::hash(&[0; 32]),
 442                      };
 443  
 444                      let gw_pay_op = gateway_client
 445                          .get_first_module::<GatewayClientModule>()
 446                          .gateway_pay_bolt11_invoice(payload)
 447                          .await?;
 448                      let mut gw_pay_sub = gateway_client
 449                          .get_first_module::<GatewayClientModule>()
 450                          .gateway_subscribe_ln_pay(gw_pay_op)
 451                          .await?
 452                          .into_stream();
 453                      assert_eq!(gw_pay_sub.ok().await?, GatewayExtPayStates::Created);
 454                      info!("### Gateway client started payment");
 455                      assert_matches!(
 456                          gw_pay_sub.ok().await?,
 457                          GatewayExtPayStates::Canceled {
 458                              error: OutgoingPaymentError {
 459                                  error_type: OutgoingPaymentErrorType::InvalidOutgoingContract {
 460                                      error: OutgoingContractError::Underfunded(_, _)
 461                                  },
 462                                  ..
 463                              }
 464                          }
 465                      );
 466                      info!("### Gateway client canceled payment");
 467                  }
 468                  _ => panic!("Expected Lightning payment!"),
 469              }
 470  
 471              Ok(())
 472          },
 473      )
 474      .await
 475  }
 476  
 477  #[tokio::test(flavor = "multi_thread")]
 478  async fn test_gateway_cannot_claim_invalid_preimage() -> anyhow::Result<()> {
 479      single_federation_test(
 480          |gateway, other_lightning_client, fed, user_client, _| async move {
 481              let gateway_id = gateway.gateway.gateway_id;
 482              let gateway_client = gateway.select_client(fed.id()).await;
 483              // Print money for user_client
 484              let dummy_module = user_client.get_first_module::<DummyClientModule>();
 485              let (_, outpoint) = dummy_module.print_money(sats(1000)).await?;
 486              dummy_module.receive_money(outpoint).await?;
 487              assert_eq!(user_client.get_balance().await, sats(1000));
 488  
 489              // Fund outgoing contract that the user client expects the gateway to pay
 490              let invoice = other_lightning_client.invoice(sats(250), None).await?;
 491              let OutgoingLightningPayment {
 492                  payment_type: _,
 493                  contract_id,
 494                  fee: _,
 495              } = user_pay_invoice(
 496                  &user_client.get_first_module(),
 497                  invoice.clone(),
 498                  &gateway_id,
 499              )
 500              .await?;
 501  
 502              // Try to directly claim the outgoing contract with an invalid preimage
 503              let gateway_module = gateway_client.get_first_module::<GatewayClientModule>();
 504  
 505              let account = gateway_module.api.wait_contract(contract_id).await?;
 506              let outgoing_contract = match account.contract {
 507                  FundedContract::Outgoing(contract) => OutgoingContractAccount {
 508                      amount: account.amount,
 509                      contract,
 510                  },
 511                  _ => {
 512                      panic!("Expected OutgoingContract");
 513                  }
 514              };
 515  
 516              // Bogus preimage
 517              let preimage = Preimage(rand::random());
 518              let claim_input = outgoing_contract.claim(preimage);
 519              let client_input = ClientInput::<LightningInput, GatewayClientStateMachines> {
 520                  input: claim_input,
 521                  state_machines: Arc::new(|_, _| vec![]),
 522                  amount: outgoing_contract.amount,
 523                  keys: vec![gateway_module.redeem_key],
 524              };
 525  
 526              let tx = TransactionBuilder::new().with_input(client_input.into_dyn(gateway_module.id));
 527              let operation_meta_gen = |_: TransactionId, _: Vec<OutPoint>| GatewayMeta::Pay {};
 528              let operation_id = OperationId(invoice.payment_hash().to_byte_array());
 529              let (txid, _) = gateway_client
 530                  .finalize_and_submit_transaction(
 531                      operation_id,
 532                      fedimint_ln_common::KIND.as_str(),
 533                      operation_meta_gen,
 534                      tx,
 535                  )
 536                  .await?;
 537  
 538              // Assert that we did not get paid for claiming a contract with a bogus preimage
 539              assert!(dummy_module
 540                  .receive_money(OutPoint { txid, out_idx: 0 })
 541                  .await
 542                  .is_err());
 543              assert_eq!(gateway_client.get_balance().await, sats(0));
 544              Ok(())
 545          },
 546      )
 547      .await
 548  }
 549  
 550  #[tokio::test(flavor = "multi_thread")]
 551  async fn test_gateway_client_pay_unpayable_invoice() -> anyhow::Result<()> {
 552      single_federation_test(
 553          |gateway, other_lightning_client, fed, user_client, _| async move {
 554              let gateway_id = gateway.gateway.gateway_id;
 555              let gateway_client = gateway.select_client(fed.id()).await;
 556              // Print money for user client
 557              let dummy_module = user_client.get_first_module::<DummyClientModule>();
 558              let lightning_module = user_client.get_first_module::<LightningClientModule>();
 559              let (_, outpoint) = dummy_module.print_money(sats(1000)).await?;
 560              dummy_module.receive_money(outpoint).await?;
 561              assert_eq!(user_client.get_balance().await, sats(1000));
 562  
 563              // Create invoice that cannot be paid
 564              let invoice = other_lightning_client.unpayable_invoice(sats(250), None);
 565  
 566              let gateway = lightning_module.select_gateway(&gateway_id).await;
 567  
 568              // User client pays test invoice
 569              let OutgoingLightningPayment {
 570                  payment_type,
 571                  contract_id,
 572                  fee: _,
 573              } = user_pay_invoice(&lightning_module, invoice.clone(), &gateway_id).await?;
 574              match payment_type {
 575                  PayType::Lightning(pay_op) => {
 576                      let mut pay_sub = lightning_module
 577                          .subscribe_ln_pay(pay_op)
 578                          .await?
 579                          .into_stream();
 580                      assert_eq!(pay_sub.ok().await?, LnPayState::Created);
 581                      let funded = pay_sub.ok().await?;
 582                      assert_matches!(funded, LnPayState::Funded);
 583  
 584                      let payload = PayInvoicePayload {
 585                          federation_id: user_client.federation_id(),
 586                          contract_id,
 587                          payment_data: get_payment_data(gateway, invoice),
 588                          preimage_auth: Hash::hash(&[0; 32]),
 589                      };
 590  
 591                      let gw_pay_op = gateway_client
 592                          .get_first_module::<GatewayClientModule>()
 593                          .gateway_pay_bolt11_invoice(payload)
 594                          .await?;
 595                      let mut gw_pay_sub = gateway_client
 596                          .get_first_module::<GatewayClientModule>()
 597                          .gateway_subscribe_ln_pay(gw_pay_op)
 598                          .await?
 599                          .into_stream();
 600                      assert_eq!(gw_pay_sub.ok().await?, GatewayExtPayStates::Created);
 601                      assert_matches!(gw_pay_sub.ok().await?, GatewayExtPayStates::Canceled { .. });
 602                  }
 603                  _ => panic!("Expected Lightning payment!"),
 604              }
 605  
 606              Ok(())
 607          },
 608      )
 609      .await
 610  }
 611  
 612  #[tokio::test(flavor = "multi_thread")]
 613  async fn test_gateway_client_intercept_valid_htlc() -> anyhow::Result<()> {
 614      single_federation_test(|gateway, _, fed, user_client, _| async move {
 615          let gateway_id = gateway.gateway.gateway_id;
 616          let gateway_client = gateway.select_client(fed.id()).await;
 617          // Print money for gateway client
 618          let initial_gateway_balance = sats(1000);
 619          let dummy_module = gateway_client.get_first_module::<DummyClientModule>();
 620          let (_, outpoint) = dummy_module.print_money(initial_gateway_balance).await?;
 621          dummy_module.receive_money(outpoint).await?;
 622          assert_eq!(gateway_client.get_balance().await, sats(1000));
 623  
 624          // User client creates invoice in federation
 625          let invoice_amount = sats(100);
 626          let ln_module = user_client.get_first_module::<LightningClientModule>();
 627          let ln_gateway = ln_module.select_gateway(&gateway_id).await;
 628          let desc = Description::new("description".to_string())?;
 629          let (_invoice_op, invoice, _) = ln_module
 630              .create_bolt11_invoice(
 631                  invoice_amount,
 632                  Bolt11InvoiceDescription::Direct(&desc),
 633                  None,
 634                  "test intercept valid HTLC",
 635                  ln_gateway,
 636              )
 637              .await?;
 638  
 639          // Run gateway state machine
 640          let htlc = Htlc {
 641              payment_hash: *invoice.payment_hash(),
 642              incoming_amount_msat: Amount::from_msats(invoice.amount_milli_satoshis().unwrap()),
 643              outgoing_amount_msat: Amount::from_msats(invoice.amount_milli_satoshis().unwrap()),
 644              incoming_expiry: u32::MAX,
 645              short_channel_id: Some(1),
 646              incoming_chan_id: 2,
 647              htlc_id: 1,
 648          };
 649          let intercept_op = gateway_client
 650              .get_first_module::<GatewayClientModule>()
 651              .gateway_handle_intercepted_htlc(htlc)
 652              .await?;
 653          let mut intercept_sub = gateway_client
 654              .get_first_module::<GatewayClientModule>()
 655              .gateway_subscribe_ln_receive(intercept_op)
 656              .await?
 657              .into_stream();
 658          assert_eq!(intercept_sub.ok().await?, GatewayExtReceiveStates::Funding);
 659          assert_matches!(
 660              intercept_sub.ok().await?,
 661              GatewayExtReceiveStates::Preimage { .. }
 662          );
 663          assert_eq!(
 664              initial_gateway_balance - invoice_amount,
 665              gateway_client.get_balance().await
 666          );
 667  
 668          Ok(())
 669      })
 670      .await
 671  }
 672  
 673  #[tokio::test(flavor = "multi_thread")]
 674  async fn test_gateway_client_intercept_offer_does_not_exist() -> anyhow::Result<()> {
 675      single_federation_test(|gateway, _, fed, _, _| async move {
 676          let gateway_client = gateway.select_client(fed.id()).await;
 677          // Print money for gateway client
 678          let initial_gateway_balance = sats(1000);
 679          let dummy_module = gateway_client.get_first_module::<DummyClientModule>();
 680          let (_, outpoint) = dummy_module.print_money(initial_gateway_balance).await?;
 681          dummy_module.receive_money(outpoint).await?;
 682          assert_eq!(gateway_client.get_balance().await, sats(1000));
 683  
 684          // Create HTLC that doesn't correspond to an offer in the federation
 685          let htlc = Htlc {
 686              payment_hash: sha256(&[15]),
 687              incoming_amount_msat: Amount::from_msats(100),
 688              outgoing_amount_msat: Amount::from_msats(100),
 689              incoming_expiry: u32::MAX,
 690              short_channel_id: Some(1),
 691              incoming_chan_id: 2,
 692              htlc_id: 1,
 693          };
 694  
 695          match gateway_client
 696              .get_first_module::<GatewayClientModule>()
 697              .gateway_handle_intercepted_htlc(htlc)
 698              .await
 699          {
 700              Ok(_) => panic!(
 701                  "Expected incoming offer validation to fail because the offer does not exist"
 702              ),
 703              Err(e) => assert_eq!(e.to_string(), "Timed out fetching the offer".to_string()),
 704          }
 705  
 706          Ok(())
 707      })
 708      .await
 709  }
 710  
 711  #[tokio::test(flavor = "multi_thread")]
 712  async fn test_gateway_client_intercept_htlc_no_funds() -> anyhow::Result<()> {
 713      single_federation_test(|gateway, _, fed, user_client, _| async move {
 714          let gateway_id = gateway.gateway.gateway_id;
 715          let gateway_client = gateway.select_client(fed.id()).await;
 716          // User client creates invoice in federation
 717          let ln_module = user_client.get_first_module::<LightningClientModule>();
 718          let ln_gateway = ln_module.select_gateway(&gateway_id).await;
 719          let desc = Description::new("description".to_string())?;
 720          let (_invoice_op, invoice, _) = ln_module
 721              .create_bolt11_invoice(
 722                  sats(100),
 723                  Bolt11InvoiceDescription::Direct(&desc),
 724                  None,
 725                  "test intercept htlc but with no funds",
 726                  ln_gateway,
 727              )
 728              .await?;
 729  
 730          // Run gateway state machine
 731          let htlc = Htlc {
 732              payment_hash: *invoice.payment_hash(),
 733              incoming_amount_msat: Amount::from_msats(invoice.amount_milli_satoshis().unwrap()),
 734              outgoing_amount_msat: Amount::from_msats(invoice.amount_milli_satoshis().unwrap()),
 735              incoming_expiry: u32::MAX,
 736              short_channel_id: Some(1),
 737              incoming_chan_id: 2,
 738              htlc_id: 1,
 739          };
 740  
 741          // Attempt to route an HTLC while the gateway has no funds
 742          match gateway_client
 743              .get_first_module::<GatewayClientModule>()
 744              .gateway_handle_intercepted_htlc(htlc)
 745              .await
 746          {
 747              Ok(_) => panic!("Expected incoming offer validation to fail due to lack of funds"),
 748              Err(e) => assert_eq!(e.to_string(), "Insufficient funds".to_string()),
 749          }
 750  
 751          Ok(())
 752      })
 753      .await
 754  }
 755  
 756  #[tokio::test(flavor = "multi_thread")]
 757  async fn test_gateway_client_intercept_htlc_invalid_offer() -> anyhow::Result<()> {
 758      single_federation_test(
 759          |gateway, other_lightning_client, fed, user_client, _| async move {
 760              let gateway_client = gateway.select_client(fed.id()).await;
 761              // Print money for gateway client
 762              let initial_gateway_balance = sats(1000);
 763              let gateway_dummy_module = gateway_client.get_first_module::<DummyClientModule>();
 764              let (_, outpoint) = gateway_dummy_module
 765                  .print_money(initial_gateway_balance)
 766                  .await?;
 767              gateway_dummy_module.receive_money(outpoint).await?;
 768              assert_eq!(gateway_client.get_balance().await, sats(1000));
 769  
 770              // Create test invoice
 771              let invoice = other_lightning_client.unpayable_invoice(sats(250), None);
 772  
 773              // Create offer with a preimage that doesn't correspond to the payment hash of
 774              // the invoice
 775              let user_lightning_module = user_client.get_first_module::<LightningClientModule>();
 776  
 777              let amount = sats(100);
 778              let preimage = BYTE_33;
 779              let ln_output = LightningOutput::new_v0_offer(IncomingContractOffer {
 780                  amount,
 781                  hash: *invoice.payment_hash(),
 782                  encrypted_preimage: EncryptedPreimage::new(
 783                      PreimageKey(preimage),
 784                      &user_lightning_module.cfg.threshold_pub_key,
 785                  ),
 786                  expiry_time: None,
 787              });
 788              // The client's receive state machine can be empty because the gateway should
 789              // not fund this contract
 790              let state_machines = Arc::new(move |_txid: TransactionId, _input_idx: u64| {
 791                  Vec::<LightningClientStateMachines>::new()
 792              });
 793              let client_output = ClientOutput {
 794                  output: ln_output,
 795                  amount: Amount::ZERO,
 796                  state_machines,
 797              };
 798              let tx = TransactionBuilder::new()
 799                  .with_output(client_output.into_dyn(user_lightning_module.id));
 800              let operation_meta_gen = |txid, _| LightningOperationMeta {
 801                  variant: LightningOperationMetaVariant::Receive {
 802                      out_point: OutPoint { txid, out_idx: 0 },
 803                      invoice: invoice.clone(),
 804                      gateway_id: None,
 805                  },
 806                  extra_meta: serde_json::to_value("test intercept HTLC with invalid offer")
 807                      .expect("Failed to serialize string into json"),
 808              };
 809  
 810              let operation_id = OperationId(invoice.payment_hash().to_byte_array());
 811              let (txid, _) = user_client
 812                  .finalize_and_submit_transaction(
 813                      operation_id,
 814                      fedimint_ln_common::KIND.as_str(),
 815                      operation_meta_gen,
 816                      tx,
 817                  )
 818                  .await?;
 819              user_client
 820                  .transaction_updates(operation_id)
 821                  .await
 822                  .await_tx_accepted(txid)
 823                  .await
 824                  .unwrap();
 825  
 826              // Run gateway state machine
 827              let htlc = Htlc {
 828                  payment_hash: *invoice.payment_hash(),
 829                  incoming_amount_msat: Amount::from_msats(invoice.amount_milli_satoshis().unwrap()),
 830                  outgoing_amount_msat: Amount::from_msats(invoice.amount_milli_satoshis().unwrap()),
 831                  incoming_expiry: u32::MAX,
 832                  short_channel_id: Some(1),
 833                  incoming_chan_id: 2,
 834                  htlc_id: 1,
 835              };
 836  
 837              let intercept_op = gateway_client
 838                  .get_first_module::<GatewayClientModule>()
 839                  .gateway_handle_intercepted_htlc(htlc)
 840                  .await?;
 841              let mut intercept_sub = gateway_client
 842                  .get_first_module::<GatewayClientModule>()
 843                  .gateway_subscribe_ln_receive(intercept_op)
 844                  .await?
 845                  .into_stream();
 846              assert_matches!(intercept_sub.ok().await?, GatewayExtReceiveStates::Funding);
 847  
 848              match intercept_sub.ok().await? {
 849                  GatewayExtReceiveStates::RefundSuccess {
 850                      out_points,
 851                      error: _,
 852                  } => {
 853                      // Assert that the gateway got it's refund
 854                      for outpoint in out_points {
 855                          gateway_dummy_module.receive_money(outpoint).await?;
 856                      }
 857  
 858                      assert_eq!(initial_gateway_balance, gateway_client.get_balance().await);
 859                  }
 860                  unexpected_state => panic!(
 861                      "Gateway receive state machine entered unexpected state: {unexpected_state:?}"
 862                  ),
 863              }
 864  
 865              Ok(())
 866          },
 867      )
 868      .await
 869  }
 870  
 871  #[tokio::test(flavor = "multi_thread")]
 872  async fn test_gateway_cannot_pay_expired_invoice() -> anyhow::Result<()> {
 873      single_federation_test(
 874          |gateway, other_lightning_client, fed, user_client, _| async move {
 875              let gateway_id = gateway.gateway.gateway_id;
 876              let gateway_client = gateway.select_client(fed.id()).await;
 877              let invoice = other_lightning_client
 878                  .invoice(sats(1000), 1.into())
 879                  .await
 880                  .unwrap();
 881              assert_eq!(invoice.expiry_time(), Duration::from_secs(1));
 882  
 883              // at seconds granularity, must wait `expiry + 1s` to make sure expired
 884              sleep_in_test("waiting for invoice to expire", Duration::from_secs(2)).await;
 885  
 886              // Print money for user_client
 887              let dummy_module = user_client.get_first_module::<DummyClientModule>();
 888              let (_, outpoint) = dummy_module.print_money(sats(2000)).await?;
 889              dummy_module.receive_money(outpoint).await?;
 890              assert_eq!(user_client.get_balance().await, sats(2000));
 891  
 892              // User client pays test invoice
 893              let lightning_module = user_client.get_first_module::<LightningClientModule>();
 894              let gateway_module = lightning_module.select_gateway(&gateway_id).await;
 895              let OutgoingLightningPayment {
 896                  payment_type,
 897                  contract_id,
 898                  fee: _,
 899              } = user_pay_invoice(&lightning_module, invoice.clone(), &gateway_id).await?;
 900              match payment_type {
 901                  PayType::Lightning(pay_op) => {
 902                      let mut pay_sub = lightning_module
 903                          .subscribe_ln_pay(pay_op)
 904                          .await?
 905                          .into_stream();
 906                      assert_eq!(pay_sub.ok().await?, LnPayState::Created);
 907                      let funded = pay_sub.ok().await?;
 908                      assert_matches!(funded, LnPayState::Funded);
 909  
 910                      let payload = PayInvoicePayload {
 911                          federation_id: user_client.federation_id(),
 912                          contract_id,
 913                          payment_data: get_payment_data(gateway_module, invoice),
 914                          preimage_auth: Hash::hash(&[0; 32]),
 915                      };
 916  
 917                      let gw_pay_op = gateway_client
 918                          .get_first_module::<GatewayClientModule>()
 919                          .gateway_pay_bolt11_invoice(payload)
 920                          .await?;
 921                      let mut gw_pay_sub = gateway_client
 922                          .get_first_module::<GatewayClientModule>()
 923                          .gateway_subscribe_ln_pay(gw_pay_op)
 924                          .await?
 925                          .into_stream();
 926  
 927                      assert_eq!(gw_pay_sub.ok().await?, GatewayExtPayStates::Created);
 928                      assert_matches!(gw_pay_sub.ok().await?, GatewayExtPayStates::Canceled { .. });
 929                  }
 930                  _ => panic!("Expected Lightning payment!"),
 931              }
 932  
 933              // Balance should be unchanged
 934              assert_eq!(gateway_client.get_balance().await, sats(0));
 935  
 936              Ok(())
 937          },
 938      )
 939      .await
 940  }
 941  
 942  // TODO: fix and re-enable https://github.com/fedimint/fedimint/issues/5001
 943  #[ignore]
 944  #[tokio::test(flavor = "multi_thread")]
 945  async fn test_gateway_configuration() -> anyhow::Result<()> {
 946      let fixtures = fixtures();
 947  
 948      let fed = fixtures.new_default_fed().await;
 949      let gateway = fixtures.new_gateway(0, None).await;
 950      let initial_rpc_client = gateway.get_rpc().await;
 951  
 952      // Verify that we can't join a federation yet because the configuration is not
 953      // set
 954      let join_payload = ConnectFedPayload {
 955          invite_code: fed.invite_code().to_string(),
 956      };
 957  
 958      verify_gateway_rpc_failure(
 959          "connect_federation",
 960          || initial_rpc_client.connect_federation(join_payload.clone()),
 961          StatusCode::NOT_FOUND,
 962      )
 963      .await;
 964  
 965      // Verify that the gateway's state is "Configuring"
 966      let gw_info = verify_gateway_rpc_success("get_info", || initial_rpc_client.get_info()).await;
 967      assert_eq!(gw_info.gateway_state, "Configuring".to_string());
 968  
 969      // Verify that the gateway's fees, and network are `None`
 970      assert_eq!(gw_info.fees, None);
 971      assert_eq!(gw_info.network, None);
 972  
 973      let test_password = "test_password".to_string();
 974      let set_configuration_payload = SetConfigurationPayload {
 975          password: Some(test_password.clone()),
 976          num_route_hints: None,
 977          routing_fees: None,
 978          network: None,
 979          per_federation_routing_fees: None,
 980      };
 981      verify_gateway_rpc_success("set_configuration", || {
 982          initial_rpc_client.set_configuration(set_configuration_payload.clone())
 983      })
 984      .await;
 985  
 986      // Verify that the gateway's password is stored correctly (i.e. the stored hash
 987      // and salt match the password)
 988      let gateway_config = gateway
 989          .gateway
 990          .gateway_config
 991          .read()
 992          .await
 993          .clone()
 994          .expect("Gateway config should be set");
 995      assert_eq!(
 996          gateway_config.hashed_password,
 997          hash_password(test_password.clone(), gateway_config.password_salt)
 998      );
 999  
1000      // Verify client with no password fails since the password has been set
1001      verify_gateway_rpc_failure(
1002          "get_info",
1003          || initial_rpc_client.get_info(),
1004          StatusCode::UNAUTHORIZED,
1005      )
1006      .await;
1007  
1008      // Verify the gateway's state is "Running" with default fee and default or
1009      // lightning node network
1010      let initial_rpc_client_with_password = initial_rpc_client.with_password(Some(test_password));
1011      let gw_info =
1012          verify_gateway_rpc_success("get_info", || initial_rpc_client_with_password.get_info())
1013              .await;
1014      assert_eq!(gw_info.gateway_state, "Running".to_string());
1015      assert_eq!(gw_info.fees, Some(DEFAULT_FEES));
1016      assert_eq!(gw_info.network, Some(DEFAULT_NETWORK));
1017  
1018      // Verify we can change configurations when the gateway is running
1019      let new_password = "new_password".to_string();
1020      let fee = "10,10000".to_string();
1021      let federation_fee = FederationRoutingFees::from_str(&fee)?;
1022      let set_configuration_payload = SetConfigurationPayload {
1023          password: Some(new_password.clone()),
1024          num_route_hints: Some(1),
1025          routing_fees: Some(federation_fee.clone()),
1026          network: None,
1027          per_federation_routing_fees: None,
1028      };
1029      verify_gateway_rpc_success("set_configuration", || {
1030          initial_rpc_client_with_password.set_configuration(set_configuration_payload.clone())
1031      })
1032      .await;
1033  
1034      // Verify info works with the new password.
1035      let new_password_rpc_client = initial_rpc_client.with_password(Some(new_password.clone()));
1036      let gw_info =
1037          verify_gateway_rpc_success("get_info", || new_password_rpc_client.get_info()).await;
1038  
1039      assert_eq!(gw_info.gateway_state, "Running".to_string());
1040      assert_eq!(gw_info.fees, Some(GatewayFee(federation_fee.into()).0));
1041      assert_eq!(gw_info.network, Some(DEFAULT_NETWORK));
1042  
1043      // Verify that get_info with the old password fails
1044      verify_gateway_rpc_failure(
1045          "get_info",
1046          || initial_rpc_client_with_password.get_info(),
1047          StatusCode::UNAUTHORIZED,
1048      )
1049      .await;
1050  
1051      // Verify we can configure gateway to a network same as than the lightning nodes
1052      let set_configuration_payload = SetConfigurationPayload {
1053          password: Some(new_password.clone()),
1054          num_route_hints: None,
1055          network: Some(DEFAULT_NETWORK), /* Same as connected
1056                                           * lightning node's
1057                                           * network */
1058          routing_fees: None,
1059          per_federation_routing_fees: None,
1060      };
1061      verify_gateway_rpc_success("set_configuration", || {
1062          new_password_rpc_client.set_configuration(set_configuration_payload.clone())
1063      })
1064      .await;
1065  
1066      // Verify we cannot reconfigure gateway to a network different than the
1067      // lightning nodes
1068      let set_configuration_payload = SetConfigurationPayload {
1069          password: Some(new_password.clone()),
1070          num_route_hints: None,
1071          network: Some(Network::Testnet), /* Different from
1072                                            * connected lightning
1073                                            * node's network */
1074          routing_fees: None,
1075          per_federation_routing_fees: None,
1076      };
1077      verify_gateway_rpc_failure(
1078          "set_configuration",
1079          || new_password_rpc_client.set_configuration(set_configuration_payload.clone()),
1080          StatusCode::INTERNAL_SERVER_ERROR,
1081      )
1082      .await;
1083  
1084      // Verify we can connect to a federation if the gateway is configured to use
1085      // the same network. Test federations are on Regtest by default
1086      verify_gateway_rpc_success("connect_federation", || {
1087          new_password_rpc_client.connect_federation(join_payload.clone())
1088      })
1089      .await;
1090  
1091      verify_gateway_rpc_success("get_balance", || {
1092          new_password_rpc_client.get_balance(BalancePayload {
1093              federation_id: fed.invite_code().federation_id(),
1094          })
1095      })
1096      .await;
1097  
1098      // Verify we can configure gateway to charge fees for specific federation
1099      let federation_routing_fees = FederationRoutingFees::from_str("10,10000")?;
1100      let set_configuration_payload = SetConfigurationPayload {
1101          password: None,
1102          num_route_hints: None,
1103          routing_fees: None,
1104          network: None,
1105          per_federation_routing_fees: Some(vec![(fed.id(), federation_routing_fees.clone())]),
1106      };
1107      verify_gateway_rpc_success("set_configuration", || {
1108          new_password_rpc_client.set_configuration(set_configuration_payload.clone())
1109      })
1110      .await;
1111      // Verify info has new per federation routing fees.
1112      let new_password_rpc_client = initial_rpc_client.with_password(Some(new_password.clone()));
1113      let gw_info =
1114          verify_gateway_rpc_success("get_info", || new_password_rpc_client.get_info()).await;
1115      assert_eq!(
1116          gw_info
1117              .federations
1118              .iter()
1119              .find(|f| f.federation_id == fed.id())
1120              .and_then(|f| f.routing_fees.clone()),
1121          Some(federation_routing_fees)
1122      );
1123  
1124      Ok(())
1125  }
1126  
1127  #[tokio::test(flavor = "multi_thread")]
1128  async fn test_gateway_supports_connecting_multiple_federations() -> anyhow::Result<()> {
1129      multi_federation_test(|gateway, rpc, fed1, fed2, _| async move {
1130          info!("Starting test_gateway_supports_connecting_multiple_federations");
1131          assert_eq!(rpc.get_info().await.unwrap().federations.len(), 0);
1132  
1133          let invite1 = fed1.invite_code();
1134          let info = rpc
1135              .connect_federation(ConnectFedPayload {
1136                  invite_code: invite1.to_string(),
1137              })
1138              .await
1139              .unwrap();
1140  
1141          assert_eq!(info.federation_id, invite1.federation_id());
1142  
1143          let invite2 = fed2.invite_code();
1144          let info = rpc
1145              .connect_federation(ConnectFedPayload {
1146                  invite_code: invite2.to_string(),
1147              })
1148              .await
1149              .unwrap();
1150          assert_eq!(info.federation_id, invite2.federation_id());
1151          drop(gateway); // keep until the end to avoid the gateway shutting down too early
1152          Ok(())
1153      })
1154      .await
1155  }
1156  
1157  #[tokio::test(flavor = "multi_thread")]
1158  async fn test_gateway_shows_info_about_all_connected_federations() -> anyhow::Result<()> {
1159      multi_federation_test(|gateway, rpc, fed1, fed2, _| async move {
1160          assert_eq!(rpc.get_info().await.unwrap().federations.len(), 0);
1161  
1162          let id1 = fed1.invite_code().federation_id();
1163          let id2 = fed2.invite_code().federation_id();
1164  
1165          connect_federations(&rpc, &[fed1, fed2]).await.unwrap();
1166  
1167          let info = rpc.get_info().await.unwrap();
1168  
1169          assert_eq!(info.federations.len(), 2);
1170          assert!(info
1171              .federations
1172              .iter()
1173              .any(|info| info.federation_id == id1 && info.balance_msat == Amount::ZERO));
1174          assert!(info
1175              .federations
1176              .iter()
1177              .any(|info| info.federation_id == id2 && info.balance_msat == Amount::ZERO));
1178          drop(gateway); // keep until the end to avoid the gateway shutting down too early
1179          Ok(())
1180      })
1181      .await
1182  }
1183  
1184  #[tokio::test(flavor = "multi_thread")]
1185  async fn test_gateway_can_leave_connected_federations() -> anyhow::Result<()> {
1186      multi_federation_test(|gateway, rpc, fed1, fed2, _| async move {
1187          assert_eq!(rpc.get_info().await.unwrap().federations.len(), 0);
1188  
1189          let invite1 = fed1.invite_code();
1190          let invite2 = fed2.invite_code();
1191  
1192          let id1 = invite1.federation_id();
1193          let id2 = invite2.federation_id();
1194  
1195          connect_federations(&rpc, &[fed1, fed2]).await.unwrap();
1196  
1197          let info = rpc.get_info().await.unwrap();
1198          assert_eq!(info.federations.len(), 2);
1199          assert!(info
1200              .federations
1201              .iter()
1202              .any(|info| info.federation_id == id1 && info.channel_id == Some(1)));
1203          assert!(info
1204              .federations
1205              .iter()
1206              .any(|info| info.federation_id == id2 && info.channel_id == Some(2)));
1207  
1208          // remove first connected federation
1209          let fed_info = rpc
1210              .leave_federation(LeaveFedPayload { federation_id: id1 })
1211              .await
1212              .unwrap();
1213          assert_eq!(fed_info.federation_id, id1);
1214          assert_eq!(fed_info.channel_id, Some(1));
1215  
1216          // reconnect the first federation
1217          let fed_info = rpc
1218              .connect_federation(ConnectFedPayload {
1219                  invite_code: invite1.to_string(),
1220              })
1221              .await
1222              .unwrap();
1223          assert_eq!(fed_info.federation_id, id1);
1224          assert_eq!(fed_info.channel_id, Some(3));
1225  
1226          // remove second connected federation
1227          let fed_info = rpc
1228              .leave_federation(LeaveFedPayload { federation_id: id2 })
1229              .await
1230              .unwrap();
1231          assert_eq!(fed_info.federation_id, id2);
1232          assert_eq!(fed_info.channel_id, Some(2));
1233  
1234          // reconnect the second federation
1235          let fed_info = rpc
1236              .connect_federation(ConnectFedPayload {
1237                  invite_code: invite2.to_string(),
1238              })
1239              .await
1240              .unwrap();
1241          assert_eq!(fed_info.federation_id, id2);
1242          assert_eq!(fed_info.channel_id, Some(4));
1243  
1244          let info = rpc.get_info().await.unwrap();
1245          assert_eq!(info.federations.len(), 2);
1246          assert_eq!(
1247              info.channels.unwrap().keys().cloned().collect::<Vec<u64>>(),
1248              vec![3, 4]
1249          );
1250  
1251          drop(gateway); // keep until the end to avoid the gateway shutting down too early
1252          Ok(())
1253      })
1254      .await
1255  }
1256  
1257  #[tokio::test(flavor = "multi_thread")]
1258  async fn test_gateway_shows_balance_for_any_connected_federation() -> anyhow::Result<()> {
1259      multi_federation_test(|gateway, rpc, fed1, fed2, _| async move {
1260          let id1 = fed1.invite_code().federation_id();
1261          let id2 = fed2.invite_code().federation_id();
1262  
1263          connect_federations(&rpc, &[fed1, fed2]).await.unwrap();
1264  
1265          let pre_balances = get_balances(&rpc, &[id1, id2]).await;
1266  
1267          send_msats_to_gateway(&gateway, id1, 5_000).await;
1268          send_msats_to_gateway(&gateway, id2, 1_000).await;
1269  
1270          let post_balances = get_balances(&rpc, &[id1, id2]).await;
1271  
1272          assert_eq!(pre_balances[0], 0);
1273          assert_eq!(pre_balances[1], 0);
1274          assert_eq!(post_balances[0], 5_000);
1275          assert_eq!(post_balances[1], 1_000);
1276          Ok(())
1277      })
1278      .await
1279  }
1280  
1281  #[tokio::test(flavor = "multi_thread")]
1282  async fn test_gateway_executes_swaps_between_connected_federations() -> anyhow::Result<()> {
1283      multi_federation_test(|gateway, rpc, fed1, fed2, _| async move {
1284          let gateway_id = gateway.gateway.gateway_id;
1285          let id1 = fed1.invite_code().federation_id();
1286          let id2 = fed2.invite_code().federation_id();
1287  
1288          connect_federations(&rpc, &[fed1.clone(), fed2.clone()])
1289              .await
1290              .unwrap();
1291  
1292          // setting specific routing fees for fed1
1293          let fed_routing_fees = FederationRoutingFees::from_str("10,10000")?;
1294          let set_configuration_payload = SetConfigurationPayload {
1295              password: None,
1296              num_route_hints: None,
1297              routing_fees: None,
1298              network: None,
1299              per_federation_routing_fees: Some(vec![(id1, fed_routing_fees.clone())]),
1300          };
1301          verify_gateway_rpc_success("set_configuration", || {
1302              rpc.set_configuration(set_configuration_payload.clone())
1303          })
1304          .await;
1305  
1306          send_msats_to_gateway(&gateway, id1, 10_000).await;
1307          send_msats_to_gateway(&gateway, id2, 10_000).await;
1308  
1309          let client1 = fed1.new_client().await;
1310          let client2 = fed2.new_client().await;
1311  
1312          // Check gateway balances before facilitating direct swap between federations
1313          let pre_balances = get_balances(&rpc, &[id1, id2]).await;
1314          assert_eq!(pre_balances[0], 10_000);
1315          assert_eq!(pre_balances[1], 10_000);
1316  
1317          let deposit_amt = msats(5_000);
1318          let client1_dummy_module = client1.get_first_module::<DummyClientModule>();
1319          let (_, outpoint) = client1_dummy_module.print_money(deposit_amt).await?;
1320          client1_dummy_module.receive_money(outpoint).await?;
1321          assert_eq!(client1.get_balance().await, deposit_amt);
1322  
1323          // User creates invoice in federation 2
1324          let invoice_amt = msats(2_500);
1325          let ln_module = client2.get_first_module::<LightningClientModule>();
1326          let ln_gateway = ln_module.select_gateway(&gateway_id).await;
1327          let desc = Description::new("description".to_string())?;
1328          let (receive_op, invoice, _) = ln_module
1329              .create_bolt11_invoice(
1330                  invoice_amt,
1331                  Bolt11InvoiceDescription::Direct(&desc),
1332                  None,
1333                  "test gw swap between federations",
1334                  ln_gateway,
1335              )
1336              .await?;
1337          let mut receive_sub = ln_module
1338              .subscribe_ln_receive(receive_op)
1339              .await?
1340              .into_stream();
1341  
1342          // A client pays invoice in federation 1
1343          let gateway_client = gateway.select_client(id1).await;
1344          gateway_pay_valid_invoice(
1345              invoice,
1346              &client1,
1347              &gateway_client,
1348              &gateway.gateway.gateway_id,
1349          )
1350          .await?;
1351  
1352          // A client receives cash via swap in federation 2
1353          assert_eq!(receive_sub.ok().await?, LnReceiveState::Created);
1354          let waiting_payment = receive_sub.ok().await?;
1355          assert_matches!(waiting_payment, LnReceiveState::WaitingForPayment { .. });
1356          let funded = receive_sub.ok().await?;
1357          assert_matches!(funded, LnReceiveState::Funded);
1358          let waiting_funds = receive_sub.ok().await?;
1359          assert_matches!(waiting_funds, LnReceiveState::AwaitingFunds { .. });
1360          let claimed = receive_sub.ok().await?;
1361          assert_matches!(claimed, LnReceiveState::Claimed);
1362          assert_eq!(client2.get_balance().await, invoice_amt);
1363  
1364          // Check gateway balances after facilitating direct swap between federations
1365          let gateway_fed1_balance = gateway_client.get_balance().await;
1366          let gateway_fed2_client = gateway.select_client(id2).await;
1367          let gateway_fed2_balance = gateway_fed2_client.get_balance().await;
1368  
1369          // Balance in gateway of sending federation is deducted the invoice amount
1370          assert_eq!(
1371              gateway_fed2_balance.msats,
1372              pre_balances[1] - invoice_amt.msats
1373          );
1374  
1375          let fee = routing_fees_in_msats(&fed_routing_fees, &invoice_amt);
1376  
1377          // Balance in gateway of receiving federation is increased `invoice_amt` + `fee`
1378          assert_eq!(
1379              gateway_fed1_balance.msats,
1380              pre_balances[0] + invoice_amt.msats + fee
1381          );
1382  
1383          Ok(())
1384      })
1385      .await
1386  }
1387  
1388  fn routing_fees_in_msats(routing_fees: &FederationRoutingFees, amount: &Amount) -> u64 {
1389      ((amount.msats * routing_fees.proportional_millionths as u64) / 1_000_000)
1390          + routing_fees.base_msat as u64
1391  }
1392  
1393  async fn reconnect_federation(rpc: &GatewayRpcClient, fed: &FederationTest) {
1394      verify_gateway_rpc_success("leave_federation", || {
1395          rpc.leave_federation(LeaveFedPayload {
1396              federation_id: fed.id(),
1397          })
1398      })
1399      .await;
1400      verify_gateway_rpc_success("connect_federation", || {
1401          rpc.connect_federation(ConnectFedPayload {
1402              invite_code: fed.invite_code().to_string(),
1403          })
1404      })
1405      .await;
1406  }
1407  
1408  /// Verifies that a gateway RPC succeeds. If it fails, the status code of the
1409  /// RPC is printed.
1410  async fn verify_gateway_rpc_success<Fut, T>(name: &str, func: impl Fn() -> Fut) -> T
1411  where
1412      Fut: Future<Output = GatewayRpcResult<T>>,
1413  {
1414      match func().await {
1415          Ok(ret) => ret,
1416          Err(GatewayRpcError::RequestError(e)) => panic!("RequestError during {name}: {e:?}"),
1417          Err(GatewayRpcError::BadStatus(status)) => {
1418              panic!("{name} returned error code {status} when success was expected")
1419          }
1420      }
1421  }
1422  
1423  /// Verifies that a gateway RPC fails with a specific `StatusCode`
1424  async fn verify_gateway_rpc_failure<Fut, T>(
1425      name: &str,
1426      func: impl Fn() -> Fut,
1427      status_code: StatusCode,
1428  ) where
1429      Fut: Future<Output = GatewayRpcResult<T>>,
1430  {
1431      match func().await {
1432          Ok(_) => panic!("{name} returned success, expected {status_code}"),
1433          Err(GatewayRpcError::RequestError(e)) => panic!("RequestError during {name}: {e:?}"),
1434          Err(GatewayRpcError::BadStatus(status)) => {
1435              assert_eq!(
1436                  status, status_code,
1437                  "Unexpected status code returned. Expected: {status_code}, found {status}"
1438              )
1439          }
1440      }
1441  }
1442  
1443  /// Connects the gateway to all federations in `feds`.
1444  async fn connect_federations(
1445      rpc: &GatewayRpcClient,
1446      feds: &[FederationTest],
1447  ) -> anyhow::Result<()> {
1448      for fed in feds {
1449          let invite_code = fed.invite_code().to_string();
1450          rpc.connect_federation(ConnectFedPayload { invite_code })
1451              .await?;
1452      }
1453      Ok(())
1454  }
1455  
1456  /// Retrieves the balance of each federation the gateway is connected to.
1457  async fn get_balances(
1458      rpc: &GatewayRpcClient,
1459      ids: impl IntoIterator<Item = &FederationId>,
1460  ) -> Vec<u64> {
1461      let mut balances = vec![];
1462      for id in ids.into_iter() {
1463          balances.push(
1464              rpc.get_balance(BalancePayload { federation_id: *id })
1465                  .await
1466                  .unwrap()
1467                  .msats,
1468          )
1469      }
1470  
1471      balances
1472  }
1473  
1474  /// Prints msats for the gateway using the dummy module.
1475  async fn send_msats_to_gateway(gateway: &GatewayTest, id: FederationId, msats: u64) {
1476      let client = gateway.select_client(id).await;
1477      let dummy_module = client.get_first_module::<DummyClientModule>();
1478      let (_, outpoint) = dummy_module
1479          .print_money(Amount::from_msats(msats))
1480          .await
1481          .unwrap();
1482      dummy_module.receive_money(outpoint).await.unwrap();
1483  }