Skip to content

Commit a140e22

Browse files
make persistent BOLT12 invoice through PendingOutboundPayment
This commit prepares the code to pass down the BOLT12 invoice inside the `PaymentSent` event. To achieve this, the `bolt12` field has been added to the `PendingOutboundPayment::Retryable` enum, allowing it to be attached to the `PaymentSent` event when the payment is completed. Link: lightningdevkit#3344 Signed-off-by: Vincenzo Palazzo <[email protected]>
1 parent c0f7de3 commit a140e22

File tree

4 files changed

+61
-30
lines changed

4 files changed

+61
-30
lines changed

lightning/src/events/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,13 @@ pub enum Event {
949949
///
950950
/// [`Route::get_total_fees`]: crate::routing::router::Route::get_total_fees
951951
fee_paid_msat: Option<u64>,
952+
/// The BOLT 12 invoice that was paid. `None` if the payment was a non BOLT 12 payment.
953+
///
954+
/// The BOLT 12 invoice is useful for proof of payment because it contains the
955+
/// payment hash. A third party can verify that the payment was made by
956+
/// showing the invoice and confirming that the payment hash matches
957+
/// the hash of the payment preimage.
958+
bolt12_invoice: Option<PaidInvoice>,
952959
},
953960
/// Indicates an outbound payment failed. Individual [`Event::PaymentPathFailed`] events
954961
/// provide failure information for each path attempt in the payment, including retries.

lightning/src/ln/functional_test_utils.rs

+16-10
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
use crate::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen, Watch, chainmonitor::Persist};
1414
use crate::chain::channelmonitor::ChannelMonitor;
1515
use crate::chain::transaction::OutPoint;
16-
use crate::events::{ClaimedHTLC, ClosureReason, Event, HTLCDestination, PathFailure, PaymentPurpose, PaymentFailureReason};
16+
use crate::events::{ClaimedHTLC, ClosureReason, Event, HTLCDestination, PaidInvoice, PathFailure, PaymentFailureReason, PaymentPurpose};
1717
use crate::events::bump_transaction::{BumpTransactionEvent, BumpTransactionEventHandler, Wallet, WalletSource};
1818
use crate::ln::types::ChannelId;
1919
use crate::types::payment::{PaymentPreimage, PaymentHash, PaymentSecret};
@@ -2294,7 +2294,7 @@ macro_rules! expect_payment_claimed {
22942294
pub fn expect_payment_sent<CM: AChannelManager, H: NodeHolder<CM=CM>>(node: &H,
22952295
expected_payment_preimage: PaymentPreimage, expected_fee_msat_opt: Option<Option<u64>>,
22962296
expect_per_path_claims: bool, expect_post_ev_mon_update: bool,
2297-
) {
2297+
) -> Option<PaidInvoice> {
22982298
let events = node.node().get_and_clear_pending_events();
22992299
let expected_payment_hash = PaymentHash(
23002300
bitcoin::hashes::sha256::Hash::hash(&expected_payment_preimage.0).to_byte_array());
@@ -2306,8 +2306,10 @@ pub fn expect_payment_sent<CM: AChannelManager, H: NodeHolder<CM=CM>>(node: &H,
23062306
if expect_post_ev_mon_update {
23072307
check_added_monitors(node, 1);
23082308
}
2309+
// We return the invoice because some test may want to check the invoice details.
2310+
let mut invoice = None;
23092311
let expected_payment_id = match events[0] {
2310-
Event::PaymentSent { ref payment_id, ref payment_preimage, ref payment_hash, ref amount_msat, ref fee_paid_msat } => {
2312+
Event::PaymentSent { ref payment_id, ref payment_preimage, ref payment_hash, ref amount_msat, ref fee_paid_msat, ref bolt12_invoice } => {
23112313
assert_eq!(expected_payment_preimage, *payment_preimage);
23122314
assert_eq!(expected_payment_hash, *payment_hash);
23132315
assert!(amount_msat.is_some());
@@ -2316,6 +2318,7 @@ pub fn expect_payment_sent<CM: AChannelManager, H: NodeHolder<CM=CM>>(node: &H,
23162318
} else {
23172319
assert!(fee_paid_msat.is_some());
23182320
}
2321+
invoice = bolt12_invoice.clone();
23192322
payment_id.unwrap()
23202323
},
23212324
_ => panic!("Unexpected event"),
@@ -2331,19 +2334,20 @@ pub fn expect_payment_sent<CM: AChannelManager, H: NodeHolder<CM=CM>>(node: &H,
23312334
}
23322335
}
23332336
}
2337+
invoice
23342338
}
23352339

23362340
#[macro_export]
23372341
macro_rules! expect_payment_sent {
23382342
($node: expr, $expected_payment_preimage: expr) => {
2339-
$crate::expect_payment_sent!($node, $expected_payment_preimage, None::<u64>, true);
2343+
$crate::expect_payment_sent!($node, $expected_payment_preimage, None::<u64>, true)
23402344
};
23412345
($node: expr, $expected_payment_preimage: expr, $expected_fee_msat_opt: expr) => {
2342-
$crate::expect_payment_sent!($node, $expected_payment_preimage, $expected_fee_msat_opt, true);
2346+
$crate::expect_payment_sent!($node, $expected_payment_preimage, $expected_fee_msat_opt, true)
23432347
};
23442348
($node: expr, $expected_payment_preimage: expr, $expected_fee_msat_opt: expr, $expect_paths: expr) => {
23452349
$crate::ln::functional_test_utils::expect_payment_sent(&$node, $expected_payment_preimage,
2346-
$expected_fee_msat_opt.map(|o| Some(o)), $expect_paths, true);
2350+
$expected_fee_msat_opt.map(|o| Some(o)), $expect_paths, true)
23472351
}
23482352
}
23492353

@@ -3106,20 +3110,22 @@ pub fn pass_claimed_payment_along_route(args: ClaimAlongRouteArgs) -> u64 {
31063110

31073111
expected_total_fee_msat
31083112
}
3109-
pub fn claim_payment_along_route(args: ClaimAlongRouteArgs) {
3113+
pub fn claim_payment_along_route(args: ClaimAlongRouteArgs) -> Option<PaidInvoice> {
31103114
let origin_node = args.origin_node;
31113115
let payment_preimage = args.payment_preimage;
31123116
let skip_last = args.skip_last;
31133117
let expected_total_fee_msat = do_claim_payment_along_route(args);
31143118
if !skip_last {
3115-
expect_payment_sent!(origin_node, payment_preimage, Some(expected_total_fee_msat));
3119+
expect_payment_sent!(origin_node, payment_preimage, Some(expected_total_fee_msat))
3120+
} else {
3121+
None
31163122
}
31173123
}
31183124

3119-
pub fn claim_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], our_payment_preimage: PaymentPreimage) {
3125+
pub fn claim_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], our_payment_preimage: PaymentPreimage) -> Option<PaidInvoice> {
31203126
claim_payment_along_route(
31213127
ClaimAlongRouteArgs::new(origin_node, &[expected_route], our_payment_preimage)
3122-
);
3128+
)
31233129
}
31243130

31253131
pub const TEST_FINAL_CLTV: u32 = 70;

lightning/src/ln/offers_tests.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ use crate::blinded_path::IntroductionNode;
4747
use crate::blinded_path::message::BlindedMessagePath;
4848
use crate::blinded_path::payment::{Bolt12OfferContext, Bolt12RefundContext, PaymentContext};
4949
use crate::blinded_path::message::{MessageContext, OffersContext};
50-
use crate::events::{ClosureReason, Event, HTLCDestination, PaymentFailureReason, PaymentPurpose};
50+
use crate::events::{ClosureReason, Event, HTLCDestination, PaidInvoice, PaymentFailureReason, PaymentPurpose};
5151
use crate::ln::channelmanager::{Bolt12PaymentError, MAX_SHORT_LIVED_RELATIVE_EXPIRY, PaymentId, RecentPaymentDetails, RecipientOnionFields, Retry, self};
5252
use crate::types::features::Bolt12InvoiceFeatures;
5353
use crate::ln::functional_test_utils::*;
@@ -188,7 +188,11 @@ fn claim_bolt12_payment<'a, 'b, 'c>(
188188
},
189189
_ => panic!("Unexpected payment purpose: {:?}", payment_purpose),
190190
}
191-
claim_payment(node, path, payment_preimage);
191+
if let Some(inv) = claim_payment(node, path, payment_preimage) {
192+
assert_eq!(inv, PaidInvoice::Bolt12Invoice(invoice.to_owned()));
193+
} else {
194+
panic!("Expected PaidInvoice::Bolt12Invoice");
195+
};
192196
}
193197

194198
fn extract_offer_nonce<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, message: &OnionMessage) -> Nonce {

lightning/src/ln/outbound_payment.rs

+32-18
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ pub(crate) enum PendingOutboundPayment {
106106
payment_metadata: Option<Vec<u8>>,
107107
keysend_preimage: Option<PaymentPreimage>,
108108
invoice_request: Option<InvoiceRequest>,
109+
// Storing the bolt12 invoice here to allow Proof of Payment after
110+
// the payment is made.
111+
bolt12_invoice: Option<Bolt12Invoice>,
109112
custom_tlvs: Vec<(u64, Vec<u8>)>,
110113
pending_amt_msat: u64,
111114
/// Used to track the fee paid. Present iff the payment was serialized on 0.0.103+.
@@ -155,6 +158,12 @@ impl_writeable_tlv_based!(RetryableInvoiceRequest, {
155158
});
156159

157160
impl PendingOutboundPayment {
161+
fn bolt12_invoice(&self) -> Option<&Bolt12Invoice> {
162+
match self {
163+
PendingOutboundPayment::Retryable { bolt12_invoice, .. } => bolt12_invoice.as_ref(),
164+
_ => None,
165+
}
166+
}
158167
fn increment_attempts(&mut self) {
159168
if let PendingOutboundPayment::Retryable { attempts, .. } = self {
160169
attempts.count += 1;
@@ -831,7 +840,7 @@ impl OutboundPayments {
831840
IH: Fn() -> InFlightHtlcs,
832841
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
833842
{
834-
self.send_payment_internal(payment_id, payment_hash, recipient_onion, None, retry_strategy,
843+
self.send_payment_for_non_bolt12_invoice(payment_id, payment_hash, recipient_onion, None, retry_strategy,
835844
route_params, router, first_hops, &compute_inflight_htlcs, entropy_source, node_signer,
836845
best_block_height, logger, pending_events, &send_payment_along_path)
837846
}
@@ -854,7 +863,7 @@ impl OutboundPayments {
854863
let preimage = payment_preimage
855864
.unwrap_or_else(|| PaymentPreimage(entropy_source.get_secure_random_bytes()));
856865
let payment_hash = PaymentHash(Sha256::hash(&preimage.0).to_byte_array());
857-
self.send_payment_internal(payment_id, payment_hash, recipient_onion, Some(preimage),
866+
self.send_payment_for_non_bolt12_invoice(payment_id, payment_hash, recipient_onion, Some(preimage),
858867
retry_strategy, route_params, router, first_hops, inflight_htlcs, entropy_source,
859868
node_signer, best_block_height, logger, pending_events, send_payment_along_path)
860869
.map(|()| payment_hash)
@@ -897,7 +906,7 @@ impl OutboundPayments {
897906
route_params.max_total_routing_fee_msat = Some(max_fee_msat);
898907
}
899908

900-
self.send_payment_internal(payment_id, payment_hash, recipient_onion, None, retry_strategy, route_params,
909+
self.send_payment_for_non_bolt12_invoice(payment_id, payment_hash, recipient_onion, None, retry_strategy, route_params,
901910
router, first_hops, compute_inflight_htlcs,
902911
entropy_source, node_signer, best_block_height, logger,
903912
pending_events, send_payment_along_path
@@ -959,7 +968,7 @@ impl OutboundPayments {
959968
route_params.max_total_routing_fee_msat = Some(max_fee_msat);
960969
}
961970
self.send_payment_for_bolt12_invoice_internal(
962-
payment_id, payment_hash, None, None, route_params, retry_strategy, router, first_hops,
971+
payment_id, payment_hash, None, None, Some(invoice), route_params, retry_strategy, router, first_hops,
963972
inflight_htlcs, entropy_source, node_signer, node_id_lookup, secp_ctx, best_block_height,
964973
logger, pending_events, send_payment_along_path
965974
)
@@ -970,6 +979,7 @@ impl OutboundPayments {
970979
>(
971980
&self, payment_id: PaymentId, payment_hash: PaymentHash,
972981
keysend_preimage: Option<PaymentPreimage>, invoice_request: Option<&InvoiceRequest>,
982+
bolt12_invoice: Option<&Bolt12Invoice>,
973983
mut route_params: RouteParameters, retry_strategy: Retry, router: &R,
974984
first_hops: Vec<ChannelDetails>, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS,
975985
node_id_lookup: &NL, secp_ctx: &Secp256k1<secp256k1::All>, best_block_height: u32, logger: &L,
@@ -1032,8 +1042,8 @@ impl OutboundPayments {
10321042
hash_map::Entry::Occupied(entry) => match entry.get() {
10331043
PendingOutboundPayment::InvoiceReceived { .. } => {
10341044
let (retryable_payment, onion_session_privs) = Self::create_pending_payment(
1035-
payment_hash, recipient_onion.clone(), keysend_preimage, None, &route,
1036-
Some(retry_strategy), payment_params, entropy_source, best_block_height
1045+
payment_hash, recipient_onion.clone(), keysend_preimage, None, bolt12_invoice.cloned(), &route,
1046+
Some(retry_strategy), payment_params, entropy_source, best_block_height,
10371047
);
10381048
*entry.into_mut() = retryable_payment;
10391049
onion_session_privs
@@ -1043,7 +1053,7 @@ impl OutboundPayments {
10431053
invoice_request
10441054
} else { unreachable!() };
10451055
let (retryable_payment, onion_session_privs) = Self::create_pending_payment(
1046-
payment_hash, recipient_onion.clone(), keysend_preimage, Some(invreq), &route,
1056+
payment_hash, recipient_onion.clone(), keysend_preimage, Some(invreq), bolt12_invoice.cloned(), &route,
10471057
Some(retry_strategy), payment_params, entropy_source, best_block_height
10481058
);
10491059
outbounds.insert(payment_id, retryable_payment);
@@ -1194,7 +1204,7 @@ impl OutboundPayments {
11941204
};
11951205

11961206
self.send_payment_for_bolt12_invoice_internal(
1197-
payment_id, payment_hash, Some(keysend_preimage), Some(&invoice_request), route_params,
1207+
payment_id, payment_hash, Some(keysend_preimage), Some(&invoice_request), None, route_params,
11981208
retry_strategy, router, first_hops, inflight_htlcs, entropy_source, node_signer,
11991209
node_id_lookup, secp_ctx, best_block_height, logger, pending_events, send_payment_along_path
12001210
)
@@ -1318,7 +1328,7 @@ impl OutboundPayments {
13181328
///
13191329
/// [`Event::PaymentPathFailed`]: crate::events::Event::PaymentPathFailed
13201330
/// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed
1321-
fn send_payment_internal<R: Deref, NS: Deref, ES: Deref, IH, SP, L: Deref>(
1331+
fn send_payment_for_non_bolt12_invoice<R: Deref, NS: Deref, ES: Deref, IH, SP, L: Deref>(
13221332
&self, payment_id: PaymentId, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields,
13231333
keysend_preimage: Option<PaymentPreimage>, retry_strategy: Retry, mut route_params: RouteParameters,
13241334
router: &R, first_hops: Vec<ChannelDetails>, inflight_htlcs: IH, entropy_source: &ES,
@@ -1340,7 +1350,7 @@ impl OutboundPayments {
13401350

13411351
let onion_session_privs = self.add_new_pending_payment(payment_hash,
13421352
recipient_onion.clone(), payment_id, keysend_preimage, &route, Some(retry_strategy),
1343-
Some(route_params.payment_params.clone()), entropy_source, best_block_height)
1353+
Some(route_params.payment_params.clone()), entropy_source, best_block_height, None)
13441354
.map_err(|_| {
13451355
log_error!(logger, "Payment with id {} is already pending. New payment had payment hash {}",
13461356
payment_id, payment_hash);
@@ -1654,7 +1664,7 @@ impl OutboundPayments {
16541664
let route = Route { paths: vec![path], route_params: None };
16551665
let onion_session_privs = self.add_new_pending_payment(payment_hash,
16561666
RecipientOnionFields::secret_only(payment_secret), payment_id, None, &route, None, None,
1657-
entropy_source, best_block_height
1667+
entropy_source, best_block_height, None
16581668
).map_err(|e| {
16591669
debug_assert!(matches!(e, PaymentSendFailure::DuplicatePayment));
16601670
ProbeSendFailure::DuplicateProbe
@@ -1709,20 +1719,21 @@ impl OutboundPayments {
17091719
&self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, payment_id: PaymentId,
17101720
route: &Route, retry_strategy: Option<Retry>, entropy_source: &ES, best_block_height: u32
17111721
) -> Result<Vec<[u8; 32]>, PaymentSendFailure> where ES::Target: EntropySource {
1712-
self.add_new_pending_payment(payment_hash, recipient_onion, payment_id, None, route, retry_strategy, None, entropy_source, best_block_height)
1722+
self.add_new_pending_payment(payment_hash, recipient_onion, payment_id, None, route, retry_strategy, None, entropy_source, best_block_height, None)
17131723
}
17141724

17151725
pub(super) fn add_new_pending_payment<ES: Deref>(
17161726
&self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, payment_id: PaymentId,
17171727
keysend_preimage: Option<PaymentPreimage>, route: &Route, retry_strategy: Option<Retry>,
1718-
payment_params: Option<PaymentParameters>, entropy_source: &ES, best_block_height: u32
1728+
payment_params: Option<PaymentParameters>, entropy_source: &ES, best_block_height: u32,
1729+
bolt12_invoice: Option<Bolt12Invoice>
17191730
) -> Result<Vec<[u8; 32]>, PaymentSendFailure> where ES::Target: EntropySource {
17201731
let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
17211732
match pending_outbounds.entry(payment_id) {
17221733
hash_map::Entry::Occupied(_) => Err(PaymentSendFailure::DuplicatePayment),
17231734
hash_map::Entry::Vacant(entry) => {
17241735
let (payment, onion_session_privs) = Self::create_pending_payment(
1725-
payment_hash, recipient_onion, keysend_preimage, None, route, retry_strategy,
1736+
payment_hash, recipient_onion, keysend_preimage, None, bolt12_invoice, route, retry_strategy,
17261737
payment_params, entropy_source, best_block_height
17271738
);
17281739
entry.insert(payment);
@@ -1734,8 +1745,8 @@ impl OutboundPayments {
17341745
fn create_pending_payment<ES: Deref>(
17351746
payment_hash: PaymentHash, recipient_onion: RecipientOnionFields,
17361747
keysend_preimage: Option<PaymentPreimage>, invoice_request: Option<InvoiceRequest>,
1737-
route: &Route, retry_strategy: Option<Retry>, payment_params: Option<PaymentParameters>,
1738-
entropy_source: &ES, best_block_height: u32
1748+
bolt12_invoice: Option<Bolt12Invoice>, route: &Route, retry_strategy: Option<Retry>,
1749+
payment_params: Option<PaymentParameters>, entropy_source: &ES, best_block_height: u32
17391750
) -> (PendingOutboundPayment, Vec<[u8; 32]>)
17401751
where
17411752
ES::Target: EntropySource,
@@ -1757,6 +1768,7 @@ impl OutboundPayments {
17571768
payment_metadata: recipient_onion.payment_metadata,
17581769
keysend_preimage,
17591770
invoice_request,
1771+
bolt12_invoice,
17601772
custom_tlvs: recipient_onion.custom_tlvs,
17611773
starting_block_height: best_block_height,
17621774
total_msat: route.get_total_amount(),
@@ -2374,6 +2386,7 @@ impl OutboundPayments {
23742386
payment_metadata: None, // only used for retries, and we'll never retry on startup
23752387
keysend_preimage: None, // only used for retries, and we'll never retry on startup
23762388
invoice_request: None, // only used for retries, and we'll never retry on startup
2389+
bolt12_invoice: None, // only used for retries, and we'll never retry on startup! TODO(vincenzopalazzo): double check this
23772390
custom_tlvs: Vec::new(), // only used for retries, and we'll never retry on startup
23782391
pending_amt_msat: path_amt,
23792392
pending_fee_msat: Some(path_fee),
@@ -2463,6 +2476,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
24632476
(10, starting_block_height, required),
24642477
(11, remaining_max_total_routing_fee_msat, option),
24652478
(13, invoice_request, option),
2479+
(15, bolt12_invoice, option),
24662480
(not_written, retry_strategy, (static_value, None)),
24672481
(not_written, attempts, (static_value, PaymentAttempts::new())),
24682482
},
@@ -2613,7 +2627,7 @@ mod tests {
26132627
outbound_payments.add_new_pending_payment(PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(),
26142628
PaymentId([0; 32]), None, &Route { paths: vec![], route_params: None },
26152629
Some(Retry::Attempts(1)), Some(expired_route_params.payment_params.clone()),
2616-
&&keys_manager, 0).unwrap();
2630+
&&keys_manager, 0, None).unwrap();
26172631
outbound_payments.find_route_and_send_payment(
26182632
PaymentHash([0; 32]), PaymentId([0; 32]), expired_route_params, &&router, vec![],
26192633
&|| InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger, &pending_events,
@@ -2656,7 +2670,7 @@ mod tests {
26562670
outbound_payments.add_new_pending_payment(PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(),
26572671
PaymentId([0; 32]), None, &Route { paths: vec![], route_params: None },
26582672
Some(Retry::Attempts(1)), Some(route_params.payment_params.clone()),
2659-
&&keys_manager, 0).unwrap();
2673+
&&keys_manager, 0, None).unwrap();
26602674
outbound_payments.find_route_and_send_payment(
26612675
PaymentHash([0; 32]), PaymentId([0; 32]), route_params, &&router, vec![],
26622676
&|| InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger, &pending_events,

0 commit comments

Comments
 (0)