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