1#![deny(clippy::pedantic)]
2#![allow(clippy::cast_possible_truncation)]
3#![allow(clippy::cast_possible_wrap)]
4#![allow(clippy::cast_sign_loss)]
5#![allow(clippy::default_trait_access)]
6#![allow(clippy::doc_markdown)]
7#![allow(clippy::missing_errors_doc)]
8#![allow(clippy::missing_panics_doc)]
9#![allow(clippy::module_name_repetitions)]
10#![allow(clippy::must_use_candidate)]
11#![allow(clippy::return_self_not_must_use)]
12#![allow(clippy::similar_names)]
13#![allow(clippy::too_many_lines)]
14
15pub mod client;
16pub mod config;
17mod db;
18pub mod envs;
19mod error;
20mod federation_manager;
21pub mod gateway_module_v2;
22pub mod lightning;
23pub mod rpc;
24pub mod state_machine;
25mod types;
26
27use std::collections::{BTreeMap, BTreeSet};
28use std::env;
29use std::fmt::Display;
30use std::net::SocketAddr;
31use std::str::FromStr;
32use std::sync::Arc;
33use std::time::Duration;
34
35use anyhow::{anyhow, Context};
36use bitcoin::hashes::sha256;
37use bitcoin::{Address, Network, Txid};
38use clap::Parser;
39use client::GatewayClientBuilder;
40use config::GatewayOpts;
41pub use config::GatewayParameters;
42use db::{GatewayConfiguration, GatewayConfigurationKey, GatewayDbtxNcExt};
43use error::FederationNotConnected;
44use federation_manager::FederationManager;
45use fedimint_api_client::api::net::Connector;
46use fedimint_bip39::{Bip39RootSecretStrategy, Language, Mnemonic};
47use fedimint_client::module::init::ClientModuleInitRegistry;
48use fedimint_client::secret::RootSecretStrategy;
49use fedimint_client::{Client, ClientHandleArc};
50use fedimint_core::config::FederationId;
51use fedimint_core::core::{
52 ModuleInstanceId, ModuleKind, LEGACY_HARDCODED_INSTANCE_ID_MINT,
53 LEGACY_HARDCODED_INSTANCE_ID_WALLET,
54};
55use fedimint_core::db::{apply_migrations_server, Database, DatabaseTransaction};
56use fedimint_core::invite_code::InviteCode;
57use fedimint_core::module::CommonModuleInit;
58use fedimint_core::secp256k1::schnorr::Signature;
59use fedimint_core::secp256k1::PublicKey;
60use fedimint_core::task::{sleep, TaskGroup, TaskHandle, TaskShutdownToken};
61use fedimint_core::time::duration_since_epoch;
62use fedimint_core::util::{SafeUrl, Spanned};
63use fedimint_core::{fedimint_build_code_version_env, Amount, BitcoinAmountOrAll};
64use fedimint_ln_common::config::{GatewayFee, LightningClientConfig};
65use fedimint_ln_common::contracts::Preimage;
66use fedimint_ln_common::LightningCommonInit;
67use fedimint_lnv2_common::contracts::{IncomingContract, PaymentImage};
68use fedimint_lnv2_common::gateway_api::{
69 CreateBolt11InvoicePayload, PaymentFee, RoutingInfo, SendPaymentPayload,
70};
71use fedimint_lnv2_common::Bolt11InvoiceDescription;
72use fedimint_mint_client::{
73 MintClientInit, MintClientModule, MintCommonInit, SelectNotesWithAtleastAmount,
74 SelectNotesWithExactAmount,
75};
76use fedimint_wallet_client::{
77 WalletClientInit, WalletClientModule, WalletCommonInit, WithdrawState,
78};
79use futures::stream::StreamExt;
80use lightning::{
81 CloseChannelsWithPeerResponse, CreateInvoiceRequest, ILnRpcClient, InterceptPaymentRequest,
82 InterceptPaymentResponse, InvoiceDescription, LightningBuilder, LightningRpcError,
83 PaymentAction,
84};
85use lightning_invoice::{Bolt11Invoice, RoutingFees};
86use rand::{thread_rng, Rng};
87use rpc::{
88 CloseChannelsWithPeerPayload, CreateInvoiceForOperatorPayload, FederationInfo,
89 GatewayFedConfig, GatewayInfo, LeaveFedPayload, MnemonicResponse, OpenChannelPayload,
90 PayInvoiceForOperatorPayload, ReceiveEcashPayload, ReceiveEcashResponse, SendOnchainPayload,
91 SetConfigurationPayload, SpendEcashPayload, SpendEcashResponse, WithdrawResponse,
92 V1_API_ENDPOINT,
93};
94use state_machine::{GatewayClientModule, GatewayExtPayStates};
95use tokio::sync::RwLock;
96use tracing::{debug, error, info, info_span, warn};
97
98use crate::config::LightningModuleMode;
99use crate::db::{get_gatewayd_database_migrations, FederationConfig};
100use crate::envs::FM_GATEWAY_MNEMONIC_ENV;
101use crate::error::{AdminGatewayError, LNv1Error, LNv2Error, PublicGatewayError};
102use crate::gateway_module_v2::GatewayClientModuleV2;
103use crate::lightning::{GatewayLightningBuilder, LightningContext, LightningMode, RouteHtlcStream};
104use crate::rpc::rpc_server::{hash_password, run_webserver};
105use crate::rpc::{
106 BackupPayload, ConnectFedPayload, DepositAddressPayload, FederationBalanceInfo,
107 GatewayBalances, WithdrawPayload,
108};
109use crate::types::PrettyInterceptPaymentRequest;
110
111const GW_ANNOUNCEMENT_TTL: Duration = Duration::from_secs(600);
113
114const DEFAULT_NUM_ROUTE_HINTS: u32 = 1;
117
118pub const DEFAULT_NETWORK: Network = Network::Regtest;
120
121pub const DEFAULT_FEES: RoutingFees = RoutingFees {
124 base_msat: 0,
126 proportional_millionths: 10000,
129};
130
131const EXPIRATION_DELTA_MINIMUM_V2: u64 = 144;
133
134pub type Result<T> = std::result::Result<T, PublicGatewayError>;
135pub type AdminResult<T> = std::result::Result<T, AdminGatewayError>;
136
137const DB_FILE: &str = "gatewayd.db";
140
141const LDK_NODE_DB_FOLDER: &str = "ldk_node";
144
145const DEFAULT_MODULE_KINDS: [(ModuleInstanceId, &ModuleKind); 2] = [
147 (LEGACY_HARDCODED_INSTANCE_ID_MINT, &MintCommonInit::KIND),
148 (LEGACY_HARDCODED_INSTANCE_ID_WALLET, &WalletCommonInit::KIND),
149];
150
151#[cfg_attr(doc, aquamarine::aquamarine)]
152#[derive(Clone, Debug)]
166pub enum GatewayState {
167 Initializing,
168 Configuring,
169 Syncing,
170 Connected,
171 Running { lightning_context: LightningContext },
172 Disconnected,
173 ShuttingDown { lightning_context: LightningContext },
174}
175
176impl Display for GatewayState {
177 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
178 match self {
179 GatewayState::Initializing => write!(f, "Initializing"),
180 GatewayState::Configuring => write!(f, "Configuring"),
181 GatewayState::Syncing => write!(f, "Syncing"),
182 GatewayState::Connected => write!(f, "Connected"),
183 GatewayState::Running { .. } => write!(f, "Running"),
184 GatewayState::Disconnected => write!(f, "Disconnected"),
185 GatewayState::ShuttingDown { .. } => write!(f, "ShuttingDown"),
186 }
187 }
188}
189
190enum ReceivePaymentStreamAction {
192 ImmediatelyRetry,
193 RetryAfterDelay,
194 NoRetry,
195}
196
197#[derive(Clone)]
198pub struct Gateway {
199 federation_manager: Arc<RwLock<FederationManager>>,
201
202 lightning_builder: Arc<dyn LightningBuilder + Send + Sync>,
205
206 gateway_config: Arc<RwLock<Option<GatewayConfiguration>>>,
208
209 state: Arc<RwLock<GatewayState>>,
211
212 client_builder: GatewayClientBuilder,
215
216 gateway_db: Database,
218
219 gateway_id: PublicKey,
222
223 versioned_api: SafeUrl,
225
226 listen: SocketAddr,
228
229 lightning_module_mode: LightningModuleMode,
231}
232
233impl std::fmt::Debug for Gateway {
234 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235 f.debug_struct("Gateway")
236 .field("federation_manager", &self.federation_manager)
237 .field("gateway_config", &self.gateway_config)
238 .field("state", &self.state)
239 .field("client_builder", &self.client_builder)
240 .field("gateway_db", &self.gateway_db)
241 .field("gateway_id", &self.gateway_id)
242 .field("versioned_api", &self.versioned_api)
243 .field("listen", &self.listen)
244 .finish_non_exhaustive()
245 }
246}
247
248impl Gateway {
249 #[allow(clippy::too_many_arguments)]
252 pub async fn new_with_custom_registry(
253 lightning_builder: Arc<dyn LightningBuilder + Send + Sync>,
254 client_builder: GatewayClientBuilder,
255 listen: SocketAddr,
256 api_addr: SafeUrl,
257 cli_password: Option<String>,
258 network: Option<Network>,
259 fees: RoutingFees,
260 num_route_hints: u32,
261 gateway_db: Database,
262 gateway_state: GatewayState,
263 lightning_module_mode: LightningModuleMode,
264 ) -> anyhow::Result<Gateway> {
265 let versioned_api = api_addr
266 .join(V1_API_ENDPOINT)
267 .expect("Failed to version gateway API address");
268 Gateway::new(
269 lightning_builder,
270 GatewayParameters {
271 listen,
272 versioned_api,
273 password: cli_password,
274 num_route_hints,
275 fees: Some(GatewayFee(fees)),
276 network,
277 lightning_module_mode,
278 },
279 gateway_db,
280 client_builder,
281 gateway_state,
282 )
283 .await
284 }
285
286 pub async fn new_with_default_modules() -> anyhow::Result<Gateway> {
289 let opts = GatewayOpts::parse();
290
291 let mut registry = ClientModuleInitRegistry::new();
294 registry.attach(MintClientInit);
295 registry.attach(WalletClientInit::default());
296
297 let decoders = registry.available_decoders(DEFAULT_MODULE_KINDS.iter().copied())?;
298
299 let gateway_db = Database::new(
300 fedimint_rocksdb::RocksDb::open(opts.data_dir.join(DB_FILE))?,
301 decoders,
302 );
303
304 let client_builder =
305 GatewayClientBuilder::new(opts.data_dir.clone(), registry, fedimint_mint_client::KIND);
306
307 info!(
308 "Starting gatewayd (version: {})",
309 fedimint_build_code_version_env!()
310 );
311
312 let mut gateway_parameters = opts.to_gateway_parameters()?;
313
314 if gateway_parameters.lightning_module_mode != LightningModuleMode::LNv2
315 && matches!(opts.mode, LightningMode::Ldk { .. })
316 {
317 warn!("Overriding LDK Gateway to only run LNv2...");
318 gateway_parameters.lightning_module_mode = LightningModuleMode::LNv2;
319 }
320
321 let mnemonic = Self::load_or_generate_mnemonic(&gateway_db).await?;
322 Gateway::new(
323 Arc::new(GatewayLightningBuilder {
324 lightning_mode: opts.mode,
325 gateway_db: gateway_db.clone(),
326 ldk_data_dir: opts.data_dir.join(LDK_NODE_DB_FOLDER),
327 mnemonic,
328 }),
329 gateway_parameters,
330 gateway_db,
331 client_builder,
332 GatewayState::Initializing,
333 )
334 .await
335 }
336
337 async fn new(
340 lightning_builder: Arc<dyn LightningBuilder + Send + Sync>,
341 gateway_parameters: GatewayParameters,
342 gateway_db: Database,
343 client_builder: GatewayClientBuilder,
344 gateway_state: GatewayState,
345 ) -> anyhow::Result<Gateway> {
346 apply_migrations_server(
349 &gateway_db,
350 "gatewayd".to_string(),
351 get_gatewayd_database_migrations(),
352 )
353 .await?;
354
355 let gateway_config =
358 Self::get_gateway_configuration(gateway_db.clone(), &gateway_parameters).await;
359
360 Ok(Self {
361 federation_manager: Arc::new(RwLock::new(FederationManager::new())),
362 lightning_builder,
363 gateway_config: Arc::new(RwLock::new(gateway_config)),
364 state: Arc::new(RwLock::new(gateway_state)),
365 client_builder,
366 gateway_id: Self::load_or_create_gateway_id(&gateway_db).await,
367 gateway_db,
368 versioned_api: gateway_parameters.versioned_api,
369 listen: gateway_parameters.listen,
370 lightning_module_mode: gateway_parameters.lightning_module_mode,
371 })
372 }
373
374 async fn load_or_create_gateway_id(gateway_db: &Database) -> PublicKey {
376 let mut dbtx = gateway_db.begin_transaction().await;
377 let keypair = dbtx.load_or_create_gateway_keypair().await;
378 dbtx.commit_tx().await;
379 keypair.public_key()
380 }
381
382 pub fn gateway_id(&self) -> PublicKey {
383 self.gateway_id
384 }
385
386 pub fn versioned_api(&self) -> &SafeUrl {
387 &self.versioned_api
388 }
389
390 pub async fn clone_gateway_config(&self) -> Option<GatewayConfiguration> {
391 self.gateway_config.read().await.clone()
392 }
393
394 async fn get_state(&self) -> GatewayState {
395 self.state.read().await.clone()
396 }
397
398 pub async fn dump_database(
401 dbtx: &mut DatabaseTransaction<'_>,
402 prefix_names: Vec<String>,
403 ) -> BTreeMap<String, Box<dyn erased_serde::Serialize + Send>> {
404 dbtx.dump_database(prefix_names).await
405 }
406
407 pub async fn run(self, tg: TaskGroup) -> anyhow::Result<TaskShutdownToken> {
412 self.register_clients_timer(&tg);
413 self.load_clients().await?;
414 self.start_gateway(&tg);
415 let handle = tg.make_handle();
417 run_webserver(Arc::new(self), tg).await?;
418 let shutdown_receiver = handle.make_shutdown_rx();
419 Ok(shutdown_receiver)
420 }
421
422 fn start_gateway(&self, task_group: &TaskGroup) {
425 const PAYMENT_STREAM_RETRY_SECONDS: u64 = 5;
426
427 let self_copy = self.clone();
428 let tg = task_group.clone();
429 task_group.spawn(
430 "Subscribe to intercepted lightning payments in stream",
431 |handle| async move {
432 loop {
434 if handle.is_shutting_down() {
435 info!("Gateway lightning payment stream handler loop is shutting down");
436 break;
437 }
438
439 let payment_stream_task_group = tg.make_subgroup();
440 let lnrpc_route = self_copy.lightning_builder.build().await;
441
442 debug!("Establishing lightning payment stream...");
443 let (stream, ln_client) = match lnrpc_route.route_htlcs(&payment_stream_task_group).await
444 {
445 Ok((stream, ln_client)) => (stream, ln_client),
446 Err(e) => {
447 warn!(?e, "Failed to open lightning payment stream");
448 continue
449 }
450 };
451
452 self_copy.set_gateway_state(GatewayState::Connected).await;
454 info!("Established lightning payment stream");
455
456 let route_payments_response =
457 self_copy.route_lightning_payments(&handle, stream, ln_client).await;
458
459 self_copy.set_gateway_state(GatewayState::Disconnected).await;
460 if let Err(e) = payment_stream_task_group.shutdown_join_all(None).await {
461 error!("Lightning payment stream task group shutdown errors: {}", e);
462 }
463
464 match route_payments_response {
465 ReceivePaymentStreamAction::ImmediatelyRetry => {},
466 ReceivePaymentStreamAction::RetryAfterDelay => {
467 warn!("Disconnected from lightning node. Waiting {PAYMENT_STREAM_RETRY_SECONDS} seconds and trying again");
468 sleep(Duration::from_secs(PAYMENT_STREAM_RETRY_SECONDS)).await;
469 }
470 ReceivePaymentStreamAction::NoRetry => break,
471 }
472 }
473 },
474 );
475 }
476
477 async fn route_lightning_payments<'a>(
481 &'a self,
482 handle: &TaskHandle,
483 mut stream: RouteHtlcStream<'a>,
484 ln_client: Arc<dyn ILnRpcClient>,
485 ) -> ReceivePaymentStreamAction {
486 let (lightning_public_key, lightning_alias, lightning_network, synced_to_chain) =
487 match ln_client.parsed_node_info().await {
488 Ok((
489 lightning_public_key,
490 lightning_alias,
491 lightning_network,
492 _block_height,
493 synced_to_chain,
494 )) => (
495 lightning_public_key,
496 lightning_alias,
497 lightning_network,
498 synced_to_chain,
499 ),
500 Err(e) => {
501 warn!("Failed to retrieve Lightning info: {e:?}");
502 return ReceivePaymentStreamAction::RetryAfterDelay;
503 }
504 };
505
506 let gateway_config = if let Some(config) = self.clone_gateway_config().await {
507 config
508 } else {
509 self.set_gateway_state(GatewayState::Configuring).await;
510 info!("Waiting for gateway to be configured...");
511 self.gateway_db
512 .wait_key_exists(&GatewayConfigurationKey)
513 .await
514 };
515
516 if gateway_config.network != lightning_network {
517 warn!(
518 "Lightning node does not match previously configured gateway network : ({:?})",
519 gateway_config.network
520 );
521 info!(
522 "Changing gateway network to match lightning node network : ({:?})",
523 lightning_network
524 );
525 self.handle_set_configuration_msg(SetConfigurationPayload {
526 password: None,
527 network: Some(lightning_network),
528 num_route_hints: None,
529 routing_fees: None,
530 per_federation_routing_fees: None,
531 })
532 .await
533 .expect("Failed to set gateway configuration");
534 return ReceivePaymentStreamAction::ImmediatelyRetry;
535 }
536
537 if synced_to_chain {
538 info!("Gateway is already synced to chain");
539 } else {
540 self.set_gateway_state(GatewayState::Syncing).await;
541 if let Err(e) = ln_client.wait_for_chain_sync().await {
542 error!(?e, "Failed to wait for chain sync");
543 return ReceivePaymentStreamAction::RetryAfterDelay;
544 }
545 }
546
547 let lightning_context = LightningContext {
548 lnrpc: ln_client,
549 lightning_public_key,
550 lightning_alias,
551 lightning_network,
552 };
553 self.set_gateway_state(GatewayState::Running { lightning_context })
554 .await;
555 info!("Gateway is running");
556
557 if handle
560 .cancel_on_shutdown(async move {
561 loop {
562 let payment_request = tokio::select! {
563 payment_request = stream.next() => {
564 payment_request
565 }
566 () = self.is_shutting_down_safely() => {
567 break;
568 }
569 };
570
571 let state_guard = self.state.read().await;
573 let GatewayState::Running { ref lightning_context } = *state_guard else {
574 warn!(
575 ?state_guard,
576 "Gateway isn't in a running state, cannot handle incoming payments."
577 );
578 break;
579 };
580
581 let payment_request = match payment_request {
582 Some(payment_request) => payment_request,
583 other => {
584 warn!(
585 ?other,
586 "Unexpected response from incoming lightning payment stream. Exiting from loop..."
587 );
588 break;
589 }
590 };
591
592 self.handle_lightning_payment(payment_request, lightning_context).await;
593 }
594 })
595 .await
596 .is_ok()
597 {
598 warn!("Lightning payment stream connection broken. Gateway is disconnected");
599 ReceivePaymentStreamAction::RetryAfterDelay
600 } else {
601 info!("Received shutdown signal");
602 ReceivePaymentStreamAction::NoRetry
603 }
604 }
605
606 async fn is_shutting_down_safely(&self) {
609 loop {
610 if let GatewayState::ShuttingDown { .. } = self.get_state().await {
611 return;
612 }
613
614 fedimint_core::task::sleep(Duration::from_secs(1)).await;
615 }
616 }
617
618 async fn handle_lightning_payment(
623 &self,
624 payment_request: InterceptPaymentRequest,
625 lightning_context: &LightningContext,
626 ) {
627 info!(
628 "Intercepting lightning payment {}",
629 PrettyInterceptPaymentRequest(&payment_request)
630 );
631
632 if self
633 .try_handle_lightning_payment_lnv2(&payment_request, lightning_context)
634 .await
635 .is_ok()
636 {
637 return;
638 }
639
640 if self
641 .try_handle_lightning_payment_ln_legacy(&payment_request)
642 .await
643 .is_ok()
644 {
645 return;
646 }
647
648 Self::forward_lightning_payment(payment_request, lightning_context).await;
649 }
650
651 async fn try_handle_lightning_payment_lnv2(
654 &self,
655 htlc_request: &InterceptPaymentRequest,
656 lightning_context: &LightningContext,
657 ) -> Result<()> {
658 let (contract, client) = self
664 .get_registered_incoming_contract_and_client_v2(
665 PaymentImage::Hash(htlc_request.payment_hash),
666 htlc_request.amount_msat,
667 )
668 .await?;
669
670 if let Err(error) = client
671 .get_first_module::<GatewayClientModuleV2>()
672 .expect("Must have client module")
673 .relay_incoming_htlc(
674 htlc_request.payment_hash,
675 htlc_request.incoming_chan_id,
676 htlc_request.htlc_id,
677 contract,
678 )
679 .await
680 {
681 error!("Error relaying incoming lightning payment: {error:?}");
682
683 let outcome = InterceptPaymentResponse {
684 action: PaymentAction::Cancel,
685 payment_hash: htlc_request.payment_hash,
686 incoming_chan_id: htlc_request.incoming_chan_id,
687 htlc_id: htlc_request.htlc_id,
688 };
689
690 if let Err(error) = lightning_context.lnrpc.complete_htlc(outcome).await {
691 error!("Error sending HTLC response to lightning node: {error:?}");
692 }
693 }
694
695 Ok(())
696 }
697
698 async fn try_handle_lightning_payment_ln_legacy(
701 &self,
702 htlc_request: &InterceptPaymentRequest,
703 ) -> Result<()> {
704 let Some(federation_index) = htlc_request.short_channel_id else {
706 return Err(PublicGatewayError::LNv1(LNv1Error::IncomingPayment(
707 "Incoming payment has not last hop short channel id".to_string(),
708 )));
709 };
710
711 let Some(client) = self
712 .federation_manager
713 .read()
714 .await
715 .get_client_for_index(federation_index)
716 else {
717 return Err(PublicGatewayError::LNv1(LNv1Error::IncomingPayment("Incoming payment has a last hop short channel id that does not map to a known federation".to_string())));
718 };
719
720 client
721 .borrow()
722 .with(|client| async {
723 let htlc = htlc_request.clone().try_into();
724 if let Ok(htlc) = htlc {
725 match client
726 .get_first_module::<GatewayClientModule>()
727 .expect("Must have client module")
728 .gateway_handle_intercepted_htlc(htlc)
729 .await
730 {
731 Ok(_) => Ok(()),
732 Err(e) => Err(PublicGatewayError::LNv1(LNv1Error::IncomingPayment(
733 format!("Error intercepting lightning payment {e:?}"),
734 ))),
735 }
736 } else {
737 Err(PublicGatewayError::LNv1(LNv1Error::IncomingPayment(
738 "Could not convert InterceptHtlcResult into an HTLC".to_string(),
739 )))
740 }
741 })
742 .await
743 }
744
745 async fn forward_lightning_payment(
749 htlc_request: InterceptPaymentRequest,
750 lightning_context: &LightningContext,
751 ) {
752 let outcome = InterceptPaymentResponse {
753 action: PaymentAction::Forward,
754 payment_hash: htlc_request.payment_hash,
755 incoming_chan_id: htlc_request.incoming_chan_id,
756 htlc_id: htlc_request.htlc_id,
757 };
758
759 if let Err(error) = lightning_context.lnrpc.complete_htlc(outcome).await {
760 error!("Error sending lightning payment response to lightning node: {error:?}");
761 }
762 }
763
764 async fn set_gateway_state(&self, state: GatewayState) {
766 let mut lock = self.state.write().await;
767 *lock = state;
768 }
769
770 pub async fn handle_get_info(&self) -> AdminResult<GatewayInfo> {
773 let GatewayState::Running { lightning_context } = self.get_state().await else {
774 return Ok(GatewayInfo {
775 federations: vec![],
776 federation_fake_scids: None,
777 version_hash: fedimint_build_code_version_env!().to_string(),
778 lightning_pub_key: None,
779 lightning_alias: None,
780 gateway_id: self.gateway_id,
781 gateway_state: self.state.read().await.to_string(),
782 network: None,
783 block_height: None,
784 synced_to_chain: false,
785 api: self.versioned_api.clone(),
786 lightning_mode: None,
787 });
788 };
789
790 let gateway_config = self
793 .clone_gateway_config()
794 .await
795 .expect("Gateway configuration should be set");
796
797 let dbtx = self.gateway_db.begin_transaction_nc().await;
798 let federations = self
799 .federation_manager
800 .read()
801 .await
802 .federation_info_all_federations(dbtx)
803 .await;
804
805 let channels: BTreeMap<u64, FederationId> = federations
806 .iter()
807 .map(|federation_info| {
808 (
809 federation_info.federation_index,
810 federation_info.federation_id,
811 )
812 })
813 .collect();
814
815 let node_info = lightning_context.lnrpc.parsed_node_info().await?;
816
817 Ok(GatewayInfo {
818 federations,
819 federation_fake_scids: Some(channels),
820 version_hash: fedimint_build_code_version_env!().to_string(),
821 lightning_pub_key: Some(lightning_context.lightning_public_key.to_string()),
822 lightning_alias: Some(lightning_context.lightning_alias.clone()),
823 gateway_id: self.gateway_id,
824 gateway_state: self.state.read().await.to_string(),
825 network: Some(gateway_config.network),
826 block_height: Some(node_info.3),
827 synced_to_chain: node_info.4,
828 api: self.versioned_api.clone(),
829 lightning_mode: self.lightning_builder.lightning_mode(),
830 })
831 }
832
833 pub async fn handle_get_federation_config(
836 &self,
837 federation_id_or: Option<FederationId>,
838 ) -> AdminResult<GatewayFedConfig> {
839 if !matches!(self.get_state().await, GatewayState::Running { .. }) {
840 return Ok(GatewayFedConfig {
841 federations: BTreeMap::new(),
842 });
843 }
844
845 let federations = if let Some(federation_id) = federation_id_or {
846 let mut federations = BTreeMap::new();
847 federations.insert(
848 federation_id,
849 self.federation_manager
850 .read()
851 .await
852 .get_federation_config(federation_id)
853 .await?,
854 );
855 federations
856 } else {
857 self.federation_manager
858 .read()
859 .await
860 .get_all_federation_configs()
861 .await
862 };
863
864 Ok(GatewayFedConfig { federations })
865 }
866
867 pub async fn handle_address_msg(&self, payload: DepositAddressPayload) -> AdminResult<Address> {
870 let (_, address, _) = self
871 .select_client(payload.federation_id)
872 .await?
873 .value()
874 .get_first_module::<WalletClientModule>()
875 .expect("Must have client module")
876 .allocate_deposit_address_expert_only(())
877 .await?;
878 Ok(address)
879 }
880
881 pub async fn handle_withdraw_msg(
884 &self,
885 payload: WithdrawPayload,
886 ) -> AdminResult<WithdrawResponse> {
887 let WithdrawPayload {
888 amount,
889 address,
890 federation_id,
891 } = payload;
892 let client = self.select_client(federation_id).await?;
893 let wallet_module = client.value().get_first_module::<WalletClientModule>()?;
894
895 let (amount, fees) = match amount {
897 BitcoinAmountOrAll::All => {
900 let balance =
901 bitcoin::Amount::from_sat(client.value().get_balance().await.msats / 1000);
902 let fees = wallet_module
903 .get_withdraw_fees(address.clone(), balance)
904 .await?;
905 let withdraw_amount = balance.checked_sub(fees.amount());
906 if withdraw_amount.is_none() {
907 return Err(AdminGatewayError::WithdrawError {
908 failure_reason: format!(
909 "Insufficient funds. Balance: {balance} Fees: {fees:?}"
910 ),
911 });
912 }
913 (withdraw_amount.unwrap(), fees)
914 }
915 BitcoinAmountOrAll::Amount(amount) => (
916 amount,
917 wallet_module
918 .get_withdraw_fees(address.clone(), amount)
919 .await?,
920 ),
921 };
922
923 let operation_id = wallet_module
924 .withdraw(address.clone(), amount, fees, ())
925 .await?;
926 let mut updates = wallet_module
927 .subscribe_withdraw_updates(operation_id)
928 .await?
929 .into_stream();
930
931 while let Some(update) = updates.next().await {
932 match update {
933 WithdrawState::Succeeded(txid) => {
934 info!(
935 "Sent {amount} funds to address {}",
936 address.assume_checked()
937 );
938 return Ok(WithdrawResponse { txid, fees });
939 }
940 WithdrawState::Failed(e) => {
941 return Err(AdminGatewayError::WithdrawError { failure_reason: e });
942 }
943 WithdrawState::Created => {}
944 }
945 }
946
947 Err(AdminGatewayError::WithdrawError {
948 failure_reason: "Ran out of state updates while withdrawing".to_string(),
949 })
950 }
951
952 async fn handle_create_invoice_for_operator_msg(
955 &self,
956 payload: CreateInvoiceForOperatorPayload,
957 ) -> AdminResult<Bolt11Invoice> {
958 let GatewayState::Running { lightning_context } = self.get_state().await else {
959 return Err(AdminGatewayError::Lightning(
960 LightningRpcError::FailedToConnect,
961 ));
962 };
963
964 Bolt11Invoice::from_str(
965 &lightning_context
966 .lnrpc
967 .create_invoice(CreateInvoiceRequest {
968 payment_hash: None, amount_msat: payload.amount_msats,
971 expiry_secs: payload.expiry_secs.unwrap_or(3600),
972 description: payload.description.map(InvoiceDescription::Direct),
973 })
974 .await?
975 .invoice,
976 )
977 .map_err(|e| {
978 AdminGatewayError::Lightning(LightningRpcError::InvalidMetadata {
979 failure_reason: e.to_string(),
980 })
981 })
982 }
983
984 async fn handle_pay_invoice_for_operator_msg(
987 &self,
988 payload: PayInvoiceForOperatorPayload,
989 ) -> AdminResult<Preimage> {
990 const BASE_FEE: u64 = 50;
992 const FEE_DENOMINATOR: u64 = 100;
993 const MAX_DELAY: u64 = 1008;
994
995 let GatewayState::Running { lightning_context } = self.get_state().await else {
996 return Err(AdminGatewayError::Lightning(
997 LightningRpcError::FailedToConnect,
998 ));
999 };
1000
1001 let max_fee = BASE_FEE
1002 + payload
1003 .invoice
1004 .amount_milli_satoshis()
1005 .context("Invoice is missing amount")?
1006 .saturating_div(FEE_DENOMINATOR);
1007
1008 let res = lightning_context
1009 .lnrpc
1010 .pay(payload.invoice, MAX_DELAY, Amount::from_msats(max_fee))
1011 .await?;
1012 Ok(res.preimage)
1013 }
1014
1015 async fn handle_pay_invoice_msg(
1018 &self,
1019 payload: fedimint_ln_client::pay::PayInvoicePayload,
1020 ) -> Result<Preimage> {
1021 let GatewayState::Running { .. } = self.get_state().await else {
1022 return Err(PublicGatewayError::Lightning(
1023 LightningRpcError::FailedToConnect,
1024 ));
1025 };
1026
1027 debug!("Handling pay invoice message: {payload:?}");
1028 let client = self.select_client(payload.federation_id).await?;
1029 let contract_id = payload.contract_id;
1030 let gateway_module = &client
1031 .value()
1032 .get_first_module::<GatewayClientModule>()
1033 .map_err(LNv1Error::OutgoingPayment)
1034 .map_err(PublicGatewayError::LNv1)?;
1035 let operation_id = gateway_module
1036 .gateway_pay_bolt11_invoice(payload)
1037 .await
1038 .map_err(LNv1Error::OutgoingPayment)
1039 .map_err(PublicGatewayError::LNv1)?;
1040 let mut updates = gateway_module
1041 .gateway_subscribe_ln_pay(operation_id)
1042 .await
1043 .map_err(LNv1Error::OutgoingPayment)
1044 .map_err(PublicGatewayError::LNv1)?
1045 .into_stream();
1046 while let Some(update) = updates.next().await {
1047 match update {
1048 GatewayExtPayStates::Success { preimage, .. } => {
1049 debug!("Successfully paid invoice: {contract_id}");
1050 return Ok(preimage);
1051 }
1052 GatewayExtPayStates::Fail {
1053 error,
1054 error_message,
1055 } => {
1056 return Err(PublicGatewayError::LNv1(LNv1Error::OutgoingContract {
1057 error: Box::new(error),
1058 message: format!(
1059 "{error_message} while paying invoice with contract id {contract_id}"
1060 ),
1061 }));
1062 }
1063 GatewayExtPayStates::Canceled { error } => {
1064 return Err(PublicGatewayError::LNv1(LNv1Error::OutgoingContract { error: Box::new(error.clone()), message: format!("Cancelled with {error} while paying invoice with contract id {contract_id}") }));
1065 }
1066 GatewayExtPayStates::Created => {
1067 debug!("Got initial state Created while paying invoice: {contract_id}");
1068 }
1069 other => {
1070 info!("Got state {other:?} while paying invoice: {contract_id}");
1071 }
1072 };
1073 }
1074
1075 Err(PublicGatewayError::LNv1(LNv1Error::OutgoingPayment(
1076 anyhow!("Ran out of state updates while paying invoice"),
1077 )))
1078 }
1079
1080 pub async fn handle_connect_federation(
1085 &self,
1086 payload: ConnectFedPayload,
1087 ) -> AdminResult<FederationInfo> {
1088 let GatewayState::Running { lightning_context } = self.get_state().await else {
1089 return Err(AdminGatewayError::Lightning(
1090 LightningRpcError::FailedToConnect,
1091 ));
1092 };
1093
1094 let invite_code = InviteCode::from_str(&payload.invite_code).map_err(|e| {
1095 AdminGatewayError::ClientCreationError(anyhow!(format!(
1096 "Invalid federation member string {e:?}"
1097 )))
1098 })?;
1099
1100 #[cfg(feature = "tor")]
1101 let connector = match &payload.use_tor {
1102 Some(true) => Connector::tor(),
1103 Some(false) => Connector::default(),
1104 None => {
1105 info!("Missing `use_tor` payload field, defaulting to `Connector::Tcp` variant!");
1106 Connector::default()
1107 }
1108 };
1109
1110 #[cfg(not(feature = "tor"))]
1111 let connector = Connector::default();
1112
1113 let federation_id = invite_code.federation_id();
1114
1115 if federation_id.to_prefix().to_bytes().starts_with(&[0x04]) {
1116 return Err(AdminGatewayError::Unexpected(anyhow!("gatewayd v0.5 cannot join federation {federation_id}, please upgrade to gatewayd v0.6")));
1117 }
1118
1119 let mut federation_manager = self.federation_manager.write().await;
1120
1121 if federation_manager.has_federation(federation_id) {
1123 return Err(AdminGatewayError::ClientCreationError(anyhow!(
1124 "Federation has already been registered"
1125 )));
1126 }
1127
1128 let gateway_config = self
1131 .clone_gateway_config()
1132 .await
1133 .expect("Gateway configuration should be set");
1134
1135 let federation_index = federation_manager.pop_next_index()?;
1138
1139 let federation_config = FederationConfig {
1140 invite_code,
1141 federation_index,
1142 timelock_delta: 10,
1143 fees: gateway_config.routing_fees,
1144 connector,
1145 };
1146
1147 let mnemonic = Self::load_or_generate_mnemonic(&self.gateway_db).await?;
1148 let recover = payload.recover.unwrap_or(false);
1149 if recover {
1150 self.client_builder
1151 .recover(federation_config.clone(), Arc::new(self.clone()), &mnemonic)
1152 .await?;
1153 }
1154
1155 let client = self
1156 .client_builder
1157 .build(federation_config.clone(), Arc::new(self.clone()), &mnemonic)
1158 .await?;
1159
1160 if recover {
1161 client.wait_for_all_active_state_machines().await?;
1162 }
1163
1164 let federation_info = FederationInfo {
1167 federation_id,
1168 federation_name: federation_manager.federation_name(&client).await,
1169 balance_msat: client.get_balance().await,
1170 federation_index,
1171 routing_fees: Some(gateway_config.routing_fees.into()),
1172 };
1173
1174 if self.is_running_lnv1() {
1175 Self::check_lnv1_federation_network(&client, gateway_config.network).await?;
1176 client
1177 .get_first_module::<GatewayClientModule>()?
1178 .try_register_with_federation(
1179 Vec::new(),
1181 GW_ANNOUNCEMENT_TTL,
1182 federation_config.fees,
1183 lightning_context,
1184 )
1185 .await;
1186 }
1187
1188 if self.is_running_lnv2() {
1189 Self::check_lnv2_federation_network(&client, gateway_config.network).await?;
1190 }
1191
1192 federation_manager.add_client(
1194 federation_index,
1195 Spanned::new(
1196 info_span!("client", federation_id=%federation_id.clone()),
1197 async { client },
1198 )
1199 .await,
1200 );
1201
1202 let mut dbtx = self.gateway_db.begin_transaction().await;
1203 dbtx.save_federation_config(&federation_config).await;
1204 dbtx.commit_tx().await;
1205 debug!("Federation with ID: {federation_id} connected and assigned federation index: {federation_index}");
1206
1207 Ok(federation_info)
1208 }
1209
1210 pub async fn handle_leave_federation(
1215 &self,
1216 payload: LeaveFedPayload,
1217 ) -> AdminResult<FederationInfo> {
1218 let mut federation_manager = self.federation_manager.write().await;
1221 let mut dbtx = self.gateway_db.begin_transaction().await;
1222
1223 let federation_info = federation_manager
1224 .leave_federation(payload.federation_id, &mut dbtx.to_ref_nc())
1225 .await?;
1226
1227 dbtx.remove_federation_config(payload.federation_id).await;
1228 dbtx.commit_tx().await;
1229 Ok(federation_info)
1230 }
1231
1232 pub async fn handle_backup_msg(
1235 &self,
1236 BackupPayload { federation_id }: BackupPayload,
1237 ) -> AdminResult<()> {
1238 let federation_manager = self.federation_manager.read().await;
1239 let client = federation_manager
1240 .client(&federation_id)
1241 .ok_or(AdminGatewayError::ClientCreationError(anyhow::anyhow!(
1242 format!("Gateway has not connected to {federation_id}")
1243 )))?
1244 .value();
1245 let metadata: BTreeMap<String, String> = BTreeMap::new();
1246 client
1247 .backup_to_federation(fedimint_client::backup::Metadata::from_json_serialized(
1248 metadata,
1249 ))
1250 .await?;
1251 Ok(())
1252 }
1253
1254 pub async fn handle_mnemonic_msg(&self) -> AdminResult<MnemonicResponse> {
1258 let mnemonic = Self::load_or_generate_mnemonic(&self.gateway_db).await?;
1259 let words = mnemonic
1260 .words()
1261 .map(std::string::ToString::to_string)
1262 .collect::<Vec<_>>();
1263 let all_federations = self
1264 .federation_manager
1265 .read()
1266 .await
1267 .get_all_federation_configs()
1268 .await
1269 .keys()
1270 .copied()
1271 .collect::<BTreeSet<_>>();
1272 let legacy_federations = self.client_builder.legacy_federations(all_federations);
1273 let mnemonic_response = MnemonicResponse {
1274 mnemonic: words,
1275 legacy_federations,
1276 };
1277 Ok(mnemonic_response)
1278 }
1279
1280 pub async fn handle_set_configuration_msg(
1286 &self,
1287 SetConfigurationPayload {
1288 password,
1289 network,
1290 num_route_hints,
1291 routing_fees,
1292 per_federation_routing_fees,
1293 }: SetConfigurationPayload,
1294 ) -> AdminResult<()> {
1295 let gw_state = self.get_state().await;
1296 let lightning_network = match gw_state {
1297 GatewayState::Running { lightning_context } => {
1298 if network.is_some() && network != Some(lightning_context.lightning_network) {
1299 return Err(AdminGatewayError::GatewayConfigurationError(
1300 "Cannot change network while connected to a lightning node".to_string(),
1301 ));
1302 }
1303 lightning_context.lightning_network
1304 }
1305 _ => DEFAULT_NETWORK,
1310 };
1311
1312 let mut dbtx = self.gateway_db.begin_transaction().await;
1313
1314 let prev_gateway_config = self.clone_gateway_config().await;
1315 let new_gateway_config = if let Some(mut prev_config) = prev_gateway_config {
1316 if let Some(password) = password.as_ref() {
1317 let hashed_password = hash_password(password, prev_config.password_salt);
1318 prev_config.hashed_password = hashed_password;
1319 }
1320
1321 if let Some(network) = network {
1322 if !self.federation_manager.read().await.is_empty() {
1323 return Err(AdminGatewayError::GatewayConfigurationError(
1324 "Cannot change network while connected to a federation".to_string(),
1325 ));
1326 }
1327 prev_config.network = network;
1328 }
1329
1330 if let Some(num_route_hints) = num_route_hints {
1331 prev_config.num_route_hints = num_route_hints;
1332 }
1333
1334 if let Some(fees) = routing_fees {
1337 let routing_fees = GatewayFee(fees.into()).0;
1338 prev_config.routing_fees = routing_fees;
1339 }
1340
1341 prev_config
1342 } else {
1343 let password = password.ok_or(AdminGatewayError::GatewayConfigurationError(
1344 "The password field is required when initially configuring the gateway".to_string(),
1345 ))?;
1346 let password_salt: [u8; 16] = rand::thread_rng().gen();
1347 let hashed_password = hash_password(&password, password_salt);
1348
1349 GatewayConfiguration {
1350 hashed_password,
1351 network: lightning_network,
1352 num_route_hints: DEFAULT_NUM_ROUTE_HINTS,
1353 routing_fees: DEFAULT_FEES,
1354 password_salt,
1355 }
1356 };
1357 dbtx.set_gateway_config(&new_gateway_config).await;
1358
1359 let mut register_federations: Vec<(FederationId, FederationConfig)> = Vec::new();
1360 if let Some(per_federation_routing_fees) = per_federation_routing_fees {
1361 for (federation_id, routing_fees) in &per_federation_routing_fees {
1362 if let Some(mut federation_config) =
1363 dbtx.load_federation_config(*federation_id).await
1364 {
1365 federation_config.fees = routing_fees.clone().into();
1366 dbtx.save_federation_config(&federation_config).await;
1367 register_federations.push((*federation_id, federation_config));
1368 } else {
1369 warn!("Given federation {federation_id} not found for updating routing fees");
1370 }
1371 }
1372 }
1373
1374 let register_task_group = TaskGroup::new();
1377 if num_route_hints.is_some() {
1378 let all_federations_configs: Vec<_> =
1379 dbtx.load_federation_configs().await.into_iter().collect();
1380 self.register_federations(
1381 &new_gateway_config,
1382 &all_federations_configs,
1383 ®ister_task_group,
1384 )
1385 .await;
1386 } else {
1387 self.register_federations(
1388 &new_gateway_config,
1389 ®ister_federations,
1390 ®ister_task_group,
1391 )
1392 .await;
1393 }
1394
1395 dbtx.commit_tx().await;
1396
1397 let mut curr_gateway_config = self.gateway_config.write().await;
1398 *curr_gateway_config = Some(new_gateway_config.clone());
1399
1400 info!("Set GatewayConfiguration successfully.");
1401
1402 Ok(())
1403 }
1404
1405 pub async fn handle_get_ln_onchain_address_msg(&self) -> AdminResult<Address> {
1407 let context = self.get_lightning_context().await?;
1408 let response = context.lnrpc.get_ln_onchain_address().await?;
1409 Address::from_str(&response.address)
1410 .map(Address::assume_checked)
1411 .map_err(|e| {
1412 AdminGatewayError::Lightning(LightningRpcError::InvalidMetadata {
1413 failure_reason: e.to_string(),
1414 })
1415 })
1416 }
1417
1418 pub async fn handle_open_channel_msg(&self, payload: OpenChannelPayload) -> AdminResult<Txid> {
1421 let context = self.get_lightning_context().await?;
1422 let res = context.lnrpc.open_channel(payload).await?;
1423 Txid::from_str(&res.funding_txid).map_err(|e| {
1424 AdminGatewayError::Lightning(LightningRpcError::InvalidMetadata {
1425 failure_reason: format!("Received invalid channel funding txid string {e}"),
1426 })
1427 })
1428 }
1429
1430 pub async fn handle_close_channels_with_peer_msg(
1433 &self,
1434 payload: CloseChannelsWithPeerPayload,
1435 ) -> AdminResult<CloseChannelsWithPeerResponse> {
1436 let context = self.get_lightning_context().await?;
1437 let response = context.lnrpc.close_channels_with_peer(payload).await?;
1438 Ok(response)
1439 }
1440
1441 pub async fn handle_list_active_channels_msg(
1444 &self,
1445 ) -> AdminResult<Vec<lightning::ChannelInfo>> {
1446 let context = self.get_lightning_context().await?;
1447 let channels = context.lnrpc.list_active_channels().await?;
1448 Ok(channels)
1449 }
1450
1451 pub async fn handle_send_onchain_msg(&self, payload: SendOnchainPayload) -> AdminResult<Txid> {
1453 let context = self.get_lightning_context().await?;
1454 let response = context.lnrpc.send_onchain(payload).await?;
1455 Txid::from_str(&response.txid).map_err(|e| AdminGatewayError::WithdrawError {
1456 failure_reason: format!("Failed to parse withdrawal TXID: {e}"),
1457 })
1458 }
1459
1460 pub async fn handle_get_balances_msg(&self) -> AdminResult<GatewayBalances> {
1463 let dbtx = self.gateway_db.begin_transaction_nc().await;
1464 let federation_infos = self
1465 .federation_manager
1466 .read()
1467 .await
1468 .federation_info_all_federations(dbtx)
1469 .await;
1470
1471 let ecash_balances: Vec<FederationBalanceInfo> = federation_infos
1472 .iter()
1473 .map(|federation_info| FederationBalanceInfo {
1474 federation_id: federation_info.federation_id,
1475 ecash_balance_msats: Amount {
1476 msats: federation_info.balance_msat.msats,
1477 },
1478 })
1479 .collect();
1480
1481 let context = self.get_lightning_context().await?;
1482 let lightning_node_balances = context.lnrpc.get_balances().await?;
1483
1484 Ok(GatewayBalances {
1485 onchain_balance_sats: lightning_node_balances.onchain_balance_sats,
1486 lightning_balance_msats: lightning_node_balances.lightning_balance_msats,
1487 ecash_balances,
1488 inbound_lightning_liquidity_msats: lightning_node_balances
1489 .inbound_lightning_liquidity_msats,
1490 })
1491 }
1492
1493 pub async fn handle_spend_ecash_msg(
1495 &self,
1496 payload: SpendEcashPayload,
1497 ) -> AdminResult<SpendEcashResponse> {
1498 let client = self
1499 .select_client(payload.federation_id)
1500 .await?
1501 .into_value();
1502 let mint_module = client.get_first_module::<MintClientModule>()?;
1503 let timeout = Duration::from_secs(payload.timeout);
1504 let (operation_id, notes) = if payload.allow_overpay {
1505 let (operation_id, notes) = mint_module
1506 .spend_notes_with_selector(
1507 &SelectNotesWithAtleastAmount,
1508 payload.amount,
1509 timeout,
1510 payload.include_invite,
1511 (),
1512 )
1513 .await?;
1514
1515 let overspend_amount = notes.total_amount() - payload.amount;
1516 if overspend_amount != Amount::ZERO {
1517 warn!(
1518 "Selected notes {} worth more than requested",
1519 overspend_amount
1520 );
1521 }
1522
1523 (operation_id, notes)
1524 } else {
1525 mint_module
1526 .spend_notes_with_selector(
1527 &SelectNotesWithExactAmount,
1528 payload.amount,
1529 timeout,
1530 payload.include_invite,
1531 (),
1532 )
1533 .await?
1534 };
1535
1536 info!("Spend ecash operation id: {:?}", operation_id);
1537 info!("Spend ecash notes: {:?}", notes);
1538
1539 Ok(SpendEcashResponse {
1540 operation_id,
1541 notes,
1542 })
1543 }
1544
1545 pub async fn handle_receive_ecash_msg(
1547 &self,
1548 payload: ReceiveEcashPayload,
1549 ) -> Result<ReceiveEcashResponse> {
1550 let amount = payload.notes.total_amount();
1551 let client = self
1552 .federation_manager
1553 .read()
1554 .await
1555 .get_client_for_federation_id_prefix(payload.notes.federation_id_prefix())
1556 .ok_or(FederationNotConnected {
1557 federation_id_prefix: payload.notes.federation_id_prefix(),
1558 })?;
1559 let mint = client
1560 .value()
1561 .get_first_module::<MintClientModule>()
1562 .map_err(|e| PublicGatewayError::ReceiveEcashError {
1563 failure_reason: format!("Mint module does not exist: {e:?}"),
1564 })?;
1565
1566 let operation_id = mint
1567 .reissue_external_notes(payload.notes, ())
1568 .await
1569 .map_err(|e| PublicGatewayError::ReceiveEcashError {
1570 failure_reason: e.to_string(),
1571 })?;
1572 if payload.wait {
1573 let mut updates = mint
1574 .subscribe_reissue_external_notes(operation_id)
1575 .await
1576 .unwrap()
1577 .into_stream();
1578
1579 while let Some(update) = updates.next().await {
1580 if let fedimint_mint_client::ReissueExternalNotesState::Failed(e) = update {
1581 return Err(PublicGatewayError::ReceiveEcashError {
1582 failure_reason: e.to_string(),
1583 });
1584 }
1585 }
1586 }
1587
1588 Ok(ReceiveEcashResponse { amount })
1589 }
1590
1591 pub async fn handle_shutdown_msg(&self, task_group: TaskGroup) -> AdminResult<()> {
1594 let mut state_guard = self.state.write().await;
1596 if let GatewayState::Running { lightning_context } = state_guard.clone() {
1597 *state_guard = GatewayState::ShuttingDown { lightning_context };
1598
1599 self.federation_manager
1600 .read()
1601 .await
1602 .wait_for_incoming_payments()
1603 .await?;
1604 }
1605
1606 let tg = task_group.clone();
1607 tg.spawn("Kill Gateway", |_task_handle| async {
1608 if let Err(e) = task_group.shutdown_join_all(Duration::from_secs(180)).await {
1609 error!(?e, "Error shutting down gateway");
1610 }
1611 });
1612 Ok(())
1613 }
1614
1615 async fn register_federations(
1617 &self,
1618 gateway_config: &GatewayConfiguration,
1619 federations: &[(FederationId, FederationConfig)],
1620 register_task_group: &TaskGroup,
1621 ) {
1622 if let Ok(lightning_context) = self.get_lightning_context().await {
1623 let route_hints = lightning_context
1624 .lnrpc
1625 .parsed_route_hints(gateway_config.num_route_hints)
1626 .await;
1627 if route_hints.is_empty() {
1628 warn!("Gateway did not retrieve any route hints, may reduce receive success rate.");
1629 }
1630
1631 for (federation_id, federation_config) in federations {
1632 let fed_manager = self.federation_manager.read().await;
1633 if let Some(client) = fed_manager.client(federation_id) {
1634 let client_arc = client.clone().into_value();
1635 let route_hints = route_hints.clone();
1636 let lightning_context = lightning_context.clone();
1637 let federation_config = federation_config.clone();
1638
1639 if let Err(e) = register_task_group
1640 .spawn_cancellable("register_federation", async move {
1641 let gateway_client = client_arc
1642 .get_first_module::<GatewayClientModule>()
1643 .expect("No GatewayClientModule exists");
1644 gateway_client
1645 .try_register_with_federation(
1646 route_hints,
1647 GW_ANNOUNCEMENT_TTL,
1648 federation_config.fees,
1649 lightning_context,
1650 )
1651 .await;
1652 })
1653 .await
1654 {
1655 warn!(?e, "Failed to shutdown register federation task");
1656 }
1657 }
1658 }
1659 }
1660 }
1661
1662 async fn get_gateway_configuration(
1669 gateway_db: Database,
1670 gateway_parameters: &GatewayParameters,
1671 ) -> Option<GatewayConfiguration> {
1672 let mut dbtx = gateway_db.begin_transaction_nc().await;
1673
1674 if let Some(gateway_config) = dbtx.load_gateway_config().await {
1676 return Some(gateway_config);
1677 }
1678
1679 let password = gateway_parameters.password.as_ref()?;
1681
1682 let num_route_hints = gateway_parameters.num_route_hints;
1686 let routing_fees = gateway_parameters
1687 .fees
1688 .clone()
1689 .unwrap_or(GatewayFee(DEFAULT_FEES));
1690 let network = gateway_parameters.network.unwrap_or(DEFAULT_NETWORK);
1691 let password_salt: [u8; 16] = rand::thread_rng().gen();
1692 let hashed_password = hash_password(password, password_salt);
1693 let gateway_config = GatewayConfiguration {
1694 hashed_password,
1695 network,
1696 num_route_hints,
1697 routing_fees: routing_fees.0,
1698 password_salt,
1699 };
1700
1701 Some(gateway_config)
1702 }
1703
1704 pub async fn select_client(
1707 &self,
1708 federation_id: FederationId,
1709 ) -> std::result::Result<Spanned<fedimint_client::ClientHandleArc>, FederationNotConnected>
1710 {
1711 self.federation_manager
1712 .read()
1713 .await
1714 .client(&federation_id)
1715 .cloned()
1716 .ok_or(FederationNotConnected {
1717 federation_id_prefix: federation_id.to_prefix(),
1718 })
1719 }
1720
1721 async fn load_or_generate_mnemonic(gateway_db: &Database) -> AdminResult<Mnemonic> {
1726 Ok(
1727 if let Ok(entropy) = Client::load_decodable_client_secret::<Vec<u8>>(gateway_db).await {
1728 Mnemonic::from_entropy(&entropy)
1729 .map_err(|e| AdminGatewayError::MnemonicError(anyhow!(e.to_string())))?
1730 } else {
1731 let mnemonic = if let Ok(words) = std::env::var(FM_GATEWAY_MNEMONIC_ENV) {
1732 info!("Using provided mnemonic from environment variable");
1733 Mnemonic::parse_in_normalized(Language::English, words.as_str()).map_err(
1734 |e| {
1735 AdminGatewayError::MnemonicError(anyhow!(format!(
1736 "Seed phrase provided in environment was invalid {e:?}"
1737 )))
1738 },
1739 )?
1740 } else {
1741 info!("Generating mnemonic and writing entropy to client storage");
1742 Bip39RootSecretStrategy::<12>::random(&mut thread_rng())
1743 };
1744
1745 Client::store_encodable_client_secret(gateway_db, mnemonic.to_entropy())
1746 .await
1747 .map_err(AdminGatewayError::MnemonicError)?;
1748 mnemonic
1749 },
1750 )
1751 }
1752
1753 async fn load_clients(&self) -> AdminResult<()> {
1757 let mut federation_manager = self.federation_manager.write().await;
1758
1759 let configs = {
1760 let mut dbtx = self.gateway_db.begin_transaction_nc().await;
1761 dbtx.load_federation_configs().await
1762 };
1763
1764 if let Some(max_federation_index) = configs.values().map(|cfg| cfg.federation_index).max() {
1765 federation_manager.set_next_index(max_federation_index + 1);
1766 }
1767
1768 let mnemonic = Self::load_or_generate_mnemonic(&self.gateway_db).await?;
1769
1770 for (federation_id, config) in configs {
1771 let federation_index = config.federation_index;
1772 if let Ok(client) = Box::pin(Spanned::try_new(
1773 info_span!("client", federation_id = %federation_id.clone()),
1774 self.client_builder
1775 .build(config, Arc::new(self.clone()), &mnemonic),
1776 ))
1777 .await
1778 {
1779 federation_manager.add_client(federation_index, client);
1780 } else {
1781 warn!("Failed to load client for federation: {federation_id}");
1782 }
1783 }
1784
1785 Ok(())
1786 }
1787
1788 fn register_clients_timer(&self, task_group: &TaskGroup) {
1794 if self.is_running_lnv1() {
1796 let lightning_module_mode = self.lightning_module_mode;
1797 info!(?lightning_module_mode, "Spawning register task...");
1798 let gateway = self.clone();
1799 let register_task_group = task_group.make_subgroup();
1800 task_group.spawn_cancellable("register clients", async move {
1801 loop {
1802 let gateway_config = gateway.clone_gateway_config().await;
1803 if let Some(gateway_config) = gateway_config {
1804 let gateway_state = gateway.get_state().await;
1805 if let GatewayState::Running { .. } = &gateway_state {
1806 let mut dbtx = gateway.gateway_db.begin_transaction_nc().await;
1807 let all_federations_configs: Vec<_> = dbtx.load_federation_configs().await.into_iter().collect();
1808 gateway.register_federations(&gateway_config, &all_federations_configs, ®ister_task_group).await;
1809 } else {
1810 const NOT_RUNNING_RETRY: Duration = Duration::from_secs(10);
1812 info!("Will not register federation yet because gateway still not in Running state. Current state: {gateway_state:?}. Will keep waiting, next retry in {NOT_RUNNING_RETRY:?}...");
1813 sleep(NOT_RUNNING_RETRY).await;
1814 continue;
1815 }
1816 } else {
1817 warn!("Cannot register clients because gateway configuration is not set.");
1818 }
1819
1820 sleep(GW_ANNOUNCEMENT_TTL.mul_f32(0.85)).await;
1823 }
1824 });
1825 }
1826 }
1827
1828 async fn check_lnv1_federation_network(
1831 client: &ClientHandleArc,
1832 network: Network,
1833 ) -> AdminResult<()> {
1834 let federation_id = client.federation_id();
1835 let config = client.config().await;
1836 let cfg = config
1837 .modules
1838 .values()
1839 .find(|m| LightningCommonInit::KIND == m.kind)
1840 .ok_or(AdminGatewayError::ClientCreationError(anyhow!(format!(
1841 "Federation {federation_id} does not have an LNv1 module"
1842 ))))?;
1843 let ln_cfg: &LightningClientConfig = cfg.cast()?;
1844
1845 if ln_cfg.network != network {
1846 error!(
1847 "Federation {federation_id} runs on {} but this gateway supports {network}",
1848 ln_cfg.network,
1849 );
1850 return Err(AdminGatewayError::ClientCreationError(anyhow!(format!(
1851 "Unsupported network {}",
1852 ln_cfg.network
1853 ))));
1854 }
1855
1856 Ok(())
1857 }
1858
1859 async fn check_lnv2_federation_network(
1862 client: &ClientHandleArc,
1863 network: Network,
1864 ) -> AdminResult<()> {
1865 let federation_id = client.federation_id();
1866 let config = client.config().await;
1867 let cfg = config
1868 .modules
1869 .values()
1870 .find(|m| fedimint_lnv2_common::LightningCommonInit::KIND == m.kind)
1871 .ok_or(AdminGatewayError::ClientCreationError(anyhow!(format!(
1872 "Federation {federation_id} does not have an LNv2 module"
1873 ))))?;
1874 let ln_cfg: &fedimint_lnv2_common::config::LightningClientConfig = cfg.cast()?;
1875
1876 if ln_cfg.network.0 != network {
1877 error!(
1878 "Federation {federation_id} runs on {} but this gateway supports {network}",
1879 ln_cfg.network.0,
1880 );
1881 return Err(AdminGatewayError::ClientCreationError(anyhow!(format!(
1882 "Unsupported network {}",
1883 ln_cfg.network.0
1884 ))));
1885 }
1886
1887 Ok(())
1888 }
1889
1890 pub async fn get_lightning_context(
1894 &self,
1895 ) -> std::result::Result<LightningContext, LightningRpcError> {
1896 match self.get_state().await {
1897 GatewayState::Running { lightning_context }
1898 | GatewayState::ShuttingDown { lightning_context } => Ok(lightning_context),
1899 _ => Err(LightningRpcError::FailedToConnect),
1900 }
1901 }
1902
1903 pub async fn unannounce_from_all_federations(&self) {
1906 let mut dbtx = self.gateway_db.begin_transaction_nc().await;
1907 let gateway_keypair = dbtx.load_gateway_keypair_assert_exists().await;
1908
1909 self.federation_manager
1910 .read()
1911 .await
1912 .unannounce_from_all_federations(gateway_keypair)
1913 .await;
1914 }
1915}
1916
1917impl Gateway {
1919 async fn public_key_v2(&self, federation_id: &FederationId) -> Option<PublicKey> {
1923 self.federation_manager
1924 .read()
1925 .await
1926 .client(federation_id)
1927 .map(|client| {
1928 client
1929 .value()
1930 .get_first_module::<GatewayClientModuleV2>()
1931 .expect("Must have client module")
1932 .keypair
1933 .public_key()
1934 })
1935 }
1936
1937 pub async fn routing_info_v2(
1940 &self,
1941 federation_id: &FederationId,
1942 ) -> Result<Option<RoutingInfo>> {
1943 let context = self.get_lightning_context().await?;
1944
1945 Ok(self
1946 .public_key_v2(federation_id)
1947 .await
1948 .map(|module_public_key| RoutingInfo {
1949 lightning_public_key: context.lightning_public_key,
1950 module_public_key,
1951 send_fee_default: PaymentFee::SEND_FEE_LIMIT,
1952 send_fee_minimum: PaymentFee {
1956 base: Amount::from_sats(50),
1957 parts_per_million: 5_000,
1958 },
1959 expiration_delta_default: 1440,
1960 expiration_delta_minimum: EXPIRATION_DELTA_MINIMUM_V2,
1961 receive_fee: PaymentFee::RECEIVE_FEE_LIMIT,
1964 }))
1965 }
1966
1967 async fn send_payment_v2(
1970 &self,
1971 payload: SendPaymentPayload,
1972 ) -> Result<std::result::Result<[u8; 32], Signature>> {
1973 self.select_client(payload.federation_id)
1974 .await?
1975 .value()
1976 .get_first_module::<GatewayClientModuleV2>()
1977 .expect("Must have client module")
1978 .send_payment(payload)
1979 .await
1980 .map_err(LNv2Error::OutgoingPayment)
1981 .map_err(PublicGatewayError::LNv2)
1982 }
1983
1984 async fn create_bolt11_invoice_v2(
1989 &self,
1990 payload: CreateBolt11InvoicePayload,
1991 ) -> Result<Bolt11Invoice> {
1992 if !payload.contract.verify() {
1993 return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
1994 "The contract is invalid".to_string(),
1995 )));
1996 }
1997
1998 let payment_info = self.routing_info_v2(&payload.federation_id).await?.ok_or(
1999 LNv2Error::IncomingPayment(format!(
2000 "Federation {} does not exist",
2001 payload.federation_id
2002 )),
2003 )?;
2004
2005 if payload.contract.commitment.refund_pk != payment_info.module_public_key {
2006 return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2007 "The incoming contract is keyed to another gateway".to_string(),
2008 )));
2009 }
2010
2011 let contract_amount = payment_info.receive_fee.subtract_from(payload.amount.msats);
2012
2013 if contract_amount == Amount::ZERO {
2014 return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2015 "Zero amount incoming contracts are not supported".to_string(),
2016 )));
2017 }
2018
2019 if contract_amount != payload.contract.commitment.amount {
2020 return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2021 "The contract amount does not pay the correct amount of fees".to_string(),
2022 )));
2023 }
2024
2025 if payload.contract.commitment.expiration <= duration_since_epoch().as_secs() {
2026 return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2027 "The contract has already expired".to_string(),
2028 )));
2029 }
2030
2031 let payment_hash = match payload.contract.commitment.payment_image {
2032 PaymentImage::Hash(payment_hash) => payment_hash,
2033 PaymentImage::Point(..) => {
2034 return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2035 "PaymentImage is not a payment hash".to_string(),
2036 )));
2037 }
2038 };
2039
2040 let invoice = self
2041 .create_invoice_via_lnrpc_v2(
2042 payment_hash,
2043 payload.amount,
2044 payload.description.clone(),
2045 payload.expiry_secs,
2046 )
2047 .await?;
2048
2049 let mut dbtx = self.gateway_db.begin_transaction().await;
2050
2051 if dbtx
2052 .save_registered_incoming_contract(
2053 payload.federation_id,
2054 payload.amount,
2055 payload.contract,
2056 )
2057 .await
2058 .is_some()
2059 {
2060 return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2061 "PaymentHash is already registered".to_string(),
2062 )));
2063 }
2064
2065 dbtx.commit_tx_result().await.map_err(|_| {
2066 PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2067 "Payment hash is already registered".to_string(),
2068 ))
2069 })?;
2070
2071 Ok(invoice)
2072 }
2073
2074 pub async fn create_invoice_via_lnrpc_v2(
2077 &self,
2078 payment_hash: sha256::Hash,
2079 amount: Amount,
2080 description: Bolt11InvoiceDescription,
2081 expiry_time: u32,
2082 ) -> std::result::Result<Bolt11Invoice, LightningRpcError> {
2083 let lnrpc = self.get_lightning_context().await?.lnrpc;
2084
2085 let response = match description {
2086 Bolt11InvoiceDescription::Direct(description) => {
2087 lnrpc
2088 .create_invoice(CreateInvoiceRequest {
2089 payment_hash: Some(payment_hash),
2090 amount_msat: amount.msats,
2091 expiry_secs: expiry_time,
2092 description: Some(InvoiceDescription::Direct(description)),
2093 })
2094 .await?
2095 }
2096 Bolt11InvoiceDescription::Hash(hash) => {
2097 lnrpc
2098 .create_invoice(CreateInvoiceRequest {
2099 payment_hash: Some(payment_hash),
2100 amount_msat: amount.msats,
2101 expiry_secs: expiry_time,
2102 description: Some(InvoiceDescription::Hash(hash)),
2103 })
2104 .await?
2105 }
2106 };
2107
2108 Bolt11Invoice::from_str(&response.invoice).map_err(|e| {
2109 LightningRpcError::FailedToGetInvoice {
2110 failure_reason: e.to_string(),
2111 }
2112 })
2113 }
2114
2115 pub async fn get_registered_incoming_contract_and_client_v2(
2119 &self,
2120 payment_image: PaymentImage,
2121 amount_msats: u64,
2122 ) -> Result<(IncomingContract, ClientHandleArc)> {
2123 let registered_incoming_contract = self
2124 .gateway_db
2125 .begin_transaction_nc()
2126 .await
2127 .load_registered_incoming_contract(payment_image)
2128 .await
2129 .ok_or(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2130 "No corresponding decryption contract available".to_string(),
2131 )))?;
2132
2133 if registered_incoming_contract.incoming_amount_msats != amount_msats {
2134 return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2135 "The available decryption contract's amount is not equal to the requested amount"
2136 .to_string(),
2137 )));
2138 }
2139
2140 let client = self
2141 .select_client(registered_incoming_contract.federation_id)
2142 .await?
2143 .into_value();
2144
2145 Ok((registered_incoming_contract.contract, client))
2146 }
2147
2148 fn is_running_lnv2(&self) -> bool {
2150 self.lightning_module_mode == LightningModuleMode::LNv2
2151 || self.lightning_module_mode == LightningModuleMode::All
2152 }
2153
2154 fn is_running_lnv1(&self) -> bool {
2156 self.lightning_module_mode == LightningModuleMode::LNv1
2157 || self.lightning_module_mode == LightningModuleMode::All
2158 }
2159}