fedimint_ln_client/
db.rs

1use std::io::Cursor;
2
3use bitcoin::hashes::sha256;
4use fedimint_core::core::OperationId;
5use fedimint_core::encoding::{Decodable, Encodable};
6use fedimint_core::module::registry::ModuleDecoderRegistry;
7use fedimint_core::secp256k1::{Keypair, PublicKey};
8use fedimint_core::{impl_db_lookup, impl_db_record, OutPoint, TransactionId};
9use fedimint_ln_common::{LightningGateway, LightningGatewayRegistration};
10use lightning_invoice::Bolt11Invoice;
11use serde::Serialize;
12use strum_macros::EnumIter;
13
14use crate::pay::lightningpay::LightningPayStates;
15use crate::pay::{
16    LightningPayCommon, LightningPayFunded, LightningPayRefund, LightningPayStateMachine,
17    PayInvoicePayload,
18};
19use crate::receive::{
20    LightningReceiveConfirmedInvoice, LightningReceiveStateMachine, LightningReceiveStates,
21    LightningReceiveSubmittedOffer, LightningReceiveSubmittedOfferV0,
22};
23use crate::{LightningClientStateMachines, OutgoingLightningPayment, ReceivingKey};
24
25#[repr(u8)]
26#[derive(Clone, EnumIter, Debug)]
27pub enum DbKeyPrefix {
28    // Deprecated
29    ActiveGateway = 0x28,
30    PaymentResult = 0x29,
31    MetaOverridesDeprecated = 0x30,
32    LightningGateway = 0x45,
33}
34
35impl std::fmt::Display for DbKeyPrefix {
36    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
37        write!(f, "{self:?}")
38    }
39}
40
41#[derive(Debug, Encodable, Decodable, Serialize)]
42pub struct ActiveGatewayKey;
43
44#[derive(Debug, Encodable, Decodable)]
45pub struct ActiveGatewayKeyPrefix;
46
47impl_db_record!(
48    key = ActiveGatewayKey,
49    value = LightningGatewayRegistration,
50    db_prefix = DbKeyPrefix::ActiveGateway,
51);
52impl_db_lookup!(
53    key = ActiveGatewayKey,
54    query_prefix = ActiveGatewayKeyPrefix
55);
56
57#[derive(Debug, Encodable, Decodable, Serialize)]
58pub struct PaymentResultKey {
59    pub payment_hash: sha256::Hash,
60}
61
62#[derive(Debug, Encodable, Decodable, Serialize)]
63pub struct PaymentResultPrefix;
64
65#[derive(Debug, Encodable, Decodable, Serialize)]
66pub struct PaymentResult {
67    pub index: u16,
68    pub completed_payment: Option<OutgoingLightningPayment>,
69}
70
71impl_db_record!(
72    key = PaymentResultKey,
73    value = PaymentResult,
74    db_prefix = DbKeyPrefix::PaymentResult,
75);
76
77impl_db_lookup!(key = PaymentResultKey, query_prefix = PaymentResultPrefix);
78
79#[derive(Debug, Encodable, Decodable, Serialize)]
80pub struct LightningGatewayKey(pub PublicKey);
81
82#[derive(Debug, Encodable, Decodable)]
83pub struct LightningGatewayKeyPrefix;
84
85impl_db_record!(
86    key = LightningGatewayKey,
87    value = LightningGatewayRegistration,
88    db_prefix = DbKeyPrefix::LightningGateway,
89);
90impl_db_lookup!(
91    key = LightningGatewayKey,
92    query_prefix = LightningGatewayKeyPrefix
93);
94
95/// Migrates `SubmittedOfferV0` to `SubmittedOffer` and `ConfirmedInvoiceV0` to
96/// `ConfirmedInvoice`
97pub(crate) fn get_v1_migrated_state(
98    operation_id: OperationId,
99    cursor: &mut Cursor<&[u8]>,
100) -> anyhow::Result<Option<(Vec<u8>, OperationId)>> {
101    #[derive(Debug, Clone, Decodable)]
102    pub struct LightningReceiveConfirmedInvoiceV0 {
103        invoice: Bolt11Invoice,
104        receiving_key: Keypair,
105    }
106
107    let decoders = ModuleDecoderRegistry::default();
108    let ln_sm_variant = u16::consensus_decode(cursor, &decoders)?;
109
110    // If the state machine is not a receive state machine, return None
111    if ln_sm_variant != 2 {
112        return Ok(None);
113    }
114
115    let _ln_sm_len = u16::consensus_decode(cursor, &decoders)?;
116    let _operation_id = OperationId::consensus_decode(cursor, &decoders)?;
117    let receive_sm_variant = u16::consensus_decode(cursor, &decoders)?;
118
119    let new = match receive_sm_variant {
120        // SubmittedOfferV0
121        0 => {
122            let _receive_sm_len = u16::consensus_decode(cursor, &decoders)?;
123
124            let v0 = LightningReceiveSubmittedOfferV0::consensus_decode(cursor, &decoders)?;
125
126            let new_offer = LightningReceiveSubmittedOffer {
127                offer_txid: v0.offer_txid,
128                invoice: v0.invoice,
129                receiving_key: ReceivingKey::Personal(v0.payment_keypair),
130            };
131            let new_recv = LightningReceiveStateMachine {
132                operation_id,
133                state: LightningReceiveStates::SubmittedOffer(new_offer),
134            };
135            LightningClientStateMachines::Receive(new_recv)
136        }
137        // ConfirmedInvoiceV0
138        2 => {
139            let _receive_sm_len = u16::consensus_decode(cursor, &decoders)?;
140            let confirmed_old =
141                LightningReceiveConfirmedInvoiceV0::consensus_decode(cursor, &decoders)?;
142            let confirmed_new = LightningReceiveConfirmedInvoice {
143                invoice: confirmed_old.invoice,
144                receiving_key: ReceivingKey::Personal(confirmed_old.receiving_key),
145            };
146            LightningClientStateMachines::Receive(LightningReceiveStateMachine {
147                operation_id,
148                state: LightningReceiveStates::ConfirmedInvoice(confirmed_new),
149            })
150        }
151        _ => return Ok(None),
152    };
153
154    let bytes = new.consensus_encode_to_vec();
155    Ok(Some((bytes, operation_id)))
156}
157
158/// Migrates `SubmittedOffer` with enum prefix 5 back to `SubmittedOffer`
159pub(crate) fn get_v2_migrated_state(
160    operation_id: OperationId,
161    cursor: &mut Cursor<&[u8]>,
162) -> anyhow::Result<Option<(Vec<u8>, OperationId)>> {
163    let decoders = ModuleDecoderRegistry::default();
164    let ln_sm_variant = u16::consensus_decode(cursor, &decoders)?;
165
166    // If the state machine is not a receive state machine, return None
167    if ln_sm_variant != 2 {
168        return Ok(None);
169    }
170
171    let _ln_sm_len = u16::consensus_decode(cursor, &decoders)?;
172    let _operation_id = OperationId::consensus_decode(cursor, &decoders)?;
173    let receive_sm_variant = u16::consensus_decode(cursor, &decoders)?;
174    if receive_sm_variant != 5 {
175        return Ok(None);
176    }
177
178    let _receive_sm_len = u16::consensus_decode(cursor, &decoders)?;
179    let old = LightningReceiveSubmittedOffer::consensus_decode(cursor, &decoders)?;
180
181    let new_recv = LightningClientStateMachines::Receive(LightningReceiveStateMachine {
182        operation_id,
183        state: LightningReceiveStates::SubmittedOffer(old),
184    });
185
186    let bytes = new_recv.consensus_encode_to_vec();
187    Ok(Some((bytes, operation_id)))
188}
189
190/// Migrates `Refund` state with enum prefix 5 to contain the `error_reason`
191/// field
192pub(crate) fn get_v3_migrated_state(
193    operation_id: OperationId,
194    cursor: &mut Cursor<&[u8]>,
195) -> anyhow::Result<Option<(Vec<u8>, OperationId)>> {
196    let decoders = ModuleDecoderRegistry::default();
197    let ln_sm_variant = u16::consensus_decode(cursor, &decoders)?;
198
199    // If the state machine is not a pay state machine, return None
200    if ln_sm_variant != 1 {
201        return Ok(None);
202    }
203
204    let _ln_sm_len = u16::consensus_decode(cursor, &decoders)?;
205    let common = LightningPayCommon::consensus_decode(cursor, &decoders)?;
206    let pay_sm_variant = u16::consensus_decode(cursor, &decoders)?;
207
208    let _pay_sm_len = u16::consensus_decode(cursor, &decoders)?;
209
210    // if the pay state machine is not `Refund` or `Funded` variant, return none
211    match pay_sm_variant {
212        // Funded
213        2 => {
214            #[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
215            pub struct LightningPayFundedV0 {
216                pub payload: PayInvoicePayload,
217                pub gateway: LightningGateway,
218                pub timelock: u32,
219            }
220
221            let v0 = LightningPayFundedV0::consensus_decode(cursor, &decoders)?;
222            let v1 = LightningPayFunded {
223                payload: v0.payload,
224                gateway: v0.gateway,
225                timelock: v0.timelock,
226                funding_time: fedimint_core::time::now(),
227            };
228
229            let new_pay = LightningPayStateMachine {
230                common,
231                state: LightningPayStates::Funded(v1),
232            };
233            let new_sm = LightningClientStateMachines::LightningPay(new_pay);
234            let bytes = new_sm.consensus_encode_to_vec();
235            Ok(Some((bytes, operation_id)))
236        }
237        // Refund
238        5 => {
239            #[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
240            pub struct LightningPayRefundV0 {
241                txid: TransactionId,
242                out_points: Vec<OutPoint>,
243            }
244
245            let v0 = LightningPayRefundV0::consensus_decode(cursor, &decoders)?;
246            let v1 = LightningPayRefund {
247                txid: v0.txid,
248                out_points: v0.out_points,
249                error_reason: "unknown error (database migration)".to_string(),
250            };
251            let new_pay = LightningPayStateMachine {
252                common,
253                state: LightningPayStates::Refund(v1),
254            };
255            let new_sm = LightningClientStateMachines::LightningPay(new_pay);
256            let bytes = new_sm.consensus_encode_to_vec();
257            Ok(Some((bytes, operation_id)))
258        }
259        _ => Ok(None),
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use std::str::FromStr;
266
267    use fedimint_client::db::migrate_state;
268    use fedimint_core::core::{IntoDynInstance, OperationId};
269    use fedimint_core::encoding::Encodable;
270    use fedimint_core::secp256k1::{Keypair, SECP256K1};
271    use fedimint_core::{BitcoinHash, TransactionId};
272    use lightning_invoice::Bolt11Invoice;
273    use rand::thread_rng;
274
275    use crate::db::{get_v1_migrated_state, get_v2_migrated_state};
276    use crate::receive::{
277        LightningReceiveConfirmedInvoice, LightningReceiveStateMachine, LightningReceiveStates,
278        LightningReceiveSubmittedOffer,
279    };
280    use crate::{LightningClientStateMachines, ReceivingKey};
281
282    #[tokio::test]
283    async fn test_sm_migration_to_v2_submitted() {
284        let instance_id = 0x42;
285
286        let dummy_invoice = Bolt11Invoice::from_str("lntbs1u1pj8308gsp5xhxz908q5usddjjm6mfq6nwc2nu62twwm6za69d32kyx8h49a4hqpp5j5egfqw9kf5e96nk\
287        6htr76a8kggl0xyz3pzgemv887pya4flguzsdp5235xzmntwvsxvmmjypex2en4dejxjmn8yp6xsefqvesh2cm9wsss\
288        cqp2rzjq0ag45qspt2vd47jvj3t5nya5vsn0hlhf5wel8h779npsrspm6eeuqtjuuqqqqgqqyqqqqqqqqqqqqqqqc9q\
289        yysgqddrv0jqhyf3q6z75rt7nrwx0crxme87s8rx2rt8xr9slzu0p3xg3f3f0zmqavtmsnqaj5v0y5mdzszah7thrmg\
290        2we42dvjggjkf44egqheymyw",).expect("Invalid invoice");
291        let claim_key = Keypair::new(SECP256K1, &mut thread_rng());
292        let operation_id = OperationId::new_random();
293        let txid = TransactionId::from_byte_array([42; 32]);
294
295        let submitted_offer_variant_old = {
296            let mut submitted_offer_variant = Vec::<u8>::new();
297            txid.consensus_encode(&mut submitted_offer_variant)
298                .expect("TransactionId is encodable");
299            dummy_invoice
300                .consensus_encode(&mut submitted_offer_variant)
301                .expect("Invoice is encodable");
302            claim_key
303                .consensus_encode(&mut submitted_offer_variant)
304                .expect("Keypair is encodable");
305
306            submitted_offer_variant
307        };
308
309        let receive_variant = {
310            let mut receive_variant = Vec::<u8>::new();
311            operation_id
312                .consensus_encode(&mut receive_variant)
313                .expect("OperationId is encodable");
314            0u64.consensus_encode(&mut receive_variant)
315                .expect("u64 is encodable"); // Submitted Invoice variant
316            submitted_offer_variant_old
317                .consensus_encode(&mut receive_variant)
318                .expect("State is encodable");
319            receive_variant
320        };
321
322        let old_state = {
323            let mut sm_bytes = Vec::<u8>::new();
324            instance_id
325                .consensus_encode(&mut sm_bytes)
326                .expect("u16 is encodable");
327            2u64.consensus_encode(&mut sm_bytes)
328                .expect("u64 is encodable"); // Receive state machine variant
329            receive_variant
330                .consensus_encode(&mut sm_bytes)
331                .expect("receive variant is encodable");
332            sm_bytes
333        };
334
335        let old_states = vec![(old_state, operation_id)];
336
337        let new_state = LightningClientStateMachines::Receive(LightningReceiveStateMachine {
338            operation_id,
339            state: LightningReceiveStates::SubmittedOffer(LightningReceiveSubmittedOffer {
340                offer_txid: txid,
341                invoice: dummy_invoice,
342                receiving_key: ReceivingKey::Personal(claim_key),
343            }),
344        })
345        .into_dyn(instance_id);
346
347        let (new_active_states, new_inactive_states) =
348            migrate_state(old_states.clone(), old_states, get_v1_migrated_state)
349                .expect("Migration failed")
350                .expect("Migration produced output");
351
352        assert_eq!(new_inactive_states.len(), 1);
353        assert_eq!(
354            new_inactive_states[0],
355            (new_state.consensus_encode_to_vec(), operation_id)
356        );
357
358        assert_eq!(new_active_states.len(), 1);
359        assert_eq!(
360            new_active_states[0],
361            (new_state.consensus_encode_to_vec(), operation_id)
362        );
363    }
364
365    #[tokio::test]
366    async fn test_sm_migration_to_v2_confirmed() -> anyhow::Result<()> {
367        let operation_id = OperationId::new_random();
368        let instance_id = 0x42;
369        let claim_key = Keypair::new(SECP256K1, &mut thread_rng());
370        let dummy_invoice = Bolt11Invoice::from_str("lntbs1u1pj8308gsp5xhxz908q5usddjjm6mfq6nwc2nu62twwm6za69d32kyx8h49a4hqpp5j5egfqw9kf5e96nk\
371        6htr76a8kggl0xyz3pzgemv887pya4flguzsdp5235xzmntwvsxvmmjypex2en4dejxjmn8yp6xsefqvesh2cm9wsss\
372        cqp2rzjq0ag45qspt2vd47jvj3t5nya5vsn0hlhf5wel8h779npsrspm6eeuqtjuuqqqqgqqyqqqqqqqqqqqqqqqc9q\
373        yysgqddrv0jqhyf3q6z75rt7nrwx0crxme87s8rx2rt8xr9slzu0p3xg3f3f0zmqavtmsnqaj5v0y5mdzszah7thrmg\
374        2we42dvjggjkf44egqheymyw",).expect("Invalid invoice");
375
376        let confirmed_variant = {
377            let mut confirmed_variant = Vec::<u8>::new();
378            dummy_invoice.consensus_encode(&mut confirmed_variant)?;
379            claim_key.consensus_encode(&mut confirmed_variant)?;
380            confirmed_variant
381        };
382
383        let receive_variant = {
384            let mut receive_variant = Vec::<u8>::new();
385            operation_id.consensus_encode(&mut receive_variant)?;
386            2u64.consensus_encode(&mut receive_variant)?; // Enum variant confirmed invoice
387            confirmed_variant.consensus_encode(&mut receive_variant)?;
388            receive_variant
389        };
390
391        let old_sm_bytes = {
392            let mut sm_bytes_old = Vec::<u8>::new();
393            instance_id.consensus_encode(&mut sm_bytes_old)?;
394            2u64.consensus_encode(&mut sm_bytes_old)?; // Enum variant Receive
395            receive_variant.consensus_encode(&mut sm_bytes_old)?;
396            sm_bytes_old
397        };
398
399        let old_states = vec![(old_sm_bytes, operation_id)];
400
401        let new_state = LightningClientStateMachines::Receive(LightningReceiveStateMachine {
402            operation_id,
403            state: LightningReceiveStates::ConfirmedInvoice(LightningReceiveConfirmedInvoice {
404                invoice: dummy_invoice,
405                receiving_key: ReceivingKey::Personal(claim_key),
406            }),
407        })
408        .into_dyn(instance_id);
409
410        let (new_active_states, new_inactive_states) =
411            migrate_state(old_states.clone(), old_states, get_v1_migrated_state)
412                .expect("Migration failed")
413                .expect("Migration produced output");
414
415        assert_eq!(new_inactive_states.len(), 1);
416        assert_eq!(
417            new_inactive_states[0],
418            (new_state.consensus_encode_to_vec(), operation_id)
419        );
420
421        assert_eq!(new_active_states.len(), 1);
422        assert_eq!(
423            new_active_states[0],
424            (new_state.consensus_encode_to_vec(), operation_id)
425        );
426
427        Ok(())
428    }
429
430    #[tokio::test]
431    async fn test_sm_migration_to_v3_submitted() {
432        let instance_id = 0x42;
433
434        let dummy_invoice = Bolt11Invoice::from_str("lntbs1u1pj8308gsp5xhxz908q5usddjjm6mfq6nwc2nu62twwm6za69d32kyx8h49a4hqpp5j5egfqw9kf5e96nk\
435        6htr76a8kggl0xyz3pzgemv887pya4flguzsdp5235xzmntwvsxvmmjypex2en4dejxjmn8yp6xsefqvesh2cm9wsss\
436        cqp2rzjq0ag45qspt2vd47jvj3t5nya5vsn0hlhf5wel8h779npsrspm6eeuqtjuuqqqqgqqyqqqqqqqqqqqqqqqc9q\
437        yysgqddrv0jqhyf3q6z75rt7nrwx0crxme87s8rx2rt8xr9slzu0p3xg3f3f0zmqavtmsnqaj5v0y5mdzszah7thrmg\
438        2we42dvjggjkf44egqheymyw",).expect("Invalid invoice");
439        let claim_key = Keypair::new(SECP256K1, &mut thread_rng());
440        let operation_id = OperationId::new_random();
441        let txid = TransactionId::from_byte_array([42; 32]);
442
443        let submitted_offer_variant_deleted = {
444            let mut submitted_offer_variant = Vec::<u8>::new();
445            txid.consensus_encode(&mut submitted_offer_variant)
446                .expect("TransactionId is encodable");
447            dummy_invoice
448                .consensus_encode(&mut submitted_offer_variant)
449                .expect("Invoice is encodable");
450            ReceivingKey::Personal(claim_key)
451                .consensus_encode(&mut submitted_offer_variant)
452                .expect("Keypair is encodable");
453
454            submitted_offer_variant
455        };
456
457        let receive_variant = {
458            let mut receive_variant = Vec::<u8>::new();
459            operation_id
460                .consensus_encode(&mut receive_variant)
461                .expect("OperationId is encodable");
462            5u64.consensus_encode(&mut receive_variant)
463                .expect("u64 is encodable"); // Deleted Submitted Invoice variant
464            submitted_offer_variant_deleted
465                .consensus_encode(&mut receive_variant)
466                .expect("State is encodable");
467            receive_variant
468        };
469
470        let old_state = {
471            let mut sm_bytes = Vec::<u8>::new();
472            instance_id
473                .consensus_encode(&mut sm_bytes)
474                .expect("u16 is encodable");
475            2u64.consensus_encode(&mut sm_bytes)
476                .expect("u64 is encodable"); // Receive state machine variant
477            receive_variant
478                .consensus_encode(&mut sm_bytes)
479                .expect("receive variant is encodable");
480            sm_bytes
481        };
482
483        let old_states = vec![(old_state, operation_id)];
484
485        let new_state = LightningClientStateMachines::Receive(LightningReceiveStateMachine {
486            operation_id,
487            state: LightningReceiveStates::SubmittedOffer(LightningReceiveSubmittedOffer {
488                offer_txid: txid,
489                invoice: dummy_invoice,
490                receiving_key: ReceivingKey::Personal(claim_key),
491            }),
492        })
493        .into_dyn(instance_id);
494
495        let (new_active_states, new_inactive_states) =
496            migrate_state(old_states.clone(), old_states, get_v2_migrated_state)
497                .expect("Migration failed")
498                .expect("Migration produced output");
499
500        assert_eq!(new_inactive_states.len(), 1);
501        assert_eq!(
502            new_inactive_states[0],
503            (new_state.consensus_encode_to_vec(), operation_id)
504        );
505
506        assert_eq!(new_active_states.len(), 1);
507        assert_eq!(
508            new_active_states[0],
509            (new_state.consensus_encode_to_vec(), operation_id)
510        );
511    }
512}