Skip to main content

fedimint_ln_client/recurring/
mod.rs

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            // We have to register the waiter before querying the DB for recurring payment
160            // code registrations so we don't miss any notification between querying the DB
161            // and registering the notifier.
162            let new_code_registered_future = new_code_registered.notified();
163
164            // We wait for all recurring payment codes to have an invoice in parallel
165            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            // TODO: isn't there some shorthand for this
200            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            // Just in case something goes wrong, we don't want to burn too much CPU
227            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        // TODO: validate invoice hash etc.
238        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        // We want to increment the invoice counter even if the operation creation
259        // fails. This should never happen and if it does, we'd rather miss an invoice
260        // than get stuck in an infinite loop.
261        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        // TODO: pipe secure secp context to here
301        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                // TODO: technically we want a state that doesn't assume the offer was accepted
316                // since we haven't checked, but for an MVP this is good enough
317                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/// Event that is fired when a recurring payment code (i.e. LNURL) had an
642/// invoice generated for it.
643///
644/// It only means we saw a new invoice, the payment status has to be tracked
645/// independently. To do so use the `operation_id` and subscribe to the update
646/// stream using [`LightningClientModule::subscribe_ln_recurring_receive`] with
647/// it.
648#[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}