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