X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Foutbound_payment.rs;h=6e927aa95ab2deed2ef86ba451741a9514ca0a9f;hb=0b196ebae6b9b1f861c2fc2ceddcc128b9f46f18;hp=30e718dccd65321cd5fee3f3d2fd632a0fce6d61;hpb=e4c44f3c5becabccdb6df4e4b30aaa14671f9705;p=rust-lightning diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 30e718dc..6e927aa9 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -17,7 +17,7 @@ use crate::sign::{EntropySource, NodeSigner, Recipient}; use crate::events::{self, PaymentFailureReason}; use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::ln::channelmanager::{ChannelDetails, EventCompletionAction, HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, PaymentId}; -use crate::ln::onion_utils::HTLCFailReason; +use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason}; use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router}; use crate::util::errors::APIError; use crate::util::logger::Logger; @@ -47,6 +47,7 @@ pub(crate) enum PendingOutboundPayment { payment_secret: Option, payment_metadata: Option>, keysend_preimage: Option, + custom_tlvs: Vec<(u64, Vec)>, pending_amt_msat: u64, /// Used to track the fee paid. Only present if the payment was serialized on 0.0.103+. pending_fee_msat: Option, @@ -431,10 +432,13 @@ pub struct RecipientOnionFields { /// [`Self::payment_secret`] and while nearly all lightning senders support secrets, metadata /// may not be supported as universally. pub payment_metadata: Option>, + /// See [`Self::custom_tlvs`] for more info. + pub(super) custom_tlvs: Vec<(u64, Vec)>, } impl_writeable_tlv_based!(RecipientOnionFields, { (0, payment_secret, option), + (1, custom_tlvs, optional_vec), (2, payment_metadata, option), }); @@ -443,7 +447,7 @@ impl RecipientOnionFields { /// set of onion fields for today's BOLT11 invoices - most nodes require a [`PaymentSecret`] /// but do not require or provide any further data. pub fn secret_only(payment_secret: PaymentSecret) -> Self { - Self { payment_secret: Some(payment_secret), payment_metadata: None } + Self { payment_secret: Some(payment_secret), payment_metadata: None, custom_tlvs: Vec::new() } } /// Creates a new [`RecipientOnionFields`] with no fields. This generally does not create @@ -455,7 +459,46 @@ impl RecipientOnionFields { /// [`ChannelManager::send_spontaneous_payment`]: super::channelmanager::ChannelManager::send_spontaneous_payment /// [`RecipientOnionFields::secret_only`]: RecipientOnionFields::secret_only pub fn spontaneous_empty() -> Self { - Self { payment_secret: None, payment_metadata: None } + Self { payment_secret: None, payment_metadata: None, custom_tlvs: Vec::new() } + } + + /// Creates a new [`RecipientOnionFields`] from an existing one, adding custom TLVs. Each + /// TLV is provided as a `(u64, Vec)` for the type number and serialized value + /// respectively. TLV type numbers must be unique and within the range + /// reserved for custom types, i.e. >= 2^16, otherwise this method will return `Err(())`. + /// + /// This method will also error for types in the experimental range which have been + /// standardized within the protocol, which only includes 5482373484 (keysend) for now. + /// + /// See [`Self::custom_tlvs`] for more info. + pub fn with_custom_tlvs(mut self, mut custom_tlvs: Vec<(u64, Vec)>) -> Result { + custom_tlvs.sort_unstable_by_key(|(typ, _)| *typ); + let mut prev_type = None; + for (typ, _) in custom_tlvs.iter() { + if *typ < 1 << 16 { return Err(()); } + if *typ == 5482373484 { return Err(()); } // keysend + match prev_type { + Some(prev) if prev >= *typ => return Err(()), + _ => {}, + } + prev_type = Some(*typ); + } + self.custom_tlvs = custom_tlvs; + Ok(self) + } + + /// Gets the custom TLVs that will be sent or have been received. + /// + /// Custom TLVs allow sending extra application-specific data with a payment. They provide + /// additional flexibility on top of payment metadata, as while other implementations may + /// require `payment_metadata` to reflect metadata provided in an invoice, custom TLVs + /// do not have this restriction. + /// + /// Note that if this field is non-empty, it will contain strictly increasing TLVs, each + /// represented by a `(u64, Vec)` for its type number and serialized value respectively. + /// This is validated when setting this field using [`Self::with_custom_tlvs`]. + pub fn custom_tlvs(&self) -> &Vec<(u64, Vec)> { + &self.custom_tlvs } /// When we have received some HTLC(s) towards an MPP payment, as we receive further HTLC(s) we @@ -468,11 +511,33 @@ impl RecipientOnionFields { pub(super) fn check_merge(&mut self, further_htlc_fields: &mut Self) -> Result<(), ()> { if self.payment_secret != further_htlc_fields.payment_secret { return Err(()); } if self.payment_metadata != further_htlc_fields.payment_metadata { return Err(()); } - // For custom TLVs we should just drop non-matching ones, but not reject the payment. + + let tlvs = &mut self.custom_tlvs; + let further_tlvs = &mut further_htlc_fields.custom_tlvs; + + let even_tlvs = tlvs.iter().filter(|(typ, _)| *typ % 2 == 0); + let further_even_tlvs = further_tlvs.iter().filter(|(typ, _)| *typ % 2 == 0); + if even_tlvs.ne(further_even_tlvs) { return Err(()) } + + tlvs.retain(|tlv| further_tlvs.iter().any(|further_tlv| tlv == further_tlv)); + further_tlvs.retain(|further_tlv| tlvs.iter().any(|tlv| tlv == further_tlv)); + Ok(()) } } +/// Arguments for [`super::channelmanager::ChannelManager::send_payment_along_path`]. +pub(super) struct SendAlongPathArgs<'a> { + pub path: &'a Path, + pub payment_hash: &'a PaymentHash, + pub recipient_onion: RecipientOnionFields, + pub total_value: u64, + pub cur_height: u32, + pub payment_id: PaymentId, + pub keysend_preimage: &'a Option, + pub session_priv_bytes: [u8; 32], +} + pub(super) struct OutboundPayments { pub(super) pending_outbound_payments: Mutex>, pub(super) retry_lock: Mutex<()>, @@ -499,8 +564,7 @@ impl OutboundPayments { NS::Target: NodeSigner, L::Target: Logger, IH: Fn() -> InFlightHtlcs, - SP: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId, - &Option, [u8; 32]) -> Result<(), APIError>, + SP: Fn(SendAlongPathArgs) -> Result<(), APIError>, { self.send_payment_internal(payment_id, payment_hash, recipient_onion, None, retry_strategy, route_params, router, first_hops, &compute_inflight_htlcs, entropy_source, node_signer, @@ -515,8 +579,7 @@ impl OutboundPayments { where ES::Target: EntropySource, NS::Target: NodeSigner, - F: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId, - &Option, [u8; 32]) -> Result<(), APIError> + F: Fn(SendAlongPathArgs) -> Result<(), APIError> { let onion_session_privs = self.add_new_pending_payment(payment_hash, recipient_onion.clone(), payment_id, None, route, None, None, entropy_source, best_block_height)?; self.pay_route_internal(route, payment_hash, recipient_onion, None, payment_id, None, @@ -537,8 +600,7 @@ impl OutboundPayments { NS::Target: NodeSigner, L::Target: Logger, IH: Fn() -> InFlightHtlcs, - SP: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId, - &Option, [u8; 32]) -> Result<(), APIError>, + SP: Fn(SendAlongPathArgs) -> Result<(), APIError>, { let preimage = payment_preimage .unwrap_or_else(|| PaymentPreimage(entropy_source.get_secure_random_bytes())); @@ -557,8 +619,7 @@ impl OutboundPayments { where ES::Target: EntropySource, NS::Target: NodeSigner, - F: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId, - &Option, [u8; 32]) -> Result<(), APIError> + F: Fn(SendAlongPathArgs) -> Result<(), APIError>, { let preimage = payment_preimage .unwrap_or_else(|| PaymentPreimage(entropy_source.get_secure_random_bytes())); @@ -587,8 +648,7 @@ impl OutboundPayments { R::Target: Router, ES::Target: EntropySource, NS::Target: NodeSigner, - SP: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId, - &Option, [u8; 32]) -> Result<(), APIError>, + SP: Fn(SendAlongPathArgs) -> Result<(), APIError>, IH: Fn() -> InFlightHtlcs, FH: Fn() -> Vec, L::Target: Logger, @@ -658,8 +718,7 @@ impl OutboundPayments { NS::Target: NodeSigner, L::Target: Logger, IH: Fn() -> InFlightHtlcs, - SP: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId, - &Option, [u8; 32]) -> Result<(), APIError> + SP: Fn(SendAlongPathArgs) -> Result<(), APIError>, { #[cfg(feature = "std")] { if has_expired(&route_params) { @@ -678,7 +737,7 @@ impl OutboundPayments { Some(route_params.payment_params.clone()), entropy_source, best_block_height) .map_err(|_| RetryableSendFailure::DuplicatePayment)?; - let res = self.pay_route_internal(&route, payment_hash, recipient_onion, None, payment_id, None, + let res = self.pay_route_internal(&route, payment_hash, recipient_onion, keysend_preimage, payment_id, None, onion_session_privs, node_signer, best_block_height, &send_payment_along_path); log_info!(logger, "Result sending payment with id {}: {:?}", log_bytes!(payment_id.0), res); if let Err(e) = res { @@ -699,8 +758,7 @@ impl OutboundPayments { NS::Target: NodeSigner, L::Target: Logger, IH: Fn() -> InFlightHtlcs, - SP: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId, - &Option, [u8; 32]) -> Result<(), APIError> + SP: Fn(SendAlongPathArgs) -> Result<(), APIError>, { #[cfg(feature = "std")] { if has_expired(&route_params) { @@ -757,7 +815,8 @@ impl OutboundPayments { hash_map::Entry::Occupied(mut payment) => { let res = match payment.get() { PendingOutboundPayment::Retryable { - total_msat, keysend_preimage, payment_secret, payment_metadata, pending_amt_msat, .. + total_msat, keysend_preimage, payment_secret, payment_metadata, + custom_tlvs, pending_amt_msat, .. } => { let retry_amt_msat = route.get_total_amount(); if retry_amt_msat + *pending_amt_msat > *total_msat * (100 + RETRY_OVERFLOW_PERCENTAGE) / 100 { @@ -768,6 +827,7 @@ impl OutboundPayments { (*total_msat, RecipientOnionFields { payment_secret: *payment_secret, payment_metadata: payment_metadata.clone(), + custom_tlvs: custom_tlvs.clone(), }, *keysend_preimage) }, PendingOutboundPayment::Legacy { .. } => { @@ -821,8 +881,7 @@ impl OutboundPayments { NS::Target: NodeSigner, L::Target: Logger, IH: Fn() -> InFlightHtlcs, - SP: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId, - &Option, [u8; 32]) -> Result<(), APIError> + SP: Fn(SendAlongPathArgs) -> Result<(), APIError>, { match err { PaymentSendFailure::AllFailedResendSafe(errs) => { @@ -894,8 +953,7 @@ impl OutboundPayments { where ES::Target: EntropySource, NS::Target: NodeSigner, - F: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId, - &Option, [u8; 32]) -> Result<(), APIError> + F: Fn(SendAlongPathArgs) -> Result<(), APIError>, { let payment_id = PaymentId(entropy_source.get_secure_random_bytes()); @@ -968,6 +1026,7 @@ impl OutboundPayments { payment_secret: recipient_onion.payment_secret, payment_metadata: recipient_onion.payment_metadata, keysend_preimage, + custom_tlvs: recipient_onion.custom_tlvs, starting_block_height: best_block_height, total_msat: route.get_total_amount(), }); @@ -989,8 +1048,7 @@ impl OutboundPayments { ) -> Result<(), PaymentSendFailure> where NS::Target: NodeSigner, - F: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId, - &Option, [u8; 32]) -> Result<(), APIError> + F: Fn(SendAlongPathArgs) -> Result<(), APIError>, { if route.paths.len() < 1 { return Err(PaymentSendFailure::ParameterError(APIError::InvalidRoute{err: "There must be at least one path to send over".to_owned()})); @@ -1031,9 +1089,11 @@ impl OutboundPayments { let cur_height = best_block_height + 1; let mut results = Vec::new(); debug_assert_eq!(route.paths.len(), onion_session_privs.len()); - for (path, session_priv) in route.paths.iter().zip(onion_session_privs.into_iter()) { - let mut path_res = send_payment_along_path(&path, &payment_hash, recipient_onion.clone(), - total_value, cur_height, payment_id, &keysend_preimage, session_priv); + for (path, session_priv_bytes) in route.paths.iter().zip(onion_session_privs.into_iter()) { + let mut path_res = send_payment_along_path(SendAlongPathArgs { + path: &path, payment_hash: &payment_hash, recipient_onion: recipient_onion.clone(), + total_value, cur_height, payment_id, keysend_preimage: &keysend_preimage, session_priv_bytes + }); match path_res { Ok(_) => {}, Err(APIError::MonitorUpdateInProgress) => { @@ -1044,7 +1104,7 @@ impl OutboundPayments { Err(_) => { let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap(); if let Some(payment) = pending_outbounds.get_mut(&payment_id) { - let removed = payment.remove(&session_priv, Some(path)); + let removed = payment.remove(&session_priv_bytes, Some(path)); debug_assert!(removed, "This can't happen as the payment has an entry for this path added by callers"); } else { debug_assert!(false, "This can't happen as the payment was added by callers"); @@ -1098,8 +1158,7 @@ impl OutboundPayments { ) -> Result<(), PaymentSendFailure> where NS::Target: NodeSigner, - F: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId, - &Option, [u8; 32]) -> Result<(), APIError> + F: Fn(SendAlongPathArgs) -> Result<(), APIError>, { self.pay_route_internal(route, payment_hash, recipient_onion, keysend_preimage, payment_id, recv_value_msat, onion_session_privs, node_signer, best_block_height, @@ -1118,7 +1177,7 @@ impl OutboundPayments { pub(super) fn claim_htlc( &self, payment_id: PaymentId, payment_preimage: PaymentPreimage, session_priv: SecretKey, - path: Path, from_onchain: bool, + path: Path, from_onchain: bool, ev_completion_action: EventCompletionAction, pending_events: &Mutex)>>, logger: &L, ) where L::Target: Logger { @@ -1135,7 +1194,7 @@ impl OutboundPayments { payment_preimage, payment_hash, fee_paid_msat, - }, None)); + }, Some(ev_completion_action.clone()))); payment.get_mut().mark_fulfilled(); } @@ -1152,7 +1211,7 @@ impl OutboundPayments { payment_id, payment_hash, path, - }, None)); + }, Some(ev_completion_action))); } } } else { @@ -1234,9 +1293,12 @@ impl OutboundPayments { pending_events: &Mutex)>>, logger: &L, ) -> bool where L::Target: Logger { #[cfg(test)] - let (network_update, short_channel_id, payment_retryable, onion_error_code, onion_error_data) = onion_error.decode_onion_failure(secp_ctx, logger, &source); + let DecodedOnionFailure { + network_update, short_channel_id, payment_retryable, onion_error_code, onion_error_data + } = onion_error.decode_onion_failure(secp_ctx, logger, &source); #[cfg(not(test))] - let (network_update, short_channel_id, payment_retryable, _, _) = onion_error.decode_onion_failure(secp_ctx, logger, &source); + let DecodedOnionFailure { network_update, short_channel_id, payment_retryable } = + onion_error.decode_onion_failure(secp_ctx, logger, &source); let payment_is_probe = payment_is_probe(payment_hash, &payment_id, probing_cookie_secret); let mut session_priv_bytes = [0; 32]; @@ -1417,6 +1479,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, (6, total_msat, required), (7, payment_metadata, option), (8, pending_amt_msat, required), + (9, custom_tlvs, optional_vec), (10, starting_block_height, required), (not_written, retry_strategy, (static_value, None)), (not_written, attempts, (static_value, PaymentAttempts::new())), @@ -1447,6 +1510,28 @@ mod tests { use alloc::collections::VecDeque; + #[test] + fn test_recipient_onion_fields_with_custom_tlvs() { + let onion_fields = RecipientOnionFields::spontaneous_empty(); + + let bad_type_range_tlvs = vec![ + (0, vec![42]), + (1, vec![42; 32]), + ]; + assert!(onion_fields.clone().with_custom_tlvs(bad_type_range_tlvs).is_err()); + + let keysend_tlv = vec![ + (5482373484, vec![42; 32]), + ]; + assert!(onion_fields.clone().with_custom_tlvs(keysend_tlv).is_err()); + + let good_tlvs = vec![ + ((1 << 16) + 1, vec![42]), + ((1 << 16) + 3, vec![42; 32]), + ]; + assert!(onion_fields.with_custom_tlvs(good_tlvs).is_ok()); + } + #[test] #[cfg(feature = "std")] fn fails_paying_after_expiration() { @@ -1480,8 +1565,8 @@ mod tests { &&keys_manager, 0).unwrap(); outbound_payments.retry_payment_internal( PaymentHash([0; 32]), PaymentId([0; 32]), expired_route_params, &&router, vec![], - &|| InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger, - &pending_events, &|_, _, _, _, _, _, _, _| Ok(())); + &|| InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger, &pending_events, + &|_| Ok(())); let events = pending_events.lock().unwrap(); assert_eq!(events.len(), 1); if let Event::PaymentFailed { ref reason, .. } = events[0].0 { @@ -1491,8 +1576,7 @@ mod tests { let err = outbound_payments.send_payment( PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]), Retry::Attempts(0), expired_route_params, &&router, vec![], || InFlightHtlcs::new(), - &&keys_manager, &&keys_manager, 0, &&logger, - &pending_events, |_, _, _, _, _, _, _, _| Ok(())).unwrap_err(); + &&keys_manager, &&keys_manager, 0, &&logger, &pending_events, |_| Ok(())).unwrap_err(); if let RetryableSendFailure::PaymentExpired = err { } else { panic!("Unexpected error"); } } } @@ -1528,8 +1612,8 @@ mod tests { &&keys_manager, 0).unwrap(); outbound_payments.retry_payment_internal( PaymentHash([0; 32]), PaymentId([0; 32]), route_params, &&router, vec![], - &|| InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger, - &pending_events, &|_, _, _, _, _, _, _, _| Ok(())); + &|| InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger, &pending_events, + &|_| Ok(())); let events = pending_events.lock().unwrap(); assert_eq!(events.len(), 1); if let Event::PaymentFailed { .. } = events[0].0 { } else { panic!("Unexpected event"); } @@ -1537,8 +1621,7 @@ mod tests { let err = outbound_payments.send_payment( PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]), Retry::Attempts(0), route_params, &&router, vec![], || InFlightHtlcs::new(), - &&keys_manager, &&keys_manager, 0, &&logger, - &pending_events, |_, _, _, _, _, _, _, _| Ok(())).unwrap_err(); + &&keys_manager, &&keys_manager, 0, &&logger, &pending_events, |_| Ok(())).unwrap_err(); if let RetryableSendFailure::RouteNotFound = err { } else { panic!("Unexpected error"); } } @@ -1587,8 +1670,7 @@ mod tests { PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]), Retry::Attempts(0), route_params.clone(), &&router, vec![], || InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger, &pending_events, - |_, _, _, _, _, _, _, _| Err(APIError::ChannelUnavailable { err: "test".to_owned() })) - .unwrap(); + |_| Err(APIError::ChannelUnavailable { err: "test".to_owned() })).unwrap(); let mut events = pending_events.lock().unwrap(); assert_eq!(events.len(), 2); if let Event::PaymentPathFailed { @@ -1606,7 +1688,7 @@ mod tests { PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]), Retry::Attempts(0), route_params.clone(), &&router, vec![], || InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger, &pending_events, - |_, _, _, _, _, _, _, _| Err(APIError::MonitorUpdateInProgress)).unwrap(); + |_| Err(APIError::MonitorUpdateInProgress)).unwrap(); assert_eq!(pending_events.lock().unwrap().len(), 0); // Ensure that any other error will result in a PaymentPathFailed event but no blamed scid. @@ -1614,8 +1696,7 @@ mod tests { PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([1; 32]), Retry::Attempts(0), route_params.clone(), &&router, vec![], || InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger, &pending_events, - |_, _, _, _, _, _, _, _| Err(APIError::APIMisuseError { err: "test".to_owned() })) - .unwrap(); + |_| Err(APIError::APIMisuseError { err: "test".to_owned() })).unwrap(); let events = pending_events.lock().unwrap(); assert_eq!(events.len(), 2); if let Event::PaymentPathFailed {