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