Skip to content

Apply MPP receive timeout to keysend payments#4558

Merged
tnull merged 1 commit into
lightningdevkit:mainfrom
tnull:2026-04-fix-keysend-mpp-timeout
Apr 14, 2026
Merged

Apply MPP receive timeout to keysend payments#4558
tnull merged 1 commit into
lightningdevkit:mainfrom
tnull:2026-04-fix-keysend-mpp-timeout

Conversation

@tnull

@tnull tnull commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #4553.

Incomplete keysend MPPs skipped the receive timeout path, allowing partial payments to hold HTLC slots until CLTV expiry instead of failing after MPP_TIMEOUT_TICKS. Apply the existing total_mpp_amount_msat completeness check to all MPP receives and add a regression test covering the keysend case.

The timeout logic was originally added only for
invoice-backed MPPs in 2022, and that invoice-only guard remained when receive-side MPP keysend support landed in 2023, leaving this gap latent until now.

Co-Authored-By: HAL 9000

@ldk-reviews-bot

ldk-reviews-bot commented Apr 13, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @carlaKC 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-claude-review-bot

ldk-claude-review-bot commented Apr 13, 2026

Copy link
Copy Markdown
Collaborator

I've thoroughly re-reviewed the entire diff, including:

  1. The core change in channelmanager.rs (lines 8881-8900): Removal of the OnionPayload::Invoice guard so MPP timeout applies to all payment types.
  2. Condition matching: total_mpp_value <= total_intended_recvd_value in timer_tick_occurred (line 8887) correctly mirrors total_intended_recvd_value >= total_mpp_value in process_pending_htlc_forwards (line 8412).
  3. Single-path keysend safety: total_mpp_amount_msat falls back to outgoing_amt_msat for non-MPP keysend, so the completeness check passes immediately — no behavioral change for single-path keysend.
  4. Test correctness: Verified send_spontaneous_payment API usage matches the signature, RecipientOnionFields::secret_only(payment_secret, 200_000) correctly sets the total MPP amount, pass_along_path uses expected_preimage = None for incomplete first path and Some(payment_preimage) for the completing second path, and PaymentParameters::for_keysend with true enables MPP.

No issues found.

@ldk-reviews-bot ldk-reviews-bot requested a review from jkczyz April 13, 2026 09:04

@carlaKC carlaKC left a comment

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.

lgtm - just one comment on the test

Comment thread lightning/src/ln/payment_tests.rs Outdated
@TheBlueMatt TheBlueMatt removed the request for review from jkczyz April 13, 2026 21:19
TheBlueMatt
TheBlueMatt previously approved these changes Apr 13, 2026

@TheBlueMatt TheBlueMatt left a comment

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.

thanks. @carlaKC should land when she likes the test.

@tnull tnull force-pushed the 2026-04-fix-keysend-mpp-timeout branch from a3457de to c2f774d Compare April 14, 2026 08:24
@tnull tnull requested a review from carlaKC April 14, 2026 08:25
Incomplete keysend MPPs skipped the receive timeout path,
allowing partial payments to hold HTLC slots until CLTV
expiry instead of failing after `MPP_TIMEOUT_TICKS`.
Apply the existing `total_mpp_amount_msat` completeness
check to all MPP receives and add a regression test
covering the keysend case.

The timeout logic was originally added only for
invoice-backed MPPs in 2022, and that invoice-only
guard remained when receive-side MPP keysend support
landed in 2023, leaving this gap latent until now.

Co-Authored-By: HAL 9000
@tnull tnull force-pushed the 2026-04-fix-keysend-mpp-timeout branch from c2f774d to fd8846b Compare April 14, 2026 08:33
@ThomsenDrake

Copy link
Copy Markdown

LGTM. The fix is minimal and correct — applying the total_mpp_amount_msat completeness check to all MPP receive paths closes the gap where partial keysend payments could leak HTLC slots.

The regression test in payment_tests.rs is a good coverage anchor. One question: does parse_recv_partial_keysend_payment have any similar paths that bypass the timeout check?

@tnull

tnull commented Apr 14, 2026

Copy link
Copy Markdown
Contributor Author

parse_recv_partial_keysend_payment

Could you expand on what your're referring to? This method doesn't seem to exist in the codebase?

@tnull tnull merged commit 8fc122d into lightningdevkit:main Apr 14, 2026
20 of 23 checks passed
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Backported to 0.1 in #4680.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Backported to 0.2 in #4683.

TheBlueMatt added a commit that referenced this pull request Jun 19, 2026
v0.1.10 - Jun 18, 2026 - "Loupe de Loupe"

API Updates
===========

 * `DefaultMessageRouter` will now always generate blinded message paths that
   provide no privacy (where our node is the introduction node) for nodes with
   public channels. This works around an issue which will appear for any nodes
   with LND peers that enable onion messaging - such peers will refuse to
   forward BOLT 12 messages from unknown third parties, which most BOLT 12
   payers rely on today (#4647).
 * Explicit `amount_msats` of 0 is rejected in BOLT 12 `Offer`s; `OfferBuilder`
   now maps 0-amounts to an amount of `None` (#4324).

Bug Fixes
=========

 * Async `ChannelMonitorUpdate` persistence operations which complete, but are
   not marked as complete in a persisted `ChannelManager` prior to restart,
   followed immediately by a block connection and then another restart could
   result in some channel operations hanging leading for force-closures (#4377).
 * If an MPP payment is claimed but `ChannelMonitorUpdate`s for some parts are
   still being completed asynchronously, further channel updates (e.g.
   forwarding another payment) are pending and the node restarts, the channel
   could have become stuck (#4520).
 * The presence of unconfirmed transactions actually no longer causes
   `ElectrumSyncClient` to spuriously fail to sync (#4590).
 * `FilesystemStore::list_all_keys` will no longer fail if there are stale
   intermediate files lying around from a previous unclean shutdown (#4618).
 * When forwarding an HTLC while in a blinded path with proportional fees over
   200%, LDK will no longer spuriously allow a forward that pays us 1 msat too
   little in fees (#4697).
 * Fixed a rare case where a channel could get stuck on reconnect when using
   both async `ChannelMonitorUpdate` persistence and async signing (#4684).
 * `Event::PaymentSent::fee_paid_msat` is no longer `None` in cases where
   `ChannelManager::abandon_payment` was called before the payment ultimately
   completes anyway (#4651).
 * Syncing a `ChainMonitor` using the `Confirm` trait will no longer write some
   full `ChannelMonitor`s to disk several times per block (#4544).
 * `OMDomainResolver` now correctly accounts for failed queries when rate
   limiting, ensuring we continue to respond to queries after failures (#4591).
 * Calling `ChannelManager::send_payment_with_route` without a `route_params`
   and with an invalid `Route` will no longer panic (#4707).
 * `lightning-custom-message`'s handling of `peer_connected` events now ensures
   that sub-handlers will see a `peer_disconnected` event if a different
   sub-handler refused the connection by `Err`ing `peer_connected` (#4595).
 * Incomplete MPP keysend payments will no longer see their HTLCs held until
   expiry (#4558).
 * `InvoiceRequestBuilder` will no longer accept a `quantity` of `0` for a
   BOLT 12 `Offer`, allowing any quantity up to a bound (#4667).
 * `lightning-custom-message` handlers that return `Ok(None)` when asked to
   deserialize a message in their defined range no longer cause panics (#4709).
 * Several spurious debug assertions were fixed (#4537, #4618).

Security
========

0.1.10 fixes a sanitization issue and several denial-of-service vulnerabilities.
 * `Bolt11Invoice::recover_payee_pub_key` no longer panics if called on an
   invoice which set an explicit public key, rather than relying on public key
   recovery. This method is called from `payment_parameters_from_invoice` and
   `payment_parameters_from_variable_amount_invoice` (#4717).
 * Maliciously-crafted unpayable invoices which have overflowing feerates will
   no longer cause an `unwrap` failure panic (#4716).
 * `possiblyrandom` did not properly generate random data except when it was
   explicitly configured to. By default this means LDK is vulnerable to various
   HashDoS attacks (#4719).
 * `OMNameResolver` will no longer panic when looking up payment instructions
   which include unicode characters at the start of a TXT record (#4718).
 * `PrintableString` did not properly sanitize unicode format characters,
   allowing an attacker to corrupt the rendering of logs or UI (#4593, #4605).
 * RGS data is now limited in how large of a graph it is able to cause a client
   to store in memory. Note that RGS data is still considered a DoS vector in
   general and you should only use semi-trusted RGS data (#4713).
 * Counterparty-provided strings in failure messages are no longer logged in
   full, reducing the ability of such a counterparty to spam our logs (#4714).
 * Reading a corrupted `ChannelManager` or `ProbabilisticScorer` can no longer
   cause us to allocate large amounts of memory (#4712).

Thanks to Project Loupe for reporting most of the issues fixed in this release.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incomplete keysend MPP payments bypass MPP timeout

6 participants