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};
use core::cmp;
use core::fmt::{self, Display, Formatter};
use core::ops::Deref;
+use core::time::Duration;
use crate::prelude::*;
use crate::sync::Mutex;
session_privs: HashSet<[u8; 32]>,
},
Retryable {
+ retry_strategy: Retry,
+ attempts: PaymentAttempts,
+ route_params: Option<RouteParameters>,
session_privs: HashSet<[u8; 32]>,
payment_hash: PaymentHash,
payment_secret: Option<PaymentSecret>,
}
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,
u32, PaymentId, &Option<PaymentPreimage>, [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<ES: Deref, NS: Deref, F>(
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)
+ }
}
}
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)
+ }
}
}
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),
} 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(())
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<L: Deref>(
payment_params: &Option<PaymentParameters>, probing_cookie_secret: [u8; 32],
secp_ctx: &Secp256k1<secp256k1::All>, pending_events: &Mutex<Vec<events::Event>>, 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
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() {
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 {
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 {
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(),
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<events::Event> {
(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),
},