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