lib.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. //! CDK lightning backend for LND
  2. // Copyright (c) 2023 Steffen (MIT)
  3. #![warn(missing_docs)]
  4. #![warn(rustdoc::bare_urls)]
  5. use std::path::PathBuf;
  6. use std::pin::Pin;
  7. use std::str::FromStr;
  8. use std::sync::atomic::{AtomicBool, Ordering};
  9. use std::sync::Arc;
  10. use anyhow::anyhow;
  11. use async_trait::async_trait;
  12. use cdk::amount::{to_unit, Amount, MSAT_IN_SAT};
  13. use cdk::cdk_lightning::{
  14. self, CreateInvoiceResponse, MintLightning, PayInvoiceResponse, PaymentQuoteResponse, Settings,
  15. };
  16. use cdk::mint::FeeReserve;
  17. use cdk::nuts::{
  18. CurrencyUnit, MeltMethodSettings, MeltQuoteBolt11Request, MeltQuoteState, MintMethodSettings,
  19. MintQuoteState,
  20. };
  21. use cdk::util::{hex, unix_time};
  22. use cdk::{mint, Bolt11Invoice};
  23. use error::Error;
  24. use fedimint_tonic_lnd::lnrpc::fee_limit::Limit;
  25. use fedimint_tonic_lnd::lnrpc::payment::PaymentStatus;
  26. use fedimint_tonic_lnd::lnrpc::FeeLimit;
  27. use fedimint_tonic_lnd::Client;
  28. use futures::{Stream, StreamExt};
  29. use tokio::sync::Mutex;
  30. use tokio_util::sync::CancellationToken;
  31. pub mod error;
  32. /// Lnd mint backend
  33. #[derive(Clone)]
  34. pub struct Lnd {
  35. address: String,
  36. cert_file: PathBuf,
  37. macaroon_file: PathBuf,
  38. client: Arc<Mutex<Client>>,
  39. fee_reserve: FeeReserve,
  40. mint_settings: MintMethodSettings,
  41. melt_settings: MeltMethodSettings,
  42. wait_invoice_cancel_token: CancellationToken,
  43. wait_invoice_is_active: Arc<AtomicBool>,
  44. }
  45. impl Lnd {
  46. /// Create new [`Lnd`]
  47. pub async fn new(
  48. address: String,
  49. cert_file: PathBuf,
  50. macaroon_file: PathBuf,
  51. fee_reserve: FeeReserve,
  52. mint_settings: MintMethodSettings,
  53. melt_settings: MeltMethodSettings,
  54. ) -> Result<Self, Error> {
  55. let client = fedimint_tonic_lnd::connect(address.to_string(), &cert_file, &macaroon_file)
  56. .await
  57. .map_err(|err| {
  58. tracing::error!("Connection error: {}", err.to_string());
  59. Error::Connection
  60. })?;
  61. Ok(Self {
  62. address,
  63. cert_file,
  64. macaroon_file,
  65. client: Arc::new(Mutex::new(client)),
  66. fee_reserve,
  67. mint_settings,
  68. melt_settings,
  69. wait_invoice_cancel_token: CancellationToken::new(),
  70. wait_invoice_is_active: Arc::new(AtomicBool::new(false)),
  71. })
  72. }
  73. }
  74. #[async_trait]
  75. impl MintLightning for Lnd {
  76. type Err = cdk_lightning::Error;
  77. fn get_settings(&self) -> Settings {
  78. Settings {
  79. mpp: true,
  80. unit: CurrencyUnit::Msat,
  81. mint_settings: self.mint_settings,
  82. melt_settings: self.melt_settings,
  83. invoice_description: true,
  84. }
  85. }
  86. fn is_wait_invoice_active(&self) -> bool {
  87. self.wait_invoice_is_active.load(Ordering::SeqCst)
  88. }
  89. fn cancel_wait_invoice(&self) {
  90. self.wait_invoice_cancel_token.cancel()
  91. }
  92. async fn wait_any_invoice(
  93. &self,
  94. ) -> Result<Pin<Box<dyn Stream<Item = String> + Send>>, Self::Err> {
  95. let mut client =
  96. fedimint_tonic_lnd::connect(self.address.clone(), &self.cert_file, &self.macaroon_file)
  97. .await
  98. .map_err(|_| Error::Connection)?;
  99. let stream_req = fedimint_tonic_lnd::lnrpc::InvoiceSubscription {
  100. add_index: 0,
  101. settle_index: 0,
  102. };
  103. let stream = client
  104. .lightning()
  105. .subscribe_invoices(stream_req)
  106. .await
  107. .unwrap()
  108. .into_inner();
  109. let cancel_token = self.wait_invoice_cancel_token.clone();
  110. Ok(futures::stream::unfold(
  111. (
  112. stream,
  113. cancel_token,
  114. Arc::clone(&self.wait_invoice_is_active),
  115. ),
  116. |(mut stream, cancel_token, is_active)| async move {
  117. is_active.store(true, Ordering::SeqCst);
  118. tokio::select! {
  119. _ = cancel_token.cancelled() => {
  120. // Stream is cancelled
  121. is_active.store(false, Ordering::SeqCst);
  122. tracing::info!("Waiting for lnd invoice ending");
  123. None
  124. }
  125. msg = stream.message() => {
  126. match msg {
  127. Ok(Some(msg)) => {
  128. if msg.state == 1 {
  129. Some((hex::encode(msg.r_hash), (stream, cancel_token, is_active)))
  130. } else {
  131. None
  132. }
  133. }
  134. Ok(None) => {
  135. is_active.store(false, Ordering::SeqCst);
  136. tracing::info!("LND invoice stream ended.");
  137. None
  138. }, // End of stream
  139. Err(err) => {
  140. is_active.store(false, Ordering::SeqCst);
  141. tracing::warn!("Encounrdered error in LND invoice stream. Stream ending");
  142. tracing::error!("{:?}", err);
  143. None
  144. }, // Handle errors gracefully, ends the stream on error
  145. }
  146. }
  147. }
  148. },
  149. )
  150. .boxed())
  151. }
  152. async fn get_payment_quote(
  153. &self,
  154. melt_quote_request: &MeltQuoteBolt11Request,
  155. ) -> Result<PaymentQuoteResponse, Self::Err> {
  156. let invoice_amount_msat = melt_quote_request
  157. .request
  158. .amount_milli_satoshis()
  159. .ok_or(Error::UnknownInvoiceAmount)?;
  160. let amount = to_unit(
  161. invoice_amount_msat,
  162. &CurrencyUnit::Msat,
  163. &melt_quote_request.unit,
  164. )?;
  165. let relative_fee_reserve =
  166. (self.fee_reserve.percent_fee_reserve * u64::from(amount) as f32) as u64;
  167. let absolute_fee_reserve: u64 = self.fee_reserve.min_fee_reserve.into();
  168. let fee = match relative_fee_reserve > absolute_fee_reserve {
  169. true => relative_fee_reserve,
  170. false => absolute_fee_reserve,
  171. };
  172. Ok(PaymentQuoteResponse {
  173. request_lookup_id: melt_quote_request.request.payment_hash().to_string(),
  174. amount,
  175. fee: fee.into(),
  176. state: MeltQuoteState::Unpaid,
  177. })
  178. }
  179. async fn pay_invoice(
  180. &self,
  181. melt_quote: mint::MeltQuote,
  182. partial_amount: Option<Amount>,
  183. max_fee: Option<Amount>,
  184. ) -> Result<PayInvoiceResponse, Self::Err> {
  185. let payment_request = melt_quote.request;
  186. let pay_req = fedimint_tonic_lnd::lnrpc::SendRequest {
  187. payment_request,
  188. fee_limit: max_fee.map(|f| {
  189. let limit = Limit::Fixed(u64::from(f) as i64);
  190. FeeLimit { limit: Some(limit) }
  191. }),
  192. amt_msat: partial_amount
  193. .map(|a| {
  194. let msat = to_unit(a, &melt_quote.unit, &CurrencyUnit::Msat).unwrap();
  195. u64::from(msat) as i64
  196. })
  197. .unwrap_or_default(),
  198. ..Default::default()
  199. };
  200. let payment_response = self
  201. .client
  202. .lock()
  203. .await
  204. .lightning()
  205. .send_payment_sync(fedimint_tonic_lnd::tonic::Request::new(pay_req))
  206. .await
  207. .map_err(|_| Error::PaymentFailed)?
  208. .into_inner();
  209. let total_amount = payment_response
  210. .payment_route
  211. .map_or(0, |route| route.total_amt_msat / MSAT_IN_SAT as i64)
  212. as u64;
  213. let (status, payment_preimage) = match total_amount == 0 {
  214. true => (MeltQuoteState::Unpaid, None),
  215. false => (
  216. MeltQuoteState::Paid,
  217. Some(hex::encode(payment_response.payment_preimage)),
  218. ),
  219. };
  220. Ok(PayInvoiceResponse {
  221. payment_lookup_id: hex::encode(payment_response.payment_hash),
  222. payment_preimage,
  223. status,
  224. total_spent: total_amount.into(),
  225. unit: CurrencyUnit::Sat,
  226. })
  227. }
  228. async fn create_invoice(
  229. &self,
  230. amount: Amount,
  231. unit: &CurrencyUnit,
  232. description: String,
  233. unix_expiry: u64,
  234. ) -> Result<CreateInvoiceResponse, Self::Err> {
  235. let time_now = unix_time();
  236. assert!(unix_expiry > time_now);
  237. let amount = to_unit(amount, unit, &CurrencyUnit::Msat)?;
  238. let invoice_request = fedimint_tonic_lnd::lnrpc::Invoice {
  239. value_msat: u64::from(amount) as i64,
  240. memo: description,
  241. ..Default::default()
  242. };
  243. let invoice = self
  244. .client
  245. .lock()
  246. .await
  247. .lightning()
  248. .add_invoice(fedimint_tonic_lnd::tonic::Request::new(invoice_request))
  249. .await
  250. .unwrap()
  251. .into_inner();
  252. let bolt11 = Bolt11Invoice::from_str(&invoice.payment_request)?;
  253. Ok(CreateInvoiceResponse {
  254. request_lookup_id: bolt11.payment_hash().to_string(),
  255. request: bolt11,
  256. expiry: Some(unix_expiry),
  257. })
  258. }
  259. async fn check_incoming_invoice_status(
  260. &self,
  261. request_lookup_id: &str,
  262. ) -> Result<MintQuoteState, Self::Err> {
  263. let invoice_request = fedimint_tonic_lnd::lnrpc::PaymentHash {
  264. r_hash: hex::decode(request_lookup_id).unwrap(),
  265. ..Default::default()
  266. };
  267. let invoice = self
  268. .client
  269. .lock()
  270. .await
  271. .lightning()
  272. .lookup_invoice(fedimint_tonic_lnd::tonic::Request::new(invoice_request))
  273. .await
  274. .unwrap()
  275. .into_inner();
  276. match invoice.state {
  277. // Open
  278. 0 => Ok(MintQuoteState::Unpaid),
  279. // Settled
  280. 1 => Ok(MintQuoteState::Paid),
  281. // Canceled
  282. 2 => Ok(MintQuoteState::Unpaid),
  283. // Accepted
  284. 3 => Ok(MintQuoteState::Unpaid),
  285. _ => Err(Self::Err::Anyhow(anyhow!("Invalid status"))),
  286. }
  287. }
  288. async fn check_outgoing_payment(
  289. &self,
  290. payment_hash: &str,
  291. ) -> Result<PayInvoiceResponse, Self::Err> {
  292. let track_request = fedimint_tonic_lnd::routerrpc::TrackPaymentRequest {
  293. payment_hash: hex::decode(payment_hash).map_err(|_| Error::InvalidHash)?,
  294. no_inflight_updates: true,
  295. };
  296. let mut payment_stream = self
  297. .client
  298. .lock()
  299. .await
  300. .router()
  301. .track_payment_v2(track_request)
  302. .await
  303. .unwrap()
  304. .into_inner();
  305. while let Some(update_result) = payment_stream.next().await {
  306. match update_result {
  307. Ok(update) => {
  308. let status = update.status();
  309. let response = match status {
  310. PaymentStatus::Unknown => PayInvoiceResponse {
  311. payment_lookup_id: payment_hash.to_string(),
  312. payment_preimage: Some(update.payment_preimage),
  313. status: MeltQuoteState::Unknown,
  314. total_spent: Amount::ZERO,
  315. unit: self.get_settings().unit,
  316. },
  317. PaymentStatus::InFlight => {
  318. // Continue waiting for the next update
  319. continue;
  320. }
  321. PaymentStatus::Succeeded => PayInvoiceResponse {
  322. payment_lookup_id: payment_hash.to_string(),
  323. payment_preimage: Some(update.payment_preimage),
  324. status: MeltQuoteState::Paid,
  325. total_spent: Amount::from((update.value_sat + update.fee_sat) as u64),
  326. unit: CurrencyUnit::Sat,
  327. },
  328. PaymentStatus::Failed => PayInvoiceResponse {
  329. payment_lookup_id: payment_hash.to_string(),
  330. payment_preimage: Some(update.payment_preimage),
  331. status: MeltQuoteState::Failed,
  332. total_spent: Amount::ZERO,
  333. unit: self.get_settings().unit,
  334. },
  335. };
  336. return Ok(response);
  337. }
  338. Err(_) => {
  339. // Handle the case where the update itself is an error (e.g., stream failure)
  340. return Err(Error::UnknownPaymentStatus.into());
  341. }
  342. }
  343. }
  344. // If the stream is exhausted without a final status
  345. Err(Error::UnknownPaymentStatus.into())
  346. }
  347. }