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::{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;