Skip to content

Fix missing PaymentSent::fee_paid_msat#4651

Open
valentinewallace wants to merge 1 commit into
lightningdevkit:mainfrom
valentinewallace:2026-06-paymentsent-unwrap
Open

Fix missing PaymentSent::fee_paid_msat#4651
valentinewallace wants to merge 1 commit into
lightningdevkit:mainfrom
valentinewallace:2026-06-paymentsent-unwrap

Conversation

@valentinewallace

Copy link
Copy Markdown
Contributor

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.

Fixes #4639

@ldk-reviews-bot

ldk-reviews-bot commented Jun 1, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@ldk-reviews-bot ldk-reviews-bot requested a review from tankyleo June 1, 2026 18:52
Comment thread lightning/src/events/mod.rs Outdated
@ldk-claude-review-bot

ldk-claude-review-bot commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Review Summary

Issue found

  • lightning/src/ln/outbound_payment.rs:169remove() does not decrement pending_fee_msat for the Abandoned variant, causing fee_paid_msat to be overstated for MPP payments where some paths fail after manual abandonment.

Details

The core fix (preserving pending_fee_msat through the RetryableAbandoned transition) is correct and works for single-path payments and MPP payments where all paths succeed. However, the remove() method (line 335) only adjusts pending_fee_msat for Retryable, not Abandoned. When a path fails on an already-abandoned MPP payment, the fee for that failed path is never subtracted, leading to an incorrect (overstated) fee_paid_msat in the PaymentSent event.

The new test only covers a single-path payment, so it doesn't exercise this edge case.

@valentinewallace

Copy link
Copy Markdown
Contributor Author

Fuzz failure looks pre-existing, seems to error with "Expected disconnect"

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@valentinewallace valentinewallace modified the milestones: 0.4, 0.3 Jun 4, 2026
Comment thread lightning/src/ln/outbound_payment.rs Outdated
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);
}
} else if let PendingOutboundPayment::Abandoned { pending_fee_msat, .. } = self {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this block should we also subtract path.final_value_msat() from total_msat so that PaymentSent reports the amount that was actually sent ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this block. If we go in this direction, we're starting to handle surfacing correct amounts/fees in the case that the recipient gave us the preimage without us paying them the full amount we originally intended. IIUC this indicates a very buggy recipient and is not a case we handle in PendingOutboundPayment::Fulfilled either, so a more "real" fix would be a larger change and probably not worth it. Similar applies to the below comment.

@@ -318,6 +324,7 @@ impl PendingOutboundPayment {
payment_hash: *payment_hash,
reason: Some(reason),
total_msat,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious here if we should be setting this to the sum of the current paths currently in-flight, and not the fixed total msat ?

TheBlueMatt
TheBlueMatt previously approved these changes Jun 8, 2026
Comment thread lightning/src/events/mod.rs Outdated
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.
total_msat: Option<u64>,
/// Preserved from `Retryable` so we can still report `fee_paid_msat` if an HTLC succeeds after
/// the payment was abandoned. Added in 0.3.
pending_fee_msat: Option<u64>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: remove() (line 335) only decrements pending_fee_msat for the Retryable variant, not for Abandoned. This means if an MPP payment is manually abandoned with multiple paths in-flight, and then some paths fail back while others succeed, the fee for the failed paths won't be subtracted — causing PaymentSent::fee_paid_msat to be overstated.

Concrete scenario:

  1. Two-path MPP, path A (fee=100) and path B (fee=200) in-flight → pending_fee_msat = Some(300)
  2. abandon_payment()Abandoned { pending_fee_msat: Some(300) }
  3. Path A fails (intermediate node timeout) → remove() removes session_priv but doesn't decrement fee → pending_fee_msat still Some(300)
  4. Path B succeeds (recipient claims) → PaymentSent { fee_paid_msat: Some(300) } — should be Some(200)

The remove() function at line 348-363 needs an additional branch for Abandoned:

if let PendingOutboundPayment::Abandoned {
    ref mut pending_fee_msat, ..
} = self {
    let path = path.expect("Removing a failed payment should always come with a path");
    if let Some(fee_msat) = pending_fee_msat.as_mut() {
        *fee_msat -= path.fee_msat();
    }
}

Note: for the auto-abandon path (in handle_update_fail_htlc), remove() is called before mark_abandoned(), so the fee is correctly captured. This bug only affects the manual-abandon path and any future failures arriving after auto-abandonment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tankyleo tankyleo requested a review from TheBlueMatt June 8, 2026 19:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Event::PaymentSent::fee_paid_msat can be None despite docs to the contrary

5 participants