Skip to content

Commit b9a07da

Browse files
Store invoice HTLCSource::OutboundRoute and ensure correct hashing
It moves the PaidBolt12Invoice (BOLT 12 invoice) into HTLCSource::OutboundRoute to ensure the invoice is available for proof-of-payment and event emission, as discussed in issue #3714. The commit also updates hashing implementations and derives to ensure correct behavior when the invoice is present, and propagates the invoice through relevant payment and event structures. This fixes a potential issue where the invoice could be lost on restart, affecting PoP reliability. Link: #3714 Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
1 parent c6921fa commit b9a07da

11 files changed

+71
-32
lines changed

fuzz/src/process_onion_failure.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,13 @@ fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
114114

115115
let path = Path { hops, blinded_tail };
116116

117-
let htlc_source =
118-
HTLCSource::OutboundRoute { path, session_priv, first_hop_htlc_msat: 0, payment_id };
117+
let htlc_source = HTLCSource::OutboundRoute {
118+
path,
119+
session_priv,
120+
first_hop_htlc_msat: 0,
121+
payment_id,
122+
bolt12_invoice: None,
123+
};
119124

120125
let failure_len = get_u16!();
121126
let failure_data = get_slice!(failure_len);

lightning/src/events/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2457,7 +2457,7 @@ impl<T: EventHandler> EventHandler for Arc<T> {
24572457
}
24582458

24592459
/// The BOLT 12 invoice that was paid, surfaced in [`Event::PaymentSent::bolt12_invoice`].
2460-
#[derive(Clone, Debug, PartialEq, Eq)]
2460+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
24612461
pub enum PaidBolt12Invoice {
24622462
/// The BOLT 12 invoice specified by the BOLT 12 specification,
24632463
/// allowing the user to perform proof of payment.

lightning/src/ln/channel.rs

+2
Original file line numberDiff line numberDiff line change
@@ -11836,6 +11836,7 @@ mod tests {
1183611836
session_priv: SecretKey::from_slice(&<Vec<u8>>::from_hex("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(),
1183711837
first_hop_htlc_msat: 548,
1183811838
payment_id: PaymentId([42; 32]),
11839+
bolt12_invoice: None,
1183911840
},
1184011841
skimmed_fee_msat: None,
1184111842
blinding_point: None,
@@ -12214,6 +12215,7 @@ mod tests {
1221412215
session_priv: test_utils::privkey(42),
1221512216
first_hop_htlc_msat: 0,
1221612217
payment_id: PaymentId([42; 32]),
12218+
bolt12_invoice: None,
1221712219
};
1221812220
let dummy_outbound_output = OutboundHTLCOutput {
1221912221
htlc_id: 0,

lightning/src/ln/channelmanager.rs

+20-9
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use bitcoin::{secp256k1, Sequence};
3434
#[cfg(splicing)]
3535
use bitcoin::{TxIn, Weight};
3636

37-
use crate::events::FundingInfo;
37+
use crate::events::{FundingInfo, PaidBolt12Invoice};
3838
use crate::blinded_path::message::{AsyncPaymentsContext, MessageContext, OffersContext};
3939
use crate::blinded_path::NodeIdLookUp;
4040
use crate::blinded_path::message::{BlindedMessagePath, MessageForwardNode};
@@ -668,6 +668,10 @@ mod fuzzy_channelmanager {
668668
/// doing a double-pass on route when we get a failure back
669669
first_hop_htlc_msat: u64,
670670
payment_id: PaymentId,
671+
/// The BOLT12 invoice associated with this payment, if any. This is stored here to ensure
672+
/// we can provide proof-of-payment details in payment claim events even after a restart
673+
/// with a stale ChannelManager state.
674+
bolt12_invoice: Option<PaidBolt12Invoice>,
671675
},
672676
}
673677

@@ -705,12 +709,13 @@ impl core::hash::Hash for HTLCSource {
705709
0u8.hash(hasher);
706710
prev_hop_data.hash(hasher);
707711
},
708-
HTLCSource::OutboundRoute { path, session_priv, payment_id, first_hop_htlc_msat } => {
712+
HTLCSource::OutboundRoute { path, session_priv, payment_id, first_hop_htlc_msat, bolt12_invoice } => {
709713
1u8.hash(hasher);
710714
path.hash(hasher);
711715
session_priv[..].hash(hasher);
712716
payment_id.hash(hasher);
713717
first_hop_htlc_msat.hash(hasher);
718+
bolt12_invoice.hash(hasher);
714719
},
715720
}
716721
}
@@ -723,6 +728,7 @@ impl HTLCSource {
723728
session_priv: SecretKey::from_slice(&[1; 32]).unwrap(),
724729
first_hop_htlc_msat: 0,
725730
payment_id: PaymentId([2; 32]),
731+
bolt12_invoice: None,
726732
}
727733
}
728734

@@ -4629,14 +4635,14 @@ where
46294635
let _lck = self.total_consistency_lock.read().unwrap();
46304636
self.send_payment_along_path(SendAlongPathArgs {
46314637
path, payment_hash, recipient_onion: &recipient_onion, total_value,
4632-
cur_height, payment_id, keysend_preimage, invoice_request: None, session_priv_bytes
4638+
cur_height, payment_id, keysend_preimage, invoice_request: None, bolt12_invoice: None, session_priv_bytes
46334639
})
46344640
}
46354641

46364642
fn send_payment_along_path(&self, args: SendAlongPathArgs) -> Result<(), APIError> {
46374643
let SendAlongPathArgs {
46384644
path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage,
4639-
invoice_request, session_priv_bytes
4645+
invoice_request, bolt12_invoice, session_priv_bytes
46404646
} = args;
46414647
// The top-level caller should hold the total_consistency_lock read lock.
46424648
debug_assert!(self.total_consistency_lock.try_write().is_err());
@@ -4686,6 +4692,7 @@ where
46864692
session_priv: session_priv.clone(),
46874693
first_hop_htlc_msat: htlc_msat,
46884694
payment_id,
4695+
bolt12_invoice: bolt12_invoice.cloned(),
46894696
}, onion_packet, None, &self.fee_estimator, &&logger);
46904697
match break_channel_entry!(self, peer_state, send_res, chan_entry) {
46914698
Some(monitor_update) => {
@@ -7447,15 +7454,15 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
74477454
next_channel_outpoint: OutPoint, next_channel_id: ChannelId, next_user_channel_id: Option<u128>,
74487455
) {
74497456
match source {
7450-
HTLCSource::OutboundRoute { session_priv, payment_id, path, .. } => {
7457+
HTLCSource::OutboundRoute { session_priv, payment_id, path, bolt12_invoice, .. } => {
74517458
debug_assert!(self.background_events_processed_since_startup.load(Ordering::Acquire),
74527459
"We don't support claim_htlc claims during startup - monitors may not be available yet");
74537460
debug_assert_eq!(next_channel_counterparty_node_id, path.hops[0].pubkey);
74547461
let ev_completion_action = EventCompletionAction::ReleaseRAAChannelMonitorUpdate {
74557462
channel_funding_outpoint: next_channel_outpoint, channel_id: next_channel_id,
74567463
counterparty_node_id: path.hops[0].pubkey,
74577464
};
7458-
self.pending_outbound_payments.claim_htlc(payment_id, payment_preimage,
7465+
self.pending_outbound_payments.claim_htlc(payment_id, payment_preimage, bolt12_invoice,
74597466
session_priv, path, from_onchain, ev_completion_action, &self.pending_events,
74607467
&self.logger);
74617468
},
@@ -13131,13 +13138,15 @@ impl Readable for HTLCSource {
1313113138
let mut payment_id = None;
1313213139
let mut payment_params: Option<PaymentParameters> = None;
1313313140
let mut blinded_tail: Option<BlindedTail> = None;
13141+
let mut bolt12_invoice: Option<PaidBolt12Invoice> = None;
1313413142
read_tlv_fields!(reader, {
1313513143
(0, session_priv, required),
1313613144
(1, payment_id, option),
1313713145
(2, first_hop_htlc_msat, required),
1313813146
(4, path_hops, required_vec),
1313913147
(5, payment_params, (option: ReadableArgs, 0)),
1314013148
(6, blinded_tail, option),
13149+
(7, bolt12_invoice, option),
1314113150
});
1314213151
if payment_id.is_none() {
1314313152
// For backwards compat, if there was no payment_id written, use the session_priv bytes
@@ -13160,6 +13169,7 @@ impl Readable for HTLCSource {
1316013169
first_hop_htlc_msat,
1316113170
path,
1316213171
payment_id: payment_id.unwrap(),
13172+
bolt12_invoice,
1316313173
})
1316413174
}
1316513175
1 => Ok(HTLCSource::PreviousHopData(Readable::read(reader)?)),
@@ -13171,7 +13181,7 @@ impl Readable for HTLCSource {
1317113181
impl Writeable for HTLCSource {
1317213182
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), crate::io::Error> {
1317313183
match self {
13174-
HTLCSource::OutboundRoute { ref session_priv, ref first_hop_htlc_msat, ref path, payment_id } => {
13184+
HTLCSource::OutboundRoute { ref session_priv, ref first_hop_htlc_msat, ref path, payment_id, bolt12_invoice } => {
1317513185
0u8.write(writer)?;
1317613186
let payment_id_opt = Some(payment_id);
1317713187
write_tlv_fields!(writer, {
@@ -13182,6 +13192,7 @@ impl Writeable for HTLCSource {
1318213192
(4, path.hops, required_vec),
1318313193
(5, None::<PaymentParameters>, option), // payment_params in LDK versions prior to 0.0.115
1318413194
(6, path.blinded_tail, option),
13195+
(7, bolt12_invoice, option),
1318513196
});
1318613197
}
1318713198
HTLCSource::PreviousHopData(ref field) => {
@@ -14368,7 +14379,7 @@ where
1436814379
} else { true }
1436914380
});
1437014381
},
14371-
HTLCSource::OutboundRoute { payment_id, session_priv, path, .. } => {
14382+
HTLCSource::OutboundRoute { payment_id, session_priv, path, bolt12_invoice, .. } => {
1437214383
if let Some(preimage) = preimage_opt {
1437314384
let pending_events = Mutex::new(pending_events_read);
1437414385
// Note that we set `from_onchain` to "false" here,
@@ -14385,7 +14396,7 @@ where
1438514396
channel_id: monitor.channel_id(),
1438614397
counterparty_node_id: path.hops[0].pubkey,
1438714398
};
14388-
pending_outbounds.claim_htlc(payment_id, preimage, session_priv,
14399+
pending_outbounds.claim_htlc(payment_id, preimage, bolt12_invoice, session_priv,
1438914400
path, false, compl_action, &pending_events, &&logger);
1439014401
pending_events_read = pending_events.into_inner().unwrap();
1439114402
}

lightning/src/ln/onion_utils.rs

+4
Original file line numberDiff line numberDiff line change
@@ -3233,6 +3233,7 @@ mod tests {
32333233
session_priv: get_test_session_key(),
32343234
first_hop_htlc_msat: 0,
32353235
payment_id: PaymentId([1; 32]),
3236+
bolt12_invoice: None,
32363237
};
32373238

32383239
process_onion_failure(&ctx_full, &logger, &htlc_source, onion_error)
@@ -3360,6 +3361,7 @@ mod tests {
33603361
session_priv,
33613362
first_hop_htlc_msat: dummy_amt_msat,
33623363
payment_id: PaymentId([1; 32]),
3364+
bolt12_invoice: None,
33633365
};
33643366

33653367
{
@@ -3547,6 +3549,7 @@ mod tests {
35473549
session_priv: session_key,
35483550
first_hop_htlc_msat: 0,
35493551
payment_id: PaymentId([1; 32]),
3552+
bolt12_invoice: None,
35503553
};
35513554

35523555
// Iterate over all possible failure positions and check that the cases that can be attributed are.
@@ -3655,6 +3658,7 @@ mod tests {
36553658
session_priv: get_test_session_key(),
36563659
first_hop_htlc_msat: 0,
36573660
payment_id: PaymentId([1; 32]),
3661+
bolt12_invoice: None,
36583662
};
36593663

36603664
let decrypted_failure = process_onion_failure(&ctx_full, &logger, &htlc_source, packet);

lightning/src/ln/outbound_payment.rs

+17-13
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ impl PendingOutboundPayment {
163163
_ => None,
164164
}
165165
}
166+
166167
fn increment_attempts(&mut self) {
167168
if let PendingOutboundPayment::Retryable { attempts, .. } = self {
168169
attempts.count += 1;
@@ -797,6 +798,7 @@ pub(super) struct SendAlongPathArgs<'a> {
797798
pub payment_id: PaymentId,
798799
pub keysend_preimage: &'a Option<PaymentPreimage>,
799800
pub invoice_request: Option<&'a InvoiceRequest>,
801+
pub bolt12_invoice: Option<&'a PaidBolt12Invoice>,
800802
pub session_priv_bytes: [u8; 32],
801803
}
802804

@@ -1042,7 +1044,7 @@ impl OutboundPayments {
10421044
hash_map::Entry::Occupied(entry) => match entry.get() {
10431045
PendingOutboundPayment::InvoiceReceived { .. } => {
10441046
let (retryable_payment, onion_session_privs) = Self::create_pending_payment(
1045-
payment_hash, recipient_onion.clone(), keysend_preimage, None, Some(bolt12_invoice), &route,
1047+
payment_hash, recipient_onion.clone(), keysend_preimage, None, Some(bolt12_invoice.clone()), &route,
10461048
Some(retry_strategy), payment_params, entropy_source, best_block_height,
10471049
);
10481050
*entry.into_mut() = retryable_payment;
@@ -1053,7 +1055,7 @@ impl OutboundPayments {
10531055
invoice_request
10541056
} else { unreachable!() };
10551057
let (retryable_payment, onion_session_privs) = Self::create_pending_payment(
1056-
payment_hash, recipient_onion.clone(), keysend_preimage, Some(invreq), Some(bolt12_invoice), &route,
1058+
payment_hash, recipient_onion.clone(), keysend_preimage, Some(invreq), Some(bolt12_invoice.clone()), &route,
10571059
Some(retry_strategy), payment_params, entropy_source, best_block_height
10581060
);
10591061
outbounds.insert(payment_id, retryable_payment);
@@ -1066,7 +1068,7 @@ impl OutboundPayments {
10661068
core::mem::drop(outbounds);
10671069

10681070
let result = self.pay_route_internal(
1069-
&route, payment_hash, &recipient_onion, keysend_preimage, invoice_request, payment_id,
1071+
&route, payment_hash, &recipient_onion, keysend_preimage, invoice_request, Some(bolt12_invoice), payment_id,
10701072
Some(route_params.final_value_msat), &onion_session_privs, node_signer, best_block_height,
10711073
&send_payment_along_path
10721074
);
@@ -1359,7 +1361,7 @@ impl OutboundPayments {
13591361
})?;
13601362

13611363
let res = self.pay_route_internal(&route, payment_hash, &recipient_onion,
1362-
keysend_preimage, None, payment_id, None, &onion_session_privs, node_signer,
1364+
keysend_preimage, None, None, payment_id, None, &onion_session_privs, node_signer,
13631365
best_block_height, &send_payment_along_path);
13641366
log_info!(logger, "Sending payment with id {} and hash {} returned {:?}",
13651367
payment_id, payment_hash, res);
@@ -1437,7 +1439,7 @@ impl OutboundPayments {
14371439
}
14381440
}
14391441
}
1440-
let (total_msat, recipient_onion, keysend_preimage, onion_session_privs, invoice_request) = {
1442+
let (total_msat, recipient_onion, keysend_preimage, onion_session_privs, invoice_request, bolt12_invoice) = {
14411443
let mut outbounds = self.pending_outbound_payments.lock().unwrap();
14421444
match outbounds.entry(payment_id) {
14431445
hash_map::Entry::Occupied(mut payment) => {
@@ -1479,8 +1481,9 @@ impl OutboundPayments {
14791481
}
14801482

14811483
payment.get_mut().increment_attempts();
1484+
let bolt12_invoice = payment.get().bolt12_invoice();
14821485

1483-
(total_msat, recipient_onion, keysend_preimage, onion_session_privs, invoice_request)
1486+
(total_msat, recipient_onion, keysend_preimage, onion_session_privs, invoice_request, bolt12_invoice.cloned())
14841487
},
14851488
PendingOutboundPayment::Legacy { .. } => {
14861489
log_error!(logger, "Unable to retry payments that were initially sent on LDK versions prior to 0.0.102");
@@ -1520,7 +1523,7 @@ impl OutboundPayments {
15201523
}
15211524
};
15221525
let res = self.pay_route_internal(&route, payment_hash, &recipient_onion, keysend_preimage,
1523-
invoice_request.as_ref(), payment_id, Some(total_msat), &onion_session_privs, node_signer,
1526+
invoice_request.as_ref(), bolt12_invoice, payment_id, Some(total_msat), &onion_session_privs, node_signer,
15241527
best_block_height, &send_payment_along_path);
15251528
log_info!(logger, "Result retrying payment id {}: {:?}", &payment_id, res);
15261529
if let Err(e) = res {
@@ -1673,7 +1676,7 @@ impl OutboundPayments {
16731676

16741677
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
16751678
match self.pay_route_internal(&route, payment_hash, &recipient_onion_fields,
1676-
None, None, payment_id, None, &onion_session_privs, node_signer, best_block_height,
1679+
None, None, None, payment_id, None, &onion_session_privs, node_signer, best_block_height,
16771680
&send_payment_along_path
16781681
) {
16791682
Ok(()) => Ok((payment_hash, payment_id)),
@@ -1865,7 +1868,7 @@ impl OutboundPayments {
18651868

18661869
fn pay_route_internal<NS: Deref, F>(
18671870
&self, route: &Route, payment_hash: PaymentHash, recipient_onion: &RecipientOnionFields,
1868-
keysend_preimage: Option<PaymentPreimage>, invoice_request: Option<&InvoiceRequest>,
1871+
keysend_preimage: Option<PaymentPreimage>, invoice_request: Option<&InvoiceRequest>, bolt12_invoice: Option<PaidBolt12Invoice>,
18691872
payment_id: PaymentId, recv_value_msat: Option<u64>, onion_session_privs: &Vec<[u8; 32]>,
18701873
node_signer: &NS, best_block_height: u32, send_payment_along_path: &F
18711874
) -> Result<(), PaymentSendFailure>
@@ -1921,6 +1924,7 @@ impl OutboundPayments {
19211924
let path_res = send_payment_along_path(SendAlongPathArgs {
19221925
path: &path, payment_hash: &payment_hash, recipient_onion, total_value,
19231926
cur_height, payment_id, keysend_preimage: &keysend_preimage, invoice_request,
1927+
bolt12_invoice: bolt12_invoice.as_ref(),
19241928
session_priv_bytes: *session_priv_bytes
19251929
});
19261930
results.push(path_res);
@@ -1987,7 +1991,7 @@ impl OutboundPayments {
19871991
F: Fn(SendAlongPathArgs) -> Result<(), APIError>,
19881992
{
19891993
self.pay_route_internal(route, payment_hash, &recipient_onion,
1990-
keysend_preimage, None, payment_id, recv_value_msat, &onion_session_privs,
1994+
keysend_preimage, None, None, payment_id, recv_value_msat, &onion_session_privs,
19911995
node_signer, best_block_height, &send_payment_along_path)
19921996
.map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e })
19931997
}
@@ -2008,8 +2012,8 @@ impl OutboundPayments {
20082012
}
20092013

20102014
pub(super) fn claim_htlc<L: Deref>(
2011-
&self, payment_id: PaymentId, payment_preimage: PaymentPreimage, session_priv: SecretKey,
2012-
path: Path, from_onchain: bool, ev_completion_action: EventCompletionAction,
2015+
&self, payment_id: PaymentId, payment_preimage: PaymentPreimage, bolt12_invoice: Option<PaidBolt12Invoice>,
2016+
session_priv: SecretKey, path: Path, from_onchain: bool, ev_completion_action: EventCompletionAction,
20132017
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>,
20142018
logger: &L,
20152019
) where L::Target: Logger {
@@ -2029,7 +2033,7 @@ impl OutboundPayments {
20292033
payment_hash,
20302034
amount_msat,
20312035
fee_paid_msat,
2032-
bolt12_invoice: payment.get().bolt12_invoice().cloned(),
2036+
bolt12_invoice: bolt12_invoice.clone(),
20332037
}, Some(ev_completion_action.clone())));
20342038
payment.get_mut().mark_fulfilled();
20352039
}

lightning/src/offers/invoice.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1495,7 +1495,7 @@ pub(super) type BlindedPayInfoIter<'a> = core::iter::Map<
14951495
>;
14961496

14971497
/// Wire representation for an on-chain fallback address.
1498-
#[derive(Clone, Debug, PartialEq)]
1498+
#[derive(Clone, Debug, PartialEq, Hash)]
14991499
pub(super) struct FallbackAddress {
15001500
pub(super) version: u8,
15011501
pub(super) program: Vec<u8>,

lightning/src/offers/nonce.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use crate::prelude::*;
2626
/// [`Offer::metadata`]: crate::offers::offer::Offer::metadata
2727
/// [`Offer::issuer_signing_pubkey`]: crate::offers::offer::Offer::issuer_signing_pubkey
2828
/// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
29-
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29+
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
3030
pub struct Nonce(pub(crate) [u8; Self::LENGTH]);
3131

3232
impl Nonce {

lightning/src/offers/offer.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ pub struct Offer {
604604
///
605605
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
606606
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
607-
#[derive(Clone, Debug)]
607+
#[derive(Clone, Debug, Hash)]
608608
#[cfg_attr(test, derive(PartialEq))]
609609
pub(super) struct OfferContents {
610610
chains: Option<Vec<ChainHash>>,
@@ -1059,7 +1059,7 @@ impl Writeable for OfferContents {
10591059

10601060
/// The minimum amount required for an item in an [`Offer`], denominated in either bitcoin or
10611061
/// another currency.
1062-
#[derive(Clone, Copy, Debug, PartialEq)]
1062+
#[derive(Clone, Copy, Debug, PartialEq, Hash)]
10631063
pub enum Amount {
10641064
/// An amount of bitcoin.
10651065
Bitcoin {
@@ -1079,7 +1079,7 @@ pub enum Amount {
10791079
pub type CurrencyCode = [u8; 3];
10801080

10811081
/// Quantity of items supported by an [`Offer`].
1082-
#[derive(Clone, Copy, Debug, PartialEq)]
1082+
#[derive(Clone, Copy, Debug, PartialEq, Hash)]
10831083
pub enum Quantity {
10841084
/// Up to a specific number of items (inclusive). Use when more than one item can be requested
10851085
/// but is limited (e.g., because of per customer or inventory limits).

0 commit comments

Comments
 (0)