diff --git a/fuzz/src/process_onion_failure.rs b/fuzz/src/process_onion_failure.rs index cc941da15a3..2b0d8c4c72b 100644 --- a/fuzz/src/process_onion_failure.rs +++ b/fuzz/src/process_onion_failure.rs @@ -114,8 +114,13 @@ fn do_test(data: &[u8], out: Out) { let path = Path { hops, blinded_tail }; - let htlc_source = - HTLCSource::OutboundRoute { path, session_priv, first_hop_htlc_msat: 0, payment_id }; + let htlc_source = HTLCSource::OutboundRoute { + path, + session_priv, + first_hop_htlc_msat: 0, + payment_id, + bolt12_invoice: None, + }; let failure_len = get_u16!(); let failure_data = get_slice!(failure_len); diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 8d28c9b4191..7998dfbb125 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -2457,7 +2457,7 @@ impl EventHandler for Arc { } /// The BOLT 12 invoice that was paid, surfaced in [`Event::PaymentSent::bolt12_invoice`]. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum PaidBolt12Invoice { /// The BOLT 12 invoice specified by the BOLT 12 specification, /// allowing the user to perform proof of payment. diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index d4d613967c7..21c06f96761 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -11836,6 +11836,7 @@ mod tests { session_priv: SecretKey::from_slice(&>::from_hex("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(), first_hop_htlc_msat: 548, payment_id: PaymentId([42; 32]), + bolt12_invoice: None, }, skimmed_fee_msat: None, blinding_point: None, @@ -12214,6 +12215,7 @@ mod tests { session_priv: test_utils::privkey(42), first_hop_htlc_msat: 0, payment_id: PaymentId([42; 32]), + bolt12_invoice: None, }; let dummy_outbound_output = OutboundHTLCOutput { htlc_id: 0, diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 363f2ffdb65..69104fbb564 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -34,7 +34,7 @@ use bitcoin::{secp256k1, Sequence}; #[cfg(splicing)] use bitcoin::{TxIn, Weight}; -use crate::events::FundingInfo; +use crate::events::{FundingInfo, PaidBolt12Invoice}; use crate::blinded_path::message::{AsyncPaymentsContext, MessageContext, OffersContext}; use crate::blinded_path::NodeIdLookUp; use crate::blinded_path::message::{BlindedMessagePath, MessageForwardNode}; @@ -668,6 +668,10 @@ mod fuzzy_channelmanager { /// doing a double-pass on route when we get a failure back first_hop_htlc_msat: u64, payment_id: PaymentId, + /// The BOLT12 invoice associated with this payment, if any. This is stored here to ensure + /// we can provide proof-of-payment details in payment claim events even after a restart + /// with a stale ChannelManager state. + bolt12_invoice: Option, }, } @@ -705,12 +709,13 @@ impl core::hash::Hash for HTLCSource { 0u8.hash(hasher); prev_hop_data.hash(hasher); }, - HTLCSource::OutboundRoute { path, session_priv, payment_id, first_hop_htlc_msat } => { + HTLCSource::OutboundRoute { path, session_priv, payment_id, first_hop_htlc_msat, bolt12_invoice } => { 1u8.hash(hasher); path.hash(hasher); session_priv[..].hash(hasher); payment_id.hash(hasher); first_hop_htlc_msat.hash(hasher); + bolt12_invoice.hash(hasher); }, } } @@ -723,6 +728,7 @@ impl HTLCSource { session_priv: SecretKey::from_slice(&[1; 32]).unwrap(), first_hop_htlc_msat: 0, payment_id: PaymentId([2; 32]), + bolt12_invoice: None, } } @@ -4629,14 +4635,14 @@ where let _lck = self.total_consistency_lock.read().unwrap(); self.send_payment_along_path(SendAlongPathArgs { path, payment_hash, recipient_onion: &recipient_onion, total_value, - cur_height, payment_id, keysend_preimage, invoice_request: None, session_priv_bytes + cur_height, payment_id, keysend_preimage, invoice_request: None, bolt12_invoice: None, session_priv_bytes }) } fn send_payment_along_path(&self, args: SendAlongPathArgs) -> Result<(), APIError> { let SendAlongPathArgs { path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, - invoice_request, session_priv_bytes + invoice_request, bolt12_invoice, session_priv_bytes } = args; // The top-level caller should hold the total_consistency_lock read lock. debug_assert!(self.total_consistency_lock.try_write().is_err()); @@ -4686,6 +4692,7 @@ where session_priv: session_priv.clone(), first_hop_htlc_msat: htlc_msat, payment_id, + bolt12_invoice: bolt12_invoice.cloned(), }, onion_packet, None, &self.fee_estimator, &&logger); match break_channel_entry!(self, peer_state, send_res, chan_entry) { Some(monitor_update) => { @@ -7447,7 +7454,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ next_channel_outpoint: OutPoint, next_channel_id: ChannelId, next_user_channel_id: Option, ) { match source { - HTLCSource::OutboundRoute { session_priv, payment_id, path, .. } => { + HTLCSource::OutboundRoute { session_priv, payment_id, path, bolt12_invoice, .. } => { debug_assert!(self.background_events_processed_since_startup.load(Ordering::Acquire), "We don't support claim_htlc claims during startup - monitors may not be available yet"); debug_assert_eq!(next_channel_counterparty_node_id, path.hops[0].pubkey); @@ -7455,7 +7462,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ channel_funding_outpoint: next_channel_outpoint, channel_id: next_channel_id, counterparty_node_id: path.hops[0].pubkey, }; - self.pending_outbound_payments.claim_htlc(payment_id, payment_preimage, + self.pending_outbound_payments.claim_htlc(payment_id, payment_preimage, bolt12_invoice, session_priv, path, from_onchain, ev_completion_action, &self.pending_events, &self.logger); }, @@ -13131,6 +13138,7 @@ impl Readable for HTLCSource { let mut payment_id = None; let mut payment_params: Option = None; let mut blinded_tail: Option = None; + let mut bolt12_invoice: Option = None; read_tlv_fields!(reader, { (0, session_priv, required), (1, payment_id, option), @@ -13138,6 +13146,7 @@ impl Readable for HTLCSource { (4, path_hops, required_vec), (5, payment_params, (option: ReadableArgs, 0)), (6, blinded_tail, option), + (7, bolt12_invoice, option), }); if payment_id.is_none() { // For backwards compat, if there was no payment_id written, use the session_priv bytes @@ -13160,6 +13169,7 @@ impl Readable for HTLCSource { first_hop_htlc_msat, path, payment_id: payment_id.unwrap(), + bolt12_invoice, }) } 1 => Ok(HTLCSource::PreviousHopData(Readable::read(reader)?)), @@ -13171,7 +13181,7 @@ impl Readable for HTLCSource { impl Writeable for HTLCSource { fn write(&self, writer: &mut W) -> Result<(), crate::io::Error> { match self { - HTLCSource::OutboundRoute { ref session_priv, ref first_hop_htlc_msat, ref path, payment_id } => { + HTLCSource::OutboundRoute { ref session_priv, ref first_hop_htlc_msat, ref path, payment_id, bolt12_invoice } => { 0u8.write(writer)?; let payment_id_opt = Some(payment_id); write_tlv_fields!(writer, { @@ -13182,6 +13192,7 @@ impl Writeable for HTLCSource { (4, path.hops, required_vec), (5, None::, option), // payment_params in LDK versions prior to 0.0.115 (6, path.blinded_tail, option), + (7, bolt12_invoice, option), }); } HTLCSource::PreviousHopData(ref field) => { @@ -14368,7 +14379,7 @@ where } else { true } }); }, - HTLCSource::OutboundRoute { payment_id, session_priv, path, .. } => { + HTLCSource::OutboundRoute { payment_id, session_priv, path, bolt12_invoice, .. } => { if let Some(preimage) = preimage_opt { let pending_events = Mutex::new(pending_events_read); // Note that we set `from_onchain` to "false" here, @@ -14385,7 +14396,7 @@ where channel_id: monitor.channel_id(), counterparty_node_id: path.hops[0].pubkey, }; - pending_outbounds.claim_htlc(payment_id, preimage, session_priv, + pending_outbounds.claim_htlc(payment_id, preimage, bolt12_invoice, session_priv, path, false, compl_action, &pending_events, &&logger); pending_events_read = pending_events.into_inner().unwrap(); } diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index f4cd412cc1d..f69ad5c10fe 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -3233,6 +3233,7 @@ mod tests { session_priv: get_test_session_key(), first_hop_htlc_msat: 0, payment_id: PaymentId([1; 32]), + bolt12_invoice: None, }; process_onion_failure(&ctx_full, &logger, &htlc_source, onion_error) @@ -3360,6 +3361,7 @@ mod tests { session_priv, first_hop_htlc_msat: dummy_amt_msat, payment_id: PaymentId([1; 32]), + bolt12_invoice: None, }; { @@ -3547,6 +3549,7 @@ mod tests { session_priv: session_key, first_hop_htlc_msat: 0, payment_id: PaymentId([1; 32]), + bolt12_invoice: None, }; // Iterate over all possible failure positions and check that the cases that can be attributed are. @@ -3655,6 +3658,7 @@ mod tests { session_priv: get_test_session_key(), first_hop_htlc_msat: 0, payment_id: PaymentId([1; 32]), + bolt12_invoice: None, }; let decrypted_failure = process_onion_failure(&ctx_full, &logger, &htlc_source, packet); diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 8cab698a59a..4affbc2d6dd 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -163,6 +163,7 @@ impl PendingOutboundPayment { _ => None, } } + fn increment_attempts(&mut self) { if let PendingOutboundPayment::Retryable { attempts, .. } = self { attempts.count += 1; @@ -797,6 +798,7 @@ pub(super) struct SendAlongPathArgs<'a> { pub payment_id: PaymentId, pub keysend_preimage: &'a Option, pub invoice_request: Option<&'a InvoiceRequest>, + pub bolt12_invoice: Option<&'a PaidBolt12Invoice>, pub session_priv_bytes: [u8; 32], } @@ -1042,7 +1044,7 @@ impl OutboundPayments { hash_map::Entry::Occupied(entry) => match entry.get() { PendingOutboundPayment::InvoiceReceived { .. } => { let (retryable_payment, onion_session_privs) = Self::create_pending_payment( - payment_hash, recipient_onion.clone(), keysend_preimage, None, Some(bolt12_invoice), &route, + payment_hash, recipient_onion.clone(), keysend_preimage, None, Some(bolt12_invoice.clone()), &route, Some(retry_strategy), payment_params, entropy_source, best_block_height, ); *entry.into_mut() = retryable_payment; @@ -1053,7 +1055,7 @@ impl OutboundPayments { invoice_request } else { unreachable!() }; let (retryable_payment, onion_session_privs) = Self::create_pending_payment( - payment_hash, recipient_onion.clone(), keysend_preimage, Some(invreq), Some(bolt12_invoice), &route, + payment_hash, recipient_onion.clone(), keysend_preimage, Some(invreq), Some(bolt12_invoice.clone()), &route, Some(retry_strategy), payment_params, entropy_source, best_block_height ); outbounds.insert(payment_id, retryable_payment); @@ -1066,7 +1068,7 @@ impl OutboundPayments { core::mem::drop(outbounds); let result = self.pay_route_internal( - &route, payment_hash, &recipient_onion, keysend_preimage, invoice_request, payment_id, + &route, payment_hash, &recipient_onion, keysend_preimage, invoice_request, Some(&bolt12_invoice), payment_id, Some(route_params.final_value_msat), &onion_session_privs, node_signer, best_block_height, &send_payment_along_path ); @@ -1359,7 +1361,7 @@ impl OutboundPayments { })?; let res = self.pay_route_internal(&route, payment_hash, &recipient_onion, - keysend_preimage, None, payment_id, None, &onion_session_privs, node_signer, + keysend_preimage, None, None, payment_id, None, &onion_session_privs, node_signer, best_block_height, &send_payment_along_path); log_info!(logger, "Sending payment with id {} and hash {} returned {:?}", payment_id, payment_hash, res); @@ -1437,7 +1439,7 @@ impl OutboundPayments { } } } - let (total_msat, recipient_onion, keysend_preimage, onion_session_privs, invoice_request) = { + let (total_msat, recipient_onion, keysend_preimage, onion_session_privs, invoice_request, bolt12_invoice) = { let mut outbounds = self.pending_outbound_payments.lock().unwrap(); match outbounds.entry(payment_id) { hash_map::Entry::Occupied(mut payment) => { @@ -1479,8 +1481,9 @@ impl OutboundPayments { } payment.get_mut().increment_attempts(); + let bolt12_invoice = payment.get().bolt12_invoice(); - (total_msat, recipient_onion, keysend_preimage, onion_session_privs, invoice_request) + (total_msat, recipient_onion, keysend_preimage, onion_session_privs, invoice_request, bolt12_invoice.cloned()) }, PendingOutboundPayment::Legacy { .. } => { 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 { } }; let res = self.pay_route_internal(&route, payment_hash, &recipient_onion, keysend_preimage, - invoice_request.as_ref(), payment_id, Some(total_msat), &onion_session_privs, node_signer, + invoice_request.as_ref(), bolt12_invoice.as_ref(), payment_id, Some(total_msat), &onion_session_privs, node_signer, best_block_height, &send_payment_along_path); log_info!(logger, "Result retrying payment id {}: {:?}", &payment_id, res); if let Err(e) = res { @@ -1673,7 +1676,7 @@ impl OutboundPayments { let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); match self.pay_route_internal(&route, payment_hash, &recipient_onion_fields, - None, None, payment_id, None, &onion_session_privs, node_signer, best_block_height, + None, None, None, payment_id, None, &onion_session_privs, node_signer, best_block_height, &send_payment_along_path ) { Ok(()) => Ok((payment_hash, payment_id)), @@ -1865,7 +1868,7 @@ impl OutboundPayments { fn pay_route_internal( &self, route: &Route, payment_hash: PaymentHash, recipient_onion: &RecipientOnionFields, - keysend_preimage: Option, invoice_request: Option<&InvoiceRequest>, + keysend_preimage: Option, invoice_request: Option<&InvoiceRequest>, bolt12_invoice: Option<&PaidBolt12Invoice>, payment_id: PaymentId, recv_value_msat: Option, onion_session_privs: &Vec<[u8; 32]>, node_signer: &NS, best_block_height: u32, send_payment_along_path: &F ) -> Result<(), PaymentSendFailure> @@ -1921,6 +1924,7 @@ impl OutboundPayments { let path_res = send_payment_along_path(SendAlongPathArgs { path: &path, payment_hash: &payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage: &keysend_preimage, invoice_request, + bolt12_invoice, session_priv_bytes: *session_priv_bytes }); results.push(path_res); @@ -1987,7 +1991,7 @@ impl OutboundPayments { F: Fn(SendAlongPathArgs) -> Result<(), APIError>, { self.pay_route_internal(route, payment_hash, &recipient_onion, - keysend_preimage, None, payment_id, recv_value_msat, &onion_session_privs, + keysend_preimage, None, None, payment_id, recv_value_msat, &onion_session_privs, node_signer, best_block_height, &send_payment_along_path) .map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e }) } @@ -2008,8 +2012,8 @@ impl OutboundPayments { } pub(super) fn claim_htlc( - &self, payment_id: PaymentId, payment_preimage: PaymentPreimage, session_priv: SecretKey, - path: Path, from_onchain: bool, ev_completion_action: EventCompletionAction, + &self, payment_id: PaymentId, payment_preimage: PaymentPreimage, bolt12_invoice: Option, + session_priv: SecretKey, path: Path, from_onchain: bool, ev_completion_action: EventCompletionAction, pending_events: &Mutex)>>, logger: &L, ) where L::Target: Logger { @@ -2029,7 +2033,7 @@ impl OutboundPayments { payment_hash, amount_msat, fee_paid_msat, - bolt12_invoice: payment.get().bolt12_invoice().cloned(), + bolt12_invoice: bolt12_invoice, }, Some(ev_completion_action.clone()))); payment.get_mut().mark_fulfilled(); } diff --git a/lightning/src/offers/static_invoice.rs b/lightning/src/offers/static_invoice.rs index e068b43af2f..08170fda867 100644 --- a/lightning/src/offers/static_invoice.rs +++ b/lightning/src/offers/static_invoice.rs @@ -80,6 +80,12 @@ impl PartialEq for StaticInvoice { impl Eq for StaticInvoice {} +impl core::hash::Hash for StaticInvoice { + fn hash(&self, state: &mut H) { + self.bytes.hash(state); + } +} + /// The contents of a [`StaticInvoice`] for responding to an [`Offer`]. /// /// [`Offer`]: crate::offers::offer::Offer