use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient};
use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
-use crate::ln::channelmanager::{HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, MIN_HTLC_RELAY_HOLDING_CELL_MILLIS, PaymentId};
+use crate::ln::channelmanager::{ChannelDetails, 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 crate::routing::router::{InFlightHtlcs, PaymentParameters, Route, RouteHop, RouteParameters, RoutePath, Router};
use crate::util::errors::APIError;
use crate::util::events;
use crate::util::logger::Logger;
}
}
+#[cfg(feature = "std")]
+pub(super) fn has_expired(route_params: &RouteParameters) -> bool {
+ if let Some(expiry_time) = route_params.payment_params.expiry_time {
+ if let Ok(elapsed) = std::time::SystemTime::UNIX_EPOCH.elapsed() {
+ return elapsed > core::time::Duration::from_secs(expiry_time)
+ }
+ }
+ false
+}
+
pub(crate) type PaymentAttempts = PaymentAttemptsUsingTime<ConfiguredTime>;
/// Storing minimal payment attempts information required for determining if a outbound payment can
}
}
+ pub(super) fn check_retry_payments<R: Deref, ES: Deref, NS: Deref, SP, IH, FH, L: Deref>(
+ &self, router: &R, first_hops: FH, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS,
+ best_block_height: u32, logger: &L, send_payment_along_path: SP,
+ )
+ where
+ R::Target: Router,
+ ES::Target: EntropySource,
+ NS::Target: NodeSigner,
+ SP: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
+ u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>,
+ IH: Fn() -> InFlightHtlcs,
+ FH: Fn() -> Vec<ChannelDetails>,
+ L::Target: Logger,
+ {
+ loop {
+ let mut outbounds = self.pending_outbound_payments.lock().unwrap();
+ let mut retry_id_route_params = None;
+ for (pmt_id, pmt) in outbounds.iter_mut() {
+ if pmt.is_retryable_now() {
+ if let PendingOutboundPayment::Retryable { pending_amt_msat, total_msat, route_params: Some(params), .. } = pmt {
+ if pending_amt_msat < total_msat {
+ retry_id_route_params = Some((*pmt_id, params.clone()));
+ pmt.increment_attempts();
+ break
+ }
+ }
+ }
+ }
+ if let Some((payment_id, route_params)) = retry_id_route_params {
+ core::mem::drop(outbounds);
+ if let Err(e) = self.pay_internal(payment_id, route_params, router, first_hops(), inflight_htlcs(), entropy_source, node_signer, best_block_height, &send_payment_along_path) {
+ log_trace!(logger, "Errored retrying payment: {:?}", e);
+ }
+ } else { break }
+ }
+ }
+
+ fn pay_internal<R: Deref, NS: Deref, ES: Deref, F>(
+ &self, payment_id: PaymentId, route_params: RouteParameters, router: &R,
+ first_hops: Vec<ChannelDetails>, inflight_htlcs: InFlightHtlcs, entropy_source: &ES,
+ node_signer: &NS, best_block_height: u32, send_payment_along_path: &F
+ ) -> Result<(), PaymentSendFailure>
+ where
+ R::Target: Router,
+ ES::Target: EntropySource,
+ NS::Target: NodeSigner,
+ F: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
+ u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
+ {
+ #[cfg(feature = "std")] {
+ if has_expired(&route_params) {
+ return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
+ err: format!("Invoice expired for payment id {}", log_bytes!(payment_id.0)),
+ }))
+ }
+ }
+
+ let route = router.find_route(
+ &node_signer.get_node_id(Recipient::Node).unwrap(), &route_params,
+ Some(&first_hops.iter().collect::<Vec<_>>()), &inflight_htlcs
+ ).map_err(|e| PaymentSendFailure::ParameterError(APIError::APIMisuseError {
+ err: format!("Failed to find a route for payment {}: {:?}", log_bytes!(payment_id.0), e), // TODO: add APIError::RouteNotFound
+ }))?;
+
+ let res = self.retry_payment_with_route(&route, payment_id, entropy_source, node_signer, best_block_height, send_payment_along_path);
+ match res {
+ Err(PaymentSendFailure::AllFailedResendSafe(_)) => {
+ let mut outbounds = self.pending_outbound_payments.lock().unwrap();
+ if let Some(payment) = outbounds.get_mut(&payment_id) {
+ let retryable = payment.is_retryable_now();
+ if retryable {
+ payment.increment_attempts();
+ } else { return res }
+ } else { return res }
+ core::mem::drop(outbounds);
+ self.pay_internal(payment_id, route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, send_payment_along_path)
+ },
+ Err(PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. }) => {
+ let mut outbounds = self.pending_outbound_payments.lock().unwrap();
+ if let Some(payment) = outbounds.get_mut(&payment_id) {
+ let retryable = payment.is_retryable_now();
+ if retryable {
+ payment.increment_attempts();
+ } else { return Err(PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, payment_id }) }
+ } else { return Err(PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, payment_id }) }
+ core::mem::drop(outbounds);
+
+ // Some paths were sent, even if we failed to send the full MPP value our recipient may
+ // misbehave and claim the funds, at which point we have to consider the payment sent, so
+ // return `Ok()` here, ignoring any retry errors.
+ let _ = self.pay_internal(payment_id, retry, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, send_payment_along_path);
+ Ok(())
+ },
+ Err(PaymentSendFailure::PartialFailure { failed_paths_retry: None, .. }) => {
+ // This may happen if we send a payment and some paths fail, but only due to a temporary
+ // monitor failure or the like, implying they're really in-flight, but we haven't sent the
+ // initial HTLC-Add messages yet.
+ Ok(())
+ },
+ res => res,
+ }
+ }
+
pub(super) fn retry_payment_with_route<ES: Deref, NS: Deref, F>(
&self, route: &Route, payment_id: PaymentId, entropy_source: &ES, node_signer: &NS, best_block_height: u32,
send_payment_along_path: F