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 ActiveGateway = 0x28,
30 PaymentResult = 0x29,
31 MetaOverridesDeprecated = 0x30,
32 LightningGateway = 0x45,
33 ExternalReservedStart = 0xb0,
36 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
102pub(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 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 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 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
165pub(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 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
197pub(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 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 match pay_sm_variant {
219 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 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()); 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()); 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()); 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()); 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()); 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()); 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}