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