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