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