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