},
AwaitingInvoice {
timer_ticks_without_response: u8,
+ retry_strategy: Retry,
},
InvoiceReceived {
payment_hash: PaymentHash,
+ retry_strategy: Retry,
},
Retryable {
retry_strategy: Option<Retry>,
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),
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,
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) {
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<HashMap<PaymentId, PendingOutboundPayment>>,
pub(super) retry_lock: Mutex<()>,
{
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),
};
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,
}
#[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(())
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;
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() {
) -> bool where L::Target: Logger {
#[cfg(test)]
let DecodedOnionFailure {
- network_update, short_channel_id, payment_retryable, onion_error_code, onion_error_data
+ network_update, short_channel_id, payment_failed_permanently, onion_error_code,
+ onion_error_data
} = onion_error.decode_onion_failure(secp_ctx, logger, &source);
#[cfg(not(test))]
- let DecodedOnionFailure { network_update, short_channel_id, payment_retryable } =
+ let DecodedOnionFailure { network_update, short_channel_id, payment_failed_permanently } =
onion_error.decode_onion_failure(secp_ctx, logger, &source);
let payment_is_probe = payment_is_probe(payment_hash, &payment_id, probing_cookie_secret);
payment.get_mut().insert_previously_failed_scid(scid);
}
- if payment_is_probe || !is_retryable_now || !payment_retryable {
- let reason = if !payment_retryable {
+ if payment_is_probe || !is_retryable_now || payment_failed_permanently {
+ let reason = if payment_failed_permanently {
PaymentFailureReason::RecipientRejected
} else {
PaymentFailureReason::RetriesExhausted
let path_failure = {
if payment_is_probe {
- if !payment_retryable {
+ if payment_failed_permanently {
events::Event::ProbeSuccessful {
payment_id: *payment_id,
payment_hash: payment_hash.clone(),
events::Event::PaymentPathFailed {
payment_id: Some(*payment_id),
payment_hash: payment_hash.clone(),
- payment_failed_permanently: !payment_retryable,
+ payment_failed_permanently,
failure: events::PathFailure::OnPath { network_update },
path: path.clone(),
short_channel_id,
},
(5, AwaitingInvoice) => {
(0, timer_ticks_without_response, required),
+ (2, retry_strategy, required),
},
(7, InvoiceReceived) => {
(0, payment_hash, required),
+ (2, retry_strategy, required),
},
);
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 {
);
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]
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(
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;
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())
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())
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!(