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 }