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
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,
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(())
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,
+ payment_params,
final_value_msat: pending_amt_unsent,
+ max_total_routing_fee_msat: None,
})
} else { None }
} else { None },
(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),
},
);
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(),
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!(
(1, self.route_params.as_ref().map(|p| &p.payment_params), option),
(2, blinded_tails, optional_vec),
(3, self.route_params.as_ref().map(|p| p.final_value_msat), option),
+ (5, self.route_params.as_ref().map(|p| p.max_total_routing_fee_msat), option),
});
Ok(())
}
(1, payment_params, (option: ReadableArgs, min_final_cltv_expiry_delta)),
(2, blinded_tails, optional_vec),
(3, final_value_msat, option),
+ (5, max_total_routing_fee_msat, option)
});
let blinded_tails = blinded_tails.unwrap_or(Vec::new());
if blinded_tails.len() != 0 {
// If we previously wrote the corresponding fields, reconstruct RouteParameters.
let route_params = match (payment_params, final_value_msat) {
(Some(payment_params), Some(final_value_msat)) => {
- Some(RouteParameters { payment_params, final_value_msat })
+ Some(RouteParameters { payment_params, final_value_msat, max_total_routing_fee_msat })
}
_ => None,
};
/// The amount in msats sent on the failed payment path.
pub final_value_msat: u64,
+
+ /// The maximum total fees, in millisatoshi, that may accrue during route finding.
+ ///
+ /// This limit also applies to the total fees that may arise while retrying failed payment
+ /// paths.
+ ///
+ /// Default value: `None`
+ pub max_total_routing_fee_msat: Option<u64>,
}
impl RouteParameters {
/// Constructs [`RouteParameters`] from the given [`PaymentParameters`] and a payment amount.
pub fn from_payment_params_and_value(payment_params: PaymentParameters, final_value_msat: u64) -> Self {
- Self { payment_params, final_value_msat }
+ Self { payment_params, final_value_msat, max_total_routing_fee_msat: None }
}
}
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
write_tlv_fields!(writer, {
(0, self.payment_params, required),
+ (1, self.max_total_routing_fee_msat, option),
(2, self.final_value_msat, required),
// LDK versions prior to 0.0.114 had the `final_cltv_expiry_delta` parameter in
// `RouteParameters` directly. For compatibility, we write it here.
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
_init_and_read_len_prefixed_tlv_fields!(reader, {
(0, payment_params, (required: ReadableArgs, 0)),
+ (1, max_total_routing_fee_msat, option),
(2, final_value_msat, required),
(4, final_cltv_delta, option),
});
Ok(Self {
payment_params,
final_value_msat: final_value_msat.0.unwrap(),
+ max_total_routing_fee_msat,
})
}
}