1pub mod api;
2
3use std::collections::BTreeMap;
4use std::fmt::{Display, Formatter};
5use std::future::pending;
6use std::str::FromStr;
7use std::sync::Arc;
8use std::time::{Duration, SystemTime};
9
10use anyhow::bail;
11use api::{RecurringdApiError, RecurringdClient};
12use async_stream::stream;
13use bitcoin::hashes::sha256;
14use bitcoin::secp256k1::SECP256K1;
15use fedimint_client_module::OperationId;
16use fedimint_client_module::module::ClientContext;
17use fedimint_client_module::oplog::UpdateStreamOrOutcome;
18use fedimint_core::BitcoinHash;
19use fedimint_core::config::FederationId;
20use fedimint_core::core::ModuleKind;
21use fedimint_core::db::IDatabaseTransactionOpsCoreTyped;
22use fedimint_core::encoding::{
23 Decodable, DecodeError, Encodable, decode_field_from_finite_reader,
24 decode_legacy_system_time_from_finite_reader, encode_legacy_system_time, with_decoding_context,
25};
26use fedimint_core::module::registry::ModuleDecoderRegistry;
27use fedimint_core::secp256k1::{Keypair, PublicKey};
28use fedimint_core::task::sleep;
29use fedimint_core::util::{BoxFuture, FmtCompact, FmtCompactAnyhow, SafeUrl};
30use fedimint_derive_secret::ChildId;
31use fedimint_eventlog::{Event, EventKind, EventPersistence};
32use futures::StreamExt;
33use futures::future::select_all;
34use lightning_invoice::Bolt11Invoice;
35use serde::{Deserialize, Serialize};
36use thiserror::Error;
37use tokio::select;
38use tokio::sync::Notify;
39use tracing::{debug, trace, warn};
40
41use crate::db::{RecurringPaymentCodeKey, RecurringPaymentCodeKeyPrefix};
42use crate::receive::LightningReceiveError;
43use crate::{
44 LightningClientModule, LightningClientStateMachines, LightningOperationMeta,
45 LightningOperationMetaVariant, LnReceiveState, tweak_user_key, tweak_user_secret_key,
46};
47
48const LOG_CLIENT_RECURRING: &str = "fm::client::ln::recurring";
49
50impl LightningClientModule {
51 pub async fn register_recurring_payment_code(
52 &self,
53 protocol: RecurringPaymentProtocol,
54 recurringd_api: SafeUrl,
55 meta: &str,
56 ) -> Result<RecurringPaymentCodeEntry, RecurringdApiError> {
57 self.client_ctx
58 .module_db()
59 .autocommit(
60 |dbtx, _| {
61 let recurringd_api_inner = recurringd_api.clone();
62 let new_recurring_payment_code = self.new_recurring_payment_code.clone();
63 Box::pin(async move {
64 let next_idx = dbtx
65 .find_by_prefix_sorted_descending(&RecurringPaymentCodeKeyPrefix)
66 .await
67 .map(|(k, _)| k.derivation_idx)
68 .next()
69 .await
70 .map_or(0, |last_idx| last_idx + 1);
71
72 let payment_code_root_key = self.get_payment_code_root_key(next_idx);
73
74 let recurringd_client =
75 RecurringdClient::new(&recurringd_api_inner.clone());
76 let register_response = recurringd_client
77 .register_recurring_payment_code(
78 self.client_ctx
79 .get_config()
80 .await
81 .global
82 .calculate_federation_id(),
83 protocol,
84 crate::recurring::PaymentCodeRootKey(
85 payment_code_root_key.public_key(),
86 ),
87 meta,
88 )
89 .await?;
90
91 debug!(
92 target: LOG_CLIENT_RECURRING,
93 ?register_response,
94 "Registered recurring payment code"
95 );
96
97 let payment_code_entry = RecurringPaymentCodeEntry {
98 protocol,
99 root_keypair: payment_code_root_key,
100 code: register_response.recurring_payment_code,
101 recurringd_api: recurringd_api_inner,
102 last_derivation_index: 0,
103 creation_time: fedimint_core::time::now(),
104 meta: meta.to_owned(),
105 };
106 dbtx.insert_new_entry(
107 &crate::db::RecurringPaymentCodeKey {
108 derivation_idx: next_idx,
109 },
110 &payment_code_entry,
111 )
112 .await;
113 dbtx.on_commit(move || new_recurring_payment_code.notify_waiters());
114
115 Ok(payment_code_entry)
116 })
117 },
118 None,
119 )
120 .await
121 .map_err(|e| match e {
122 fedimint_core::db::AutocommitError::ClosureError { error, .. } => error,
123 fedimint_core::db::AutocommitError::CommitFailed { last_error, .. } => {
124 panic!("Commit failed: {last_error}")
125 }
126 })
127 }
128
129 pub async fn get_recurring_payment_codes(&self) -> Vec<(u64, RecurringPaymentCodeEntry)> {
130 Self::get_recurring_payment_codes_static(self.client_ctx.module_db()).await
131 }
132
133 pub async fn get_recurring_payment_codes_static(
134 db: &fedimint_core::db::Database,
135 ) -> Vec<(u64, RecurringPaymentCodeEntry)> {
136 assert!(!db.is_global(), "Needs to run in module context");
137 db.begin_transaction_nc()
138 .await
139 .find_by_prefix(&RecurringPaymentCodeKeyPrefix)
140 .await
141 .map(|(idx, entry)| (idx.derivation_idx, entry))
142 .collect()
143 .await
144 }
145
146 fn get_payment_code_root_key(&self, payment_code_registration_idx: u64) -> Keypair {
147 self.recurring_payment_code_secret
148 .child_key(ChildId(payment_code_registration_idx))
149 .to_secp_key(&self.secp)
150 }
151
152 pub async fn scan_recurring_payment_code_invoices(
153 client: ClientContext<Self>,
154 new_code_registered: Arc<Notify>,
155 ) {
156 const QUERY_RETRY_DELAY: Duration = Duration::from_mins(1);
157
158 loop {
159 let new_code_registered_future = new_code_registered.notified();
163
164 let all_recurring_invoice_futures = Self::get_recurring_payment_codes_static(client.module_db())
166 .await
167 .into_iter()
168 .map(|(payment_code_idx, payment_code)| Box::pin(async move {
169 let client = RecurringdClient::new(&payment_code.recurringd_api.clone());
170 let invoice_index = payment_code.last_derivation_index + 1;
171
172 trace!(
173 target: LOG_CLIENT_RECURRING,
174 root_key=?payment_code.root_keypair.public_key(),
175 %invoice_index,
176 server=%payment_code.recurringd_api,
177 "Waiting for new invoice from recurringd"
178 );
179
180 match client.await_new_invoice(crate::recurring::PaymentCodeRootKey(payment_code.root_keypair.public_key()), invoice_index).await {
181 Ok(invoice) => {Ok((payment_code_idx, payment_code, invoice_index, invoice))}
182 Err(err) => {
183 debug!(
184 target: LOG_CLIENT_RECURRING,
185 err=%err.fmt_compact(),
186 root_key=?payment_code.root_keypair.public_key(),
187 invoice_index=%invoice_index,
188 server=%payment_code.recurringd_api,
189 "Failed querying recurring payment code invoice, will retry in {:?}",
190 QUERY_RETRY_DELAY,
191 );
192 sleep(QUERY_RETRY_DELAY).await;
193 Err(err)
194 }
195 }
196 }))
197 .collect::<Vec<_>>();
198
199 let await_any_invoice: BoxFuture<_> = if all_recurring_invoice_futures.is_empty() {
201 Box::pin(pending())
202 } else {
203 Box::pin(select_all(all_recurring_invoice_futures))
204 };
205
206 let (payment_code_idx, _payment_code, invoice_idx, invoice) = select! {
207 (ret, _, _) = await_any_invoice => match ret {
208 Ok(ret) => ret,
209 Err(_) => {
210 continue;
211 }
212 },
213 () = new_code_registered_future => {
214 continue;
215 }
216 };
217
218 Self::process_recurring_payment_code_invoice(
219 &client,
220 payment_code_idx,
221 invoice_idx,
222 invoice,
223 )
224 .await;
225
226 sleep(Duration::from_secs(1)).await;
228 }
229 }
230
231 async fn process_recurring_payment_code_invoice(
232 client: &ClientContext<Self>,
233 payment_code_idx: u64,
234 invoice_idx: u64,
235 invoice: lightning_invoice::Bolt11Invoice,
236 ) {
237 let mut dbtx = client.module_db().begin_transaction().await;
239 let old_payment_code_entry = dbtx
240 .get_value(&crate::db::RecurringPaymentCodeKey {
241 derivation_idx: payment_code_idx,
242 })
243 .await
244 .expect("We queried it, so it exists in our DB");
245
246 let new_payment_code_entry = RecurringPaymentCodeEntry {
247 last_derivation_index: invoice_idx,
248 ..old_payment_code_entry.clone()
249 };
250 dbtx.insert_entry(
251 &crate::db::RecurringPaymentCodeKey {
252 derivation_idx: payment_code_idx,
253 },
254 &new_payment_code_entry,
255 )
256 .await;
257
258 let mut dbtx_nc = dbtx.to_ref_nc();
262 if let Ok(operation_id) = Self::create_recurring_receive_operation(
263 client,
264 &mut dbtx_nc,
265 &old_payment_code_entry,
266 invoice_idx,
267 invoice,
268 )
269 .await
270 {
271 client
272 .log_event(
273 &mut dbtx_nc,
274 RecurringInvoiceCreatedEvent {
275 payment_code_idx,
276 invoice_idx,
277 operation_id,
278 },
279 )
280 .await;
281 } else {
282 debug_assert!(
283 false,
284 "Recurring invoice operation creation failed, this should never happen"
285 );
286 }
287 drop(dbtx_nc);
288
289 dbtx.commit_tx().await;
290 }
291
292 #[allow(clippy::pedantic)]
293 async fn create_recurring_receive_operation(
294 client: &ClientContext<Self>,
295 dbtx: &mut fedimint_core::db::DatabaseTransaction<'_>,
296 payment_code: &RecurringPaymentCodeEntry,
297 invoice_index: u64,
298 invoice: lightning_invoice::Bolt11Invoice,
299 ) -> anyhow::Result<OperationId> {
300 let invoice_key =
302 tweak_user_secret_key(SECP256K1, payment_code.root_keypair, invoice_index);
303
304 let operation_id = OperationId(*invoice.payment_hash().as_ref());
305 debug!(
306 target: LOG_CLIENT_RECURRING,
307 ?operation_id,
308 payment_code_key=?payment_code.root_keypair.public_key(),
309 invoice_index=%invoice_index,
310 "Creating recurring receive operation"
311 );
312 let ln_state =
313 LightningClientStateMachines::Receive(crate::receive::LightningReceiveStateMachine {
314 operation_id,
315 state: crate::receive::LightningReceiveStates::ConfirmedInvoice(
318 crate::receive::LightningReceiveConfirmedInvoice {
319 invoice: invoice.clone(),
320 receiving_key: crate::ReceivingKey::Personal(invoice_key),
321 },
322 ),
323 });
324
325 if let Err(e) = client
326 .manual_operation_start_dbtx(
327 dbtx,
328 operation_id,
329 "ln",
330 LightningOperationMeta {
331 variant: LightningOperationMetaVariant::RecurringPaymentReceive(
332 ReurringPaymentReceiveMeta {
333 payment_code_id: PaymentCodeRootKey(
334 payment_code.root_keypair.public_key(),
335 )
336 .to_payment_code_id(),
337 invoice,
338 },
339 ),
340 extra_meta: serde_json::Value::Null,
341 },
342 vec![client.make_dyn_state(ln_state)],
343 )
344 .await
345 {
346 warn!(
347 target: LOG_CLIENT_RECURRING,
348 ?operation_id,
349 payment_code_key=?payment_code.root_keypair.public_key(),
350 invoice_index=%invoice_index,
351 err = %e.fmt_compact_anyhow(),
352 "Failed to create recurring receive operation"
353 );
354 Err(e)
355 } else {
356 Ok(operation_id)
357 }
358 }
359
360 pub async fn subscribe_ln_recurring_receive(
361 &self,
362 operation_id: OperationId,
363 ) -> anyhow::Result<UpdateStreamOrOutcome<LnReceiveState>> {
364 let operation = self.client_ctx.get_operation(operation_id).await?;
365 let LightningOperationMetaVariant::RecurringPaymentReceive(ReurringPaymentReceiveMeta {
366 invoice,
367 ..
368 }) = operation.meta::<LightningOperationMeta>().variant
369 else {
370 bail!("Operation is not a recurring lightning receive")
371 };
372
373 let client_ctx = self.client_ctx.clone();
374
375 Ok(self.client_ctx.outcome_or_updates(&operation, operation_id, |state| match state {
376 LnReceiveState::Created
377 | LnReceiveState::WaitingForPayment { .. }
378 | LnReceiveState::Funded
379 | LnReceiveState::AwaitingFunds => false,
380 LnReceiveState::Canceled { .. } | LnReceiveState::Claimed => true,
381 }, move || {
382 stream! {
383 let self_ref = client_ctx.self_ref();
384
385 yield LnReceiveState::Created;
386 yield LnReceiveState::WaitingForPayment { invoice: invoice.to_string(), timeout: invoice.expiry_time() };
387
388 match self_ref.await_receive_success(operation_id).await {
389 Ok(()) => {
390 yield LnReceiveState::Funded;
391
392 if let Ok(out_points) = self_ref.await_claim_acceptance(operation_id).await {
393 yield LnReceiveState::AwaitingFunds;
394
395 if client_ctx.await_primary_module_outputs(operation_id, out_points).await.is_ok() {
396 yield LnReceiveState::Claimed;
397 return;
398 }
399 }
400
401 yield LnReceiveState::Canceled { reason: LightningReceiveError::Rejected };
402 }
403 Err(e) => {
404 yield LnReceiveState::Canceled { reason: e };
405 }
406 }
407 }
408 }))
409 }
410
411 pub async fn list_recurring_payment_codes(&self) -> BTreeMap<u64, RecurringPaymentCodeEntry> {
412 self.client_ctx
413 .module_db()
414 .begin_transaction_nc()
415 .await
416 .find_by_prefix(&RecurringPaymentCodeKeyPrefix)
417 .await
418 .map(|(idx, entry)| (idx.derivation_idx, entry))
419 .collect()
420 .await
421 }
422
423 pub async fn get_recurring_payment_code(
424 &self,
425 payment_code_idx: u64,
426 ) -> Option<RecurringPaymentCodeEntry> {
427 self.client_ctx
428 .module_db()
429 .begin_transaction_nc()
430 .await
431 .get_value(&RecurringPaymentCodeKey {
432 derivation_idx: payment_code_idx,
433 })
434 .await
435 }
436
437 pub async fn list_recurring_payment_code_invoices(
438 &self,
439 payment_code_idx: u64,
440 ) -> Option<BTreeMap<u64, OperationId>> {
441 let payment_code = self.get_recurring_payment_code(payment_code_idx).await?;
442
443 let operations = (1..=payment_code.last_derivation_index)
444 .map(|invoice_idx: u64| {
445 let invoice_key = tweak_user_key(
446 SECP256K1,
447 payment_code.root_keypair.public_key(),
448 invoice_idx,
449 );
450 let payment_hash =
451 sha256::Hash::hash(&sha256::Hash::hash(&invoice_key.serialize())[..]);
452 let operation_id = OperationId(*payment_hash.as_ref());
453
454 (invoice_idx, operation_id)
455 })
456 .collect();
457
458 Some(operations)
459 }
460}
461
462#[derive(
463 Debug,
464 Clone,
465 Copy,
466 PartialOrd,
467 Eq,
468 PartialEq,
469 Hash,
470 Encodable,
471 Decodable,
472 Serialize,
473 Deserialize,
474)]
475pub struct PaymentCodeRootKey(pub PublicKey);
476
477#[derive(
478 Debug,
479 Clone,
480 Copy,
481 PartialOrd,
482 Eq,
483 PartialEq,
484 Hash,
485 Encodable,
486 Decodable,
487 Serialize,
488 Deserialize,
489)]
490pub struct PaymentCodeId(sha256::Hash);
491
492impl PaymentCodeRootKey {
493 pub fn to_payment_code_id(&self) -> PaymentCodeId {
494 PaymentCodeId(sha256::Hash::hash(&self.0.serialize()))
495 }
496}
497
498impl Display for PaymentCodeId {
499 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
500 write!(f, "{}", self.0)
501 }
502}
503
504impl FromStr for PaymentCodeId {
505 type Err = anyhow::Error;
506
507 fn from_str(s: &str) -> Result<Self, Self::Err> {
508 Ok(Self(sha256::Hash::from_str(s)?))
509 }
510}
511
512impl Display for PaymentCodeRootKey {
513 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
514 write!(f, "{}", self.0)
515 }
516}
517
518impl FromStr for PaymentCodeRootKey {
519 type Err = anyhow::Error;
520
521 fn from_str(s: &str) -> Result<Self, Self::Err> {
522 Ok(Self(PublicKey::from_str(s)?))
523 }
524}
525
526#[derive(
527 Debug,
528 Clone,
529 Copy,
530 Eq,
531 PartialEq,
532 PartialOrd,
533 Hash,
534 Encodable,
535 Decodable,
536 Serialize,
537 Deserialize,
538)]
539pub enum RecurringPaymentProtocol {
540 LNURL,
541 BOLT12,
542}
543
544#[derive(Debug, Clone, Serialize, Deserialize)]
545pub struct ReurringPaymentReceiveMeta {
546 pub payment_code_id: PaymentCodeId,
547 pub invoice: Bolt11Invoice,
548}
549
550#[derive(Debug, Error)]
551pub enum RecurringPaymentError {
552 #[error("Unsupported protocol: {0:?}")]
553 UnsupportedProtocol(RecurringPaymentProtocol),
554 #[error("Unknown federation ID: {0}")]
555 UnknownFederationId(FederationId),
556 #[error("Unknown payment code: {0:?}")]
557 UnknownPaymentCode(PaymentCodeId),
558 #[error("Unknown lightning receive operation: {0:?}")]
559 UnknownInvoice(OperationId),
560 #[error("No compatible lightning module found")]
561 NoLightningModuleFound,
562 #[error("No gateway found")]
563 NoGatewayFound,
564 #[error("Payment code already exists with different settings: {0:?}")]
565 PaymentCodeAlreadyExists(PaymentCodeRootKey),
566 #[error("Federation already registered: {0}")]
567 FederationAlreadyRegistered(FederationId),
568 #[error("Error joining federation: {0}")]
569 JoiningFederationFailed(anyhow::Error),
570 #[error("Error registering with recurring payment service: {0}")]
571 Other(#[from] anyhow::Error),
572}
573
574#[derive(Debug, Clone, Serialize)]
575pub struct RecurringPaymentCodeEntry {
576 pub protocol: RecurringPaymentProtocol,
577 pub root_keypair: Keypair,
578 pub code: String,
579 pub recurringd_api: SafeUrl,
580 pub last_derivation_index: u64,
581 pub creation_time: SystemTime,
582 pub meta: String,
583}
584
585impl Encodable for RecurringPaymentCodeEntry {
586 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
587 self.protocol.consensus_encode(writer)?;
588 self.root_keypair.consensus_encode(writer)?;
589 self.code.consensus_encode(writer)?;
590 self.recurringd_api.consensus_encode(writer)?;
591 self.last_derivation_index.consensus_encode(writer)?;
592 encode_legacy_system_time(&self.creation_time, writer)?;
593 self.meta.consensus_encode(writer)
594 }
595}
596
597impl Decodable for RecurringPaymentCodeEntry {
598 fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
599 decoder: &mut D,
600 modules: &ModuleDecoderRegistry,
601 ) -> Result<Self, DecodeError> {
602 Ok(Self {
603 protocol: decode_field_from_finite_reader(
604 decoder,
605 modules,
606 "Decoding named block field: RecurringPaymentCodeEntry{ ... protocol ... }",
607 )?,
608 root_keypair: decode_field_from_finite_reader(
609 decoder,
610 modules,
611 "Decoding named block field: RecurringPaymentCodeEntry{ ... root_keypair ... }",
612 )?,
613 code: decode_field_from_finite_reader(
614 decoder,
615 modules,
616 "Decoding named block field: RecurringPaymentCodeEntry{ ... code ... }",
617 )?,
618 recurringd_api: decode_field_from_finite_reader(
619 decoder,
620 modules,
621 "Decoding named block field: RecurringPaymentCodeEntry{ ... recurringd_api ... }",
622 )?,
623 last_derivation_index: decode_field_from_finite_reader(
624 decoder,
625 modules,
626 "Decoding named block field: RecurringPaymentCodeEntry{ ... last_derivation_index ... }",
627 )?,
628 creation_time: with_decoding_context(
629 decode_legacy_system_time_from_finite_reader(decoder, modules),
630 "Decoding named block field: RecurringPaymentCodeEntry{ ... creation_time ... }",
631 )?,
632 meta: decode_field_from_finite_reader(
633 decoder,
634 modules,
635 "Decoding named block field: RecurringPaymentCodeEntry{ ... meta ... }",
636 )?,
637 })
638 }
639}
640
641#[derive(Debug, Clone, Serialize, Deserialize)]
649pub struct RecurringInvoiceCreatedEvent {
650 pub payment_code_idx: u64,
651 pub invoice_idx: u64,
652 pub operation_id: OperationId,
653}
654
655impl Event for RecurringInvoiceCreatedEvent {
656 const MODULE: Option<ModuleKind> = Some(fedimint_ln_common::KIND);
657 const KIND: EventKind = EventKind::from_static("recurring_invoice_created");
658 const PERSISTENCE: EventPersistence = EventPersistence::Persistent;
659}