Skip to main content

fedimint_ln_client/
db.rs

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