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