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