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