Skip to content

Commit e94f80f

Browse files
storage: persist paid BOLT12 invoices in payment details
Store the paid BOLT12 invoice in BOLT12 offer and refund payment records so successful outbound payments retain the invoice in persisted payment details. This keeps invoice storage separate from payer-proof support and extends the existing BOLT12 integration coverage. AI-assisted-by: Codex
1 parent e15e44b commit e94f80f

4 files changed

Lines changed: 60 additions & 11 deletions

File tree

src/event.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,7 @@ where
840840
offer_id,
841841
payer_note,
842842
quantity,
843+
bolt12_invoice: None,
843844
};
844845

845846
let payment = PaymentDetails::new(
@@ -1053,12 +1054,14 @@ where
10531054
debug_assert!(false, "payment_id should always be set.");
10541055
return Ok(());
10551056
};
1057+
let bolt12_invoice = bolt12_invoice.map(Into::into);
10561058

10571059
let update = PaymentDetailsUpdate {
10581060
hash: Some(Some(payment_hash)),
10591061
preimage: Some(Some(payment_preimage)),
10601062
fee_paid_msat: Some(fee_paid_msat),
10611063
status: Some(PaymentStatus::Succeeded),
1064+
bolt12_invoice: Some(bolt12_invoice.clone()),
10621065
..PaymentDetailsUpdate::new(payment_id)
10631066
};
10641067

@@ -1090,7 +1093,7 @@ where
10901093
payment_hash,
10911094
payment_preimage: Some(payment_preimage),
10921095
fee_paid_msat,
1093-
bolt12_invoice: bolt12_invoice.map(Into::into),
1096+
bolt12_invoice,
10941097
};
10951098

10961099
match self.event_queue.add_event(event).await {

src/payment/bolt12.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use std::sync::{Arc, RwLock};
1414
use std::time::{Duration, SystemTime, UNIX_EPOCH};
1515

1616
use lightning::blinded_path::message::BlindedMessagePath;
17+
use lightning::events::PaidBolt12Invoice as LdkPaidBolt12Invoice;
1718
use lightning::ln::channelmanager::{OptionalOfferPaymentParams, PaymentId};
1819
use lightning::ln::outbound_payment::Retry;
1920
use lightning::offers::offer::{Amount, Offer as LdkOffer, OfferFromHrn, Quantity};
@@ -154,6 +155,7 @@ impl Bolt12Payment {
154155
offer_id: offer.id(),
155156
payer_note: payer_note.map(UntrustedString),
156157
quantity,
158+
bolt12_invoice: None,
157159
};
158160
let payment = PaymentDetails::new(
159161
payment_id,
@@ -179,6 +181,7 @@ impl Bolt12Payment {
179181
offer_id: offer.id(),
180182
payer_note: payer_note.map(UntrustedString),
181183
quantity,
184+
bolt12_invoice: None,
182185
};
183186
let payment = PaymentDetails::new(
184187
payment_id,
@@ -314,6 +317,7 @@ impl Bolt12Payment {
314317
offer_id: offer.id(),
315318
payer_note: payer_note.map(UntrustedString),
316319
quantity,
320+
bolt12_invoice: None,
317321
};
318322
let payment = PaymentDetails::new(
319323
payment_id,
@@ -339,6 +343,7 @@ impl Bolt12Payment {
339343
offer_id: offer.id(),
340344
payer_note: payer_note.map(UntrustedString),
341345
quantity,
346+
bolt12_invoice: None,
342347
};
343348
let payment = PaymentDetails::new(
344349
payment_id,
@@ -444,6 +449,7 @@ impl Bolt12Payment {
444449
secret: None,
445450
payer_note: refund.payer_note().map(|note| UntrustedString(note.0.to_string())),
446451
quantity: refund.quantity(),
452+
bolt12_invoice: Some(LdkPaidBolt12Invoice::Bolt12Invoice(invoice.clone()).into()),
447453
};
448454

449455
let payment = PaymentDetails::new(
@@ -514,6 +520,7 @@ impl Bolt12Payment {
514520
secret: None,
515521
payer_note: payer_note.map(|note| UntrustedString(note)),
516522
quantity,
523+
bolt12_invoice: None,
517524
};
518525
let payment = PaymentDetails::new(
519526
payment_id,

src/payment/store.rs

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use std::time::{Duration, SystemTime, UNIX_EPOCH};
99

1010
use bitcoin::{BlockHash, Txid};
11+
#[cfg(not(feature = "uniffi"))]
12+
use lightning::events::PaidBolt12Invoice;
1113
use lightning::ln::channelmanager::PaymentId;
1214
use lightning::ln::msgs::DecodeError;
1315
use lightning::offers::offer::OfferId;
@@ -20,6 +22,8 @@ use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
2022
use lightning_types::string::UntrustedString;
2123

2224
use crate::data_store::{StorableObject, StorableObjectId, StorableObjectUpdate};
25+
#[cfg(feature = "uniffi")]
26+
use crate::ffi::PaidBolt12Invoice;
2327
use crate::hex_utils;
2428

2529
/// Represents a payment.
@@ -267,6 +271,18 @@ impl StorableObject for PaymentDetails {
267271
update_if_necessary!(self.fee_paid_msat, fee_paid_msat_opt);
268272
}
269273

274+
if let Some(ref bolt12_invoice_opt) = update.bolt12_invoice {
275+
match self.kind {
276+
PaymentKind::Bolt12Offer { ref mut bolt12_invoice, .. } => {
277+
update_if_necessary!(*bolt12_invoice, bolt12_invoice_opt.clone());
278+
},
279+
PaymentKind::Bolt12Refund { ref mut bolt12_invoice, .. } => {
280+
update_if_necessary!(*bolt12_invoice, bolt12_invoice_opt.clone());
281+
},
282+
_ => {},
283+
}
284+
}
285+
270286
if let Some(skimmed_fee_msat) = update.counterparty_skimmed_fee_msat {
271287
match self.kind {
272288
PaymentKind::Bolt11Jit { ref mut counterparty_skimmed_fee_msat, .. } => {
@@ -428,6 +444,8 @@ pub enum PaymentKind {
428444
///
429445
/// This will always be `None` for payments serialized with version `v0.3.0`.
430446
quantity: Option<u64>,
447+
/// The BOLT12 invoice associated with the payment, once available.
448+
bolt12_invoice: Option<PaidBolt12Invoice>,
431449
},
432450
/// A [BOLT 12] 'refund' payment, i.e., a payment for a [`Refund`].
433451
///
@@ -448,6 +466,8 @@ pub enum PaymentKind {
448466
///
449467
/// This will always be `None` for payments serialized with version `v0.3.0`.
450468
quantity: Option<u64>,
469+
/// The BOLT12 invoice associated with the payment, once available.
470+
bolt12_invoice: Option<PaidBolt12Invoice>,
451471
},
452472
/// A spontaneous ("keysend") payment.
453473
Spontaneous {
@@ -482,6 +502,7 @@ impl_writeable_tlv_based_enum!(PaymentKind,
482502
(3, quantity, option),
483503
(4, secret, option),
484504
(6, offer_id, required),
505+
(8, bolt12_invoice, option),
485506
},
486507
(8, Spontaneous) => {
487508
(0, hash, required),
@@ -493,6 +514,7 @@ impl_writeable_tlv_based_enum!(PaymentKind,
493514
(2, preimage, option),
494515
(3, quantity, option),
495516
(4, secret, option),
517+
(6, bolt12_invoice, option),
496518
}
497519
);
498520

@@ -555,6 +577,7 @@ pub(crate) struct PaymentDetailsUpdate {
555577
pub direction: Option<PaymentDirection>,
556578
pub status: Option<PaymentStatus>,
557579
pub confirmation_status: Option<ConfirmationStatus>,
580+
pub bolt12_invoice: Option<Option<PaidBolt12Invoice>>,
558581
pub txid: Option<Txid>,
559582
}
560583

@@ -571,30 +594,39 @@ impl PaymentDetailsUpdate {
571594
direction: None,
572595
status: None,
573596
confirmation_status: None,
597+
bolt12_invoice: None,
574598
txid: None,
575599
}
576600
}
577601
}
578602

579603
impl From<&PaymentDetails> for PaymentDetailsUpdate {
580604
fn from(value: &PaymentDetails) -> Self {
581-
let (hash, preimage, secret) = match value.kind {
582-
PaymentKind::Bolt11 { hash, preimage, secret, .. } => (Some(hash), preimage, secret),
583-
PaymentKind::Bolt11Jit { hash, preimage, secret, .. } => (Some(hash), preimage, secret),
584-
PaymentKind::Bolt12Offer { hash, preimage, secret, .. } => (hash, preimage, secret),
585-
PaymentKind::Bolt12Refund { hash, preimage, secret, .. } => (hash, preimage, secret),
586-
PaymentKind::Spontaneous { hash, preimage, .. } => (Some(hash), preimage, None),
587-
_ => (None, None, None),
605+
let (hash, preimage, secret, bolt12_invoice) = match &value.kind {
606+
PaymentKind::Bolt11 { hash, preimage, secret, .. } => {
607+
(Some(*hash), *preimage, *secret, None)
608+
},
609+
PaymentKind::Bolt11Jit { hash, preimage, secret, .. } => {
610+
(Some(*hash), *preimage, *secret, None)
611+
},
612+
PaymentKind::Bolt12Offer { hash, preimage, secret, bolt12_invoice, .. } => {
613+
(*hash, *preimage, *secret, Some(bolt12_invoice.clone()))
614+
},
615+
PaymentKind::Bolt12Refund { hash, preimage, secret, bolt12_invoice, .. } => {
616+
(*hash, *preimage, *secret, Some(bolt12_invoice.clone()))
617+
},
618+
PaymentKind::Spontaneous { hash, preimage, .. } => (Some(*hash), *preimage, None, None),
619+
_ => (None, None, None, None),
588620
};
589621

590622
let (confirmation_status, txid) = match &value.kind {
591623
PaymentKind::Onchain { status, txid, .. } => (Some(*status), Some(*txid)),
592624
_ => (None, None),
593625
};
594626

595-
let counterparty_skimmed_fee_msat = match value.kind {
627+
let counterparty_skimmed_fee_msat = match &value.kind {
596628
PaymentKind::Bolt11Jit { counterparty_skimmed_fee_msat, .. } => {
597-
Some(counterparty_skimmed_fee_msat)
629+
Some(*counterparty_skimmed_fee_msat)
598630
},
599631
_ => None,
600632
};
@@ -610,6 +642,7 @@ impl From<&PaymentDetails> for PaymentDetailsUpdate {
610642
direction: Some(value.direction),
611643
status: Some(value.status),
612644
confirmation_status,
645+
bolt12_invoice,
613646
txid,
614647
}
615648
}

tests/integration_tests_rust.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1113,12 +1113,14 @@ async fn simple_bolt12_send_receive() {
11131113
offer_id,
11141114
quantity: ref qty,
11151115
payer_note: ref note,
1116+
bolt12_invoice: ref invoice,
11161117
} => {
11171118
assert!(hash.is_some());
11181119
assert!(preimage.is_some());
11191120
assert_eq!(offer_id, offer.id());
11201121
assert_eq!(&expected_quantity, qty);
11211122
assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0);
1123+
assert!(invoice.is_some());
11221124
// TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12
11231125
// API currently doesn't allow to do that.
11241126
},
@@ -1180,12 +1182,14 @@ async fn simple_bolt12_send_receive() {
11801182
offer_id,
11811183
quantity: ref qty,
11821184
payer_note: ref note,
1185+
bolt12_invoice: ref invoice,
11831186
} => {
11841187
assert!(hash.is_some());
11851188
assert!(preimage.is_some());
11861189
assert_eq!(offer_id, offer.id());
11871190
assert_eq!(&expected_quantity, qty);
11881191
assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0);
1192+
assert!(invoice.is_some());
11891193
// TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12
11901194
// API currently doesn't allow to do that.
11911195
hash.unwrap()
@@ -1253,11 +1257,13 @@ async fn simple_bolt12_send_receive() {
12531257
secret: _,
12541258
quantity: ref qty,
12551259
payer_note: ref note,
1260+
bolt12_invoice: ref invoice,
12561261
} => {
12571262
assert!(hash.is_some());
12581263
assert!(preimage.is_some());
12591264
assert_eq!(&expected_quantity, qty);
1260-
assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0)
1265+
assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0);
1266+
assert!(invoice.is_some());
12611267
// TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12
12621268
// API currently doesn't allow to do that.
12631269
},

0 commit comments

Comments
 (0)