fedimint_gateway_server/
lib.rs

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#![allow(clippy::large_futures)]
15#![allow(clippy::struct_field_names)]
16
17pub mod client;
18pub mod config;
19pub mod envs;
20mod error;
21mod events;
22mod federation_manager;
23pub mod rpc_server;
24mod types;
25
26use std::collections::{BTreeMap, BTreeSet};
27use std::env;
28use std::fmt::Display;
29use std::net::SocketAddr;
30use std::str::FromStr;
31use std::sync::Arc;
32use std::time::{Duration, UNIX_EPOCH};
33
34use anyhow::{Context, anyhow, ensure};
35use async_trait::async_trait;
36use bitcoin::hashes::sha256;
37use bitcoin::{Address, Network, Txid};
38use clap::Parser;
39use client::GatewayClientBuilder;
40pub use config::GatewayParameters;
41use config::{DatabaseBackend, GatewayOpts};
42use envs::{FM_GATEWAY_OVERRIDE_LN_MODULE_CHECK_ENV, FM_GATEWAY_SKIP_WAIT_FOR_SYNC_ENV};
43use error::FederationNotConnected;
44use events::ALL_GATEWAY_EVENTS;
45use federation_manager::FederationManager;
46use fedimint_api_client::api::net::Connector;
47use fedimint_bip39::{Bip39RootSecretStrategy, Language, Mnemonic};
48use fedimint_client::module_init::ClientModuleInitRegistry;
49use fedimint_client::secret::RootSecretStrategy;
50use fedimint_client::{Client, ClientHandleArc};
51use fedimint_core::config::FederationId;
52use fedimint_core::core::{
53    LEGACY_HARDCODED_INSTANCE_ID_MINT, LEGACY_HARDCODED_INSTANCE_ID_WALLET, ModuleInstanceId,
54    ModuleKind,
55};
56use fedimint_core::db::{Database, DatabaseTransaction, apply_migrations};
57use fedimint_core::envs::is_env_var_set;
58use fedimint_core::invite_code::InviteCode;
59use fedimint_core::module::CommonModuleInit;
60use fedimint_core::rustls::install_crypto_provider;
61use fedimint_core::secp256k1::PublicKey;
62use fedimint_core::secp256k1::schnorr::Signature;
63use fedimint_core::task::{TaskGroup, TaskHandle, TaskShutdownToken, sleep};
64use fedimint_core::time::duration_since_epoch;
65use fedimint_core::util::{FmtCompact, FmtCompactAnyhow, SafeUrl, Spanned};
66use fedimint_core::{
67    Amount, BitcoinAmountOrAll, crit, default_esplora_server, fedimint_build_code_version_env,
68    get_network_for_address,
69};
70use fedimint_eventlog::{DBTransactionEventLogExt, EventLogId, StructuredPaymentEvents};
71use fedimint_gateway_common::{
72    BackupPayload, CloseChannelsWithPeerRequest, CloseChannelsWithPeerResponse, ConnectFedPayload,
73    CreateInvoiceForOperatorPayload, CreateOfferPayload, CreateOfferResponse,
74    DepositAddressPayload, DepositAddressRecheckPayload, FederationBalanceInfo, FederationConfig,
75    FederationInfo, GatewayBalances, GatewayFedConfig, GatewayInfo, GetInvoiceRequest,
76    GetInvoiceResponse, LeaveFedPayload, LightningMode, ListTransactionsPayload,
77    ListTransactionsResponse, MnemonicResponse, OpenChannelRequest, PayInvoiceForOperatorPayload,
78    PayOfferPayload, PayOfferResponse, PaymentLogPayload, PaymentLogResponse, PaymentStats,
79    PaymentSummaryPayload, PaymentSummaryResponse, ReceiveEcashPayload, ReceiveEcashResponse,
80    SendOnchainRequest, SetFeesPayload, SpendEcashPayload, SpendEcashResponse, V1_API_ENDPOINT,
81    WithdrawPayload, WithdrawResponse,
82};
83use fedimint_gateway_server_db::{GatewayDbtxNcExt as _, get_gatewayd_database_migrations};
84use fedimint_gw_client::events::compute_lnv1_stats;
85use fedimint_gw_client::pay::{OutgoingPaymentError, OutgoingPaymentErrorType};
86use fedimint_gw_client::{GatewayClientModule, GatewayExtPayStates, IGatewayClientV1};
87use fedimint_gwv2_client::events::compute_lnv2_stats;
88use fedimint_gwv2_client::{EXPIRATION_DELTA_MINIMUM_V2, GatewayClientModuleV2, IGatewayClientV2};
89use fedimint_lightning::ldk::{self, GatewayLdkChainSourceConfig};
90use fedimint_lightning::lnd::GatewayLndClient;
91use fedimint_lightning::{
92    CreateInvoiceRequest, ILnRpcClient, InterceptPaymentRequest, InterceptPaymentResponse,
93    InvoiceDescription, LightningContext, LightningRpcError, PayInvoiceResponse, PaymentAction,
94    RouteHtlcStream,
95};
96use fedimint_ln_client::pay::PaymentData;
97use fedimint_ln_common::LightningCommonInit;
98use fedimint_ln_common::config::LightningClientConfig;
99use fedimint_ln_common::contracts::outgoing::OutgoingContractAccount;
100use fedimint_ln_common::contracts::{IdentifiableContract, Preimage};
101use fedimint_lnv2_common::Bolt11InvoiceDescription;
102use fedimint_lnv2_common::contracts::{IncomingContract, PaymentImage};
103use fedimint_lnv2_common::gateway_api::{
104    CreateBolt11InvoicePayload, PaymentFee, RoutingInfo, SendPaymentPayload,
105};
106use fedimint_logging::LOG_GATEWAY;
107use fedimint_mint_client::{
108    MintClientInit, MintClientModule, MintCommonInit, SelectNotesWithAtleastAmount,
109    SelectNotesWithExactAmount,
110};
111use fedimint_wallet_client::envs::FM_PORT_ESPLORA_ENV;
112use fedimint_wallet_client::{
113    WalletClientInit, WalletClientModule, WalletCommonInit, WithdrawState,
114};
115use futures::stream::StreamExt;
116use lightning_invoice::{Bolt11Invoice, RoutingFees};
117use rand::thread_rng;
118use tokio::sync::RwLock;
119use tracing::{debug, info, info_span, warn};
120
121use crate::config::LightningModuleMode;
122use crate::envs::FM_GATEWAY_MNEMONIC_ENV;
123use crate::error::{AdminGatewayError, LNv1Error, LNv2Error, PublicGatewayError};
124use crate::events::get_events_for_duration;
125use crate::rpc_server::run_webserver;
126use crate::types::PrettyInterceptPaymentRequest;
127
128/// How long a gateway announcement stays valid
129const GW_ANNOUNCEMENT_TTL: Duration = Duration::from_secs(600);
130
131/// The default number of route hints that the legacy gateway provides for
132/// invoice creation.
133const DEFAULT_NUM_ROUTE_HINTS: u32 = 1;
134
135/// Default Bitcoin network for testing purposes.
136pub const DEFAULT_NETWORK: Network = Network::Regtest;
137
138pub type Result<T> = std::result::Result<T, PublicGatewayError>;
139pub type AdminResult<T> = std::result::Result<T, AdminGatewayError>;
140
141/// Name of the gateway's database that is used for metadata and configuration
142/// storage.
143const DB_FILE: &str = "gatewayd.db";
144
145/// Name of the folder that the gateway uses to store its node database when
146/// running in LDK mode.
147const LDK_NODE_DB_FOLDER: &str = "ldk_node";
148
149/// The non-lightning default module types that the Gateway supports.
150const DEFAULT_MODULE_KINDS: [(ModuleInstanceId, &ModuleKind); 2] = [
151    (LEGACY_HARDCODED_INSTANCE_ID_MINT, &MintCommonInit::KIND),
152    (LEGACY_HARDCODED_INSTANCE_ID_WALLET, &WalletCommonInit::KIND),
153];
154
155#[cfg_attr(doc, aquamarine::aquamarine)]
156/// ```mermaid
157/// graph LR
158/// classDef virtual fill:#fff,stroke-dasharray: 5 5
159///
160///    Disconnected -- establish lightning connection --> Connected
161///    Connected -- load federation clients --> Running
162///    Connected -- not synced to chain --> Syncing
163///    Syncing -- load federation clients --> Running
164///    Running -- disconnected from lightning node --> Disconnected
165///    Running -- shutdown initiated --> ShuttingDown
166/// ```
167#[derive(Clone, Debug)]
168pub enum GatewayState {
169    Disconnected,
170    Syncing,
171    Connected,
172    Running { lightning_context: LightningContext },
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::Disconnected => write!(f, "Disconnected"),
180            GatewayState::Syncing => write!(f, "Syncing"),
181            GatewayState::Connected => write!(f, "Connected"),
182            GatewayState::Running { .. } => write!(f, "Running"),
183            GatewayState::ShuttingDown { .. } => write!(f, "ShuttingDown"),
184        }
185    }
186}
187
188/// The action to take after handling a payment stream.
189enum ReceivePaymentStreamAction {
190    RetryAfterDelay,
191    NoRetry,
192}
193
194#[derive(Clone)]
195pub struct Gateway {
196    /// The gateway's federation manager.
197    federation_manager: Arc<RwLock<FederationManager>>,
198
199    // The mnemonic for the gateway
200    mnemonic: Mnemonic,
201
202    /// The mode that specifies the lightning connection parameters
203    lightning_mode: LightningMode,
204
205    /// The current state of the Gateway.
206    state: Arc<RwLock<GatewayState>>,
207
208    /// Builder struct that allows the gateway to build a Fedimint client, which
209    /// handles the communication with a federation.
210    client_builder: GatewayClientBuilder,
211
212    /// Database for Gateway metadata.
213    gateway_db: Database,
214
215    /// A public key representing the identity of the gateway. Private key is
216    /// not used.
217    gateway_id: PublicKey,
218
219    /// The Gateway's API URL.
220    versioned_api: SafeUrl,
221
222    /// The socket the gateway listens on.
223    listen: SocketAddr,
224
225    /// The "module mode" of the gateway. Options are LNv1, LNv2, or All.
226    lightning_module_mode: LightningModuleMode,
227
228    /// The task group for all tasks related to the gateway.
229    task_group: TaskGroup,
230
231    /// The bcrypt password hash used to authenticate the gateway.
232    /// This is an `Arc` because `bcrypt::HashParts` does not implement `Clone`.
233    bcrypt_password_hash: Arc<bcrypt::HashParts>,
234
235    /// The number of route hints to include in LNv1 invoices.
236    num_route_hints: u32,
237
238    /// The Bitcoin network that the Lightning network is configured to.
239    network: Network,
240}
241
242impl std::fmt::Debug for Gateway {
243    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244        f.debug_struct("Gateway")
245            .field("federation_manager", &self.federation_manager)
246            .field("state", &self.state)
247            .field("client_builder", &self.client_builder)
248            .field("gateway_db", &self.gateway_db)
249            .field("gateway_id", &self.gateway_id)
250            .field("versioned_api", &self.versioned_api)
251            .field("listen", &self.listen)
252            .finish_non_exhaustive()
253    }
254}
255
256impl Gateway {
257    /// Creates a new gateway but with a custom module registry provided inside
258    /// `client_builder`. Currently only used for testing.
259    #[allow(clippy::too_many_arguments)]
260    pub async fn new_with_custom_registry(
261        lightning_mode: LightningMode,
262        client_builder: GatewayClientBuilder,
263        listen: SocketAddr,
264        api_addr: SafeUrl,
265        bcrypt_password_hash: bcrypt::HashParts,
266        network: Network,
267        num_route_hints: u32,
268        gateway_db: Database,
269        gateway_state: GatewayState,
270        lightning_module_mode: LightningModuleMode,
271    ) -> anyhow::Result<Gateway> {
272        let versioned_api = api_addr
273            .join(V1_API_ENDPOINT)
274            .expect("Failed to version gateway API address");
275        Gateway::new(
276            lightning_mode,
277            GatewayParameters {
278                listen,
279                versioned_api,
280                bcrypt_password_hash,
281                network,
282                num_route_hints,
283                lightning_module_mode,
284            },
285            gateway_db,
286            client_builder,
287            gateway_state,
288        )
289        .await
290    }
291
292    /// Default function for creating a gateway with the `Mint`, `Wallet`, and
293    /// `Gateway` modules.
294    pub async fn new_with_default_modules() -> anyhow::Result<Gateway> {
295        let opts = GatewayOpts::parse();
296
297        // Gateway module will be attached when the federation clients are created
298        // because the LN RPC will be injected with `GatewayClientGen`.
299        let mut registry = ClientModuleInitRegistry::new();
300        registry.attach(MintClientInit);
301        registry.attach(WalletClientInit::default());
302
303        let decoders = registry.available_decoders(DEFAULT_MODULE_KINDS.iter().copied())?;
304
305        let db_path = opts.data_dir.join(DB_FILE);
306        let gateway_db = match opts.db_backend {
307            DatabaseBackend::RocksDb => {
308                debug!(target: LOG_GATEWAY, "Using RocksDB database backend");
309                Database::new(fedimint_rocksdb::RocksDb::open(db_path).await?, decoders)
310            }
311            DatabaseBackend::CursedRedb => {
312                debug!(target: LOG_GATEWAY, "Using CursedRedb database backend");
313                Database::new(
314                    fedimint_cursed_redb::MemAndRedb::new(db_path).await?,
315                    decoders,
316                )
317            }
318        };
319
320        let client_builder = GatewayClientBuilder::new(
321            opts.data_dir.clone(),
322            registry,
323            fedimint_mint_client::KIND,
324            opts.db_backend,
325        );
326
327        info!(
328            target: LOG_GATEWAY,
329            version = %fedimint_build_code_version_env!(),
330            "Starting gatewayd",
331        );
332
333        let mut gateway_parameters = opts.to_gateway_parameters()?;
334
335        if gateway_parameters.lightning_module_mode != LightningModuleMode::LNv2
336            && matches!(opts.mode, LightningMode::Ldk { .. })
337        {
338            warn!(target: LOG_GATEWAY, "Overriding LDK Gateway to only run LNv2...");
339            gateway_parameters.lightning_module_mode = LightningModuleMode::LNv2;
340        }
341
342        Gateway::new(
343            opts.mode,
344            gateway_parameters,
345            gateway_db,
346            client_builder,
347            GatewayState::Disconnected,
348        )
349        .await
350    }
351
352    /// Helper function for creating a gateway from either
353    /// `new_with_default_modules` or `new_with_custom_registry`.
354    async fn new(
355        lightning_mode: LightningMode,
356        gateway_parameters: GatewayParameters,
357        gateway_db: Database,
358        client_builder: GatewayClientBuilder,
359        gateway_state: GatewayState,
360    ) -> anyhow::Result<Gateway> {
361        // Apply database migrations before using the database to ensure old database
362        // structures are readable.
363        apply_migrations(
364            &gateway_db,
365            (),
366            "gatewayd".to_string(),
367            get_gatewayd_database_migrations(),
368            None,
369            None,
370        )
371        .await?;
372
373        let num_route_hints = gateway_parameters.num_route_hints;
374        let network = gateway_parameters.network;
375
376        let task_group = TaskGroup::new();
377        task_group.install_kill_handler();
378
379        Ok(Self {
380            federation_manager: Arc::new(RwLock::new(FederationManager::new())),
381            mnemonic: Self::load_or_generate_mnemonic(&gateway_db).await?,
382            lightning_mode,
383            state: Arc::new(RwLock::new(gateway_state)),
384            client_builder,
385            gateway_id: Self::load_or_create_gateway_id(&gateway_db).await,
386            gateway_db,
387            versioned_api: gateway_parameters.versioned_api,
388            listen: gateway_parameters.listen,
389            lightning_module_mode: gateway_parameters.lightning_module_mode,
390            task_group,
391            bcrypt_password_hash: Arc::new(gateway_parameters.bcrypt_password_hash),
392            num_route_hints,
393            network,
394        })
395    }
396
397    /// Returns a `PublicKey` that uniquely identifies the Gateway.
398    async fn load_or_create_gateway_id(gateway_db: &Database) -> PublicKey {
399        let mut dbtx = gateway_db.begin_transaction().await;
400        let keypair = dbtx.load_or_create_gateway_keypair().await;
401        dbtx.commit_tx().await;
402        keypair.public_key()
403    }
404
405    pub fn gateway_id(&self) -> PublicKey {
406        self.gateway_id
407    }
408
409    pub fn versioned_api(&self) -> &SafeUrl {
410        &self.versioned_api
411    }
412
413    async fn get_state(&self) -> GatewayState {
414        self.state.read().await.clone()
415    }
416
417    /// Reads and serializes structures from the Gateway's database for the
418    /// purpose for serializing to JSON for inspection.
419    pub async fn dump_database(
420        dbtx: &mut DatabaseTransaction<'_>,
421        prefix_names: Vec<String>,
422    ) -> BTreeMap<String, Box<dyn erased_serde::Serialize + Send>> {
423        dbtx.dump_database(prefix_names).await
424    }
425
426    /// Main entrypoint into the gateway that starts the client registration
427    /// timer, loads the federation clients from the persisted config,
428    /// begins listening for intercepted payments, and starts the webserver
429    /// to service requests.
430    pub async fn run(
431        self,
432        runtime: Arc<tokio::runtime::Runtime>,
433    ) -> anyhow::Result<TaskShutdownToken> {
434        install_crypto_provider().await;
435        self.verify_lightning_module_mode()?;
436        self.register_clients_timer();
437        self.load_clients().await?;
438        self.start_gateway(runtime);
439        // start webserver last to avoid handling requests before fully initialized
440        let handle = self.task_group.make_handle();
441        run_webserver(Arc::new(self)).await?;
442        let shutdown_receiver = handle.make_shutdown_rx();
443        Ok(shutdown_receiver)
444    }
445
446    /// Verifies that the gateway is not running on mainnet with
447    /// `LightningModuleMode::All`
448    fn verify_lightning_module_mode(&self) -> anyhow::Result<()> {
449        if !is_env_var_set(FM_GATEWAY_OVERRIDE_LN_MODULE_CHECK_ENV)
450            && self.network == Network::Bitcoin
451            && self.lightning_module_mode == LightningModuleMode::All
452        {
453            crit!(
454                "It is not recommended to run the Gateway with `LightningModuleMode::All`, because LNv2 invoices cannot be paid with LNv1 clients. If you really know what you're doing and want to bypass this, please set FM_GATEWAY_OVERRIDE_LN_MODULE_CHECK"
455            );
456            return Err(anyhow!(
457                "Cannot run gateway with LightningModuleMode::All on mainnet"
458            ));
459        }
460
461        Ok(())
462    }
463
464    /// Begins the task for listening for intercepted payments from the
465    /// lightning node.
466    fn start_gateway(&self, runtime: Arc<tokio::runtime::Runtime>) {
467        const PAYMENT_STREAM_RETRY_SECONDS: u64 = 5;
468
469        let self_copy = self.clone();
470        let tg = self.task_group.clone();
471        self.task_group.spawn(
472            "Subscribe to intercepted lightning payments in stream",
473            |handle| async move {
474                // Repeatedly attempt to establish a connection to the lightning node and create a payment stream, re-trying if the connection is broken.
475                loop {
476                    if handle.is_shutting_down() {
477                        info!(target: LOG_GATEWAY, "Gateway lightning payment stream handler loop is shutting down");
478                        break;
479                    }
480
481                    let payment_stream_task_group = tg.make_subgroup();
482                    let lnrpc_route = self_copy.create_lightning_client(runtime.clone());
483
484                    debug!(target: LOG_GATEWAY, "Establishing lightning payment stream...");
485                    let (stream, ln_client) = match lnrpc_route.route_htlcs(&payment_stream_task_group).await
486                    {
487                        Ok((stream, ln_client)) => (stream, ln_client),
488                        Err(err) => {
489                            warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Failed to open lightning payment stream");
490                            continue
491                        }
492                    };
493
494                    // Successful calls to `route_htlcs` establish a connection
495                    self_copy.set_gateway_state(GatewayState::Connected).await;
496                    info!(target: LOG_GATEWAY, "Established lightning payment stream");
497
498                    let route_payments_response =
499                        self_copy.route_lightning_payments(&handle, stream, ln_client).await;
500
501                    self_copy.set_gateway_state(GatewayState::Disconnected).await;
502                    if let Err(err) = payment_stream_task_group.shutdown_join_all(None).await {
503                        crit!(target: LOG_GATEWAY, err = %err.fmt_compact_anyhow(), "Lightning payment stream task group shutdown");
504                    }
505
506                    self_copy.unannounce_from_all_federations().await;
507
508                    match route_payments_response {
509                        ReceivePaymentStreamAction::RetryAfterDelay => {
510                            warn!(target: LOG_GATEWAY, retry_interval = %PAYMENT_STREAM_RETRY_SECONDS, "Disconnected from lightning node");
511                            sleep(Duration::from_secs(PAYMENT_STREAM_RETRY_SECONDS)).await;
512                        }
513                        ReceivePaymentStreamAction::NoRetry => break,
514                    }
515                }
516            },
517        );
518    }
519
520    /// Handles a stream of incoming payments from the lightning node after
521    /// ensuring the gateway is properly configured. Awaits until the stream
522    /// is closed, then returns with the appropriate action to take.
523    async fn route_lightning_payments<'a>(
524        &'a self,
525        handle: &TaskHandle,
526        mut stream: RouteHtlcStream<'a>,
527        ln_client: Arc<dyn ILnRpcClient>,
528    ) -> ReceivePaymentStreamAction {
529        let (lightning_public_key, lightning_alias, lightning_network, synced_to_chain) =
530            match ln_client.parsed_node_info().await {
531                Ok((
532                    lightning_public_key,
533                    lightning_alias,
534                    lightning_network,
535                    _block_height,
536                    synced_to_chain,
537                )) => (
538                    lightning_public_key,
539                    lightning_alias,
540                    lightning_network,
541                    synced_to_chain,
542                ),
543                Err(err) => {
544                    warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Failed to retrieve Lightning info");
545                    return ReceivePaymentStreamAction::RetryAfterDelay;
546                }
547            };
548
549        assert!(
550            self.network == lightning_network,
551            "Lightning node network does not match Gateway's network. LN: {lightning_network} Gateway: {}",
552            self.network
553        );
554
555        if synced_to_chain || is_env_var_set(FM_GATEWAY_SKIP_WAIT_FOR_SYNC_ENV) {
556            info!(target: LOG_GATEWAY, "Gateway is already synced to chain");
557        } else {
558            self.set_gateway_state(GatewayState::Syncing).await;
559            info!(target: LOG_GATEWAY, "Waiting for chain sync");
560            if let Err(err) = ln_client.wait_for_chain_sync().await {
561                warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Failed to wait for chain sync");
562                return ReceivePaymentStreamAction::RetryAfterDelay;
563            }
564        }
565
566        let lightning_context = LightningContext {
567            lnrpc: ln_client,
568            lightning_public_key,
569            lightning_alias,
570            lightning_network,
571        };
572        self.set_gateway_state(GatewayState::Running { lightning_context })
573            .await;
574        info!(target: LOG_GATEWAY, "Gateway is running");
575
576        if self.is_running_lnv1() {
577            // Re-register the gateway with all federations after connecting to the
578            // lightning node
579            let mut dbtx = self.gateway_db.begin_transaction_nc().await;
580            let all_federations_configs =
581                dbtx.load_federation_configs().await.into_iter().collect();
582            self.register_federations(&all_federations_configs, &self.task_group)
583                .await;
584        }
585
586        // Runs until the connection to the lightning node breaks or we receive the
587        // shutdown signal.
588        if handle
589            .cancel_on_shutdown(async move {
590                loop {
591                    let payment_request_or = tokio::select! {
592                        payment_request_or = stream.next() => {
593                            payment_request_or
594                        }
595                        () = self.is_shutting_down_safely() => {
596                            break;
597                        }
598                    };
599
600                    let Some(payment_request) = payment_request_or else {
601                        warn!(
602                            target: LOG_GATEWAY,
603                            "Unexpected response from incoming lightning payment stream. Shutting down payment processor"
604                        );
605                        break;
606                    };
607
608                    let state_guard = self.state.read().await;
609                    if let GatewayState::Running { ref lightning_context } = *state_guard {
610                        self.handle_lightning_payment(payment_request, lightning_context).await;
611                    } else {
612                        warn!(
613                            target: LOG_GATEWAY,
614                            state = %state_guard,
615                            "Gateway isn't in a running state, cannot handle incoming payments."
616                        );
617                        break;
618                    }
619                }
620            })
621            .await
622            .is_ok()
623        {
624            warn!(target: LOG_GATEWAY, "Lightning payment stream connection broken. Gateway is disconnected");
625            ReceivePaymentStreamAction::RetryAfterDelay
626        } else {
627            info!(target: LOG_GATEWAY, "Received shutdown signal");
628            ReceivePaymentStreamAction::NoRetry
629        }
630    }
631
632    /// Polls the Gateway's state waiting for it to shutdown so the thread
633    /// processing payment requests can exit.
634    async fn is_shutting_down_safely(&self) {
635        loop {
636            if let GatewayState::ShuttingDown { .. } = self.get_state().await {
637                return;
638            }
639
640            fedimint_core::task::sleep(Duration::from_secs(1)).await;
641        }
642    }
643
644    /// Handles an intercepted lightning payment. If the payment is part of an
645    /// incoming payment to a federation, spawns a state machine and hands the
646    /// payment off to it. Otherwise, forwards the payment to the next hop like
647    /// a normal lightning node.
648    async fn handle_lightning_payment(
649        &self,
650        payment_request: InterceptPaymentRequest,
651        lightning_context: &LightningContext,
652    ) {
653        info!(
654            target: LOG_GATEWAY,
655            lightning_payment = %PrettyInterceptPaymentRequest(&payment_request),
656            "Intercepting lightning payment",
657        );
658
659        if self
660            .try_handle_lightning_payment_lnv2(&payment_request, lightning_context)
661            .await
662            .is_ok()
663        {
664            return;
665        }
666
667        if self
668            .try_handle_lightning_payment_ln_legacy(&payment_request)
669            .await
670            .is_ok()
671        {
672            return;
673        }
674
675        Self::forward_lightning_payment(payment_request, lightning_context).await;
676    }
677
678    /// Tries to handle a lightning payment using the LNv2 protocol.
679    /// Returns `Ok` if the payment was handled, `Err` otherwise.
680    async fn try_handle_lightning_payment_lnv2(
681        &self,
682        htlc_request: &InterceptPaymentRequest,
683        lightning_context: &LightningContext,
684    ) -> Result<()> {
685        // If `payment_hash` has been registered as a LNv2 payment, we try to complete
686        // the payment by getting the preimage from the federation
687        // using the LNv2 protocol. If the `payment_hash` is not registered,
688        // this payment is either a legacy Lightning payment or the end destination is
689        // not a Fedimint.
690        let (contract, client) = self
691            .get_registered_incoming_contract_and_client_v2(
692                PaymentImage::Hash(htlc_request.payment_hash),
693                htlc_request.amount_msat,
694            )
695            .await?;
696
697        if let Err(err) = client
698            .get_first_module::<GatewayClientModuleV2>()
699            .expect("Must have client module")
700            .relay_incoming_htlc(
701                htlc_request.payment_hash,
702                htlc_request.incoming_chan_id,
703                htlc_request.htlc_id,
704                contract,
705                htlc_request.amount_msat,
706            )
707            .await
708        {
709            warn!(target: LOG_GATEWAY, err = %err.fmt_compact_anyhow(), "Error relaying incoming lightning payment");
710
711            let outcome = InterceptPaymentResponse {
712                action: PaymentAction::Cancel,
713                payment_hash: htlc_request.payment_hash,
714                incoming_chan_id: htlc_request.incoming_chan_id,
715                htlc_id: htlc_request.htlc_id,
716            };
717
718            if let Err(err) = lightning_context.lnrpc.complete_htlc(outcome).await {
719                warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Error sending HTLC response to lightning node");
720            }
721        }
722
723        Ok(())
724    }
725
726    /// Tries to handle a lightning payment using the legacy lightning protocol.
727    /// Returns `Ok` if the payment was handled, `Err` otherwise.
728    async fn try_handle_lightning_payment_ln_legacy(
729        &self,
730        htlc_request: &InterceptPaymentRequest,
731    ) -> Result<()> {
732        // Check if the payment corresponds to a federation supporting legacy Lightning.
733        let Some(federation_index) = htlc_request.short_channel_id else {
734            return Err(PublicGatewayError::LNv1(LNv1Error::IncomingPayment(
735                "Incoming payment has not last hop short channel id".to_string(),
736            )));
737        };
738
739        let Some(client) = self
740            .federation_manager
741            .read()
742            .await
743            .get_client_for_index(federation_index)
744        else {
745            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())));
746        };
747
748        client
749            .borrow()
750            .with(|client| async {
751                let htlc = htlc_request.clone().try_into();
752                match htlc {
753                    Ok(htlc) => {
754                        match client
755                            .get_first_module::<GatewayClientModule>()
756                            .expect("Must have client module")
757                            .gateway_handle_intercepted_htlc(htlc)
758                            .await
759                        {
760                            Ok(_) => Ok(()),
761                            Err(e) => Err(PublicGatewayError::LNv1(LNv1Error::IncomingPayment(
762                                format!("Error intercepting lightning payment {e:?}"),
763                            ))),
764                        }
765                    }
766                    _ => Err(PublicGatewayError::LNv1(LNv1Error::IncomingPayment(
767                        "Could not convert InterceptHtlcResult into an HTLC".to_string(),
768                    ))),
769                }
770            })
771            .await
772    }
773
774    /// Forwards a lightning payment to the next hop like a normal lightning
775    /// node. Only necessary for LNv1, since LNv2 uses hold invoices instead
776    /// of HTLC interception for routing incoming payments.
777    async fn forward_lightning_payment(
778        htlc_request: InterceptPaymentRequest,
779        lightning_context: &LightningContext,
780    ) {
781        let outcome = InterceptPaymentResponse {
782            action: PaymentAction::Forward,
783            payment_hash: htlc_request.payment_hash,
784            incoming_chan_id: htlc_request.incoming_chan_id,
785            htlc_id: htlc_request.htlc_id,
786        };
787
788        if let Err(err) = lightning_context.lnrpc.complete_htlc(outcome).await {
789            warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Error sending lightning payment response to lightning node");
790        }
791    }
792
793    /// Helper function for atomically changing the Gateway's internal state.
794    async fn set_gateway_state(&self, state: GatewayState) {
795        let mut lock = self.state.write().await;
796        *lock = state;
797    }
798
799    /// Returns information about the Gateway back to the client when requested
800    /// via the webserver.
801    pub async fn handle_get_info(&self) -> AdminResult<GatewayInfo> {
802        let GatewayState::Running { lightning_context } = self.get_state().await else {
803            return Ok(GatewayInfo {
804                federations: vec![],
805                federation_fake_scids: None,
806                version_hash: fedimint_build_code_version_env!().to_string(),
807                lightning_pub_key: None,
808                lightning_alias: None,
809                gateway_id: self.gateway_id,
810                gateway_state: self.state.read().await.to_string(),
811                network: self.network,
812                block_height: None,
813                synced_to_chain: false,
814                api: self.versioned_api.clone(),
815                lightning_mode: self.lightning_mode.clone(),
816            });
817        };
818
819        let dbtx = self.gateway_db.begin_transaction_nc().await;
820        let federations = self
821            .federation_manager
822            .read()
823            .await
824            .federation_info_all_federations(dbtx)
825            .await;
826
827        let channels: BTreeMap<u64, FederationId> = federations
828            .iter()
829            .map(|federation_info| {
830                (
831                    federation_info.config.federation_index,
832                    federation_info.federation_id,
833                )
834            })
835            .collect();
836
837        let node_info = lightning_context.lnrpc.parsed_node_info().await?;
838
839        Ok(GatewayInfo {
840            federations,
841            federation_fake_scids: Some(channels),
842            version_hash: fedimint_build_code_version_env!().to_string(),
843            lightning_pub_key: Some(lightning_context.lightning_public_key.to_string()),
844            lightning_alias: Some(lightning_context.lightning_alias.clone()),
845            gateway_id: self.gateway_id,
846            gateway_state: self.state.read().await.to_string(),
847            network: self.network,
848            block_height: Some(node_info.3),
849            synced_to_chain: node_info.4,
850            api: self.versioned_api.clone(),
851            lightning_mode: self.lightning_mode.clone(),
852        })
853    }
854
855    /// If the Gateway is connected to the Lightning node, returns the
856    /// `ClientConfig` for each federation that the Gateway is connected to.
857    pub async fn handle_get_federation_config(
858        &self,
859        federation_id_or: Option<FederationId>,
860    ) -> AdminResult<GatewayFedConfig> {
861        if !matches!(self.get_state().await, GatewayState::Running { .. }) {
862            return Ok(GatewayFedConfig {
863                federations: BTreeMap::new(),
864            });
865        }
866
867        let federations = if let Some(federation_id) = federation_id_or {
868            let mut federations = BTreeMap::new();
869            federations.insert(
870                federation_id,
871                self.federation_manager
872                    .read()
873                    .await
874                    .get_federation_config(federation_id)
875                    .await?,
876            );
877            federations
878        } else {
879            self.federation_manager
880                .read()
881                .await
882                .get_all_federation_configs()
883                .await
884        };
885
886        Ok(GatewayFedConfig { federations })
887    }
888
889    /// Returns a Bitcoin deposit on-chain address for pegging in Bitcoin for a
890    /// specific connected federation.
891    pub async fn handle_address_msg(&self, payload: DepositAddressPayload) -> AdminResult<Address> {
892        let (_, address, _) = self
893            .select_client(payload.federation_id)
894            .await?
895            .value()
896            .get_first_module::<WalletClientModule>()
897            .expect("Must have client module")
898            .allocate_deposit_address_expert_only(())
899            .await?;
900        Ok(address)
901    }
902
903    /// Returns a Bitcoin TXID from a peg-out transaction for a specific
904    /// connected federation.
905    pub async fn handle_withdraw_msg(
906        &self,
907        payload: WithdrawPayload,
908    ) -> AdminResult<WithdrawResponse> {
909        let WithdrawPayload {
910            amount,
911            address,
912            federation_id,
913        } = payload;
914
915        let address_network = get_network_for_address(&address);
916        let gateway_network = self.network;
917        let Ok(address) = address.require_network(gateway_network) else {
918            return Err(AdminGatewayError::WithdrawError {
919                failure_reason: format!(
920                    "Gateway is running on network {gateway_network}, but provided withdraw address is for network {address_network}"
921                ),
922            });
923        };
924
925        let client = self.select_client(federation_id).await?;
926        let wallet_module = client.value().get_first_module::<WalletClientModule>()?;
927
928        // TODO: Fees should probably be passed in as a parameter
929        let (amount, fees) = match amount {
930            // If the amount is "all", then we need to subtract the fees from
931            // the amount we are withdrawing
932            BitcoinAmountOrAll::All => {
933                let balance =
934                    bitcoin::Amount::from_sat(client.value().get_balance().await.msats / 1000);
935                let fees = wallet_module.get_withdraw_fees(&address, balance).await?;
936                let withdraw_amount = balance.checked_sub(fees.amount());
937                if withdraw_amount.is_none() {
938                    return Err(AdminGatewayError::WithdrawError {
939                        failure_reason: format!(
940                            "Insufficient funds. Balance: {balance} Fees: {fees:?}"
941                        ),
942                    });
943                }
944                (withdraw_amount.unwrap(), fees)
945            }
946            BitcoinAmountOrAll::Amount(amount) => (
947                amount,
948                wallet_module.get_withdraw_fees(&address, amount).await?,
949            ),
950        };
951
952        let operation_id = wallet_module.withdraw(&address, amount, fees, ()).await?;
953        let mut updates = wallet_module
954            .subscribe_withdraw_updates(operation_id)
955            .await?
956            .into_stream();
957
958        while let Some(update) = updates.next().await {
959            match update {
960                WithdrawState::Succeeded(txid) => {
961                    info!(target: LOG_GATEWAY, amount = %amount, address = %address, "Sent funds");
962                    return Ok(WithdrawResponse { txid, fees });
963                }
964                WithdrawState::Failed(e) => {
965                    return Err(AdminGatewayError::WithdrawError { failure_reason: e });
966                }
967                WithdrawState::Created => {}
968            }
969        }
970
971        Err(AdminGatewayError::WithdrawError {
972            failure_reason: "Ran out of state updates while withdrawing".to_string(),
973        })
974    }
975
976    /// Creates an invoice that is directly payable to the gateway's lightning
977    /// node.
978    async fn handle_create_invoice_for_operator_msg(
979        &self,
980        payload: CreateInvoiceForOperatorPayload,
981    ) -> AdminResult<Bolt11Invoice> {
982        let GatewayState::Running { lightning_context } = self.get_state().await else {
983            return Err(AdminGatewayError::Lightning(
984                LightningRpcError::FailedToConnect,
985            ));
986        };
987
988        Bolt11Invoice::from_str(
989            &lightning_context
990                .lnrpc
991                .create_invoice(CreateInvoiceRequest {
992                    payment_hash: None, /* Empty payment hash indicates an invoice payable
993                                         * directly to the gateway. */
994                    amount_msat: payload.amount_msats,
995                    expiry_secs: payload.expiry_secs.unwrap_or(3600),
996                    description: payload.description.map(InvoiceDescription::Direct),
997                })
998                .await?
999                .invoice,
1000        )
1001        .map_err(|e| {
1002            AdminGatewayError::Lightning(LightningRpcError::InvalidMetadata {
1003                failure_reason: e.to_string(),
1004            })
1005        })
1006    }
1007
1008    /// Requests the gateway to pay an outgoing LN invoice using its own funds.
1009    /// Returns the payment hash's preimage on success.
1010    async fn handle_pay_invoice_for_operator_msg(
1011        &self,
1012        payload: PayInvoiceForOperatorPayload,
1013    ) -> AdminResult<Preimage> {
1014        // Those are the ldk defaults
1015        const BASE_FEE: u64 = 50;
1016        const FEE_DENOMINATOR: u64 = 100;
1017        const MAX_DELAY: u64 = 1008;
1018
1019        let GatewayState::Running { lightning_context } = self.get_state().await else {
1020            return Err(AdminGatewayError::Lightning(
1021                LightningRpcError::FailedToConnect,
1022            ));
1023        };
1024
1025        let max_fee = BASE_FEE
1026            + payload
1027                .invoice
1028                .amount_milli_satoshis()
1029                .context("Invoice is missing amount")?
1030                .saturating_div(FEE_DENOMINATOR);
1031
1032        let res = lightning_context
1033            .lnrpc
1034            .pay(payload.invoice, MAX_DELAY, Amount::from_msats(max_fee))
1035            .await?;
1036        Ok(res.preimage)
1037    }
1038
1039    /// Requests the gateway to pay an outgoing LN invoice on behalf of a
1040    /// Fedimint client. Returns the payment hash's preimage on success.
1041    async fn handle_pay_invoice_msg(
1042        &self,
1043        payload: fedimint_ln_client::pay::PayInvoicePayload,
1044    ) -> Result<Preimage> {
1045        let GatewayState::Running { .. } = self.get_state().await else {
1046            return Err(PublicGatewayError::Lightning(
1047                LightningRpcError::FailedToConnect,
1048            ));
1049        };
1050
1051        debug!(target: LOG_GATEWAY, "Handling pay invoice message");
1052        let client = self.select_client(payload.federation_id).await?;
1053        let contract_id = payload.contract_id;
1054        let gateway_module = &client
1055            .value()
1056            .get_first_module::<GatewayClientModule>()
1057            .map_err(LNv1Error::OutgoingPayment)
1058            .map_err(PublicGatewayError::LNv1)?;
1059        let operation_id = gateway_module
1060            .gateway_pay_bolt11_invoice(payload)
1061            .await
1062            .map_err(LNv1Error::OutgoingPayment)
1063            .map_err(PublicGatewayError::LNv1)?;
1064        let mut updates = gateway_module
1065            .gateway_subscribe_ln_pay(operation_id)
1066            .await
1067            .map_err(LNv1Error::OutgoingPayment)
1068            .map_err(PublicGatewayError::LNv1)?
1069            .into_stream();
1070        while let Some(update) = updates.next().await {
1071            match update {
1072                GatewayExtPayStates::Success { preimage, .. } => {
1073                    debug!(target: LOG_GATEWAY, contract_id = %contract_id, "Successfully paid invoice");
1074                    return Ok(preimage);
1075                }
1076                GatewayExtPayStates::Fail {
1077                    error,
1078                    error_message,
1079                } => {
1080                    return Err(PublicGatewayError::LNv1(LNv1Error::OutgoingContract {
1081                        error: Box::new(error),
1082                        message: format!(
1083                            "{error_message} while paying invoice with contract id {contract_id}"
1084                        ),
1085                    }));
1086                }
1087                GatewayExtPayStates::Canceled { error } => {
1088                    return Err(PublicGatewayError::LNv1(LNv1Error::OutgoingContract {
1089                        error: Box::new(error.clone()),
1090                        message: format!(
1091                            "Cancelled with {error} while paying invoice with contract id {contract_id}"
1092                        ),
1093                    }));
1094                }
1095                GatewayExtPayStates::Created => {
1096                    debug!(target: LOG_GATEWAY, contract_id = %contract_id, "Start pay invoice state machine");
1097                }
1098                other => {
1099                    debug!(target: LOG_GATEWAY, state = ?other, contract_id = %contract_id, "Got state while paying invoice");
1100                }
1101            }
1102        }
1103
1104        Err(PublicGatewayError::LNv1(LNv1Error::OutgoingPayment(
1105            anyhow!("Ran out of state updates while paying invoice"),
1106        )))
1107    }
1108
1109    /// Handles a connection request to join a new federation. The gateway will
1110    /// download the federation's client configuration, construct a new
1111    /// client, registers, the gateway with the federation, and persists the
1112    /// necessary config to reconstruct the client when restarting the gateway.
1113    pub async fn handle_connect_federation(
1114        &self,
1115        payload: ConnectFedPayload,
1116    ) -> AdminResult<FederationInfo> {
1117        let GatewayState::Running { lightning_context } = self.get_state().await else {
1118            return Err(AdminGatewayError::Lightning(
1119                LightningRpcError::FailedToConnect,
1120            ));
1121        };
1122
1123        let invite_code = InviteCode::from_str(&payload.invite_code).map_err(|e| {
1124            AdminGatewayError::ClientCreationError(anyhow!(format!(
1125                "Invalid federation member string {e:?}"
1126            )))
1127        })?;
1128
1129        #[cfg(feature = "tor")]
1130        let connector = match &payload.use_tor {
1131            Some(true) => Connector::tor(),
1132            Some(false) => Connector::default(),
1133            None => {
1134                debug!(target: LOG_GATEWAY, "Missing `use_tor` payload field, defaulting to `Connector::Tcp` variant!");
1135                Connector::default()
1136            }
1137        };
1138
1139        #[cfg(not(feature = "tor"))]
1140        let connector = Connector::default();
1141
1142        let federation_id = invite_code.federation_id();
1143
1144        let mut federation_manager = self.federation_manager.write().await;
1145
1146        // Check if this federation has already been registered
1147        if federation_manager.has_federation(federation_id) {
1148            return Err(AdminGatewayError::ClientCreationError(anyhow!(
1149                "Federation has already been registered"
1150            )));
1151        }
1152
1153        // The gateway deterministically assigns a unique identifier (u64) to each
1154        // federation connected.
1155        let federation_index = federation_manager.pop_next_index()?;
1156
1157        let federation_config = FederationConfig {
1158            invite_code,
1159            federation_index,
1160            lightning_fee: PaymentFee::TRANSACTION_FEE_DEFAULT,
1161            transaction_fee: PaymentFee::TRANSACTION_FEE_DEFAULT,
1162            connector,
1163        };
1164
1165        let recover = payload.recover.unwrap_or(false);
1166        if recover {
1167            self.client_builder
1168                .recover(
1169                    federation_config.clone(),
1170                    Arc::new(self.clone()),
1171                    &self.mnemonic,
1172                )
1173                .await?;
1174        }
1175
1176        let client = self
1177            .client_builder
1178            .build(
1179                federation_config.clone(),
1180                Arc::new(self.clone()),
1181                &self.mnemonic,
1182            )
1183            .await?;
1184
1185        if recover {
1186            client.wait_for_all_active_state_machines().await?;
1187        }
1188
1189        // Instead of using `FederationManager::federation_info`, we manually create
1190        // federation info here because short channel id is not yet persisted.
1191        let federation_info = FederationInfo {
1192            federation_id,
1193            federation_name: federation_manager.federation_name(&client).await,
1194            balance_msat: client.get_balance().await,
1195            config: federation_config.clone(),
1196        };
1197
1198        if self.is_running_lnv1() {
1199            Self::check_lnv1_federation_network(&client, self.network).await?;
1200            client
1201                .get_first_module::<GatewayClientModule>()?
1202                .try_register_with_federation(
1203                    // Route hints will be updated in the background
1204                    Vec::new(),
1205                    GW_ANNOUNCEMENT_TTL,
1206                    federation_config.lightning_fee.into(),
1207                    lightning_context,
1208                    self.versioned_api.clone(),
1209                    self.gateway_id,
1210                )
1211                .await;
1212        }
1213
1214        if self.is_running_lnv2() {
1215            Self::check_lnv2_federation_network(&client, self.network).await?;
1216        }
1217
1218        // no need to enter span earlier, because connect-fed has a span
1219        federation_manager.add_client(
1220            federation_index,
1221            Spanned::new(
1222                info_span!(target: LOG_GATEWAY, "client", federation_id=%federation_id.clone()),
1223                async { client },
1224            )
1225            .await,
1226        );
1227
1228        let mut dbtx = self.gateway_db.begin_transaction().await;
1229        dbtx.save_federation_config(&federation_config).await;
1230        dbtx.commit_tx().await;
1231        debug!(
1232            target: LOG_GATEWAY,
1233            federation_id = %federation_id,
1234            federation_index = %federation_index,
1235            "Federation connected"
1236        );
1237
1238        Ok(federation_info)
1239    }
1240
1241    /// Handle a request to have the Gateway leave a federation. The Gateway
1242    /// will request the federation to remove the registration record and
1243    /// the gateway will remove the configuration needed to construct the
1244    /// federation client.
1245    pub async fn handle_leave_federation(
1246        &self,
1247        payload: LeaveFedPayload,
1248    ) -> AdminResult<FederationInfo> {
1249        // Lock the federation manager before starting the db transaction to reduce the
1250        // chance of db write conflicts.
1251        let mut federation_manager = self.federation_manager.write().await;
1252        let mut dbtx = self.gateway_db.begin_transaction().await;
1253
1254        let federation_info = federation_manager
1255            .leave_federation(payload.federation_id, &mut dbtx.to_ref_nc())
1256            .await?;
1257
1258        dbtx.remove_federation_config(payload.federation_id).await;
1259        dbtx.commit_tx().await;
1260        Ok(federation_info)
1261    }
1262
1263    /// Handles a request for the gateway to backup a connected federation's
1264    /// ecash.
1265    pub async fn handle_backup_msg(
1266        &self,
1267        BackupPayload { federation_id }: BackupPayload,
1268    ) -> AdminResult<()> {
1269        let federation_manager = self.federation_manager.read().await;
1270        let client = federation_manager
1271            .client(&federation_id)
1272            .ok_or(AdminGatewayError::ClientCreationError(anyhow::anyhow!(
1273                format!("Gateway has not connected to {federation_id}")
1274            )))?
1275            .value();
1276        let metadata: BTreeMap<String, String> = BTreeMap::new();
1277        client
1278            .backup_to_federation(fedimint_client::backup::Metadata::from_json_serialized(
1279                metadata,
1280            ))
1281            .await?;
1282        Ok(())
1283    }
1284
1285    /// Handles an authenticated request for the gateway's mnemonic. This also
1286    /// returns a vector of federations that are not using the mnemonic
1287    /// backup strategy.
1288    pub async fn handle_mnemonic_msg(&self) -> AdminResult<MnemonicResponse> {
1289        let words = self
1290            .mnemonic
1291            .words()
1292            .map(std::string::ToString::to_string)
1293            .collect::<Vec<_>>();
1294        let all_federations = self
1295            .federation_manager
1296            .read()
1297            .await
1298            .get_all_federation_configs()
1299            .await
1300            .keys()
1301            .copied()
1302            .collect::<BTreeSet<_>>();
1303        let legacy_federations = self.client_builder.legacy_federations(all_federations);
1304        let mnemonic_response = MnemonicResponse {
1305            mnemonic: words,
1306            legacy_federations,
1307        };
1308        Ok(mnemonic_response)
1309    }
1310
1311    /// Handles a request to change the lightning or transaction fees for all
1312    /// federations or a federation specified by the `FederationId`.
1313    pub async fn handle_set_fees_msg(
1314        &self,
1315        SetFeesPayload {
1316            federation_id,
1317            lightning_base,
1318            lightning_parts_per_million,
1319            transaction_base,
1320            transaction_parts_per_million,
1321        }: SetFeesPayload,
1322    ) -> AdminResult<()> {
1323        let mut dbtx = self.gateway_db.begin_transaction().await;
1324        let mut fed_configs = if let Some(fed_id) = federation_id {
1325            dbtx.load_federation_configs()
1326                .await
1327                .into_iter()
1328                .filter(|(id, _)| *id == fed_id)
1329                .collect::<BTreeMap<_, _>>()
1330        } else {
1331            dbtx.load_federation_configs().await
1332        };
1333
1334        for config in &mut fed_configs.values_mut() {
1335            let mut lightning_fee = config.lightning_fee;
1336            if let Some(lightning_base) = lightning_base {
1337                lightning_fee.base = lightning_base;
1338            }
1339
1340            if let Some(lightning_ppm) = lightning_parts_per_million {
1341                lightning_fee.parts_per_million = lightning_ppm;
1342            }
1343
1344            let mut transaction_fee = config.transaction_fee;
1345            if let Some(transaction_base) = transaction_base {
1346                transaction_fee.base = transaction_base;
1347            }
1348
1349            if let Some(transaction_ppm) = transaction_parts_per_million {
1350                transaction_fee.parts_per_million = transaction_ppm;
1351            }
1352
1353            // Check if the lightning fee + transaction fee is higher than the send limit
1354            let send_fees = lightning_fee + transaction_fee;
1355            if !self.is_running_lnv1() && send_fees.gt(&PaymentFee::SEND_FEE_LIMIT) {
1356                return Err(AdminGatewayError::GatewayConfigurationError(format!(
1357                    "Total Send fees exceeded {}",
1358                    PaymentFee::SEND_FEE_LIMIT
1359                )));
1360            }
1361
1362            // Check if the transaction fee is higher than the receive limit
1363            if !self.is_running_lnv1() && transaction_fee.gt(&PaymentFee::RECEIVE_FEE_LIMIT) {
1364                return Err(AdminGatewayError::GatewayConfigurationError(format!(
1365                    "Transaction fees exceeded RECEIVE LIMIT {}",
1366                    PaymentFee::RECEIVE_FEE_LIMIT
1367                )));
1368            }
1369
1370            config.lightning_fee = lightning_fee;
1371            config.transaction_fee = transaction_fee;
1372            dbtx.save_federation_config(config).await;
1373        }
1374
1375        dbtx.commit_tx().await;
1376
1377        if self.is_running_lnv1() {
1378            let register_task_group = TaskGroup::new();
1379
1380            self.register_federations(&fed_configs, &register_task_group)
1381                .await;
1382        }
1383
1384        Ok(())
1385    }
1386
1387    /// Generates an onchain address to fund the gateway's lightning node.
1388    pub async fn handle_get_ln_onchain_address_msg(&self) -> AdminResult<Address> {
1389        let context = self.get_lightning_context().await?;
1390        let response = context.lnrpc.get_ln_onchain_address().await?;
1391
1392        let address = Address::from_str(&response.address).map_err(|e| {
1393            AdminGatewayError::Lightning(LightningRpcError::InvalidMetadata {
1394                failure_reason: e.to_string(),
1395            })
1396        })?;
1397
1398        address.require_network(self.network).map_err(|e| {
1399            AdminGatewayError::Lightning(LightningRpcError::InvalidMetadata {
1400                failure_reason: e.to_string(),
1401            })
1402        })
1403    }
1404
1405    /// Instructs the Gateway's Lightning node to open a channel to a peer
1406    /// specified by `pubkey`.
1407    pub async fn handle_open_channel_msg(&self, payload: OpenChannelRequest) -> AdminResult<Txid> {
1408        info!(target: LOG_GATEWAY, pubkey = %payload.pubkey, host = %payload.host, amount = %payload.channel_size_sats, "Opening Lightning channel...");
1409        let context = self.get_lightning_context().await?;
1410        let res = context.lnrpc.open_channel(payload).await?;
1411        info!(target: LOG_GATEWAY, txid = %res.funding_txid, "Initiated channel open");
1412        Txid::from_str(&res.funding_txid).map_err(|e| {
1413            AdminGatewayError::Lightning(LightningRpcError::InvalidMetadata {
1414                failure_reason: format!("Received invalid channel funding txid string {e}"),
1415            })
1416        })
1417    }
1418
1419    /// Instructs the Gateway's Lightning node to close all channels with a peer
1420    /// specified by `pubkey`.
1421    pub async fn handle_close_channels_with_peer_msg(
1422        &self,
1423        payload: CloseChannelsWithPeerRequest,
1424    ) -> AdminResult<CloseChannelsWithPeerResponse> {
1425        let context = self.get_lightning_context().await?;
1426        let response = context.lnrpc.close_channels_with_peer(payload).await?;
1427        Ok(response)
1428    }
1429
1430    /// Returns a list of Lightning network channels from the Gateway's
1431    /// Lightning node.
1432    pub async fn handle_list_channels_msg(
1433        &self,
1434    ) -> AdminResult<Vec<fedimint_gateway_common::ChannelInfo>> {
1435        let context = self.get_lightning_context().await?;
1436        let response = context.lnrpc.list_channels().await?;
1437        Ok(response.channels)
1438    }
1439
1440    /// Send funds from the gateway's lightning node on-chain wallet.
1441    pub async fn handle_send_onchain_msg(&self, payload: SendOnchainRequest) -> AdminResult<Txid> {
1442        let context = self.get_lightning_context().await?;
1443        let response = context.lnrpc.send_onchain(payload).await?;
1444        Txid::from_str(&response.txid).map_err(|e| AdminGatewayError::WithdrawError {
1445            failure_reason: format!("Failed to parse withdrawal TXID: {e}"),
1446        })
1447    }
1448
1449    /// Trigger rechecking for deposits on an address
1450    pub async fn handle_recheck_address_msg(
1451        &self,
1452        payload: DepositAddressRecheckPayload,
1453    ) -> AdminResult<()> {
1454        self.select_client(payload.federation_id)
1455            .await?
1456            .value()
1457            .get_first_module::<WalletClientModule>()
1458            .expect("Must have client module")
1459            .recheck_pegin_address_by_address(payload.address)
1460            .await?;
1461        Ok(())
1462    }
1463
1464    /// Returns the ecash, lightning, and onchain balances for the gateway and
1465    /// the gateway's lightning node.
1466    pub async fn handle_get_balances_msg(&self) -> AdminResult<GatewayBalances> {
1467        let dbtx = self.gateway_db.begin_transaction_nc().await;
1468        let federation_infos = self
1469            .federation_manager
1470            .read()
1471            .await
1472            .federation_info_all_federations(dbtx)
1473            .await;
1474
1475        let ecash_balances: Vec<FederationBalanceInfo> = federation_infos
1476            .iter()
1477            .map(|federation_info| FederationBalanceInfo {
1478                federation_id: federation_info.federation_id,
1479                ecash_balance_msats: Amount {
1480                    msats: federation_info.balance_msat.msats,
1481                },
1482            })
1483            .collect();
1484
1485        let context = self.get_lightning_context().await?;
1486        let lightning_node_balances = context.lnrpc.get_balances().await?;
1487
1488        Ok(GatewayBalances {
1489            onchain_balance_sats: lightning_node_balances.onchain_balance_sats,
1490            lightning_balance_msats: lightning_node_balances.lightning_balance_msats,
1491            ecash_balances,
1492            inbound_lightning_liquidity_msats: lightning_node_balances
1493                .inbound_lightning_liquidity_msats,
1494        })
1495    }
1496
1497    // Handles a request the spend the gateway's ecash for a given federation.
1498    pub async fn handle_spend_ecash_msg(
1499        &self,
1500        payload: SpendEcashPayload,
1501    ) -> AdminResult<SpendEcashResponse> {
1502        let client = self
1503            .select_client(payload.federation_id)
1504            .await?
1505            .into_value();
1506        let mint_module = client.get_first_module::<MintClientModule>()?;
1507        let timeout = Duration::from_secs(payload.timeout);
1508        let (operation_id, notes) = if payload.allow_overpay {
1509            let (operation_id, notes) = mint_module
1510                .spend_notes_with_selector(
1511                    &SelectNotesWithAtleastAmount,
1512                    payload.amount,
1513                    timeout,
1514                    payload.include_invite,
1515                    (),
1516                )
1517                .await?;
1518
1519            let overspend_amount = notes.total_amount().saturating_sub(payload.amount);
1520            if overspend_amount != Amount::ZERO {
1521                warn!(
1522                    target: LOG_GATEWAY,
1523                    overspend_amount = %overspend_amount,
1524                    "Selected notes worth more than requested",
1525                );
1526            }
1527
1528            (operation_id, notes)
1529        } else {
1530            mint_module
1531                .spend_notes_with_selector(
1532                    &SelectNotesWithExactAmount,
1533                    payload.amount,
1534                    timeout,
1535                    payload.include_invite,
1536                    (),
1537                )
1538                .await?
1539        };
1540
1541        debug!(target: LOG_GATEWAY, ?operation_id, ?notes, "Spend ecash notes");
1542
1543        Ok(SpendEcashResponse {
1544            operation_id,
1545            notes,
1546        })
1547    }
1548
1549    /// Handles a request to receive ecash into the gateway.
1550    pub async fn handle_receive_ecash_msg(
1551        &self,
1552        payload: ReceiveEcashPayload,
1553    ) -> Result<ReceiveEcashResponse> {
1554        let amount = payload.notes.total_amount();
1555        let client = self
1556            .federation_manager
1557            .read()
1558            .await
1559            .get_client_for_federation_id_prefix(payload.notes.federation_id_prefix())
1560            .ok_or(FederationNotConnected {
1561                federation_id_prefix: payload.notes.federation_id_prefix(),
1562            })?;
1563        let mint = client
1564            .value()
1565            .get_first_module::<MintClientModule>()
1566            .map_err(|e| PublicGatewayError::ReceiveEcashError {
1567                failure_reason: format!("Mint module does not exist: {e:?}"),
1568            })?;
1569
1570        let operation_id = mint
1571            .reissue_external_notes(payload.notes, ())
1572            .await
1573            .map_err(|e| PublicGatewayError::ReceiveEcashError {
1574                failure_reason: e.to_string(),
1575            })?;
1576        if payload.wait {
1577            let mut updates = mint
1578                .subscribe_reissue_external_notes(operation_id)
1579                .await
1580                .unwrap()
1581                .into_stream();
1582
1583            while let Some(update) = updates.next().await {
1584                if let fedimint_mint_client::ReissueExternalNotesState::Failed(e) = update {
1585                    return Err(PublicGatewayError::ReceiveEcashError {
1586                        failure_reason: e.to_string(),
1587                    });
1588                }
1589            }
1590        }
1591
1592        Ok(ReceiveEcashResponse { amount })
1593    }
1594
1595    /// Instructs the gateway to shutdown, but only after all incoming payments
1596    /// have been handlded.
1597    pub async fn handle_shutdown_msg(&self, task_group: TaskGroup) -> AdminResult<()> {
1598        // Take the write lock on the state so that no additional payments are processed
1599        let mut state_guard = self.state.write().await;
1600        if let GatewayState::Running { lightning_context } = state_guard.clone() {
1601            *state_guard = GatewayState::ShuttingDown { lightning_context };
1602
1603            self.federation_manager
1604                .read()
1605                .await
1606                .wait_for_incoming_payments()
1607                .await?;
1608        }
1609
1610        let tg = task_group.clone();
1611        tg.spawn("Kill Gateway", |_task_handle| async {
1612            if let Err(err) = task_group.shutdown_join_all(Duration::from_secs(180)).await {
1613                warn!(target: LOG_GATEWAY, err = %err.fmt_compact_anyhow(), "Error shutting down gateway");
1614            }
1615        });
1616        Ok(())
1617    }
1618
1619    /// Queries the client log for payment events and returns to the user.
1620    pub async fn handle_payment_log_msg(
1621        &self,
1622        PaymentLogPayload {
1623            end_position,
1624            pagination_size,
1625            federation_id,
1626            event_kinds,
1627        }: PaymentLogPayload,
1628    ) -> AdminResult<PaymentLogResponse> {
1629        const BATCH_SIZE: u64 = 10_000;
1630        let federation_manager = self.federation_manager.read().await;
1631        let client = federation_manager
1632            .client(&federation_id)
1633            .ok_or(FederationNotConnected {
1634                federation_id_prefix: federation_id.to_prefix(),
1635            })?
1636            .value();
1637
1638        let event_kinds = if event_kinds.is_empty() {
1639            ALL_GATEWAY_EVENTS.to_vec()
1640        } else {
1641            event_kinds
1642        };
1643
1644        let end_position = if let Some(position) = end_position {
1645            position
1646        } else {
1647            let mut dbtx = client.db().begin_transaction_nc().await;
1648            dbtx.get_next_event_log_id().await
1649        };
1650
1651        let mut start_position = end_position.saturating_sub(BATCH_SIZE);
1652
1653        let mut payment_log = Vec::new();
1654
1655        while payment_log.len() < pagination_size {
1656            let batch = client.get_event_log(Some(start_position), BATCH_SIZE).await;
1657            let mut filtered_batch = batch
1658                .into_iter()
1659                .filter(|e| e.event_id <= end_position && event_kinds.contains(&e.event_kind))
1660                .collect::<Vec<_>>();
1661            filtered_batch.reverse();
1662            payment_log.extend(filtered_batch);
1663
1664            // Compute the start position for the next batch query
1665            start_position = start_position.saturating_sub(BATCH_SIZE);
1666
1667            if start_position == EventLogId::LOG_START {
1668                break;
1669            }
1670        }
1671
1672        // Truncate the payment log to the expected pagination size
1673        payment_log.truncate(pagination_size);
1674
1675        Ok(PaymentLogResponse(payment_log))
1676    }
1677
1678    /// Computes the 24 hour payment summary statistics for this gateway.
1679    /// Combines the LNv1 and LNv2 stats together.
1680    pub async fn handle_payment_summary_msg(
1681        &self,
1682        PaymentSummaryPayload {
1683            start_millis,
1684            end_millis,
1685        }: PaymentSummaryPayload,
1686    ) -> AdminResult<PaymentSummaryResponse> {
1687        let federation_manager = self.federation_manager.read().await;
1688        let fed_configs = federation_manager.get_all_federation_configs().await;
1689        let federation_ids = fed_configs.keys().collect::<Vec<_>>();
1690        let start = UNIX_EPOCH + Duration::from_millis(start_millis);
1691        let end = UNIX_EPOCH + Duration::from_millis(end_millis);
1692
1693        if start > end {
1694            return Err(AdminGatewayError::Unexpected(anyhow!("Invalid time range")));
1695        }
1696
1697        let mut outgoing = StructuredPaymentEvents::default();
1698        let mut incoming = StructuredPaymentEvents::default();
1699        for fed_id in federation_ids {
1700            let client = federation_manager
1701                .client(fed_id)
1702                .expect("No client available")
1703                .value();
1704            let all_events = &get_events_for_duration(client, start, end).await;
1705
1706            if self.is_running_lnv1() && self.is_running_lnv2() {
1707                let (mut lnv1_outgoing, mut lnv1_incoming) = compute_lnv1_stats(all_events);
1708                let (mut lnv2_outgoing, mut lnv2_incoming) = compute_lnv2_stats(all_events);
1709                outgoing.combine(&mut lnv1_outgoing);
1710                incoming.combine(&mut lnv1_incoming);
1711                outgoing.combine(&mut lnv2_outgoing);
1712                incoming.combine(&mut lnv2_incoming);
1713            } else if self.is_running_lnv1() {
1714                let (mut lnv1_outgoing, mut lnv1_incoming) = compute_lnv1_stats(all_events);
1715                outgoing.combine(&mut lnv1_outgoing);
1716                incoming.combine(&mut lnv1_incoming);
1717            } else {
1718                let (mut lnv2_outgoing, mut lnv2_incoming) = compute_lnv2_stats(all_events);
1719                outgoing.combine(&mut lnv2_outgoing);
1720                incoming.combine(&mut lnv2_incoming);
1721            }
1722        }
1723
1724        Ok(PaymentSummaryResponse {
1725            outgoing: PaymentStats::compute(&outgoing),
1726            incoming: PaymentStats::compute(&incoming),
1727        })
1728    }
1729
1730    /// Retrieves an invoice by the payment hash if it exists, otherwise returns
1731    /// `None`.
1732    pub async fn handle_get_invoice_msg(
1733        &self,
1734        payload: GetInvoiceRequest,
1735    ) -> AdminResult<Option<GetInvoiceResponse>> {
1736        let lightning_context = self.get_lightning_context().await?;
1737        let invoice = lightning_context.lnrpc.get_invoice(payload).await?;
1738        Ok(invoice)
1739    }
1740
1741    pub async fn handle_list_transactions_msg(
1742        &self,
1743        payload: ListTransactionsPayload,
1744    ) -> AdminResult<ListTransactionsResponse> {
1745        let lightning_context = self.get_lightning_context().await?;
1746        let response = lightning_context
1747            .lnrpc
1748            .list_transactions(payload.start_secs, payload.end_secs)
1749            .await?;
1750        Ok(response)
1751    }
1752
1753    /// Creates a BOLT12 offer using the gateway's lightning node
1754    pub async fn handle_create_offer_for_operator_msg(
1755        &self,
1756        payload: CreateOfferPayload,
1757    ) -> AdminResult<CreateOfferResponse> {
1758        let lightning_context = self.get_lightning_context().await?;
1759        let offer = lightning_context.lnrpc.create_offer(
1760            payload.amount,
1761            payload.description,
1762            payload.expiry_secs,
1763            payload.quantity,
1764        )?;
1765        Ok(CreateOfferResponse { offer })
1766    }
1767
1768    /// Pays a BOLT12 offer using the gateway's lightning node
1769    pub async fn handle_pay_offer_for_operator_msg(
1770        &self,
1771        payload: PayOfferPayload,
1772    ) -> AdminResult<PayOfferResponse> {
1773        let lightning_context = self.get_lightning_context().await?;
1774        let preimage = lightning_context
1775            .lnrpc
1776            .pay_offer(
1777                payload.offer,
1778                payload.quantity,
1779                payload.amount,
1780                payload.payer_note,
1781            )
1782            .await?;
1783        Ok(PayOfferResponse {
1784            preimage: preimage.to_string(),
1785        })
1786    }
1787
1788    /// Registers the gateway with each specified federation.
1789    async fn register_federations(
1790        &self,
1791        federations: &BTreeMap<FederationId, FederationConfig>,
1792        register_task_group: &TaskGroup,
1793    ) {
1794        if let Ok(lightning_context) = self.get_lightning_context().await {
1795            let route_hints = lightning_context
1796                .lnrpc
1797                .parsed_route_hints(self.num_route_hints)
1798                .await;
1799            if route_hints.is_empty() {
1800                warn!(target: LOG_GATEWAY, "Gateway did not retrieve any route hints, may reduce receive success rate.");
1801            }
1802
1803            for (federation_id, federation_config) in federations {
1804                let fed_manager = self.federation_manager.read().await;
1805                if let Some(client) = fed_manager.client(federation_id) {
1806                    let client_arc = client.clone().into_value();
1807                    let route_hints = route_hints.clone();
1808                    let lightning_context = lightning_context.clone();
1809                    let federation_config = federation_config.clone();
1810                    let api = self.versioned_api.clone();
1811                    let gateway_id = self.gateway_id;
1812
1813                    if let Err(err) = register_task_group
1814                        .spawn_cancellable("register_federation", async move {
1815                            let gateway_client = client_arc
1816                                .get_first_module::<GatewayClientModule>()
1817                                .expect("No GatewayClientModule exists");
1818                            gateway_client
1819                                .try_register_with_federation(
1820                                    route_hints,
1821                                    GW_ANNOUNCEMENT_TTL,
1822                                    federation_config.lightning_fee.into(),
1823                                    lightning_context,
1824                                    api,
1825                                    gateway_id,
1826                                )
1827                                .await;
1828                        })
1829                        .await
1830                    {
1831                        warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Failed to shutdown register federation task");
1832                    }
1833                }
1834            }
1835        }
1836    }
1837
1838    /// Retrieves a `ClientHandleArc` from the Gateway's in memory structures
1839    /// that keep track of available clients, given a `federation_id`.
1840    pub async fn select_client(
1841        &self,
1842        federation_id: FederationId,
1843    ) -> std::result::Result<Spanned<fedimint_client::ClientHandleArc>, FederationNotConnected>
1844    {
1845        self.federation_manager
1846            .read()
1847            .await
1848            .client(&federation_id)
1849            .cloned()
1850            .ok_or(FederationNotConnected {
1851                federation_id_prefix: federation_id.to_prefix(),
1852            })
1853    }
1854
1855    /// Loads a mnemonic from the database or generates a new one if the
1856    /// mnemonic does not exist. Before generating a new mnemonic, this
1857    /// function will check if a mnemonic has been provided in the environment
1858    /// variable and use that if provided.
1859    async fn load_or_generate_mnemonic(gateway_db: &Database) -> AdminResult<Mnemonic> {
1860        Ok(
1861            if let Ok(entropy) = Client::load_decodable_client_secret::<Vec<u8>>(gateway_db).await {
1862                Mnemonic::from_entropy(&entropy)
1863                    .map_err(|e| AdminGatewayError::MnemonicError(anyhow!(e.to_string())))?
1864            } else {
1865                let mnemonic = if let Ok(words) = std::env::var(FM_GATEWAY_MNEMONIC_ENV) {
1866                    info!(target: LOG_GATEWAY, "Using provided mnemonic from environment variable");
1867                    Mnemonic::parse_in_normalized(Language::English, words.as_str()).map_err(
1868                        |e| {
1869                            AdminGatewayError::MnemonicError(anyhow!(format!(
1870                                "Seed phrase provided in environment was invalid {e:?}"
1871                            )))
1872                        },
1873                    )?
1874                } else {
1875                    debug!(target: LOG_GATEWAY, "Generating mnemonic and writing entropy to client storage");
1876                    Bip39RootSecretStrategy::<12>::random(&mut thread_rng())
1877                };
1878
1879                Client::store_encodable_client_secret(gateway_db, mnemonic.to_entropy())
1880                    .await
1881                    .map_err(AdminGatewayError::MnemonicError)?;
1882                mnemonic
1883            },
1884        )
1885    }
1886
1887    /// Reads the connected federation client configs from the Gateway's
1888    /// database and reconstructs the clients necessary for interacting with
1889    /// connection federations.
1890    async fn load_clients(&self) -> AdminResult<()> {
1891        let mut federation_manager = self.federation_manager.write().await;
1892
1893        let configs = {
1894            let mut dbtx = self.gateway_db.begin_transaction_nc().await;
1895            dbtx.load_federation_configs().await
1896        };
1897
1898        if let Some(max_federation_index) = configs.values().map(|cfg| cfg.federation_index).max() {
1899            federation_manager.set_next_index(max_federation_index + 1);
1900        }
1901
1902        for (federation_id, config) in configs {
1903            let federation_index = config.federation_index;
1904            match Box::pin(Spanned::try_new(
1905                info_span!(target: LOG_GATEWAY, "client", federation_id  = %federation_id.clone()),
1906                self.client_builder
1907                    .build(config, Arc::new(self.clone()), &self.mnemonic),
1908            ))
1909            .await
1910            {
1911                Ok(client) => {
1912                    federation_manager.add_client(federation_index, client);
1913                }
1914                _ => {
1915                    warn!(target: LOG_GATEWAY, federation_id = %federation_id, "Failed to load client");
1916                }
1917            }
1918        }
1919
1920        Ok(())
1921    }
1922
1923    /// Legacy mechanism for registering the Gateway with connected federations.
1924    /// This will spawn a task that will re-register the Gateway with
1925    /// connected federations every 8.5 mins. Only registers the Gateway if it
1926    /// has successfully connected to the Lightning node, so that it can
1927    /// include route hints in the registration.
1928    fn register_clients_timer(&self) {
1929        // Only spawn background registration thread if gateway is running LNv1
1930        if self.is_running_lnv1() {
1931            let lightning_module_mode = self.lightning_module_mode;
1932            info!(target: LOG_GATEWAY, lightning_module_mode = %lightning_module_mode, "Spawning register task...");
1933            let gateway = self.clone();
1934            let register_task_group = self.task_group.make_subgroup();
1935            self.task_group.spawn_cancellable("register clients", async move {
1936                loop {
1937                    let gateway_state = gateway.get_state().await;
1938                    if let GatewayState::Running { .. } = &gateway_state {
1939                        let mut dbtx = gateway.gateway_db.begin_transaction_nc().await;
1940                        let all_federations_configs = dbtx.load_federation_configs().await.into_iter().collect();
1941                        gateway.register_federations(&all_federations_configs, &register_task_group).await;
1942                    } else {
1943                        // We need to retry more often if the gateway is not in the Running state
1944                        const NOT_RUNNING_RETRY: Duration = Duration::from_secs(10);
1945                        warn!(target: LOG_GATEWAY, gateway_state = %gateway_state, retry_interval = ?NOT_RUNNING_RETRY, "Will not register federation yet because gateway still not in Running state");
1946                        sleep(NOT_RUNNING_RETRY).await;
1947                        continue;
1948                    }
1949
1950                    // Allow a 15% buffer of the TTL before the re-registering gateway
1951                    // with the federations.
1952                    sleep(GW_ANNOUNCEMENT_TTL.mul_f32(0.85)).await;
1953                }
1954            });
1955        }
1956    }
1957
1958    /// Verifies that the supplied `network` matches the Bitcoin network in the
1959    /// connected client's LNv1 configuration.
1960    async fn check_lnv1_federation_network(
1961        client: &ClientHandleArc,
1962        network: Network,
1963    ) -> AdminResult<()> {
1964        let federation_id = client.federation_id();
1965        let config = client.config().await;
1966        let cfg = config
1967            .modules
1968            .values()
1969            .find(|m| LightningCommonInit::KIND == m.kind)
1970            .ok_or(AdminGatewayError::ClientCreationError(anyhow!(format!(
1971                "Federation {federation_id} does not have an LNv1 module"
1972            ))))?;
1973        let ln_cfg: &LightningClientConfig = cfg.cast()?;
1974
1975        if ln_cfg.network.0 != network {
1976            crit!(
1977                target: LOG_GATEWAY,
1978                federation_id = %federation_id,
1979                network = %network,
1980                "Incorrect network for federation",
1981            );
1982            return Err(AdminGatewayError::ClientCreationError(anyhow!(format!(
1983                "Unsupported network {}",
1984                ln_cfg.network
1985            ))));
1986        }
1987
1988        Ok(())
1989    }
1990
1991    /// Verifies that the supplied `network` matches the Bitcoin network in the
1992    /// connected client's LNv2 configuration.
1993    async fn check_lnv2_federation_network(
1994        client: &ClientHandleArc,
1995        network: Network,
1996    ) -> AdminResult<()> {
1997        let federation_id = client.federation_id();
1998        let config = client.config().await;
1999        let cfg = config
2000            .modules
2001            .values()
2002            .find(|m| fedimint_lnv2_common::LightningCommonInit::KIND == m.kind)
2003            .ok_or(AdminGatewayError::ClientCreationError(anyhow!(format!(
2004                "Federation {federation_id} does not have an LNv2 module"
2005            ))))?;
2006        let ln_cfg: &fedimint_lnv2_common::config::LightningClientConfig = cfg.cast()?;
2007
2008        if ln_cfg.network != network {
2009            crit!(
2010                target: LOG_GATEWAY,
2011                federation_id = %federation_id,
2012                network = %network,
2013                "Incorrect network for federation",
2014            );
2015            return Err(AdminGatewayError::ClientCreationError(anyhow!(format!(
2016                "Unsupported network {}",
2017                ln_cfg.network
2018            ))));
2019        }
2020
2021        Ok(())
2022    }
2023
2024    /// Checks the Gateway's current state and returns the proper
2025    /// `LightningContext` if it is available. Sometimes the lightning node
2026    /// will not be connected and this will return an error.
2027    pub async fn get_lightning_context(
2028        &self,
2029    ) -> std::result::Result<LightningContext, LightningRpcError> {
2030        match self.get_state().await {
2031            GatewayState::Running { lightning_context }
2032            | GatewayState::ShuttingDown { lightning_context } => Ok(lightning_context),
2033            _ => Err(LightningRpcError::FailedToConnect),
2034        }
2035    }
2036
2037    /// Iterates through all of the federations the gateway is registered with
2038    /// and requests to remove the registration record.
2039    pub async fn unannounce_from_all_federations(&self) {
2040        if self.is_running_lnv1() {
2041            let mut dbtx = self.gateway_db.begin_transaction_nc().await;
2042            let gateway_keypair = dbtx.load_gateway_keypair_assert_exists().await;
2043
2044            self.federation_manager
2045                .read()
2046                .await
2047                .unannounce_from_all_federations(gateway_keypair)
2048                .await;
2049        }
2050    }
2051
2052    fn create_lightning_client(
2053        &self,
2054        runtime: Arc<tokio::runtime::Runtime>,
2055    ) -> Box<dyn ILnRpcClient> {
2056        match self.lightning_mode.clone() {
2057            LightningMode::Lnd {
2058                lnd_rpc_addr,
2059                lnd_tls_cert,
2060                lnd_macaroon,
2061            } => Box::new(GatewayLndClient::new(
2062                lnd_rpc_addr,
2063                lnd_tls_cert,
2064                lnd_macaroon,
2065                None,
2066            )),
2067            LightningMode::Ldk {
2068                esplora_server_url,
2069                bitcoind_rpc_url,
2070                network,
2071                lightning_port,
2072                alias,
2073            } => {
2074                let chain_source_config = {
2075                    match (esplora_server_url, bitcoind_rpc_url) {
2076                        (Some(esplora_server_url), None) => GatewayLdkChainSourceConfig::Esplora {
2077                            server_url: SafeUrl::parse(&esplora_server_url.clone())
2078                                .expect("Could not parse esplora server url"),
2079                        },
2080                        (None, Some(bitcoind_rpc_url)) => GatewayLdkChainSourceConfig::Bitcoind {
2081                            server_url: SafeUrl::parse(&bitcoind_rpc_url.clone())
2082                                .expect("Could not parse bitcoind rpc url"),
2083                        },
2084                        (None, None) => {
2085                            info!("No chain source URL provided, defaulting to esplora...");
2086                            GatewayLdkChainSourceConfig::Esplora {
2087                                server_url: default_esplora_server(
2088                                    self.network,
2089                                    std::env::var(FM_PORT_ESPLORA_ENV).ok(),
2090                                )
2091                                .url,
2092                            }
2093                        }
2094                        (Some(_), Some(bitcoind_rpc_url)) => {
2095                            warn!(
2096                                "Esplora and bitcoind connection parameters are both set, using bitcoind..."
2097                            );
2098                            GatewayLdkChainSourceConfig::Bitcoind {
2099                                server_url: SafeUrl::parse(&bitcoind_rpc_url.clone())
2100                                    .expect("Could not parse bitcoind rpc url"),
2101                            }
2102                        }
2103                    }
2104                };
2105
2106                Box::new(
2107                    ldk::GatewayLdkClient::new(
2108                        &self.client_builder.data_dir().join(LDK_NODE_DB_FOLDER),
2109                        chain_source_config,
2110                        network,
2111                        lightning_port,
2112                        alias,
2113                        self.mnemonic.clone(),
2114                        runtime,
2115                    )
2116                    .expect("Failed to create LDK client"),
2117                )
2118            }
2119        }
2120    }
2121}
2122
2123// LNv2 Gateway implementation
2124impl Gateway {
2125    /// Retrieves the `PublicKey` of the Gateway module for a given federation
2126    /// for LNv2. This is NOT the same as the `gateway_id`, it is different
2127    /// per-connected federation.
2128    async fn public_key_v2(&self, federation_id: &FederationId) -> Option<PublicKey> {
2129        self.federation_manager
2130            .read()
2131            .await
2132            .client(federation_id)
2133            .map(|client| {
2134                client
2135                    .value()
2136                    .get_first_module::<GatewayClientModuleV2>()
2137                    .expect("Must have client module")
2138                    .keypair
2139                    .public_key()
2140            })
2141    }
2142
2143    /// Returns payment information that LNv2 clients can use to instruct this
2144    /// Gateway to pay an invoice or receive a payment.
2145    pub async fn routing_info_v2(
2146        &self,
2147        federation_id: &FederationId,
2148    ) -> Result<Option<RoutingInfo>> {
2149        let context = self.get_lightning_context().await?;
2150
2151        let mut dbtx = self.gateway_db.begin_transaction_nc().await;
2152        let fed_config = dbtx.load_federation_config(*federation_id).await.ok_or(
2153            PublicGatewayError::FederationNotConnected(FederationNotConnected {
2154                federation_id_prefix: federation_id.to_prefix(),
2155            }),
2156        )?;
2157
2158        let lightning_fee = fed_config.lightning_fee;
2159        let transaction_fee = fed_config.transaction_fee;
2160
2161        Ok(self
2162            .public_key_v2(federation_id)
2163            .await
2164            .map(|module_public_key| RoutingInfo {
2165                lightning_public_key: context.lightning_public_key,
2166                module_public_key,
2167                send_fee_default: lightning_fee + transaction_fee,
2168                // The base fee ensures that the gateway does not loose sats sending the payment due
2169                // to fees paid on the transaction claiming the outgoing contract or
2170                // subsequent transactions spending the newly issued ecash
2171                send_fee_minimum: transaction_fee,
2172                expiration_delta_default: 1440,
2173                expiration_delta_minimum: EXPIRATION_DELTA_MINIMUM_V2,
2174                // The base fee ensures that the gateway does not loose sats receiving the payment
2175                // due to fees paid on the transaction funding the incoming contract
2176                receive_fee: transaction_fee,
2177            }))
2178    }
2179
2180    /// Instructs this gateway to pay a Lightning network invoice via the LNv2
2181    /// protocol.
2182    async fn send_payment_v2(
2183        &self,
2184        payload: SendPaymentPayload,
2185    ) -> Result<std::result::Result<[u8; 32], Signature>> {
2186        self.select_client(payload.federation_id)
2187            .await?
2188            .value()
2189            .get_first_module::<GatewayClientModuleV2>()
2190            .expect("Must have client module")
2191            .send_payment(payload)
2192            .await
2193            .map_err(LNv2Error::OutgoingPayment)
2194            .map_err(PublicGatewayError::LNv2)
2195    }
2196
2197    /// For the LNv2 protocol, this will create an invoice by fetching it from
2198    /// the connected Lightning node, then save the payment hash so that
2199    /// incoming lightning payments can be matched as a receive attempt to a
2200    /// specific federation.
2201    async fn create_bolt11_invoice_v2(
2202        &self,
2203        payload: CreateBolt11InvoicePayload,
2204    ) -> Result<Bolt11Invoice> {
2205        if !payload.contract.verify() {
2206            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2207                "The contract is invalid".to_string(),
2208            )));
2209        }
2210
2211        let payment_info = self.routing_info_v2(&payload.federation_id).await?.ok_or(
2212            LNv2Error::IncomingPayment(format!(
2213                "Federation {} does not exist",
2214                payload.federation_id
2215            )),
2216        )?;
2217
2218        if payload.contract.commitment.refund_pk != payment_info.module_public_key {
2219            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2220                "The incoming contract is keyed to another gateway".to_string(),
2221            )));
2222        }
2223
2224        let contract_amount = payment_info.receive_fee.subtract_from(payload.amount.msats);
2225
2226        if contract_amount == Amount::ZERO {
2227            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2228                "Zero amount incoming contracts are not supported".to_string(),
2229            )));
2230        }
2231
2232        if contract_amount != payload.contract.commitment.amount {
2233            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2234                "The contract amount does not pay the correct amount of fees".to_string(),
2235            )));
2236        }
2237
2238        if payload.contract.commitment.expiration <= duration_since_epoch().as_secs() {
2239            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2240                "The contract has already expired".to_string(),
2241            )));
2242        }
2243
2244        let payment_hash = match payload.contract.commitment.payment_image {
2245            PaymentImage::Hash(payment_hash) => payment_hash,
2246            PaymentImage::Point(..) => {
2247                return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2248                    "PaymentImage is not a payment hash".to_string(),
2249                )));
2250            }
2251        };
2252
2253        let invoice = self
2254            .create_invoice_via_lnrpc_v2(
2255                payment_hash,
2256                payload.amount,
2257                payload.description.clone(),
2258                payload.expiry_secs,
2259            )
2260            .await?;
2261
2262        let mut dbtx = self.gateway_db.begin_transaction().await;
2263
2264        if dbtx
2265            .save_registered_incoming_contract(
2266                payload.federation_id,
2267                payload.amount,
2268                payload.contract,
2269            )
2270            .await
2271            .is_some()
2272        {
2273            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2274                "PaymentHash is already registered".to_string(),
2275            )));
2276        }
2277
2278        dbtx.commit_tx_result().await.map_err(|_| {
2279            PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2280                "Payment hash is already registered".to_string(),
2281            ))
2282        })?;
2283
2284        Ok(invoice)
2285    }
2286
2287    /// Retrieves a BOLT11 invoice from the connected Lightning node with a
2288    /// specific `payment_hash`.
2289    pub async fn create_invoice_via_lnrpc_v2(
2290        &self,
2291        payment_hash: sha256::Hash,
2292        amount: Amount,
2293        description: Bolt11InvoiceDescription,
2294        expiry_time: u32,
2295    ) -> std::result::Result<Bolt11Invoice, LightningRpcError> {
2296        let lnrpc = self.get_lightning_context().await?.lnrpc;
2297
2298        let response = match description {
2299            Bolt11InvoiceDescription::Direct(description) => {
2300                lnrpc
2301                    .create_invoice(CreateInvoiceRequest {
2302                        payment_hash: Some(payment_hash),
2303                        amount_msat: amount.msats,
2304                        expiry_secs: expiry_time,
2305                        description: Some(InvoiceDescription::Direct(description)),
2306                    })
2307                    .await?
2308            }
2309            Bolt11InvoiceDescription::Hash(hash) => {
2310                lnrpc
2311                    .create_invoice(CreateInvoiceRequest {
2312                        payment_hash: Some(payment_hash),
2313                        amount_msat: amount.msats,
2314                        expiry_secs: expiry_time,
2315                        description: Some(InvoiceDescription::Hash(hash)),
2316                    })
2317                    .await?
2318            }
2319        };
2320
2321        Bolt11Invoice::from_str(&response.invoice).map_err(|e| {
2322            LightningRpcError::FailedToGetInvoice {
2323                failure_reason: e.to_string(),
2324            }
2325        })
2326    }
2327
2328    /// Retrieves the persisted `CreateInvoicePayload` from the database
2329    /// specified by the `payment_hash` and the `ClientHandleArc` specified
2330    /// by the payload's `federation_id`.
2331    pub async fn get_registered_incoming_contract_and_client_v2(
2332        &self,
2333        payment_image: PaymentImage,
2334        amount_msats: u64,
2335    ) -> Result<(IncomingContract, ClientHandleArc)> {
2336        let registered_incoming_contract = self
2337            .gateway_db
2338            .begin_transaction_nc()
2339            .await
2340            .load_registered_incoming_contract(payment_image)
2341            .await
2342            .ok_or(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2343                "No corresponding decryption contract available".to_string(),
2344            )))?;
2345
2346        if registered_incoming_contract.incoming_amount_msats != amount_msats {
2347            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2348                "The available decryption contract's amount is not equal to the requested amount"
2349                    .to_string(),
2350            )));
2351        }
2352
2353        let client = self
2354            .select_client(registered_incoming_contract.federation_id)
2355            .await?
2356            .into_value();
2357
2358        Ok((registered_incoming_contract.contract, client))
2359    }
2360
2361    /// Helper function for determining if the gateway supports LNv2.
2362    fn is_running_lnv2(&self) -> bool {
2363        self.lightning_module_mode == LightningModuleMode::LNv2
2364            || self.lightning_module_mode == LightningModuleMode::All
2365    }
2366
2367    /// Helper function for determining if the gateway supports LNv1.
2368    fn is_running_lnv1(&self) -> bool {
2369        self.lightning_module_mode == LightningModuleMode::LNv1
2370            || self.lightning_module_mode == LightningModuleMode::All
2371    }
2372}
2373
2374#[async_trait]
2375impl IGatewayClientV2 for Gateway {
2376    async fn complete_htlc(&self, htlc_response: InterceptPaymentResponse) {
2377        loop {
2378            match self.get_lightning_context().await {
2379                Ok(lightning_context) => {
2380                    match lightning_context
2381                        .lnrpc
2382                        .complete_htlc(htlc_response.clone())
2383                        .await
2384                    {
2385                        Ok(..) => return,
2386                        Err(err) => {
2387                            warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Failure trying to complete payment");
2388                        }
2389                    }
2390                }
2391                Err(err) => {
2392                    warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Failure trying to complete payment");
2393                }
2394            }
2395
2396            sleep(Duration::from_secs(5)).await;
2397        }
2398    }
2399
2400    async fn is_direct_swap(
2401        &self,
2402        invoice: &Bolt11Invoice,
2403    ) -> anyhow::Result<Option<(IncomingContract, ClientHandleArc)>> {
2404        let lightning_context = self.get_lightning_context().await?;
2405        if lightning_context.lightning_public_key == invoice.get_payee_pub_key() {
2406            let (contract, client) = self
2407                .get_registered_incoming_contract_and_client_v2(
2408                    PaymentImage::Hash(*invoice.payment_hash()),
2409                    invoice
2410                        .amount_milli_satoshis()
2411                        .expect("The amount invoice has been previously checked"),
2412                )
2413                .await?;
2414            Ok(Some((contract, client)))
2415        } else {
2416            Ok(None)
2417        }
2418    }
2419
2420    async fn pay(
2421        &self,
2422        invoice: Bolt11Invoice,
2423        max_delay: u64,
2424        max_fee: Amount,
2425    ) -> std::result::Result<[u8; 32], LightningRpcError> {
2426        let lightning_context = self.get_lightning_context().await?;
2427        lightning_context
2428            .lnrpc
2429            .pay(invoice, max_delay, max_fee)
2430            .await
2431            .map(|response| response.preimage.0)
2432    }
2433
2434    async fn min_contract_amount(
2435        &self,
2436        federation_id: &FederationId,
2437        amount: u64,
2438    ) -> anyhow::Result<Amount> {
2439        Ok(self
2440            .routing_info_v2(federation_id)
2441            .await?
2442            .ok_or(anyhow!("Routing Info not available"))?
2443            .send_fee_minimum
2444            .add_to(amount))
2445    }
2446}
2447
2448#[async_trait]
2449impl IGatewayClientV1 for Gateway {
2450    async fn verify_preimage_authentication(
2451        &self,
2452        payment_hash: sha256::Hash,
2453        preimage_auth: sha256::Hash,
2454        contract: OutgoingContractAccount,
2455    ) -> std::result::Result<(), OutgoingPaymentError> {
2456        let mut dbtx = self.gateway_db.begin_transaction().await;
2457        if let Some(secret_hash) = dbtx.load_preimage_authentication(payment_hash).await {
2458            if secret_hash != preimage_auth {
2459                return Err(OutgoingPaymentError {
2460                    error_type: OutgoingPaymentErrorType::InvalidInvoicePreimage,
2461                    contract_id: contract.contract.contract_id(),
2462                    contract: Some(contract),
2463                });
2464            }
2465        } else {
2466            // Committing the `preimage_auth` to the database can fail if two users try to
2467            // pay the same invoice at the same time.
2468            dbtx.save_new_preimage_authentication(payment_hash, preimage_auth)
2469                .await;
2470            return dbtx
2471                .commit_tx_result()
2472                .await
2473                .map_err(|_| OutgoingPaymentError {
2474                    error_type: OutgoingPaymentErrorType::InvoiceAlreadyPaid,
2475                    contract_id: contract.contract.contract_id(),
2476                    contract: Some(contract),
2477                });
2478        }
2479
2480        Ok(())
2481    }
2482
2483    async fn verify_pruned_invoice(&self, payment_data: PaymentData) -> anyhow::Result<()> {
2484        let lightning_context = self.get_lightning_context().await?;
2485
2486        if matches!(payment_data, PaymentData::PrunedInvoice { .. }) {
2487            ensure!(
2488                lightning_context.lnrpc.supports_private_payments(),
2489                "Private payments are not supported by the lightning node"
2490            );
2491        }
2492
2493        Ok(())
2494    }
2495
2496    async fn get_routing_fees(&self, federation_id: FederationId) -> Option<RoutingFees> {
2497        let mut gateway_dbtx = self.gateway_db.begin_transaction_nc().await;
2498        gateway_dbtx
2499            .load_federation_config(federation_id)
2500            .await
2501            .map(|c| c.lightning_fee.into())
2502    }
2503
2504    async fn get_client(&self, federation_id: &FederationId) -> Option<Spanned<ClientHandleArc>> {
2505        self.federation_manager
2506            .read()
2507            .await
2508            .client(federation_id)
2509            .cloned()
2510    }
2511
2512    async fn get_client_for_invoice(
2513        &self,
2514        payment_data: PaymentData,
2515    ) -> Option<Spanned<ClientHandleArc>> {
2516        let rhints = payment_data.route_hints();
2517        match rhints.first().and_then(|rh| rh.0.last()) {
2518            None => None,
2519            Some(hop) => match self.get_lightning_context().await {
2520                Ok(lightning_context) => {
2521                    if hop.src_node_id != lightning_context.lightning_public_key {
2522                        return None;
2523                    }
2524
2525                    self.federation_manager
2526                        .read()
2527                        .await
2528                        .get_client_for_index(hop.short_channel_id)
2529                }
2530                Err(_) => None,
2531            },
2532        }
2533    }
2534
2535    async fn pay(
2536        &self,
2537        payment_data: PaymentData,
2538        max_delay: u64,
2539        max_fee: Amount,
2540    ) -> std::result::Result<PayInvoiceResponse, LightningRpcError> {
2541        let lightning_context = self.get_lightning_context().await?;
2542
2543        match payment_data {
2544            PaymentData::Invoice(invoice) => {
2545                lightning_context
2546                    .lnrpc
2547                    .pay(invoice, max_delay, max_fee)
2548                    .await
2549            }
2550            PaymentData::PrunedInvoice(invoice) => {
2551                lightning_context
2552                    .lnrpc
2553                    .pay_private(invoice, max_delay, max_fee)
2554                    .await
2555            }
2556        }
2557    }
2558
2559    async fn complete_htlc(
2560        &self,
2561        htlc: InterceptPaymentResponse,
2562    ) -> std::result::Result<(), LightningRpcError> {
2563        // Wait until the lightning node is online to complete the HTLC.
2564        let lightning_context = loop {
2565            match self.get_lightning_context().await {
2566                Ok(lightning_context) => break lightning_context,
2567                Err(err) => {
2568                    warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Failure trying to complete payment");
2569                    sleep(Duration::from_secs(5)).await;
2570                }
2571            }
2572        };
2573
2574        lightning_context.lnrpc.complete_htlc(htlc).await
2575    }
2576}