ln_gateway/
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
15pub mod client;
16pub mod config;
17mod db;
18pub mod envs;
19mod error;
20mod federation_manager;
21pub mod gateway_module_v2;
22pub mod lightning;
23pub mod rpc;
24pub mod state_machine;
25mod types;
26
27use std::collections::{BTreeMap, BTreeSet};
28use std::env;
29use std::fmt::Display;
30use std::net::SocketAddr;
31use std::str::FromStr;
32use std::sync::Arc;
33use std::time::Duration;
34
35use anyhow::{anyhow, Context};
36use bitcoin::hashes::sha256;
37use bitcoin::{Address, Network, Txid};
38use clap::Parser;
39use client::GatewayClientBuilder;
40use config::GatewayOpts;
41pub use config::GatewayParameters;
42use db::{GatewayConfiguration, GatewayConfigurationKey, GatewayDbtxNcExt};
43use error::FederationNotConnected;
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    ModuleInstanceId, ModuleKind, LEGACY_HARDCODED_INSTANCE_ID_MINT,
53    LEGACY_HARDCODED_INSTANCE_ID_WALLET,
54};
55use fedimint_core::db::{apply_migrations_server, Database, DatabaseTransaction};
56use fedimint_core::invite_code::InviteCode;
57use fedimint_core::module::CommonModuleInit;
58use fedimint_core::secp256k1::schnorr::Signature;
59use fedimint_core::secp256k1::PublicKey;
60use fedimint_core::task::{sleep, TaskGroup, TaskHandle, TaskShutdownToken};
61use fedimint_core::time::duration_since_epoch;
62use fedimint_core::util::{SafeUrl, Spanned};
63use fedimint_core::{fedimint_build_code_version_env, Amount, BitcoinAmountOrAll};
64use fedimint_ln_common::config::{GatewayFee, LightningClientConfig};
65use fedimint_ln_common::contracts::Preimage;
66use fedimint_ln_common::LightningCommonInit;
67use fedimint_lnv2_common::contracts::{IncomingContract, PaymentImage};
68use fedimint_lnv2_common::gateway_api::{
69    CreateBolt11InvoicePayload, PaymentFee, RoutingInfo, SendPaymentPayload,
70};
71use fedimint_lnv2_common::Bolt11InvoiceDescription;
72use fedimint_mint_client::{
73    MintClientInit, MintClientModule, MintCommonInit, SelectNotesWithAtleastAmount,
74    SelectNotesWithExactAmount,
75};
76use fedimint_wallet_client::{
77    WalletClientInit, WalletClientModule, WalletCommonInit, WithdrawState,
78};
79use futures::stream::StreamExt;
80use lightning::{
81    CloseChannelsWithPeerResponse, CreateInvoiceRequest, ILnRpcClient, InterceptPaymentRequest,
82    InterceptPaymentResponse, InvoiceDescription, LightningBuilder, LightningRpcError,
83    PaymentAction,
84};
85use lightning_invoice::{Bolt11Invoice, RoutingFees};
86use rand::{thread_rng, Rng};
87use rpc::{
88    CloseChannelsWithPeerPayload, CreateInvoiceForOperatorPayload, FederationInfo,
89    GatewayFedConfig, GatewayInfo, LeaveFedPayload, MnemonicResponse, OpenChannelPayload,
90    PayInvoiceForOperatorPayload, ReceiveEcashPayload, ReceiveEcashResponse, SendOnchainPayload,
91    SetConfigurationPayload, SpendEcashPayload, SpendEcashResponse, WithdrawResponse,
92    V1_API_ENDPOINT,
93};
94use state_machine::{GatewayClientModule, GatewayExtPayStates};
95use tokio::sync::RwLock;
96use tracing::{debug, error, info, info_span, warn};
97
98use crate::config::LightningModuleMode;
99use crate::db::{get_gatewayd_database_migrations, FederationConfig};
100use crate::envs::FM_GATEWAY_MNEMONIC_ENV;
101use crate::error::{AdminGatewayError, LNv1Error, LNv2Error, PublicGatewayError};
102use crate::gateway_module_v2::GatewayClientModuleV2;
103use crate::lightning::{GatewayLightningBuilder, LightningContext, LightningMode, RouteHtlcStream};
104use crate::rpc::rpc_server::{hash_password, run_webserver};
105use crate::rpc::{
106    BackupPayload, ConnectFedPayload, DepositAddressPayload, FederationBalanceInfo,
107    GatewayBalances, WithdrawPayload,
108};
109use crate::types::PrettyInterceptPaymentRequest;
110
111/// How long a gateway announcement stays valid
112const GW_ANNOUNCEMENT_TTL: Duration = Duration::from_secs(600);
113
114/// The default number of route hints that the legacy gateway provides for
115/// invoice creation.
116const DEFAULT_NUM_ROUTE_HINTS: u32 = 1;
117
118/// Default Bitcoin network for testing purposes.
119pub const DEFAULT_NETWORK: Network = Network::Regtest;
120
121/// The default routing fees that the gateway charges for incoming and outgoing
122/// payments. Identical to the Lightning Network.
123pub const DEFAULT_FEES: RoutingFees = RoutingFees {
124    // Base routing fee. Default is 0 msat
125    base_msat: 0,
126    // Liquidity-based routing fee in millionths of a routed amount.
127    // In other words, 10000 is 1%. The default is 10000 (1%).
128    proportional_millionths: 10000,
129};
130
131/// LNv2 CLTV Delta in blocks
132const EXPIRATION_DELTA_MINIMUM_V2: u64 = 144;
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///    Initializing -- begin intercepting lightning payments --> Connected
157///    Initializing -- gateway needs config --> Configuring
158///    Configuring -- configuration set --> Connected
159///    Connected -- load federation clients --> Running
160///    Connected -- not synced to chain --> Syncing
161///    Syncing -- load federation clients --> Running
162///    Running -- disconnected from lightning node --> Disconnected
163///    Disconnected -- re-established lightning connection --> Connected
164/// ```
165#[derive(Clone, Debug)]
166pub enum GatewayState {
167    Initializing,
168    Configuring,
169    Syncing,
170    Connected,
171    Running { lightning_context: LightningContext },
172    Disconnected,
173    ShuttingDown { lightning_context: LightningContext },
174}
175
176impl Display for GatewayState {
177    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
178        match self {
179            GatewayState::Initializing => write!(f, "Initializing"),
180            GatewayState::Configuring => write!(f, "Configuring"),
181            GatewayState::Syncing => write!(f, "Syncing"),
182            GatewayState::Connected => write!(f, "Connected"),
183            GatewayState::Running { .. } => write!(f, "Running"),
184            GatewayState::Disconnected => write!(f, "Disconnected"),
185            GatewayState::ShuttingDown { .. } => write!(f, "ShuttingDown"),
186        }
187    }
188}
189
190/// The action to take after handling a payment stream.
191enum ReceivePaymentStreamAction {
192    ImmediatelyRetry,
193    RetryAfterDelay,
194    NoRetry,
195}
196
197#[derive(Clone)]
198pub struct Gateway {
199    /// The gateway's federation manager.
200    federation_manager: Arc<RwLock<FederationManager>>,
201
202    /// Builder struct that allows the gateway to build a `ILnRpcClient`, which
203    /// represents a connection to a lightning node.
204    lightning_builder: Arc<dyn LightningBuilder + Send + Sync>,
205
206    /// The gateway's current configuration
207    gateway_config: Arc<RwLock<Option<GatewayConfiguration>>>,
208
209    /// The current state of the Gateway.
210    state: Arc<RwLock<GatewayState>>,
211
212    /// Builder struct that allows the gateway to build a Fedimint client, which
213    /// handles the communication with a federation.
214    client_builder: GatewayClientBuilder,
215
216    /// Database for Gateway metadata.
217    gateway_db: Database,
218
219    /// A public key representing the identity of the gateway. Private key is
220    /// not used.
221    gateway_id: PublicKey,
222
223    /// The Gateway's API URL.
224    versioned_api: SafeUrl,
225
226    /// The socket the gateway listens on.
227    listen: SocketAddr,
228
229    /// The "module mode" of the gateway. Options are LNv1, LNv2, or All.
230    lightning_module_mode: LightningModuleMode,
231}
232
233impl std::fmt::Debug for Gateway {
234    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        f.debug_struct("Gateway")
236            .field("federation_manager", &self.federation_manager)
237            .field("gateway_config", &self.gateway_config)
238            .field("state", &self.state)
239            .field("client_builder", &self.client_builder)
240            .field("gateway_db", &self.gateway_db)
241            .field("gateway_id", &self.gateway_id)
242            .field("versioned_api", &self.versioned_api)
243            .field("listen", &self.listen)
244            .finish_non_exhaustive()
245    }
246}
247
248impl Gateway {
249    /// Creates a new gateway but with a custom module registry provided inside
250    /// `client_builder`. Currently only used for testing.
251    #[allow(clippy::too_many_arguments)]
252    pub async fn new_with_custom_registry(
253        lightning_builder: Arc<dyn LightningBuilder + Send + Sync>,
254        client_builder: GatewayClientBuilder,
255        listen: SocketAddr,
256        api_addr: SafeUrl,
257        cli_password: Option<String>,
258        network: Option<Network>,
259        fees: RoutingFees,
260        num_route_hints: u32,
261        gateway_db: Database,
262        gateway_state: GatewayState,
263        lightning_module_mode: LightningModuleMode,
264    ) -> anyhow::Result<Gateway> {
265        let versioned_api = api_addr
266            .join(V1_API_ENDPOINT)
267            .expect("Failed to version gateway API address");
268        Gateway::new(
269            lightning_builder,
270            GatewayParameters {
271                listen,
272                versioned_api,
273                password: cli_password,
274                num_route_hints,
275                fees: Some(GatewayFee(fees)),
276                network,
277                lightning_module_mode,
278            },
279            gateway_db,
280            client_builder,
281            gateway_state,
282        )
283        .await
284    }
285
286    /// Default function for creating a gateway with the `Mint`, `Wallet`, and
287    /// `Gateway` modules.
288    pub async fn new_with_default_modules() -> anyhow::Result<Gateway> {
289        let opts = GatewayOpts::parse();
290
291        // Gateway module will be attached when the federation clients are created
292        // because the LN RPC will be injected with `GatewayClientGen`.
293        let mut registry = ClientModuleInitRegistry::new();
294        registry.attach(MintClientInit);
295        registry.attach(WalletClientInit::default());
296
297        let decoders = registry.available_decoders(DEFAULT_MODULE_KINDS.iter().copied())?;
298
299        let gateway_db = Database::new(
300            fedimint_rocksdb::RocksDb::open(opts.data_dir.join(DB_FILE))?,
301            decoders,
302        );
303
304        let client_builder =
305            GatewayClientBuilder::new(opts.data_dir.clone(), registry, fedimint_mint_client::KIND);
306
307        info!(
308            "Starting gatewayd (version: {})",
309            fedimint_build_code_version_env!()
310        );
311
312        let mut gateway_parameters = opts.to_gateway_parameters()?;
313
314        if gateway_parameters.lightning_module_mode != LightningModuleMode::LNv2
315            && matches!(opts.mode, LightningMode::Ldk { .. })
316        {
317            warn!("Overriding LDK Gateway to only run LNv2...");
318            gateway_parameters.lightning_module_mode = LightningModuleMode::LNv2;
319        }
320
321        let mnemonic = Self::load_or_generate_mnemonic(&gateway_db).await?;
322        Gateway::new(
323            Arc::new(GatewayLightningBuilder {
324                lightning_mode: opts.mode,
325                gateway_db: gateway_db.clone(),
326                ldk_data_dir: opts.data_dir.join(LDK_NODE_DB_FOLDER),
327                mnemonic,
328            }),
329            gateway_parameters,
330            gateway_db,
331            client_builder,
332            GatewayState::Initializing,
333        )
334        .await
335    }
336
337    /// Helper function for creating a gateway from either
338    /// `new_with_default_modules` or `new_with_custom_registry`.
339    async fn new(
340        lightning_builder: Arc<dyn LightningBuilder + Send + Sync>,
341        gateway_parameters: GatewayParameters,
342        gateway_db: Database,
343        client_builder: GatewayClientBuilder,
344        gateway_state: GatewayState,
345    ) -> anyhow::Result<Gateway> {
346        // Apply database migrations before using the database to ensure old database
347        // structures are readable.
348        apply_migrations_server(
349            &gateway_db,
350            "gatewayd".to_string(),
351            get_gatewayd_database_migrations(),
352        )
353        .await?;
354
355        // Reads the `GatewayConfig` from the database if it exists or is provided from
356        // the command line.
357        let gateway_config =
358            Self::get_gateway_configuration(gateway_db.clone(), &gateway_parameters).await;
359
360        Ok(Self {
361            federation_manager: Arc::new(RwLock::new(FederationManager::new())),
362            lightning_builder,
363            gateway_config: Arc::new(RwLock::new(gateway_config)),
364            state: Arc::new(RwLock::new(gateway_state)),
365            client_builder,
366            gateway_id: Self::load_or_create_gateway_id(&gateway_db).await,
367            gateway_db,
368            versioned_api: gateway_parameters.versioned_api,
369            listen: gateway_parameters.listen,
370            lightning_module_mode: gateway_parameters.lightning_module_mode,
371        })
372    }
373
374    /// Returns a `PublicKey` that uniquely identifies the Gateway.
375    async fn load_or_create_gateway_id(gateway_db: &Database) -> PublicKey {
376        let mut dbtx = gateway_db.begin_transaction().await;
377        let keypair = dbtx.load_or_create_gateway_keypair().await;
378        dbtx.commit_tx().await;
379        keypair.public_key()
380    }
381
382    pub fn gateway_id(&self) -> PublicKey {
383        self.gateway_id
384    }
385
386    pub fn versioned_api(&self) -> &SafeUrl {
387        &self.versioned_api
388    }
389
390    pub async fn clone_gateway_config(&self) -> Option<GatewayConfiguration> {
391        self.gateway_config.read().await.clone()
392    }
393
394    async fn get_state(&self) -> GatewayState {
395        self.state.read().await.clone()
396    }
397
398    /// Reads and serializes structures from the Gateway's database for the
399    /// purpose for serializing to JSON for inspection.
400    pub async fn dump_database(
401        dbtx: &mut DatabaseTransaction<'_>,
402        prefix_names: Vec<String>,
403    ) -> BTreeMap<String, Box<dyn erased_serde::Serialize + Send>> {
404        dbtx.dump_database(prefix_names).await
405    }
406
407    /// Main entrypoint into the gateway that starts the client registration
408    /// timer, loads the federation clients from the persisted config,
409    /// begins listening for intercepted payments, and starts the webserver
410    /// to service requests.
411    pub async fn run(self, tg: TaskGroup) -> anyhow::Result<TaskShutdownToken> {
412        self.register_clients_timer(&tg);
413        self.load_clients().await?;
414        self.start_gateway(&tg);
415        // start webserver last to avoid handling requests before fully initialized
416        let handle = tg.make_handle();
417        run_webserver(Arc::new(self), tg).await?;
418        let shutdown_receiver = handle.make_shutdown_rx();
419        Ok(shutdown_receiver)
420    }
421
422    /// Begins the task for listening for intercepted payments from the
423    /// lightning node.
424    fn start_gateway(&self, task_group: &TaskGroup) {
425        const PAYMENT_STREAM_RETRY_SECONDS: u64 = 5;
426
427        let self_copy = self.clone();
428        let tg = task_group.clone();
429        task_group.spawn(
430            "Subscribe to intercepted lightning payments in stream",
431            |handle| async move {
432                // Repeatedly attempt to establish a connection to the lightning node and create a payment stream, re-trying if the connection is broken.
433                loop {
434                    if handle.is_shutting_down() {
435                        info!("Gateway lightning payment stream handler loop is shutting down");
436                        break;
437                    }
438
439                    let payment_stream_task_group = tg.make_subgroup();
440                    let lnrpc_route = self_copy.lightning_builder.build().await;
441
442                    debug!("Establishing lightning payment stream...");
443                    let (stream, ln_client) = match lnrpc_route.route_htlcs(&payment_stream_task_group).await
444                    {
445                        Ok((stream, ln_client)) => (stream, ln_client),
446                        Err(e) => {
447                            warn!(?e, "Failed to open lightning payment stream");
448                            continue
449                        }
450                    };
451
452                    // Successful calls to `route_htlcs` establish a connection
453                    self_copy.set_gateway_state(GatewayState::Connected).await;
454                    info!("Established lightning payment stream");
455
456                    let route_payments_response =
457                        self_copy.route_lightning_payments(&handle, stream, ln_client).await;
458
459                    self_copy.set_gateway_state(GatewayState::Disconnected).await;
460                    if let Err(e) = payment_stream_task_group.shutdown_join_all(None).await {
461                        error!("Lightning payment stream task group shutdown errors: {}", e);
462                    }
463
464                    match route_payments_response {
465                        ReceivePaymentStreamAction::ImmediatelyRetry => {},
466                        ReceivePaymentStreamAction::RetryAfterDelay => {
467                            warn!("Disconnected from lightning node. Waiting {PAYMENT_STREAM_RETRY_SECONDS} seconds and trying again");
468                            sleep(Duration::from_secs(PAYMENT_STREAM_RETRY_SECONDS)).await;
469                        }
470                        ReceivePaymentStreamAction::NoRetry => break,
471                    }
472                }
473            },
474        );
475    }
476
477    /// Handles a stream of incoming payments from the lightning node after
478    /// ensuring the gateway is properly configured. Awaits until the stream
479    /// is closed, then returns with the appropriate action to take.
480    async fn route_lightning_payments<'a>(
481        &'a self,
482        handle: &TaskHandle,
483        mut stream: RouteHtlcStream<'a>,
484        ln_client: Arc<dyn ILnRpcClient>,
485    ) -> ReceivePaymentStreamAction {
486        let (lightning_public_key, lightning_alias, lightning_network, synced_to_chain) =
487            match ln_client.parsed_node_info().await {
488                Ok((
489                    lightning_public_key,
490                    lightning_alias,
491                    lightning_network,
492                    _block_height,
493                    synced_to_chain,
494                )) => (
495                    lightning_public_key,
496                    lightning_alias,
497                    lightning_network,
498                    synced_to_chain,
499                ),
500                Err(e) => {
501                    warn!("Failed to retrieve Lightning info: {e:?}");
502                    return ReceivePaymentStreamAction::RetryAfterDelay;
503                }
504            };
505
506        let gateway_config = if let Some(config) = self.clone_gateway_config().await {
507            config
508        } else {
509            self.set_gateway_state(GatewayState::Configuring).await;
510            info!("Waiting for gateway to be configured...");
511            self.gateway_db
512                .wait_key_exists(&GatewayConfigurationKey)
513                .await
514        };
515
516        if gateway_config.network != lightning_network {
517            warn!(
518                "Lightning node does not match previously configured gateway network : ({:?})",
519                gateway_config.network
520            );
521            info!(
522                "Changing gateway network to match lightning node network : ({:?})",
523                lightning_network
524            );
525            self.handle_set_configuration_msg(SetConfigurationPayload {
526                password: None,
527                network: Some(lightning_network),
528                num_route_hints: None,
529                routing_fees: None,
530                per_federation_routing_fees: None,
531            })
532            .await
533            .expect("Failed to set gateway configuration");
534            return ReceivePaymentStreamAction::ImmediatelyRetry;
535        }
536
537        if synced_to_chain {
538            info!("Gateway is already synced to chain");
539        } else {
540            self.set_gateway_state(GatewayState::Syncing).await;
541            if let Err(e) = ln_client.wait_for_chain_sync().await {
542                error!(?e, "Failed to wait for chain sync");
543                return ReceivePaymentStreamAction::RetryAfterDelay;
544            }
545        }
546
547        let lightning_context = LightningContext {
548            lnrpc: ln_client,
549            lightning_public_key,
550            lightning_alias,
551            lightning_network,
552        };
553        self.set_gateway_state(GatewayState::Running { lightning_context })
554            .await;
555        info!("Gateway is running");
556
557        // Runs until the connection to the lightning node breaks or we receive the
558        // shutdown signal.
559        if handle
560            .cancel_on_shutdown(async move {
561                loop {
562                    let payment_request = tokio::select! {
563                        payment_request = stream.next() => {
564                            payment_request
565                        }
566                        () = self.is_shutting_down_safely() => {
567                            break;
568                        }
569                    };
570
571                    // Hold the Gateway state's lock so that it doesn't change before `handle_lightning_payment`.
572                    let state_guard = self.state.read().await;
573                    let GatewayState::Running { ref lightning_context } = *state_guard else {
574                        warn!(
575                            ?state_guard,
576                            "Gateway isn't in a running state, cannot handle incoming payments."
577                        );
578                        break;
579                    };
580
581                    let payment_request = match payment_request {
582                        Some(payment_request) => payment_request,
583                        other => {
584                            warn!(
585                                ?other,
586                                "Unexpected response from incoming lightning payment stream. Exiting from loop..."
587                            );
588                            break;
589                        }
590                    };
591
592                    self.handle_lightning_payment(payment_request, lightning_context).await;
593                }
594            })
595            .await
596            .is_ok()
597        {
598            warn!("Lightning payment stream connection broken. Gateway is disconnected");
599            ReceivePaymentStreamAction::RetryAfterDelay
600        } else {
601            info!("Received shutdown signal");
602            ReceivePaymentStreamAction::NoRetry
603        }
604    }
605
606    /// Polls the Gateway's state waiting for it to shutdown so the thread
607    /// processing payment requests can exit.
608    async fn is_shutting_down_safely(&self) {
609        loop {
610            if let GatewayState::ShuttingDown { .. } = self.get_state().await {
611                return;
612            }
613
614            fedimint_core::task::sleep(Duration::from_secs(1)).await;
615        }
616    }
617
618    /// Handles an intercepted lightning payment. If the payment is part of an
619    /// incoming payment to a federation, spawns a state machine and hands the
620    /// payment off to it. Otherwise, forwards the payment to the next hop like
621    /// a normal lightning node.
622    async fn handle_lightning_payment(
623        &self,
624        payment_request: InterceptPaymentRequest,
625        lightning_context: &LightningContext,
626    ) {
627        info!(
628            "Intercepting lightning payment {}",
629            PrettyInterceptPaymentRequest(&payment_request)
630        );
631
632        if self
633            .try_handle_lightning_payment_lnv2(&payment_request, lightning_context)
634            .await
635            .is_ok()
636        {
637            return;
638        }
639
640        if self
641            .try_handle_lightning_payment_ln_legacy(&payment_request)
642            .await
643            .is_ok()
644        {
645            return;
646        }
647
648        Self::forward_lightning_payment(payment_request, lightning_context).await;
649    }
650
651    /// Tries to handle a lightning payment using the LNv2 protocol.
652    /// Returns `Ok` if the payment was handled, `Err` otherwise.
653    async fn try_handle_lightning_payment_lnv2(
654        &self,
655        htlc_request: &InterceptPaymentRequest,
656        lightning_context: &LightningContext,
657    ) -> Result<()> {
658        // If `payment_hash` has been registered as a LNv2 payment, we try to complete
659        // the payment by getting the preimage from the federation
660        // using the LNv2 protocol. If the `payment_hash` is not registered,
661        // this payment is either a legacy Lightning payment or the end destination is
662        // not a Fedimint.
663        let (contract, client) = self
664            .get_registered_incoming_contract_and_client_v2(
665                PaymentImage::Hash(htlc_request.payment_hash),
666                htlc_request.amount_msat,
667            )
668            .await?;
669
670        if let Err(error) = client
671            .get_first_module::<GatewayClientModuleV2>()
672            .expect("Must have client module")
673            .relay_incoming_htlc(
674                htlc_request.payment_hash,
675                htlc_request.incoming_chan_id,
676                htlc_request.htlc_id,
677                contract,
678            )
679            .await
680        {
681            error!("Error relaying incoming lightning payment: {error:?}");
682
683            let outcome = InterceptPaymentResponse {
684                action: PaymentAction::Cancel,
685                payment_hash: htlc_request.payment_hash,
686                incoming_chan_id: htlc_request.incoming_chan_id,
687                htlc_id: htlc_request.htlc_id,
688            };
689
690            if let Err(error) = lightning_context.lnrpc.complete_htlc(outcome).await {
691                error!("Error sending HTLC response to lightning node: {error:?}");
692            }
693        }
694
695        Ok(())
696    }
697
698    /// Tries to handle a lightning payment using the legacy lightning protocol.
699    /// Returns `Ok` if the payment was handled, `Err` otherwise.
700    async fn try_handle_lightning_payment_ln_legacy(
701        &self,
702        htlc_request: &InterceptPaymentRequest,
703    ) -> Result<()> {
704        // Check if the payment corresponds to a federation supporting legacy Lightning.
705        let Some(federation_index) = htlc_request.short_channel_id else {
706            return Err(PublicGatewayError::LNv1(LNv1Error::IncomingPayment(
707                "Incoming payment has not last hop short channel id".to_string(),
708            )));
709        };
710
711        let Some(client) = self
712            .federation_manager
713            .read()
714            .await
715            .get_client_for_index(federation_index)
716        else {
717            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())));
718        };
719
720        client
721            .borrow()
722            .with(|client| async {
723                let htlc = htlc_request.clone().try_into();
724                if let Ok(htlc) = htlc {
725                    match client
726                        .get_first_module::<GatewayClientModule>()
727                        .expect("Must have client module")
728                        .gateway_handle_intercepted_htlc(htlc)
729                        .await
730                    {
731                        Ok(_) => Ok(()),
732                        Err(e) => Err(PublicGatewayError::LNv1(LNv1Error::IncomingPayment(
733                            format!("Error intercepting lightning payment {e:?}"),
734                        ))),
735                    }
736                } else {
737                    Err(PublicGatewayError::LNv1(LNv1Error::IncomingPayment(
738                        "Could not convert InterceptHtlcResult into an HTLC".to_string(),
739                    )))
740                }
741            })
742            .await
743    }
744
745    /// Forwards a lightning payment to the next hop like a normal lightning
746    /// node. Only necessary for LNv1, since LNv2 uses hold invoices instead
747    /// of HTLC interception for routing incoming payments.
748    async fn forward_lightning_payment(
749        htlc_request: InterceptPaymentRequest,
750        lightning_context: &LightningContext,
751    ) {
752        let outcome = InterceptPaymentResponse {
753            action: PaymentAction::Forward,
754            payment_hash: htlc_request.payment_hash,
755            incoming_chan_id: htlc_request.incoming_chan_id,
756            htlc_id: htlc_request.htlc_id,
757        };
758
759        if let Err(error) = lightning_context.lnrpc.complete_htlc(outcome).await {
760            error!("Error sending lightning payment response to lightning node: {error:?}");
761        }
762    }
763
764    /// Helper function for atomically changing the Gateway's internal state.
765    async fn set_gateway_state(&self, state: GatewayState) {
766        let mut lock = self.state.write().await;
767        *lock = state;
768    }
769
770    /// Returns information about the Gateway back to the client when requested
771    /// via the webserver.
772    pub async fn handle_get_info(&self) -> AdminResult<GatewayInfo> {
773        let GatewayState::Running { lightning_context } = self.get_state().await else {
774            return Ok(GatewayInfo {
775                federations: vec![],
776                federation_fake_scids: None,
777                version_hash: fedimint_build_code_version_env!().to_string(),
778                lightning_pub_key: None,
779                lightning_alias: None,
780                gateway_id: self.gateway_id,
781                gateway_state: self.state.read().await.to_string(),
782                network: None,
783                block_height: None,
784                synced_to_chain: false,
785                api: self.versioned_api.clone(),
786                lightning_mode: None,
787            });
788        };
789
790        // `GatewayConfiguration` should always exist in the database when we are in the
791        // `Running` state.
792        let gateway_config = self
793            .clone_gateway_config()
794            .await
795            .expect("Gateway configuration should be set");
796
797        let dbtx = self.gateway_db.begin_transaction_nc().await;
798        let federations = self
799            .federation_manager
800            .read()
801            .await
802            .federation_info_all_federations(dbtx)
803            .await;
804
805        let channels: BTreeMap<u64, FederationId> = federations
806            .iter()
807            .map(|federation_info| {
808                (
809                    federation_info.federation_index,
810                    federation_info.federation_id,
811                )
812            })
813            .collect();
814
815        let node_info = lightning_context.lnrpc.parsed_node_info().await?;
816
817        Ok(GatewayInfo {
818            federations,
819            federation_fake_scids: Some(channels),
820            version_hash: fedimint_build_code_version_env!().to_string(),
821            lightning_pub_key: Some(lightning_context.lightning_public_key.to_string()),
822            lightning_alias: Some(lightning_context.lightning_alias.clone()),
823            gateway_id: self.gateway_id,
824            gateway_state: self.state.read().await.to_string(),
825            network: Some(gateway_config.network),
826            block_height: Some(node_info.3),
827            synced_to_chain: node_info.4,
828            api: self.versioned_api.clone(),
829            lightning_mode: self.lightning_builder.lightning_mode(),
830        })
831    }
832
833    /// If the Gateway is connected to the Lightning node, returns the
834    /// `ClientConfig` for each federation that the Gateway is connected to.
835    pub async fn handle_get_federation_config(
836        &self,
837        federation_id_or: Option<FederationId>,
838    ) -> AdminResult<GatewayFedConfig> {
839        if !matches!(self.get_state().await, GatewayState::Running { .. }) {
840            return Ok(GatewayFedConfig {
841                federations: BTreeMap::new(),
842            });
843        }
844
845        let federations = if let Some(federation_id) = federation_id_or {
846            let mut federations = BTreeMap::new();
847            federations.insert(
848                federation_id,
849                self.federation_manager
850                    .read()
851                    .await
852                    .get_federation_config(federation_id)
853                    .await?,
854            );
855            federations
856        } else {
857            self.federation_manager
858                .read()
859                .await
860                .get_all_federation_configs()
861                .await
862        };
863
864        Ok(GatewayFedConfig { federations })
865    }
866
867    /// Returns a Bitcoin deposit on-chain address for pegging in Bitcoin for a
868    /// specific connected federation.
869    pub async fn handle_address_msg(&self, payload: DepositAddressPayload) -> AdminResult<Address> {
870        let (_, address, _) = self
871            .select_client(payload.federation_id)
872            .await?
873            .value()
874            .get_first_module::<WalletClientModule>()
875            .expect("Must have client module")
876            .allocate_deposit_address_expert_only(())
877            .await?;
878        Ok(address)
879    }
880
881    /// Returns a Bitcoin TXID from a peg-out transaction for a specific
882    /// connected federation.
883    pub async fn handle_withdraw_msg(
884        &self,
885        payload: WithdrawPayload,
886    ) -> AdminResult<WithdrawResponse> {
887        let WithdrawPayload {
888            amount,
889            address,
890            federation_id,
891        } = payload;
892        let client = self.select_client(federation_id).await?;
893        let wallet_module = client.value().get_first_module::<WalletClientModule>()?;
894
895        // TODO: Fees should probably be passed in as a parameter
896        let (amount, fees) = match amount {
897            // If the amount is "all", then we need to subtract the fees from
898            // the amount we are withdrawing
899            BitcoinAmountOrAll::All => {
900                let balance =
901                    bitcoin::Amount::from_sat(client.value().get_balance().await.msats / 1000);
902                let fees = wallet_module
903                    .get_withdraw_fees(address.clone(), balance)
904                    .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
918                    .get_withdraw_fees(address.clone(), amount)
919                    .await?,
920            ),
921        };
922
923        let operation_id = wallet_module
924            .withdraw(address.clone(), amount, fees, ())
925            .await?;
926        let mut updates = wallet_module
927            .subscribe_withdraw_updates(operation_id)
928            .await?
929            .into_stream();
930
931        while let Some(update) = updates.next().await {
932            match update {
933                WithdrawState::Succeeded(txid) => {
934                    info!(
935                        "Sent {amount} funds to address {}",
936                        address.assume_checked()
937                    );
938                    return Ok(WithdrawResponse { txid, fees });
939                }
940                WithdrawState::Failed(e) => {
941                    return Err(AdminGatewayError::WithdrawError { failure_reason: e });
942                }
943                WithdrawState::Created => {}
944            }
945        }
946
947        Err(AdminGatewayError::WithdrawError {
948            failure_reason: "Ran out of state updates while withdrawing".to_string(),
949        })
950    }
951
952    /// Creates an invoice that is directly payable to the gateway's lightning
953    /// node.
954    async fn handle_create_invoice_for_operator_msg(
955        &self,
956        payload: CreateInvoiceForOperatorPayload,
957    ) -> AdminResult<Bolt11Invoice> {
958        let GatewayState::Running { lightning_context } = self.get_state().await else {
959            return Err(AdminGatewayError::Lightning(
960                LightningRpcError::FailedToConnect,
961            ));
962        };
963
964        Bolt11Invoice::from_str(
965            &lightning_context
966                .lnrpc
967                .create_invoice(CreateInvoiceRequest {
968                    payment_hash: None, /* Empty payment hash indicates an invoice payable
969                                         * directly to the gateway. */
970                    amount_msat: payload.amount_msats,
971                    expiry_secs: payload.expiry_secs.unwrap_or(3600),
972                    description: payload.description.map(InvoiceDescription::Direct),
973                })
974                .await?
975                .invoice,
976        )
977        .map_err(|e| {
978            AdminGatewayError::Lightning(LightningRpcError::InvalidMetadata {
979                failure_reason: e.to_string(),
980            })
981        })
982    }
983
984    /// Requests the gateway to pay an outgoing LN invoice using its own funds.
985    /// Returns the payment hash's preimage on success.
986    async fn handle_pay_invoice_for_operator_msg(
987        &self,
988        payload: PayInvoiceForOperatorPayload,
989    ) -> AdminResult<Preimage> {
990        // Those are the ldk defaults
991        const BASE_FEE: u64 = 50;
992        const FEE_DENOMINATOR: u64 = 100;
993        const MAX_DELAY: u64 = 1008;
994
995        let GatewayState::Running { lightning_context } = self.get_state().await else {
996            return Err(AdminGatewayError::Lightning(
997                LightningRpcError::FailedToConnect,
998            ));
999        };
1000
1001        let max_fee = BASE_FEE
1002            + payload
1003                .invoice
1004                .amount_milli_satoshis()
1005                .context("Invoice is missing amount")?
1006                .saturating_div(FEE_DENOMINATOR);
1007
1008        let res = lightning_context
1009            .lnrpc
1010            .pay(payload.invoice, MAX_DELAY, Amount::from_msats(max_fee))
1011            .await?;
1012        Ok(res.preimage)
1013    }
1014
1015    /// Requests the gateway to pay an outgoing LN invoice on behalf of a
1016    /// Fedimint client. Returns the payment hash's preimage on success.
1017    async fn handle_pay_invoice_msg(
1018        &self,
1019        payload: fedimint_ln_client::pay::PayInvoicePayload,
1020    ) -> Result<Preimage> {
1021        let GatewayState::Running { .. } = self.get_state().await else {
1022            return Err(PublicGatewayError::Lightning(
1023                LightningRpcError::FailedToConnect,
1024            ));
1025        };
1026
1027        debug!("Handling pay invoice message: {payload:?}");
1028        let client = self.select_client(payload.federation_id).await?;
1029        let contract_id = payload.contract_id;
1030        let gateway_module = &client
1031            .value()
1032            .get_first_module::<GatewayClientModule>()
1033            .map_err(LNv1Error::OutgoingPayment)
1034            .map_err(PublicGatewayError::LNv1)?;
1035        let operation_id = gateway_module
1036            .gateway_pay_bolt11_invoice(payload)
1037            .await
1038            .map_err(LNv1Error::OutgoingPayment)
1039            .map_err(PublicGatewayError::LNv1)?;
1040        let mut updates = gateway_module
1041            .gateway_subscribe_ln_pay(operation_id)
1042            .await
1043            .map_err(LNv1Error::OutgoingPayment)
1044            .map_err(PublicGatewayError::LNv1)?
1045            .into_stream();
1046        while let Some(update) = updates.next().await {
1047            match update {
1048                GatewayExtPayStates::Success { preimage, .. } => {
1049                    debug!("Successfully paid invoice: {contract_id}");
1050                    return Ok(preimage);
1051                }
1052                GatewayExtPayStates::Fail {
1053                    error,
1054                    error_message,
1055                } => {
1056                    return Err(PublicGatewayError::LNv1(LNv1Error::OutgoingContract {
1057                        error: Box::new(error),
1058                        message: format!(
1059                            "{error_message} while paying invoice with contract id {contract_id}"
1060                        ),
1061                    }));
1062                }
1063                GatewayExtPayStates::Canceled { error } => {
1064                    return Err(PublicGatewayError::LNv1(LNv1Error::OutgoingContract { error: Box::new(error.clone()), message: format!("Cancelled with {error} while paying invoice with contract id {contract_id}") }));
1065                }
1066                GatewayExtPayStates::Created => {
1067                    debug!("Got initial state Created while paying invoice: {contract_id}");
1068                }
1069                other => {
1070                    info!("Got state {other:?} while paying invoice: {contract_id}");
1071                }
1072            };
1073        }
1074
1075        Err(PublicGatewayError::LNv1(LNv1Error::OutgoingPayment(
1076            anyhow!("Ran out of state updates while paying invoice"),
1077        )))
1078    }
1079
1080    /// Handles a connection request to join a new federation. The gateway will
1081    /// download the federation's client configuration, construct a new
1082    /// client, registers, the gateway with the federation, and persists the
1083    /// necessary config to reconstruct the client when restarting the gateway.
1084    pub async fn handle_connect_federation(
1085        &self,
1086        payload: ConnectFedPayload,
1087    ) -> AdminResult<FederationInfo> {
1088        let GatewayState::Running { lightning_context } = self.get_state().await else {
1089            return Err(AdminGatewayError::Lightning(
1090                LightningRpcError::FailedToConnect,
1091            ));
1092        };
1093
1094        let invite_code = InviteCode::from_str(&payload.invite_code).map_err(|e| {
1095            AdminGatewayError::ClientCreationError(anyhow!(format!(
1096                "Invalid federation member string {e:?}"
1097            )))
1098        })?;
1099
1100        #[cfg(feature = "tor")]
1101        let connector = match &payload.use_tor {
1102            Some(true) => Connector::tor(),
1103            Some(false) => Connector::default(),
1104            None => {
1105                info!("Missing `use_tor` payload field, defaulting to `Connector::Tcp` variant!");
1106                Connector::default()
1107            }
1108        };
1109
1110        #[cfg(not(feature = "tor"))]
1111        let connector = Connector::default();
1112
1113        let federation_id = invite_code.federation_id();
1114
1115        if federation_id.to_prefix().to_bytes().starts_with(&[0x04]) {
1116            return Err(AdminGatewayError::Unexpected(anyhow!("gatewayd v0.5 cannot join federation {federation_id}, please upgrade to gatewayd v0.6")));
1117        }
1118
1119        let mut federation_manager = self.federation_manager.write().await;
1120
1121        // Check if this federation has already been registered
1122        if federation_manager.has_federation(federation_id) {
1123            return Err(AdminGatewayError::ClientCreationError(anyhow!(
1124                "Federation has already been registered"
1125            )));
1126        }
1127
1128        // `GatewayConfiguration` should always exist in the database when we are in the
1129        // `Running` state.
1130        let gateway_config = self
1131            .clone_gateway_config()
1132            .await
1133            .expect("Gateway configuration should be set");
1134
1135        // The gateway deterministically assigns a unique identifier (u64) to each
1136        // federation connected.
1137        let federation_index = federation_manager.pop_next_index()?;
1138
1139        let federation_config = FederationConfig {
1140            invite_code,
1141            federation_index,
1142            timelock_delta: 10,
1143            fees: gateway_config.routing_fees,
1144            connector,
1145        };
1146
1147        let mnemonic = Self::load_or_generate_mnemonic(&self.gateway_db).await?;
1148        let recover = payload.recover.unwrap_or(false);
1149        if recover {
1150            self.client_builder
1151                .recover(federation_config.clone(), Arc::new(self.clone()), &mnemonic)
1152                .await?;
1153        }
1154
1155        let client = self
1156            .client_builder
1157            .build(federation_config.clone(), Arc::new(self.clone()), &mnemonic)
1158            .await?;
1159
1160        if recover {
1161            client.wait_for_all_active_state_machines().await?;
1162        }
1163
1164        // Instead of using `FederationManager::federation_info`, we manually create
1165        // federation info here because short channel id is not yet persisted.
1166        let federation_info = FederationInfo {
1167            federation_id,
1168            federation_name: federation_manager.federation_name(&client).await,
1169            balance_msat: client.get_balance().await,
1170            federation_index,
1171            routing_fees: Some(gateway_config.routing_fees.into()),
1172        };
1173
1174        if self.is_running_lnv1() {
1175            Self::check_lnv1_federation_network(&client, gateway_config.network).await?;
1176            client
1177                .get_first_module::<GatewayClientModule>()?
1178                .try_register_with_federation(
1179                    // Route hints will be updated in the background
1180                    Vec::new(),
1181                    GW_ANNOUNCEMENT_TTL,
1182                    federation_config.fees,
1183                    lightning_context,
1184                )
1185                .await;
1186        }
1187
1188        if self.is_running_lnv2() {
1189            Self::check_lnv2_federation_network(&client, gateway_config.network).await?;
1190        }
1191
1192        // no need to enter span earlier, because connect-fed has a span
1193        federation_manager.add_client(
1194            federation_index,
1195            Spanned::new(
1196                info_span!("client", federation_id=%federation_id.clone()),
1197                async { client },
1198            )
1199            .await,
1200        );
1201
1202        let mut dbtx = self.gateway_db.begin_transaction().await;
1203        dbtx.save_federation_config(&federation_config).await;
1204        dbtx.commit_tx().await;
1205        debug!("Federation with ID: {federation_id} connected and assigned federation index: {federation_index}");
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 mnemonic = Self::load_or_generate_mnemonic(&self.gateway_db).await?;
1259        let words = 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    /// Handle a request to change a connected federation's configuration or
1281    /// gateway metadata. If `num_route_hints` is changed, the Gateway
1282    /// will re-register with all connected federations. If
1283    /// `per_federation_routing_fees` is changed, the Gateway will only
1284    /// re-register with the specified federation.
1285    pub async fn handle_set_configuration_msg(
1286        &self,
1287        SetConfigurationPayload {
1288            password,
1289            network,
1290            num_route_hints,
1291            routing_fees,
1292            per_federation_routing_fees,
1293        }: SetConfigurationPayload,
1294    ) -> AdminResult<()> {
1295        let gw_state = self.get_state().await;
1296        let lightning_network = match gw_state {
1297            GatewayState::Running { lightning_context } => {
1298                if network.is_some() && network != Some(lightning_context.lightning_network) {
1299                    return Err(AdminGatewayError::GatewayConfigurationError(
1300                        "Cannot change network while connected to a lightning node".to_string(),
1301                    ));
1302                }
1303                lightning_context.lightning_network
1304            }
1305            // In the case the gateway is not yet running and not yet connected to a lightning node,
1306            // we start off with a default network configuration. This default gets replaced later
1307            // when the gateway connects to a lightning node, or when a user sets a different
1308            // configuration
1309            _ => DEFAULT_NETWORK,
1310        };
1311
1312        let mut dbtx = self.gateway_db.begin_transaction().await;
1313
1314        let prev_gateway_config = self.clone_gateway_config().await;
1315        let new_gateway_config = if let Some(mut prev_config) = prev_gateway_config {
1316            if let Some(password) = password.as_ref() {
1317                let hashed_password = hash_password(password, prev_config.password_salt);
1318                prev_config.hashed_password = hashed_password;
1319            }
1320
1321            if let Some(network) = network {
1322                if !self.federation_manager.read().await.is_empty() {
1323                    return Err(AdminGatewayError::GatewayConfigurationError(
1324                        "Cannot change network while connected to a federation".to_string(),
1325                    ));
1326                }
1327                prev_config.network = network;
1328            }
1329
1330            if let Some(num_route_hints) = num_route_hints {
1331                prev_config.num_route_hints = num_route_hints;
1332            }
1333
1334            // Using this routing fee config as a default for all federation that has none
1335            // routing fees specified.
1336            if let Some(fees) = routing_fees {
1337                let routing_fees = GatewayFee(fees.into()).0;
1338                prev_config.routing_fees = routing_fees;
1339            }
1340
1341            prev_config
1342        } else {
1343            let password = password.ok_or(AdminGatewayError::GatewayConfigurationError(
1344                "The password field is required when initially configuring the gateway".to_string(),
1345            ))?;
1346            let password_salt: [u8; 16] = rand::thread_rng().gen();
1347            let hashed_password = hash_password(&password, password_salt);
1348
1349            GatewayConfiguration {
1350                hashed_password,
1351                network: lightning_network,
1352                num_route_hints: DEFAULT_NUM_ROUTE_HINTS,
1353                routing_fees: DEFAULT_FEES,
1354                password_salt,
1355            }
1356        };
1357        dbtx.set_gateway_config(&new_gateway_config).await;
1358
1359        let mut register_federations: Vec<(FederationId, FederationConfig)> = Vec::new();
1360        if let Some(per_federation_routing_fees) = per_federation_routing_fees {
1361            for (federation_id, routing_fees) in &per_federation_routing_fees {
1362                if let Some(mut federation_config) =
1363                    dbtx.load_federation_config(*federation_id).await
1364                {
1365                    federation_config.fees = routing_fees.clone().into();
1366                    dbtx.save_federation_config(&federation_config).await;
1367                    register_federations.push((*federation_id, federation_config));
1368                } else {
1369                    warn!("Given federation {federation_id} not found for updating routing fees");
1370                }
1371            }
1372        }
1373
1374        // If 'num_route_hints' is provided, all federations must be re-registered.
1375        // Otherwise, only those affected by the new fees need to be re-registered.
1376        let register_task_group = TaskGroup::new();
1377        if num_route_hints.is_some() {
1378            let all_federations_configs: Vec<_> =
1379                dbtx.load_federation_configs().await.into_iter().collect();
1380            self.register_federations(
1381                &new_gateway_config,
1382                &all_federations_configs,
1383                &register_task_group,
1384            )
1385            .await;
1386        } else {
1387            self.register_federations(
1388                &new_gateway_config,
1389                &register_federations,
1390                &register_task_group,
1391            )
1392            .await;
1393        }
1394
1395        dbtx.commit_tx().await;
1396
1397        let mut curr_gateway_config = self.gateway_config.write().await;
1398        *curr_gateway_config = Some(new_gateway_config.clone());
1399
1400        info!("Set GatewayConfiguration successfully.");
1401
1402        Ok(())
1403    }
1404
1405    /// Generates an onchain address to fund the gateway's lightning node.
1406    pub async fn handle_get_ln_onchain_address_msg(&self) -> AdminResult<Address> {
1407        let context = self.get_lightning_context().await?;
1408        let response = context.lnrpc.get_ln_onchain_address().await?;
1409        Address::from_str(&response.address)
1410            .map(Address::assume_checked)
1411            .map_err(|e| {
1412                AdminGatewayError::Lightning(LightningRpcError::InvalidMetadata {
1413                    failure_reason: e.to_string(),
1414                })
1415            })
1416    }
1417
1418    /// Instructs the Gateway's Lightning node to open a channel to a peer
1419    /// specified by `pubkey`.
1420    pub async fn handle_open_channel_msg(&self, payload: OpenChannelPayload) -> AdminResult<Txid> {
1421        let context = self.get_lightning_context().await?;
1422        let res = context.lnrpc.open_channel(payload).await?;
1423        Txid::from_str(&res.funding_txid).map_err(|e| {
1424            AdminGatewayError::Lightning(LightningRpcError::InvalidMetadata {
1425                failure_reason: format!("Received invalid channel funding txid string {e}"),
1426            })
1427        })
1428    }
1429
1430    /// Instructs the Gateway's Lightning node to close all channels with a peer
1431    /// specified by `pubkey`.
1432    pub async fn handle_close_channels_with_peer_msg(
1433        &self,
1434        payload: CloseChannelsWithPeerPayload,
1435    ) -> AdminResult<CloseChannelsWithPeerResponse> {
1436        let context = self.get_lightning_context().await?;
1437        let response = context.lnrpc.close_channels_with_peer(payload).await?;
1438        Ok(response)
1439    }
1440
1441    /// Returns a list of Lightning network channels from the Gateway's
1442    /// Lightning node.
1443    pub async fn handle_list_active_channels_msg(
1444        &self,
1445    ) -> AdminResult<Vec<lightning::ChannelInfo>> {
1446        let context = self.get_lightning_context().await?;
1447        let channels = context.lnrpc.list_active_channels().await?;
1448        Ok(channels)
1449    }
1450
1451    /// Send funds from the gateway's lightning node on-chain wallet.
1452    pub async fn handle_send_onchain_msg(&self, payload: SendOnchainPayload) -> AdminResult<Txid> {
1453        let context = self.get_lightning_context().await?;
1454        let response = context.lnrpc.send_onchain(payload).await?;
1455        Txid::from_str(&response.txid).map_err(|e| AdminGatewayError::WithdrawError {
1456            failure_reason: format!("Failed to parse withdrawal TXID: {e}"),
1457        })
1458    }
1459
1460    /// Returns the ecash, lightning, and onchain balances for the gateway and
1461    /// the gateway's lightning node.
1462    pub async fn handle_get_balances_msg(&self) -> AdminResult<GatewayBalances> {
1463        let dbtx = self.gateway_db.begin_transaction_nc().await;
1464        let federation_infos = self
1465            .federation_manager
1466            .read()
1467            .await
1468            .federation_info_all_federations(dbtx)
1469            .await;
1470
1471        let ecash_balances: Vec<FederationBalanceInfo> = federation_infos
1472            .iter()
1473            .map(|federation_info| FederationBalanceInfo {
1474                federation_id: federation_info.federation_id,
1475                ecash_balance_msats: Amount {
1476                    msats: federation_info.balance_msat.msats,
1477                },
1478            })
1479            .collect();
1480
1481        let context = self.get_lightning_context().await?;
1482        let lightning_node_balances = context.lnrpc.get_balances().await?;
1483
1484        Ok(GatewayBalances {
1485            onchain_balance_sats: lightning_node_balances.onchain_balance_sats,
1486            lightning_balance_msats: lightning_node_balances.lightning_balance_msats,
1487            ecash_balances,
1488            inbound_lightning_liquidity_msats: lightning_node_balances
1489                .inbound_lightning_liquidity_msats,
1490        })
1491    }
1492
1493    // Handles a request the spend the gateway's ecash for a given federation.
1494    pub async fn handle_spend_ecash_msg(
1495        &self,
1496        payload: SpendEcashPayload,
1497    ) -> AdminResult<SpendEcashResponse> {
1498        let client = self
1499            .select_client(payload.federation_id)
1500            .await?
1501            .into_value();
1502        let mint_module = client.get_first_module::<MintClientModule>()?;
1503        let timeout = Duration::from_secs(payload.timeout);
1504        let (operation_id, notes) = if payload.allow_overpay {
1505            let (operation_id, notes) = mint_module
1506                .spend_notes_with_selector(
1507                    &SelectNotesWithAtleastAmount,
1508                    payload.amount,
1509                    timeout,
1510                    payload.include_invite,
1511                    (),
1512                )
1513                .await?;
1514
1515            let overspend_amount = notes.total_amount() - payload.amount;
1516            if overspend_amount != Amount::ZERO {
1517                warn!(
1518                    "Selected notes {} worth more than requested",
1519                    overspend_amount
1520                );
1521            }
1522
1523            (operation_id, notes)
1524        } else {
1525            mint_module
1526                .spend_notes_with_selector(
1527                    &SelectNotesWithExactAmount,
1528                    payload.amount,
1529                    timeout,
1530                    payload.include_invite,
1531                    (),
1532                )
1533                .await?
1534        };
1535
1536        info!("Spend ecash operation id: {:?}", operation_id);
1537        info!("Spend ecash notes: {:?}", notes);
1538
1539        Ok(SpendEcashResponse {
1540            operation_id,
1541            notes,
1542        })
1543    }
1544
1545    /// Handles a request to receive ecash into the gateway.
1546    pub async fn handle_receive_ecash_msg(
1547        &self,
1548        payload: ReceiveEcashPayload,
1549    ) -> Result<ReceiveEcashResponse> {
1550        let amount = payload.notes.total_amount();
1551        let client = self
1552            .federation_manager
1553            .read()
1554            .await
1555            .get_client_for_federation_id_prefix(payload.notes.federation_id_prefix())
1556            .ok_or(FederationNotConnected {
1557                federation_id_prefix: payload.notes.federation_id_prefix(),
1558            })?;
1559        let mint = client
1560            .value()
1561            .get_first_module::<MintClientModule>()
1562            .map_err(|e| PublicGatewayError::ReceiveEcashError {
1563                failure_reason: format!("Mint module does not exist: {e:?}"),
1564            })?;
1565
1566        let operation_id = mint
1567            .reissue_external_notes(payload.notes, ())
1568            .await
1569            .map_err(|e| PublicGatewayError::ReceiveEcashError {
1570                failure_reason: e.to_string(),
1571            })?;
1572        if payload.wait {
1573            let mut updates = mint
1574                .subscribe_reissue_external_notes(operation_id)
1575                .await
1576                .unwrap()
1577                .into_stream();
1578
1579            while let Some(update) = updates.next().await {
1580                if let fedimint_mint_client::ReissueExternalNotesState::Failed(e) = update {
1581                    return Err(PublicGatewayError::ReceiveEcashError {
1582                        failure_reason: e.to_string(),
1583                    });
1584                }
1585            }
1586        }
1587
1588        Ok(ReceiveEcashResponse { amount })
1589    }
1590
1591    /// Instructs the gateway to shutdown, but only after all incoming payments
1592    /// have been handlded.
1593    pub async fn handle_shutdown_msg(&self, task_group: TaskGroup) -> AdminResult<()> {
1594        // Take the write lock on the state so that no additional payments are processed
1595        let mut state_guard = self.state.write().await;
1596        if let GatewayState::Running { lightning_context } = state_guard.clone() {
1597            *state_guard = GatewayState::ShuttingDown { lightning_context };
1598
1599            self.federation_manager
1600                .read()
1601                .await
1602                .wait_for_incoming_payments()
1603                .await?;
1604        }
1605
1606        let tg = task_group.clone();
1607        tg.spawn("Kill Gateway", |_task_handle| async {
1608            if let Err(e) = task_group.shutdown_join_all(Duration::from_secs(180)).await {
1609                error!(?e, "Error shutting down gateway");
1610            }
1611        });
1612        Ok(())
1613    }
1614
1615    /// Registers the gateway with each specified federation.
1616    async fn register_federations(
1617        &self,
1618        gateway_config: &GatewayConfiguration,
1619        federations: &[(FederationId, FederationConfig)],
1620        register_task_group: &TaskGroup,
1621    ) {
1622        if let Ok(lightning_context) = self.get_lightning_context().await {
1623            let route_hints = lightning_context
1624                .lnrpc
1625                .parsed_route_hints(gateway_config.num_route_hints)
1626                .await;
1627            if route_hints.is_empty() {
1628                warn!("Gateway did not retrieve any route hints, may reduce receive success rate.");
1629            }
1630
1631            for (federation_id, federation_config) in federations {
1632                let fed_manager = self.federation_manager.read().await;
1633                if let Some(client) = fed_manager.client(federation_id) {
1634                    let client_arc = client.clone().into_value();
1635                    let route_hints = route_hints.clone();
1636                    let lightning_context = lightning_context.clone();
1637                    let federation_config = federation_config.clone();
1638
1639                    if let Err(e) = register_task_group
1640                        .spawn_cancellable("register_federation", async move {
1641                            let gateway_client = client_arc
1642                                .get_first_module::<GatewayClientModule>()
1643                                .expect("No GatewayClientModule exists");
1644                            gateway_client
1645                                .try_register_with_federation(
1646                                    route_hints,
1647                                    GW_ANNOUNCEMENT_TTL,
1648                                    federation_config.fees,
1649                                    lightning_context,
1650                                )
1651                                .await;
1652                        })
1653                        .await
1654                    {
1655                        warn!(?e, "Failed to shutdown register federation task");
1656                    }
1657                }
1658            }
1659        }
1660    }
1661
1662    /// This function will return a `GatewayConfiguration` one of two
1663    /// ways. To avoid conflicting configs, the below order is the
1664    /// order in which the gateway will respect configurations:
1665    /// - `GatewayConfiguration` is read from the database.
1666    /// - All cli or environment variables are set such that we can create a
1667    ///   `GatewayConfiguration`
1668    async fn get_gateway_configuration(
1669        gateway_db: Database,
1670        gateway_parameters: &GatewayParameters,
1671    ) -> Option<GatewayConfiguration> {
1672        let mut dbtx = gateway_db.begin_transaction_nc().await;
1673
1674        // Always use the gateway configuration from the database if it exists.
1675        if let Some(gateway_config) = dbtx.load_gateway_config().await {
1676            return Some(gateway_config);
1677        }
1678
1679        // If the password is not provided, return None
1680        let password = gateway_parameters.password.as_ref()?;
1681
1682        // If the DB does not have the gateway configuration, we can construct one from
1683        // the provided password (required) and the defaults.
1684        // Use gateway parameters provided by the environment or CLI
1685        let num_route_hints = gateway_parameters.num_route_hints;
1686        let routing_fees = gateway_parameters
1687            .fees
1688            .clone()
1689            .unwrap_or(GatewayFee(DEFAULT_FEES));
1690        let network = gateway_parameters.network.unwrap_or(DEFAULT_NETWORK);
1691        let password_salt: [u8; 16] = rand::thread_rng().gen();
1692        let hashed_password = hash_password(password, password_salt);
1693        let gateway_config = GatewayConfiguration {
1694            hashed_password,
1695            network,
1696            num_route_hints,
1697            routing_fees: routing_fees.0,
1698            password_salt,
1699        };
1700
1701        Some(gateway_config)
1702    }
1703
1704    /// Retrieves a `ClientHandleArc` from the Gateway's in memory structures
1705    /// that keep track of available clients, given a `federation_id`.
1706    pub async fn select_client(
1707        &self,
1708        federation_id: FederationId,
1709    ) -> std::result::Result<Spanned<fedimint_client::ClientHandleArc>, FederationNotConnected>
1710    {
1711        self.federation_manager
1712            .read()
1713            .await
1714            .client(&federation_id)
1715            .cloned()
1716            .ok_or(FederationNotConnected {
1717                federation_id_prefix: federation_id.to_prefix(),
1718            })
1719    }
1720
1721    /// Loads a mnemonic from the database or generates a new one if the
1722    /// mnemonic does not exist. Before generating a new mnemonic, this
1723    /// function will check if a mnemonic has been provided in the environment
1724    /// variable and use that if provided.
1725    async fn load_or_generate_mnemonic(gateway_db: &Database) -> AdminResult<Mnemonic> {
1726        Ok(
1727            if let Ok(entropy) = Client::load_decodable_client_secret::<Vec<u8>>(gateway_db).await {
1728                Mnemonic::from_entropy(&entropy)
1729                    .map_err(|e| AdminGatewayError::MnemonicError(anyhow!(e.to_string())))?
1730            } else {
1731                let mnemonic = if let Ok(words) = std::env::var(FM_GATEWAY_MNEMONIC_ENV) {
1732                    info!("Using provided mnemonic from environment variable");
1733                    Mnemonic::parse_in_normalized(Language::English, words.as_str()).map_err(
1734                        |e| {
1735                            AdminGatewayError::MnemonicError(anyhow!(format!(
1736                                "Seed phrase provided in environment was invalid {e:?}"
1737                            )))
1738                        },
1739                    )?
1740                } else {
1741                    info!("Generating mnemonic and writing entropy to client storage");
1742                    Bip39RootSecretStrategy::<12>::random(&mut thread_rng())
1743                };
1744
1745                Client::store_encodable_client_secret(gateway_db, mnemonic.to_entropy())
1746                    .await
1747                    .map_err(AdminGatewayError::MnemonicError)?;
1748                mnemonic
1749            },
1750        )
1751    }
1752
1753    /// Reads the connected federation client configs from the Gateway's
1754    /// database and reconstructs the clients necessary for interacting with
1755    /// connection federations.
1756    async fn load_clients(&self) -> AdminResult<()> {
1757        let mut federation_manager = self.federation_manager.write().await;
1758
1759        let configs = {
1760            let mut dbtx = self.gateway_db.begin_transaction_nc().await;
1761            dbtx.load_federation_configs().await
1762        };
1763
1764        if let Some(max_federation_index) = configs.values().map(|cfg| cfg.federation_index).max() {
1765            federation_manager.set_next_index(max_federation_index + 1);
1766        }
1767
1768        let mnemonic = Self::load_or_generate_mnemonic(&self.gateway_db).await?;
1769
1770        for (federation_id, config) in configs {
1771            let federation_index = config.federation_index;
1772            if let Ok(client) = Box::pin(Spanned::try_new(
1773                info_span!("client", federation_id  = %federation_id.clone()),
1774                self.client_builder
1775                    .build(config, Arc::new(self.clone()), &mnemonic),
1776            ))
1777            .await
1778            {
1779                federation_manager.add_client(federation_index, client);
1780            } else {
1781                warn!("Failed to load client for federation: {federation_id}");
1782            }
1783        }
1784
1785        Ok(())
1786    }
1787
1788    /// Legacy mechanism for registering the Gateway with connected federations.
1789    /// This will spawn a task that will re-register the Gateway with
1790    /// connected federations every 8.5 mins. Only registers the Gateway if it
1791    /// has successfully connected to the Lightning node, so that it can
1792    /// include route hints in the registration.
1793    fn register_clients_timer(&self, task_group: &TaskGroup) {
1794        // Only spawn background registration thread if gateway is running LNv1
1795        if self.is_running_lnv1() {
1796            let lightning_module_mode = self.lightning_module_mode;
1797            info!(?lightning_module_mode, "Spawning register task...");
1798            let gateway = self.clone();
1799            let register_task_group = task_group.make_subgroup();
1800            task_group.spawn_cancellable("register clients", async move {
1801                loop {
1802                    let gateway_config = gateway.clone_gateway_config().await;
1803                    if let Some(gateway_config) = gateway_config {
1804                        let gateway_state = gateway.get_state().await;
1805                        if let GatewayState::Running { .. } = &gateway_state {
1806                            let mut dbtx = gateway.gateway_db.begin_transaction_nc().await;
1807                            let all_federations_configs: Vec<_> = dbtx.load_federation_configs().await.into_iter().collect();
1808                            gateway.register_federations(&gateway_config, &all_federations_configs, &register_task_group).await;
1809                        } else {
1810                            // We need to retry more often if the gateway is not in the Running state
1811                            const NOT_RUNNING_RETRY: Duration = Duration::from_secs(10);
1812                            info!("Will not register federation yet because gateway still not in Running state. Current state: {gateway_state:?}. Will keep waiting, next retry in {NOT_RUNNING_RETRY:?}...");
1813                            sleep(NOT_RUNNING_RETRY).await;
1814                            continue;
1815                        }
1816                    } else {
1817                        warn!("Cannot register clients because gateway configuration is not set.");
1818                    }
1819
1820                    // Allow a 15% buffer of the TTL before the re-registering gateway
1821                    // with the federations.
1822                    sleep(GW_ANNOUNCEMENT_TTL.mul_f32(0.85)).await;
1823                }
1824            });
1825        }
1826    }
1827
1828    /// Verifies that the supplied `network` matches the Bitcoin network in the
1829    /// connected client's LNv1 configuration.
1830    async fn check_lnv1_federation_network(
1831        client: &ClientHandleArc,
1832        network: Network,
1833    ) -> AdminResult<()> {
1834        let federation_id = client.federation_id();
1835        let config = client.config().await;
1836        let cfg = config
1837            .modules
1838            .values()
1839            .find(|m| LightningCommonInit::KIND == m.kind)
1840            .ok_or(AdminGatewayError::ClientCreationError(anyhow!(format!(
1841                "Federation {federation_id} does not have an LNv1 module"
1842            ))))?;
1843        let ln_cfg: &LightningClientConfig = cfg.cast()?;
1844
1845        if ln_cfg.network != network {
1846            error!(
1847                "Federation {federation_id} runs on {} but this gateway supports {network}",
1848                ln_cfg.network,
1849            );
1850            return Err(AdminGatewayError::ClientCreationError(anyhow!(format!(
1851                "Unsupported network {}",
1852                ln_cfg.network
1853            ))));
1854        }
1855
1856        Ok(())
1857    }
1858
1859    /// Verifies that the supplied `network` matches the Bitcoin network in the
1860    /// connected client's LNv2 configuration.
1861    async fn check_lnv2_federation_network(
1862        client: &ClientHandleArc,
1863        network: Network,
1864    ) -> AdminResult<()> {
1865        let federation_id = client.federation_id();
1866        let config = client.config().await;
1867        let cfg = config
1868            .modules
1869            .values()
1870            .find(|m| fedimint_lnv2_common::LightningCommonInit::KIND == m.kind)
1871            .ok_or(AdminGatewayError::ClientCreationError(anyhow!(format!(
1872                "Federation {federation_id} does not have an LNv2 module"
1873            ))))?;
1874        let ln_cfg: &fedimint_lnv2_common::config::LightningClientConfig = cfg.cast()?;
1875
1876        if ln_cfg.network.0 != network {
1877            error!(
1878                "Federation {federation_id} runs on {} but this gateway supports {network}",
1879                ln_cfg.network.0,
1880            );
1881            return Err(AdminGatewayError::ClientCreationError(anyhow!(format!(
1882                "Unsupported network {}",
1883                ln_cfg.network.0
1884            ))));
1885        }
1886
1887        Ok(())
1888    }
1889
1890    /// Checks the Gateway's current state and returns the proper
1891    /// `LightningContext` if it is available. Sometimes the lightning node
1892    /// will not be connected and this will return an error.
1893    pub async fn get_lightning_context(
1894        &self,
1895    ) -> std::result::Result<LightningContext, LightningRpcError> {
1896        match self.get_state().await {
1897            GatewayState::Running { lightning_context }
1898            | GatewayState::ShuttingDown { lightning_context } => Ok(lightning_context),
1899            _ => Err(LightningRpcError::FailedToConnect),
1900        }
1901    }
1902
1903    /// Iterates through all of the federations the gateway is registered with
1904    /// and requests to remove the registration record.
1905    pub async fn unannounce_from_all_federations(&self) {
1906        let mut dbtx = self.gateway_db.begin_transaction_nc().await;
1907        let gateway_keypair = dbtx.load_gateway_keypair_assert_exists().await;
1908
1909        self.federation_manager
1910            .read()
1911            .await
1912            .unannounce_from_all_federations(gateway_keypair)
1913            .await;
1914    }
1915}
1916
1917// LNv2 Gateway implementation
1918impl Gateway {
1919    /// Retrieves the `PublicKey` of the Gateway module for a given federation
1920    /// for LNv2. This is NOT the same as the `gateway_id`, it is different
1921    /// per-connected federation.
1922    async fn public_key_v2(&self, federation_id: &FederationId) -> Option<PublicKey> {
1923        self.federation_manager
1924            .read()
1925            .await
1926            .client(federation_id)
1927            .map(|client| {
1928                client
1929                    .value()
1930                    .get_first_module::<GatewayClientModuleV2>()
1931                    .expect("Must have client module")
1932                    .keypair
1933                    .public_key()
1934            })
1935    }
1936
1937    /// Returns payment information that LNv2 clients can use to instruct this
1938    /// Gateway to pay an invoice or receive a payment.
1939    pub async fn routing_info_v2(
1940        &self,
1941        federation_id: &FederationId,
1942    ) -> Result<Option<RoutingInfo>> {
1943        let context = self.get_lightning_context().await?;
1944
1945        Ok(self
1946            .public_key_v2(federation_id)
1947            .await
1948            .map(|module_public_key| RoutingInfo {
1949                lightning_public_key: context.lightning_public_key,
1950                module_public_key,
1951                send_fee_default: PaymentFee::SEND_FEE_LIMIT,
1952                // The base fee ensures that the gateway does not loose sats sending the payment due
1953                // to fees paid on the transaction claiming the outgoing contract or
1954                // subsequent transactions spending the newly issued ecash
1955                send_fee_minimum: PaymentFee {
1956                    base: Amount::from_sats(50),
1957                    parts_per_million: 5_000,
1958                },
1959                expiration_delta_default: 1440,
1960                expiration_delta_minimum: EXPIRATION_DELTA_MINIMUM_V2,
1961                // The base fee ensures that the gateway does not loose sats receiving the payment
1962                // due to fees paid on the transaction funding the incoming contract
1963                receive_fee: PaymentFee::RECEIVE_FEE_LIMIT,
1964            }))
1965    }
1966
1967    /// Instructs this gateway to pay a Lightning network invoice via the LNv2
1968    /// protocol.
1969    async fn send_payment_v2(
1970        &self,
1971        payload: SendPaymentPayload,
1972    ) -> Result<std::result::Result<[u8; 32], Signature>> {
1973        self.select_client(payload.federation_id)
1974            .await?
1975            .value()
1976            .get_first_module::<GatewayClientModuleV2>()
1977            .expect("Must have client module")
1978            .send_payment(payload)
1979            .await
1980            .map_err(LNv2Error::OutgoingPayment)
1981            .map_err(PublicGatewayError::LNv2)
1982    }
1983
1984    /// For the LNv2 protocol, this will create an invoice by fetching it from
1985    /// the connected Lightning node, then save the payment hash so that
1986    /// incoming lightning payments can be matched as a receive attempt to a
1987    /// specific federation.
1988    async fn create_bolt11_invoice_v2(
1989        &self,
1990        payload: CreateBolt11InvoicePayload,
1991    ) -> Result<Bolt11Invoice> {
1992        if !payload.contract.verify() {
1993            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
1994                "The contract is invalid".to_string(),
1995            )));
1996        }
1997
1998        let payment_info = self.routing_info_v2(&payload.federation_id).await?.ok_or(
1999            LNv2Error::IncomingPayment(format!(
2000                "Federation {} does not exist",
2001                payload.federation_id
2002            )),
2003        )?;
2004
2005        if payload.contract.commitment.refund_pk != payment_info.module_public_key {
2006            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2007                "The incoming contract is keyed to another gateway".to_string(),
2008            )));
2009        }
2010
2011        let contract_amount = payment_info.receive_fee.subtract_from(payload.amount.msats);
2012
2013        if contract_amount == Amount::ZERO {
2014            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2015                "Zero amount incoming contracts are not supported".to_string(),
2016            )));
2017        }
2018
2019        if contract_amount != payload.contract.commitment.amount {
2020            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2021                "The contract amount does not pay the correct amount of fees".to_string(),
2022            )));
2023        }
2024
2025        if payload.contract.commitment.expiration <= duration_since_epoch().as_secs() {
2026            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2027                "The contract has already expired".to_string(),
2028            )));
2029        }
2030
2031        let payment_hash = match payload.contract.commitment.payment_image {
2032            PaymentImage::Hash(payment_hash) => payment_hash,
2033            PaymentImage::Point(..) => {
2034                return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2035                    "PaymentImage is not a payment hash".to_string(),
2036                )));
2037            }
2038        };
2039
2040        let invoice = self
2041            .create_invoice_via_lnrpc_v2(
2042                payment_hash,
2043                payload.amount,
2044                payload.description.clone(),
2045                payload.expiry_secs,
2046            )
2047            .await?;
2048
2049        let mut dbtx = self.gateway_db.begin_transaction().await;
2050
2051        if dbtx
2052            .save_registered_incoming_contract(
2053                payload.federation_id,
2054                payload.amount,
2055                payload.contract,
2056            )
2057            .await
2058            .is_some()
2059        {
2060            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2061                "PaymentHash is already registered".to_string(),
2062            )));
2063        }
2064
2065        dbtx.commit_tx_result().await.map_err(|_| {
2066            PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2067                "Payment hash is already registered".to_string(),
2068            ))
2069        })?;
2070
2071        Ok(invoice)
2072    }
2073
2074    /// Retrieves a BOLT11 invoice from the connected Lightning node with a
2075    /// specific `payment_hash`.
2076    pub async fn create_invoice_via_lnrpc_v2(
2077        &self,
2078        payment_hash: sha256::Hash,
2079        amount: Amount,
2080        description: Bolt11InvoiceDescription,
2081        expiry_time: u32,
2082    ) -> std::result::Result<Bolt11Invoice, LightningRpcError> {
2083        let lnrpc = self.get_lightning_context().await?.lnrpc;
2084
2085        let response = match description {
2086            Bolt11InvoiceDescription::Direct(description) => {
2087                lnrpc
2088                    .create_invoice(CreateInvoiceRequest {
2089                        payment_hash: Some(payment_hash),
2090                        amount_msat: amount.msats,
2091                        expiry_secs: expiry_time,
2092                        description: Some(InvoiceDescription::Direct(description)),
2093                    })
2094                    .await?
2095            }
2096            Bolt11InvoiceDescription::Hash(hash) => {
2097                lnrpc
2098                    .create_invoice(CreateInvoiceRequest {
2099                        payment_hash: Some(payment_hash),
2100                        amount_msat: amount.msats,
2101                        expiry_secs: expiry_time,
2102                        description: Some(InvoiceDescription::Hash(hash)),
2103                    })
2104                    .await?
2105            }
2106        };
2107
2108        Bolt11Invoice::from_str(&response.invoice).map_err(|e| {
2109            LightningRpcError::FailedToGetInvoice {
2110                failure_reason: e.to_string(),
2111            }
2112        })
2113    }
2114
2115    /// Retrieves the persisted `CreateInvoicePayload` from the database
2116    /// specified by the `payment_hash` and the `ClientHandleArc` specified
2117    /// by the payload's `federation_id`.
2118    pub async fn get_registered_incoming_contract_and_client_v2(
2119        &self,
2120        payment_image: PaymentImage,
2121        amount_msats: u64,
2122    ) -> Result<(IncomingContract, ClientHandleArc)> {
2123        let registered_incoming_contract = self
2124            .gateway_db
2125            .begin_transaction_nc()
2126            .await
2127            .load_registered_incoming_contract(payment_image)
2128            .await
2129            .ok_or(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2130                "No corresponding decryption contract available".to_string(),
2131            )))?;
2132
2133        if registered_incoming_contract.incoming_amount_msats != amount_msats {
2134            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2135                "The available decryption contract's amount is not equal to the requested amount"
2136                    .to_string(),
2137            )));
2138        }
2139
2140        let client = self
2141            .select_client(registered_incoming_contract.federation_id)
2142            .await?
2143            .into_value();
2144
2145        Ok((registered_incoming_contract.contract, client))
2146    }
2147
2148    /// Helper function for determining if the gateway supports LNv2.
2149    fn is_running_lnv2(&self) -> bool {
2150        self.lightning_module_mode == LightningModuleMode::LNv2
2151            || self.lightning_module_mode == LightningModuleMode::All
2152    }
2153
2154    /// Helper function for determining if the gateway supports LNv1.
2155    fn is_running_lnv1(&self) -> bool {
2156        self.lightning_module_mode == LightningModuleMode::LNv1
2157            || self.lightning_module_mode == LightningModuleMode::All
2158    }
2159}