Skip to content

Commit fb34479

Browse files
Fix missing PaymentSent::fee_paid_msat
If an outbound payment was abandoned with htlcs in-flight and later claimed, we would previously have the PaymentSent::fee_paid_msat be set to None. This contradicted some docs on the event that stated the field would always be Some after 0.0.103.
1 parent 00613c9 commit fb34479

4 files changed

Lines changed: 67 additions & 2 deletions

File tree

lightning/src/events/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1201,7 +1201,7 @@ pub enum Event {
12011201
/// If the recipient or an intermediate node misbehaves and gives us free money, this may
12021202
/// overstate the amount paid, though this is unlikely.
12031203
///
1204-
/// This is only `None` for payments initiated on LDK versions prior to 0.0.103.
1204+
/// May be `None` for payments initiated on LDK versions prior to 0.4.
12051205
///
12061206
/// [`Route::get_total_fees`]: crate::routing::router::Route::get_total_fees
12071207
fee_paid_msat: Option<u64>,

lightning/src/ln/functional_tests.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8437,6 +8437,17 @@ pub fn test_dup_htlc_second_rejected() {
84378437

84388438
#[xtest(feature = "_externalize_tests")]
84398439
pub fn test_inconsistent_mpp_params() {
8440+
do_test_inconsistent_mpp_params(false);
8441+
}
8442+
8443+
#[xtest(feature = "_externalize_tests")]
8444+
pub fn test_inconsistent_mpp_params_abandoned_then_retried() {
8445+
// Same as test_inconsistent_mpp_params, but abandon the payment before it is claimed to check
8446+
// that we'll still provide the correct fee in the `PaymentSent` in that case.
8447+
do_test_inconsistent_mpp_params(true);
8448+
}
8449+
8450+
fn do_test_inconsistent_mpp_params(abandon_early: bool) {
84408451
// Test that if we recieve two HTLCs with different payment parameters we fail back the first
84418452
// such HTLC and allow the second to stay.
84428453
let chanmon_cfgs = create_chanmon_cfgs(4);
@@ -8512,6 +8523,14 @@ pub fn test_inconsistent_mpp_params() {
85128523
.unwrap();
85138524
check_added_monitors(&nodes[0], 1);
85148525

8526+
if abandon_early {
8527+
// Abandon before the mismatched MPP part's rejection makes it back to us. HTLCs are still
8528+
// in-flight at this point, so when the rejection arrives, we'll need to do fee accounting with
8529+
// the payment in the `Abandoned` rather than `Retryable` state.
8530+
nodes[0].node.abandon_payment(id);
8531+
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
8532+
}
8533+
85158534
{
85168535
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
85178536
assert_eq!(events.len(), 1);
@@ -8579,7 +8598,7 @@ pub fn test_inconsistent_mpp_params() {
85798598
pass_along_path(&nodes[0], path_b, real_amt, hash, Some(payment_secret), event, true, None);
85808599

85818600
do_claim_payment_along_route(ClaimAlongRouteArgs::new(&nodes[0], &[path_a, path_b], preimage));
8582-
expect_payment_sent(&nodes[0], preimage, Some(None), true, true);
8601+
expect_payment_sent(&nodes[0], preimage, Some(Some(2_000)), true, true);
85838602
}
85848603

85858604
#[xtest(feature = "_externalize_tests")]

lightning/src/ln/outbound_payment.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ pub(crate) enum PendingOutboundPayment {
164164
/// The total payment amount across all paths, used to be able to issue `PaymentSent` if
165165
/// an HTLC still happens to succeed after we marked the payment as abandoned.
166166
total_msat: Option<u64>,
167+
/// Preserved from `Retryable` so we can still report `fee_paid_msat` if an HTLC succeeds
168+
/// after the payment was abandoned. `None` for payments abandoned prior to this field being
169+
/// added, or for payments that were never in the `Retryable` state.
170+
pending_fee_msat: Option<u64>,
167171
},
168172
}
169173

@@ -252,6 +256,7 @@ impl PendingOutboundPayment {
252256
fn get_pending_fee_msat(&self) -> Option<u64> {
253257
match self {
254258
PendingOutboundPayment::Retryable { pending_fee_msat, .. } => pending_fee_msat.clone(),
259+
PendingOutboundPayment::Abandoned { pending_fee_msat, .. } => pending_fee_msat.clone(),
255260
_ => None,
256261
}
257262
}
@@ -308,6 +313,7 @@ impl PendingOutboundPayment {
308313
_ => new_hash_set(),
309314
};
310315
let total_msat = self.total_msat();
316+
let pending_fee_msat = self.get_pending_fee_msat();
311317
match self {
312318
Self::Retryable { payment_hash, .. } |
313319
Self::InvoiceReceived { payment_hash, .. } |
@@ -318,6 +324,7 @@ impl PendingOutboundPayment {
318324
payment_hash: *payment_hash,
319325
reason: Some(reason),
320326
total_msat,
327+
pending_fee_msat,
321328
};
322329
},
323330
_ => {}
@@ -354,6 +361,14 @@ impl PendingOutboundPayment {
354361
if let Some(max_total_routing_fee_msat) = remaining_max_total_routing_fee_msat.as_mut() {
355362
*max_total_routing_fee_msat = max_total_routing_fee_msat.saturating_add(path_fee_msat);
356363
}
364+
} else if let PendingOutboundPayment::Abandoned { pending_fee_msat, .. } = self {
365+
// Keep `pending_fee_msat` in sync with the remaining in-flight HTLCs so that if
366+
// any path still succeeds after abandonment, the eventual `PaymentSent` reports
367+
// only the fee actually paid.
368+
let path = path.expect("Removing a failed payment should always come with a path");
369+
if let Some(fee_msat) = pending_fee_msat.as_mut() {
370+
*fee_msat -= path.fee_msat();
371+
}
357372
}
358373
}
359374
remove_res
@@ -2778,6 +2793,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
27782793
(1, reason, upgradable_option),
27792794
(2, payment_hash, required),
27802795
(3, total_msat, option),
2796+
(5, pending_fee_msat, option),
27812797
},
27822798
(5, AwaitingInvoice) => {
27832799
(0, expiration, required),

lightning/src/ln/payment_tests.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2241,6 +2241,36 @@ fn abandoned_send_payment_idempotent() {
22412241
claim_payment(&nodes[0], &[&nodes[1]], second_payment_preimage);
22422242
}
22432243

2244+
#[test]
2245+
fn abandoned_payment_fulfilled_preserves_fee_paid_msat() {
2246+
// Previously, if we abandoned a payment with HTLCs in-flight and the payment eventually
2247+
// succeeded, we would set the `Event::PaymentSent::fee_paid_msat` to None, even though we had
2248+
// docs guaranteeing that it would always be Some after 0.0.103.
2249+
let chanmon_cfgs = create_chanmon_cfgs(3);
2250+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
2251+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
2252+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
2253+
2254+
create_announced_chan_between_nodes(&nodes, 0, 1);
2255+
create_announced_chan_between_nodes(&nodes, 1, 2);
2256+
2257+
let amt_msat = 10_000_000;
2258+
let (route, payment_hash, payment_preimage, payment_secret) =
2259+
get_route_and_payment_hash!(&nodes[0], nodes[2], amt_msat);
2260+
let payment_id = PaymentId(payment_hash.0);
2261+
let onion = RecipientOnionFields::secret_only(payment_secret, amt_msat);
2262+
nodes[0].node.send_payment_with_route(route, payment_hash, onion, payment_id).unwrap();
2263+
check_added_monitors(&nodes[0], 1);
2264+
2265+
let path: &[&Node] = &[&nodes[1], &nodes[2]];
2266+
pass_along_route(&nodes[0], &[path], amt_msat, payment_hash, payment_secret);
2267+
2268+
nodes[0].node.abandon_payment(payment_id);
2269+
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
2270+
2271+
claim_payment_along_route(ClaimAlongRouteArgs::new(&nodes[0], &[path], payment_preimage));
2272+
}
2273+
22442274
#[derive(PartialEq)]
22452275
enum InterceptTest {
22462276
Forward,

0 commit comments

Comments
 (0)