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