X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Foutbound_payment.rs;h=c2d0a6b62c9d11285439645a7a168d410ad9d76f;hb=72a7da8d5175f656d6e4b73b7ab7f5b2ee9a89f5;hp=e230002a04ed7c00fea2785cc6564726bb565c8d;hpb=ae3453393227f932eef574735f8feee4278eccc8;p=rust-lightning diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index e230002a..c2d0a6b6 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -15,7 +15,7 @@ use bitcoin::secp256k1::{self, Secp256k1, SecretKey}; use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient}; use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; -use crate::ln::channelmanager::{HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, PaymentId}; +use crate::ln::channelmanager::{HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, MIN_HTLC_RELAY_HOLDING_CELL_MILLIS, PaymentId}; use crate::ln::msgs::DecodeError; use crate::ln::onion_utils::HTLCFailReason; use crate::routing::router::{PaymentParameters, Route, RouteHop, RouteParameters, RoutePath}; @@ -29,6 +29,7 @@ use crate::util::time::tests::SinceEpoch; use core::cmp; use core::fmt::{self, Display, Formatter}; use core::ops::Deref; +use core::time::Duration; use crate::prelude::*; use crate::sync::Mutex; @@ -40,6 +41,9 @@ pub(crate) enum PendingOutboundPayment { session_privs: HashSet<[u8; 32]>, }, Retryable { + retry_strategy: Retry, + attempts: PaymentAttempts, + route_params: Option, session_privs: HashSet<[u8; 32]>, payment_hash: PaymentHash, payment_secret: Option, @@ -73,6 +77,22 @@ pub(crate) enum PendingOutboundPayment { } impl PendingOutboundPayment { + fn increment_attempts(&mut self) { + if let PendingOutboundPayment::Retryable { attempts, .. } = self { + attempts.count += 1; + } + } + fn is_retryable_now(&self) -> bool { + if let PendingOutboundPayment::Retryable { retry_strategy, attempts, .. } = self { + return retry_strategy.is_retryable_now(&attempts) + } + false + } + pub fn insert_previously_failed_scid(&mut self, scid: u64) { + if let PendingOutboundPayment::Retryable { route_params: Some(params), .. } = self { + params.payment_params.previously_failed_channels.push(scid); + } + } pub(super) fn is_fulfilled(&self) -> bool { match self { PendingOutboundPayment::Fulfilled { .. } => true, @@ -360,7 +380,9 @@ impl OutboundPayments { u32, PaymentId, &Option, [u8; 32]) -> Result<(), APIError> { let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret, payment_id, route, Retry::Attempts(0), None, entropy_source, best_block_height)?; - self.send_payment_internal(route, payment_hash, payment_secret, None, payment_id, None, onion_session_privs, node_signer, best_block_height, send_payment_along_path) + self.send_payment_internal(route, payment_hash, payment_secret, None, payment_id, None, + 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 }) } pub(super) fn send_spontaneous_payment( @@ -382,7 +404,10 @@ impl OutboundPayments { match self.send_payment_internal(route, payment_hash, &None, Some(preimage), payment_id, None, onion_session_privs, node_signer, best_block_height, send_payment_along_path) { Ok(()) => Ok(payment_hash), - Err(e) => Err(e) + Err(e) => { + self.remove_outbound_if_all_failed(payment_id, &e); + Err(e) + } } } @@ -481,7 +506,10 @@ impl OutboundPayments { match self.send_payment_internal(&route, payment_hash, &None, None, payment_id, None, onion_session_privs, node_signer, best_block_height, send_payment_along_path) { Ok(()) => Ok((payment_hash, payment_id)), - Err(e) => Err(e) + Err(e) => { + self.remove_outbound_if_all_failed(payment_id, &e); + Err(e) + } } } @@ -508,6 +536,9 @@ impl OutboundPayments { hash_map::Entry::Occupied(_) => Err(PaymentSendFailure::DuplicatePayment), hash_map::Entry::Vacant(entry) => { let payment = entry.insert(PendingOutboundPayment::Retryable { + retry_strategy, + attempts: PaymentAttempts::new(), + route_params, session_privs: HashSet::new(), pending_amt_msat: 0, pending_fee_msat: Some(0), @@ -625,10 +656,6 @@ impl OutboundPayments { } else { None }, }) } else if has_err { - // If we failed to send any paths, we should remove the new PaymentId from the - // `pending_outbound_payments` map, as the user isn't expected to `abandon_payment`. - let removed = self.pending_outbound_payments.lock().unwrap().remove(&payment_id).is_some(); - debug_assert!(removed, "We should always have a pending payment to remove here"); Err(PaymentSendFailure::AllFailedResendSafe(results.drain(..).map(|r| r.unwrap_err()).collect())) } else { Ok(()) @@ -650,6 +677,16 @@ impl OutboundPayments { self.send_payment_internal(route, payment_hash, payment_secret, keysend_preimage, 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 }) + } + + // If we failed to send any paths, we should remove the new PaymentId from the + // `pending_outbound_payments` map, as the user isn't expected to `abandon_payment`. + fn remove_outbound_if_all_failed(&self, payment_id: PaymentId, err: &PaymentSendFailure) { + if let &PaymentSendFailure::AllFailedResendSafe(_) = err { + let removed = self.pending_outbound_payments.lock().unwrap().remove(&payment_id).is_some(); + debug_assert!(removed, "We should always have a pending payment to remove here"); + } } pub(super) fn claim_htlc( @@ -766,12 +803,18 @@ impl OutboundPayments { payment_params: &Option, probing_cookie_secret: [u8; 32], secp_ctx: &Secp256k1, pending_events: &Mutex>, logger: &L ) 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); + #[cfg(not(test))] + let (network_update, short_channel_id, payment_retryable, _, _) = onion_error.decode_onion_failure(secp_ctx, logger, &source); + let mut session_priv_bytes = [0; 32]; session_priv_bytes.copy_from_slice(&session_priv[..]); let mut outbounds = self.pending_outbound_payments.lock().unwrap(); let mut all_paths_failed = false; let mut full_failure_ev = None; - if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(*payment_id) { + let mut pending_retry_ev = None; + let attempts_remaining = if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(*payment_id) { if !payment.get_mut().remove(&session_priv_bytes, Some(&path)) { log_trace!(logger, "Received duplicative fail for HTLC with payment_hash {}", log_bytes!(payment_hash.0)); return @@ -780,6 +823,10 @@ impl OutboundPayments { log_trace!(logger, "Received failure of HTLC with payment_hash {} after payment completion", log_bytes!(payment_hash.0)); return } + let is_retryable_now = payment.get().is_retryable_now(); + if let Some(scid) = short_channel_id { + payment.get_mut().insert_previously_failed_scid(scid); + } if payment.get().remaining_parts() == 0 { all_paths_failed = true; if payment.get().abandoned() { @@ -790,10 +837,12 @@ impl OutboundPayments { payment.remove(); } } + is_retryable_now } else { log_trace!(logger, "Received duplicative fail for HTLC with payment_hash {}", log_bytes!(payment_hash.0)); return - } + }; + core::mem::drop(outbounds); let mut retry = if let Some(payment_params_data) = payment_params { let path_last_hop = path.last().expect("Outbound payments must have had a valid path"); Some(RouteParameters { @@ -805,11 +854,6 @@ impl OutboundPayments { log_trace!(logger, "Failing outbound payment HTLC with payment_hash {}", log_bytes!(payment_hash.0)); let path_failure = { - #[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); - #[cfg(not(test))] - let (network_update, short_channel_id, payment_retryable, _, _) = onion_error.decode_onion_failure(secp_ctx, logger, &source); - if payment_is_probe(payment_hash, &payment_id, probing_cookie_secret) { if !payment_retryable { events::Event::ProbeSuccessful { @@ -832,6 +876,12 @@ impl OutboundPayments { if let Some(scid) = short_channel_id { retry.as_mut().map(|r| r.payment_params.previously_failed_channels.push(scid)); } + if payment_retryable && attempts_remaining && retry.is_some() { + debug_assert!(full_failure_ev.is_none()); + pending_retry_ev = Some(events::Event::PendingHTLCsForwardable { + time_forwardable: Duration::from_millis(MIN_HTLC_RELAY_HOLDING_CELL_MILLIS), + }); + } events::Event::PaymentPathFailed { payment_id: Some(*payment_id), payment_hash: payment_hash.clone(), @@ -851,6 +901,7 @@ impl OutboundPayments { let mut pending_events = pending_events.lock().unwrap(); pending_events.push(path_failure); if let Some(ev) = full_failure_ev { pending_events.push(ev); } + if let Some(ev) = pending_retry_ev { pending_events.push(ev); } } pub(super) fn abandon_payment(&self, payment_id: PaymentId) -> Option { @@ -911,8 +962,11 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, (0, session_privs, required), (1, pending_fee_msat, option), (2, payment_hash, required), + (not_written, retry_strategy, (static_value, Retry::Attempts(0))), (4, payment_secret, option), + (not_written, attempts, (static_value, PaymentAttempts::new())), (6, total_msat, required), + (not_written, route_params, (static_value, None)), (8, pending_amt_msat, required), (10, starting_block_height, required), },