AwaitingInvoice {
timer_ticks_without_response: u8,
retry_strategy: Retry,
+ max_total_routing_fee_msat: Option<u64>,
},
InvoiceReceived {
payment_hash: PaymentHash,
retry_strategy: Retry,
+ // Note this field is currently just replicated from AwaitingInvoice but not actually
+ // used anywhere.
+ max_total_routing_fee_msat: Option<u64>,
},
Retryable {
retry_strategy: Option<Retry>,
total_msat: u64,
/// Our best known block height at the time this payment was initiated.
starting_block_height: u32,
+ remaining_max_total_routing_fee_msat: Option<u64>,
},
/// When a pending payment is fulfilled, we continue tracking it until all pending HTLCs have
/// been resolved. This ensures we don't look up pending payments in ChannelMonitors on restart
PendingOutboundPayment::InvoiceReceived { .. } => { debug_assert!(false); false },
};
if remove_res {
- if let PendingOutboundPayment::Retryable { ref mut pending_amt_msat, ref mut pending_fee_msat, .. } = self {
- let path = path.expect("Fulfilling a payment should always come with a path");
+ if let PendingOutboundPayment::Retryable {
+ ref mut pending_amt_msat, ref mut pending_fee_msat,
+ ref mut remaining_max_total_routing_fee_msat, ..
+ } = self {
+ let path = path.expect("Removing a failed payment should always come with a path");
*pending_amt_msat -= path.final_value_msat();
+ let path_fee_msat = path.fee_msat();
if let Some(fee_msat) = pending_fee_msat.as_mut() {
- *fee_msat -= path.fee_msat();
+ *fee_msat -= path_fee_msat;
+ }
+
+ if let Some(max_total_routing_fee_msat) = remaining_max_total_routing_fee_msat.as_mut() {
+ *max_total_routing_fee_msat = max_total_routing_fee_msat.saturating_add(path_fee_msat);
}
}
}
PendingOutboundPayment::Abandoned { .. } => false,
};
if insert_res {
- if let PendingOutboundPayment::Retryable { ref mut pending_amt_msat, ref mut pending_fee_msat, .. } = self {
- *pending_amt_msat += path.final_value_msat();
- if let Some(fee_msat) = pending_fee_msat.as_mut() {
- *fee_msat += path.fee_msat();
- }
+ if let PendingOutboundPayment::Retryable {
+ ref mut pending_amt_msat, ref mut pending_fee_msat,
+ ref mut remaining_max_total_routing_fee_msat, ..
+ } = self {
+ *pending_amt_msat += path.final_value_msat();
+ let path_fee_msat = path.fee_msat();
+ if let Some(fee_msat) = pending_fee_msat.as_mut() {
+ *fee_msat += path_fee_msat;
+ }
+
+ if let Some(max_total_routing_fee_msat) = remaining_max_total_routing_fee_msat.as_mut() {
+ *max_total_routing_fee_msat = max_total_routing_fee_msat.saturating_sub(path_fee_msat);
+ }
}
}
insert_res
/// 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.
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
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
{
let payment_hash = invoice.payment_hash();
+ let mut max_total_routing_fee_msat = None;
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
hash_map::Entry::Occupied(entry) => match entry.get() {
- PendingOutboundPayment::AwaitingInvoice { retry_strategy, .. } => {
+ PendingOutboundPayment::AwaitingInvoice { retry_strategy, max_total_routing_fee_msat: max_total_fee, .. } => {
+ max_total_routing_fee_msat = *max_total_fee;
*entry.into_mut() = PendingOutboundPayment::InvoiceReceived {
payment_hash,
retry_strategy: *retry_strategy,
+ max_total_routing_fee_msat,
};
},
_ => return Err(Bolt12PaymentError::DuplicateInvoice),
let route_params = RouteParameters {
payment_params: PaymentParameters::from_bolt12_invoice(&invoice),
final_value_msat: invoice.amount_msats(),
+ max_total_routing_fee_msat,
};
self.find_route_and_send_payment(
let mut retry_id_route_params = None;
for (pmt_id, pmt) in outbounds.iter_mut() {
if pmt.is_auto_retryable_now() {
- if let PendingOutboundPayment::Retryable { pending_amt_msat, total_msat, payment_params: Some(params), payment_hash, .. } = pmt {
+ if let PendingOutboundPayment::Retryable { pending_amt_msat, total_msat, payment_params: Some(params), payment_hash, remaining_max_total_routing_fee_msat, .. } = pmt {
if pending_amt_msat < total_msat {
retry_id_route_params = Some((*payment_hash, *pmt_id, RouteParameters {
final_value_msat: *total_msat - *pending_amt_msat,
payment_params: params.clone(),
+ max_total_routing_fee_msat: *remaining_max_total_routing_fee_msat,
}));
break
}
log_error!(logger, "Payment not yet sent");
return
},
- PendingOutboundPayment::InvoiceReceived { payment_hash, retry_strategy } => {
+ PendingOutboundPayment::InvoiceReceived { payment_hash, retry_strategy, .. } => {
let total_amount = route_params.final_value_msat;
let recipient_onion = RecipientOnionFields {
payment_secret: None,
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);
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(),
custom_tlvs: recipient_onion.custom_tlvs,
starting_block_height: best_block_height,
total_msat: route.get_total_amount(),
+ remaining_max_total_routing_fee_msat:
+ route.route_params.as_ref().and_then(|p| p.max_total_routing_fee_msat),
};
for (path, session_priv_bytes) in route.paths.iter().zip(onion_session_privs.iter()) {
#[allow(unused)]
pub(super) fn add_new_awaiting_invoice(
- &self, payment_id: PaymentId, retry_strategy: Retry
+ &self, payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option<u64>
) -> Result<(), ()> {
let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
match pending_outbounds.entry(payment_id) {
entry.insert(PendingOutboundPayment::AwaitingInvoice {
timer_ticks_without_response: 0,
retry_strategy,
+ max_total_routing_fee_msat,
});
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() {
let mut has_ok = false;
let mut has_err = false;
let mut pending_amt_unsent = 0;
+ let mut total_ok_fees_msat = 0;
for (res, path) in results.iter().zip(route.paths.iter()) {
- if res.is_ok() { has_ok = true; }
+ if res.is_ok() {
+ has_ok = true;
+ total_ok_fees_msat += path.fee_msat();
+ }
if res.is_err() { has_err = true; }
if let &Err(APIError::MonitorUpdateInProgress) = res {
// MonitorUpdateInProgress is inherently unsafe to retry, so we call it a
// PartialFailure.
has_err = true;
has_ok = true;
+ total_ok_fees_msat += path.fee_msat();
} else if res.is_err() {
pending_amt_unsent += path.final_value_msat();
}
results,
payment_id,
failed_paths_retry: if pending_amt_unsent != 0 {
- if let Some(payment_params) = route.route_params.as_ref().map(|p| p.payment_params.clone()) {
- Some(RouteParameters {
- payment_params: payment_params,
- final_value_msat: pending_amt_unsent,
- })
+ if let Some(route_params) = &route.route_params {
+ let mut route_params = route_params.clone();
+ // We calculate the leftover fee budget we're allowed to spend by
+ // subtracting the used fee from the total fee budget.
+ route_params.max_total_routing_fee_msat = route_params
+ .max_total_routing_fee_msat.map(|m| m.saturating_sub(total_ok_fees_msat));
+ route_params.final_value_msat = pending_amt_unsent;
+
+ Some(route_params)
} else { None }
} else { None },
})
) -> 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
is_retryable_now = false;
}
if payment.get().remaining_parts() == 0 {
- if let PendingOutboundPayment::Abandoned { payment_hash, reason, .. }= payment.get() {
+ if let PendingOutboundPayment::Abandoned { payment_hash, reason, .. } = payment.get() {
if !payment_is_probe {
full_failure_ev = Some(events::Event::PaymentFailed {
payment_id: *payment_id,
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,
(8, pending_amt_msat, required),
(9, custom_tlvs, optional_vec),
(10, starting_block_height, required),
+ (11, remaining_max_total_routing_fee_msat, option),
(not_written, retry_strategy, (static_value, None)),
(not_written, attempts, (static_value, PaymentAttempts::new())),
},
(5, AwaitingInvoice) => {
(0, timer_ticks_without_response, required),
(2, retry_strategy, required),
+ (4, max_total_routing_fee_msat, option),
},
(7, InvoiceReceived) => {
(0, payment_hash, required),
(2, retry_strategy, required),
+ (4, max_total_routing_fee_msat, option),
},
);
channel_features: ChannelFeatures::empty(),
fee_msat: 0,
cltv_expiry_delta: 0,
+ maybe_announced_channel: true,
}], blinded_tail: None }],
route_params: Some(route_params.clone()),
};
let payment_id = PaymentId([0; 32]);
assert!(!outbound_payments.has_pending_payments());
- assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_ok());
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).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, Retry::Attempts(0)).is_ok());
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
+ );
assert!(outbound_payments.has_pending_payments());
- assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_err());
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None)
+ .is_err()
+ );
}
#[test]
let payment_id = PaymentId([0; 32]);
assert!(!outbound_payments.has_pending_payments());
- assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_ok());
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).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, Retry::Attempts(0)).is_ok());
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).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, Retry::Attempts(0)).is_ok());
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
+ );
assert!(outbound_payments.has_pending_payments());
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
.sign(recipient_sign).unwrap();
router.expect_find_route(
- RouteParameters {
- payment_params: PaymentParameters::from_bolt12_invoice(&invoice),
- final_value_msat: invoice.amount_msats(),
- },
+ RouteParameters::from_payment_params_and_value(
+ PaymentParameters::from_bolt12_invoice(&invoice),
+ invoice.amount_msats(),
+ ),
Err(LightningError { err: String::new(), action: ErrorAction::IgnoreError }),
);
let outbound_payments = OutboundPayments::new();
let payment_id = PaymentId([0; 32]);
- assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_ok());
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
+ );
assert!(outbound_payments.has_pending_payments());
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
.build().unwrap()
.sign(recipient_sign).unwrap();
- let route_params = RouteParameters {
- payment_params: PaymentParameters::from_bolt12_invoice(&invoice),
- final_value_msat: invoice.amount_msats(),
- };
+ let route_params = RouteParameters::from_payment_params_and_value(
+ PaymentParameters::from_bolt12_invoice(&invoice),
+ invoice.amount_msats(),
+ );
router.expect_find_route(
route_params.clone(), Ok(Route { paths: vec![], route_params: Some(route_params) })
);
let route_params = RouteParameters {
payment_params: PaymentParameters::from_bolt12_invoice(&invoice),
final_value_msat: invoice.amount_msats(),
+ max_total_routing_fee_msat: Some(1234),
};
router.expect_find_route(
route_params.clone(),
channel_features: ChannelFeatures::empty(),
fee_msat: invoice.amount_msats(),
cltv_expiry_delta: 0,
+ maybe_announced_channel: true,
}
],
blinded_tail: None,
assert!(!outbound_payments.has_pending_payments());
assert!(pending_events.lock().unwrap().is_empty());
- assert!(outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0)).is_ok());
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), Some(1234)).is_ok()
+ );
assert!(outbound_payments.has_pending_payments());
assert_eq!(