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::{OutPoint, TransactionId, impl_db_lookup, impl_db_record};
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::recurring::RecurringPaymentCodeEntry;
24use crate::{LightningClientStateMachines, OutgoingLightningPayment, ReceivingKey};
25
26#[repr(u8)]
27#[derive(Clone, EnumIter, Debug)]
28pub enum DbKeyPrefix {
29    // Deprecated
30    ActiveGateway = 0x28,
31    PaymentResult = 0x29,
32    MetaOverridesDeprecated = 0x30,
33    LightningGateway = 0x45,
34    RecurringPaymentKey = 0x46,
35    /// Prefixes between 0xb0..=0xcf shall all be considered allocated for
36    /// historical and future external use
37    ExternalReservedStart = 0xb0,
38    /// Prefixes between 0xd0..=0xff shall all be considered allocated for
39    /// historical and future internal use
40    CoreInternalReservedStart = 0xd0,
41    CoreInternalReservedEnd = 0xff,
42}
43
44impl std::fmt::Display for DbKeyPrefix {
45    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
46        write!(f, "{self:?}")
47    }
48}
49
50#[derive(Debug, Encodable, Decodable, Serialize)]
51pub struct ActiveGatewayKey;
52
53#[derive(Debug, Encodable, Decodable)]
54pub struct ActiveGatewayKeyPrefix;
55
56impl_db_record!(
57    key = ActiveGatewayKey,
58    value = LightningGatewayRegistration,
59    db_prefix = DbKeyPrefix::ActiveGateway,
60);
61impl_db_lookup!(
62    key = ActiveGatewayKey,
63    query_prefix = ActiveGatewayKeyPrefix
64);
65
66#[derive(Debug, Encodable, Decodable, Serialize)]
67pub struct PaymentResultKey {
68    pub payment_hash: sha256::Hash,
69}
70
71#[derive(Debug, Encodable, Decodable, Serialize)]
72pub struct PaymentResultPrefix;
73
74#[derive(Debug, Encodable, Decodable, Serialize)]
75pub struct PaymentResult {
76    pub index: u16,
77    pub completed_payment: Option<OutgoingLightningPayment>,
78}
79
80impl_db_record!(
81    key = PaymentResultKey,
82    value = PaymentResult,
83    db_prefix = DbKeyPrefix::PaymentResult,
84);
85
86impl_db_lookup!(key = PaymentResultKey, query_prefix = PaymentResultPrefix);
87
88#[derive(Debug, Encodable, Decodable, Serialize)]
89pub struct LightningGatewayKey(pub PublicKey);
90
91#[derive(Debug, Encodable, Decodable)]
92pub struct LightningGatewayKeyPrefix;
93
94impl_db_record!(
95    key = LightningGatewayKey,
96    value = LightningGatewayRegistration,
97    db_prefix = DbKeyPrefix::LightningGateway,
98);
99impl_db_lookup!(
100    key = LightningGatewayKey,
101    query_prefix = LightningGatewayKeyPrefix
102);
103
104/// A single recurring payment code (e.g. LNURL) that was registered with a
105/// server
106#[derive(Debug, Encodable, Decodable)]
107pub struct RecurringPaymentCodeKey {
108    pub derivation_idx: u64,
109}
110
111#[derive(Debug, Encodable, Decodable)]
112pub struct RecurringPaymentCodeKeyPrefix;
113
114impl_db_record!(
115    key = RecurringPaymentCodeKey,
116    value = RecurringPaymentCodeEntry,
117    db_prefix = DbKeyPrefix::RecurringPaymentKey,
118);
119
120impl_db_lookup!(
121    key = RecurringPaymentCodeKey,
122    query_prefix = RecurringPaymentCodeKeyPrefix
123);
124
125/// Migrates `SubmittedOfferV0` to `SubmittedOffer` and `ConfirmedInvoiceV0` to
126/// `ConfirmedInvoice`
127pub(crate) fn get_v1_migrated_state(
128    operation_id: OperationId,
129    cursor: &mut Cursor<&[u8]>,
130) -> anyhow::Result<Option<(Vec<u8>, OperationId)>> {
131    #[derive(Debug, Clone, Decodable)]
132    pub struct LightningReceiveConfirmedInvoiceV0 {
133        invoice: Bolt11Invoice,
134        receiving_key: Keypair,
135    }
136
137    let decoders = ModuleDecoderRegistry::default();
138    let ln_sm_variant = u16::consensus_decode_partial(cursor, &decoders)?;
139
140    // If the state machine is not a receive state machine, return None
141    if ln_sm_variant != 2 {
142        return Ok(None);
143    }
144
145    let _ln_sm_len = u16::consensus_decode_partial(cursor, &decoders)?;
146    let _operation_id = OperationId::consensus_decode_partial(cursor, &decoders)?;
147    let receive_sm_variant = u16::consensus_decode_partial(cursor, &decoders)?;
148
149    let new = match receive_sm_variant {
150        // SubmittedOfferV0
151        0 => {
152            let _receive_sm_len = u16::consensus_decode_partial(cursor, &decoders)?;
153
154            let v0 = LightningReceiveSubmittedOfferV0::consensus_decode_partial(cursor, &decoders)?;
155
156            let new_offer = LightningReceiveSubmittedOffer {
157                offer_txid: v0.offer_txid,
158                invoice: v0.invoice,
159                receiving_key: ReceivingKey::Personal(v0.payment_keypair),
160            };
161            let new_recv = LightningReceiveStateMachine {
162                operation_id,
163                state: LightningReceiveStates::SubmittedOffer(new_offer),
164            };
165            LightningClientStateMachines::Receive(new_recv)
166        }
167        // ConfirmedInvoiceV0
168        2 => {
169            let _receive_sm_len = u16::consensus_decode_partial(cursor, &decoders)?;
170            let confirmed_old =
171                LightningReceiveConfirmedInvoiceV0::consensus_decode_partial(cursor, &decoders)?;
172            let confirmed_new = LightningReceiveConfirmedInvoice {
173                invoice: confirmed_old.invoice,
174                receiving_key: ReceivingKey::Personal(confirmed_old.receiving_key),
175            };
176            LightningClientStateMachines::Receive(LightningReceiveStateMachine {
177                operation_id,
178                state: LightningReceiveStates::ConfirmedInvoice(confirmed_new),
179            })
180        }
181        _ => return Ok(None),
182    };
183
184    let bytes = new.consensus_encode_to_vec();
185    Ok(Some((bytes, operation_id)))
186}
187
188/// Migrates `SubmittedOffer` with enum prefix 5 back to `SubmittedOffer`
189pub(crate) fn get_v2_migrated_state(
190    operation_id: OperationId,
191    cursor: &mut Cursor<&[u8]>,
192) -> anyhow::Result<Option<(Vec<u8>, OperationId)>> {
193    let decoders = ModuleDecoderRegistry::default();
194    let ln_sm_variant = u16::consensus_decode_partial(cursor, &decoders)?;
195
196    // If the state machine is not a receive state machine, return None
197    if ln_sm_variant != 2 {
198        return Ok(None);
199    }
200
201    let _ln_sm_len = u16::consensus_decode_partial(cursor, &decoders)?;
202    let _operation_id = OperationId::consensus_decode_partial(cursor, &decoders)?;
203    let receive_sm_variant = u16::consensus_decode_partial(cursor, &decoders)?;
204    if receive_sm_variant != 5 {
205        return Ok(None);
206    }
207
208    let _receive_sm_len = u16::consensus_decode_partial(cursor, &decoders)?;
209    let old = LightningReceiveSubmittedOffer::consensus_decode_partial(cursor, &decoders)?;
210
211    let new_recv = LightningClientStateMachines::Receive(LightningReceiveStateMachine {
212        operation_id,
213        state: LightningReceiveStates::SubmittedOffer(old),
214    });
215
216    let bytes = new_recv.consensus_encode_to_vec();
217    Ok(Some((bytes, operation_id)))
218}
219
220/// Migrates `Refund` state with enum prefix 5 to contain the `error_reason`
221/// field
222pub(crate) fn get_v3_migrated_state(
223    operation_id: OperationId,
224    cursor: &mut Cursor<&[u8]>,
225) -> anyhow::Result<Option<(Vec<u8>, OperationId)>> {
226    let decoders = ModuleDecoderRegistry::default();
227    let ln_sm_variant = u16::consensus_decode_partial(cursor, &decoders)?;
228
229    // If the state machine is not a pay state machine, return None
230    if ln_sm_variant != 1 {
231        return Ok(None);
232    }
233
234    let _ln_sm_len = u16::consensus_decode_partial(cursor, &decoders)?;
235    let common = LightningPayCommon::consensus_decode_partial(cursor, &decoders)?;
236    let pay_sm_variant = u16::consensus_decode_partial(cursor, &decoders)?;
237
238    let _pay_sm_len = u16::consensus_decode_partial(cursor, &decoders)?;
239
240    // if the pay state machine is not `Refund` or `Funded` variant, return none
241    match pay_sm_variant {
242        // Funded
243        2 => {
244            #[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
245            pub struct LightningPayFundedV0 {
246                pub payload: PayInvoicePayload,
247                pub gateway: LightningGateway,
248                pub timelock: u32,
249            }
250
251            let v0 = LightningPayFundedV0::consensus_decode_partial(cursor, &decoders)?;
252            let v1 = LightningPayFunded {
253                payload: v0.payload,
254                gateway: v0.gateway,
255                timelock: v0.timelock,
256                funding_time: fedimint_core::time::now(),
257            };
258
259            let new_pay = LightningPayStateMachine {
260                common,
261                state: LightningPayStates::Funded(v1),
262            };
263            let new_sm = LightningClientStateMachines::LightningPay(new_pay);
264            let bytes = new_sm.consensus_encode_to_vec();
265            Ok(Some((bytes, operation_id)))
266        }
267        // Refund
268        5 => {
269            #[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
270            pub struct LightningPayRefundV0 {
271                txid: TransactionId,
272                out_points: Vec<OutPoint>,
273            }
274
275            let v0 = LightningPayRefundV0::consensus_decode_partial(cursor, &decoders)?;
276            let v1 = LightningPayRefund {
277                txid: v0.txid,
278                out_points: v0.out_points,
279                error_reason: "unknown error (database migration)".to_string(),
280            };
281            let new_pay = LightningPayStateMachine {
282                common,
283                state: LightningPayStates::Refund(v1),
284            };
285            let new_sm = LightningClientStateMachines::LightningPay(new_pay);
286            let bytes = new_sm.consensus_encode_to_vec();
287            Ok(Some((bytes, operation_id)))
288        }
289        _ => Ok(None),
290    }
291}
292
293#[cfg(test)]
294mod tests;