X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Foutbound_payment.rs;h=023412e1afb56cdcdd0a2d9195064e5744ccab6c;hb=6b0d94a3029f74de3a7542cbba0d48c2f7e5866b;hp=712f33e99f235eef1dc7b79b28dcf5c57ea16021;hpb=50336b3c7bcfea979c65fc34443bdd24b6fcc372;p=rust-lightning diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 712f33e9..023412e1 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -53,9 +53,11 @@ pub(crate) enum PendingOutboundPayment { }, AwaitingInvoice { timer_ticks_without_response: u8, + retry_strategy: Retry, }, InvoiceReceived { payment_hash: PaymentHash, + retry_strategy: Retry, }, Retryable { retry_strategy: Option, @@ -156,7 +158,7 @@ impl PendingOutboundPayment { match self { PendingOutboundPayment::Legacy { .. } => None, PendingOutboundPayment::AwaitingInvoice { .. } => None, - PendingOutboundPayment::InvoiceReceived { payment_hash } => Some(*payment_hash), + PendingOutboundPayment::InvoiceReceived { payment_hash, .. } => Some(*payment_hash), PendingOutboundPayment::Retryable { payment_hash, .. } => Some(*payment_hash), PendingOutboundPayment::Fulfilled { payment_hash, .. } => *payment_hash, PendingOutboundPayment::Abandoned { payment_hash, .. } => Some(*payment_hash), @@ -186,7 +188,7 @@ impl PendingOutboundPayment { payment_hash: *payment_hash, reason: Some(reason) }; - } else if let PendingOutboundPayment::InvoiceReceived { payment_hash } = self { + } else if let PendingOutboundPayment::InvoiceReceived { payment_hash, .. } = self { *self = PendingOutboundPayment::Abandoned { session_privs: HashSet::new(), payment_hash: *payment_hash, @@ -263,7 +265,7 @@ pub enum Retry { /// Each attempt may be multiple HTLCs along multiple paths if the router decides to split up a /// retry, and may retry multiple failed HTLCs at once if they failed around the same time and /// were retried along a route from a single call to [`Router::find_route_with_id`]. - Attempts(usize), + Attempts(u32), #[cfg(not(feature = "no-std"))] /// Time elapsed before abandoning retries for a payment. At least one attempt at payment is made; /// see [`PaymentParameters::expiry_time`] to avoid any attempt at payment after a specific time. @@ -272,6 +274,19 @@ pub enum Retry { Timeout(core::time::Duration), } +#[cfg(feature = "no-std")] +impl_writeable_tlv_based_enum!(Retry, + ; + (0, Attempts) +); + +#[cfg(not(feature = "no-std"))] +impl_writeable_tlv_based_enum!(Retry, + ; + (0, Attempts), + (2, Timeout) +); + impl Retry { pub(crate) fn is_retryable_now(&self, attempts: &PaymentAttempts) -> bool { match (self, attempts) { @@ -305,7 +320,7 @@ pub(crate) type PaymentAttempts = PaymentAttemptsUsingTime; pub(crate) struct PaymentAttemptsUsingTime { /// This count will be incremented only after the result of the attempt is known. When it's 0, /// it means the result of the first attempt is not known yet. - pub(crate) count: usize, + pub(crate) count: u32, /// This field is only used when retry is `Retry::Timeout` which is only build with feature std #[cfg(not(feature = "no-std"))] first_attempted_at: T, @@ -376,7 +391,7 @@ pub enum RetryableSendFailure { /// is in, see the description of individual enum states for more. /// /// [`ChannelManager::send_payment_with_route`]: crate::ln::channelmanager::ChannelManager::send_payment_with_route -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum PaymentSendFailure { /// A parameter which was passed to send_payment was invalid, preventing us from attempting to /// send the payment at all. @@ -450,6 +465,18 @@ pub(super) enum Bolt12PaymentError { DuplicateInvoice, } +/// Indicates that we failed to send a payment probe. Further errors may be surfaced later via +/// [`Event::ProbeFailed`]. +/// +/// [`Event::ProbeFailed`]: crate::events::Event::ProbeFailed +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ProbeSendFailure { + /// We were unable to find a route to the destination. + RouteNotFound, + /// We failed to send the payment probes. + SendingFailed(PaymentSendFailure), +} + /// Information which is provided, encrypted, to the payment recipient when sending HTLCs. /// /// This should generally be constructed with data communicated to us from the recipient (via a @@ -587,8 +614,6 @@ pub(super) struct SendAlongPathArgs<'a> { pub session_priv_bytes: [u8; 32], } -const BOLT_12_INVOICE_RETRY_STRATEGY: Retry = Retry::Attempts(3); - pub(super) struct OutboundPayments { pub(super) pending_outbound_payments: Mutex>, pub(super) retry_lock: Mutex<()>, @@ -707,10 +732,15 @@ impl OutboundPayments { { let payment_hash = invoice.payment_hash(); match self.pending_outbound_payments.lock().unwrap().entry(payment_id) { - hash_map::Entry::Occupied(entry) if entry.get().is_awaiting_invoice() => { - *entry.into_mut() = PendingOutboundPayment::InvoiceReceived { payment_hash }; + hash_map::Entry::Occupied(entry) => match entry.get() { + PendingOutboundPayment::AwaitingInvoice { retry_strategy, .. } => { + *entry.into_mut() = PendingOutboundPayment::InvoiceReceived { + payment_hash, + retry_strategy: *retry_strategy, + }; + }, + _ => return Err(Bolt12PaymentError::DuplicateInvoice), }, - hash_map::Entry::Occupied(_) => return Err(Bolt12PaymentError::DuplicateInvoice), hash_map::Entry::Vacant(_) => return Err(Bolt12PaymentError::UnexpectedInvoice), }; @@ -957,14 +987,14 @@ impl OutboundPayments { log_error!(logger, "Payment not yet sent"); return }, - PendingOutboundPayment::InvoiceReceived { payment_hash } => { + PendingOutboundPayment::InvoiceReceived { payment_hash, retry_strategy } => { let total_amount = route_params.final_value_msat; let recipient_onion = RecipientOnionFields { payment_secret: None, payment_metadata: None, custom_tlvs: vec![], }; - let retry_strategy = Some(BOLT_12_INVOICE_RETRY_STRATEGY); + let retry_strategy = Some(*retry_strategy); let payment_params = Some(route_params.payment_params.clone()); let (retryable_payment, onion_session_privs) = self.create_pending_payment( *payment_hash, recipient_onion.clone(), None, &route, @@ -1085,6 +1115,7 @@ impl OutboundPayments { F: Fn(SendAlongPathArgs) -> Result<(), APIError>, { let payment_id = PaymentId(entropy_source.get_secure_random_bytes()); + let payment_secret = PaymentSecret(entropy_source.get_secure_random_bytes()); let payment_hash = probing_cookie_from_id(&payment_id, probing_cookie_secret); @@ -1096,7 +1127,7 @@ impl OutboundPayments { let route = Route { paths: vec![path], route_params: None }; let onion_session_privs = self.add_new_pending_payment(payment_hash, - RecipientOnionFields::spontaneous_empty(), payment_id, None, &route, None, None, + RecipientOnionFields::secret_only(payment_secret), payment_id, None, &route, None, None, entropy_source, best_block_height)?; match self.pay_route_internal(&route, payment_hash, RecipientOnionFields::spontaneous_empty(), @@ -1186,13 +1217,16 @@ impl OutboundPayments { } #[allow(unused)] - pub(super) fn add_new_awaiting_invoice(&self, payment_id: PaymentId) -> Result<(), ()> { + pub(super) fn add_new_awaiting_invoice( + &self, payment_id: PaymentId, retry_strategy: Retry + ) -> Result<(), ()> { let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap(); match pending_outbounds.entry(payment_id) { hash_map::Entry::Occupied(_) => Err(()), hash_map::Entry::Vacant(entry) => { entry.insert(PendingOutboundPayment::AwaitingInvoice { timer_ticks_without_response: 0, + retry_strategy, }); Ok(()) @@ -1213,7 +1247,9 @@ impl OutboundPayments { if route.paths.len() < 1 { return Err(PaymentSendFailure::ParameterError(APIError::InvalidRoute{err: "There must be at least one path to send over".to_owned()})); } - if recipient_onion.payment_secret.is_none() && route.paths.len() > 1 { + if recipient_onion.payment_secret.is_none() && route.paths.len() > 1 + && !route.paths.iter().any(|p| p.blinded_tail.is_some()) + { return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError{err: "Payment secret is required for multi-path payments".to_owned()})); } let mut total_value = 0; @@ -1224,10 +1260,6 @@ impl OutboundPayments { path_errs.push(Err(APIError::InvalidRoute{err: "Path didn't go anywhere/had bogus size".to_owned()})); continue 'path_check; } - if path.blinded_tail.is_some() { - path_errs.push(Err(APIError::InvalidRoute{err: "Sending to blinded paths isn't supported yet".to_owned()})); - continue 'path_check; - } let dest_hop_idx = if path.blinded_tail.is_some() && path.blinded_tail.as_ref().unwrap().hops.len() > 1 { usize::max_value() } else { path.hops.len() - 1 }; for (idx, hop) in path.hops.iter().enumerate() { @@ -1667,9 +1699,11 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, }, (5, AwaitingInvoice) => { (0, timer_ticks_without_response, required), + (2, retry_strategy, required), }, (7, InvoiceReceived) => { (0, payment_hash, required), + (2, retry_strategy, required), }, ); @@ -1829,6 +1863,7 @@ mod tests { channel_features: ChannelFeatures::empty(), fee_msat: 0, cltv_expiry_delta: 0, + maybe_announced_channel: true, }], blinded_tail: None }], route_params: Some(route_params.clone()), }; @@ -1891,7 +1926,7 @@ mod tests { let payment_id = PaymentId([0; 32]); assert!(!outbound_payments.has_pending_payments()); - assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_ok()); + assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_ok()); assert!(outbound_payments.has_pending_payments()); for _ in 0..INVOICE_REQUEST_TIMEOUT_TICKS { @@ -1909,10 +1944,10 @@ mod tests { ); assert!(pending_events.lock().unwrap().is_empty()); - assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_ok()); + assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_ok()); assert!(outbound_payments.has_pending_payments()); - assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_err()); + assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_err()); } #[test] @@ -1922,7 +1957,7 @@ mod tests { let payment_id = PaymentId([0; 32]); assert!(!outbound_payments.has_pending_payments()); - assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_ok()); + assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_ok()); assert!(outbound_payments.has_pending_payments()); outbound_payments.abandon_payment( @@ -1950,7 +1985,7 @@ mod tests { let outbound_payments = OutboundPayments::new(); let payment_id = PaymentId([0; 32]); - assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_ok()); + assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_ok()); assert!(outbound_payments.has_pending_payments()); let created_at = now() - DEFAULT_RELATIVE_EXPIRY; @@ -1996,7 +2031,7 @@ mod tests { let outbound_payments = OutboundPayments::new(); let payment_id = PaymentId([0; 32]); - assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_ok()); + assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_ok()); assert!(outbound_payments.has_pending_payments()); let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -2049,7 +2084,7 @@ mod tests { let outbound_payments = OutboundPayments::new(); let payment_id = PaymentId([0; 32]); - assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_ok()); + assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_ok()); assert!(outbound_payments.has_pending_payments()); let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -2129,6 +2164,7 @@ mod tests { channel_features: ChannelFeatures::empty(), fee_msat: invoice.amount_msats(), cltv_expiry_delta: 0, + maybe_announced_channel: true, } ], blinded_tail: None, @@ -2149,7 +2185,7 @@ mod tests { assert!(!outbound_payments.has_pending_payments()); assert!(pending_events.lock().unwrap().is_empty()); - assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_ok()); + assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_ok()); assert!(outbound_payments.has_pending_payments()); assert_eq!(