ln/refactor: introduce HasMppPart generic to share incoming mpp
What changed, and why it matters
This is a straightforward internal code cleanup (refactor) in the Lightning payment handling code. It introduces a small generic trait so that the same multi-part payment logic can be reused for a future feature (trampoline payments). No user-facing behavior changes are visible in the diff, and no security bug is being fixed.
No security action needed. Treat as normal code maintenance; review as part of standard code quality process.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit adds a HasMppPart trait implemented for MppPart and ClaimableHTLC, then changes check_incoming_mpp_part from taking a &mut ClaimablePayment to taking a generic Vec<H> and a separate RecipientOnionFields. It also moves a debug_assert! about counterparty skimmed fees from inside the generic helper to the non-trampoline caller. This is preparatory refactoring for trampoline payment support and does not alter validation rules or fix a vulnerability.
Changed components
lightning/src/ln/channelmanager.rscheck_incoming_mpp_partClaimableHTLCMppPartInspect captured patch +52 / −24
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 3bb64ba..9da8e1f 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -560,6 +560,20 @@ impl Ord for MppPart {
}
}
+trait HasMppPart {
+ fn mpp_part(&self) -> &MppPart;
+ fn mpp_part_mut(&mut self) -> &mut MppPart;
+}
+
+impl HasMppPart for MppPart {
+ fn mpp_part(&self) -> &MppPart {
+ self
+ }
+ fn mpp_part_mut(&mut self) -> &mut MppPart {
+ self
+ }
+}
+
/// Represents an incoming HTLC that can be claimed or failed by the user.
#[derive(PartialEq, Eq)]
struct ClaimableHTLC {
@@ -569,6 +583,15 @@ struct ClaimableHTLC {
counterparty_skimmed_fee_msat: Option<u64>,
}
+impl HasMppPart for ClaimableHTLC {
+ fn mpp_part(&self) -> &MppPart {
+ &self.mpp_part
+ }
+ fn mpp_part_mut(&mut self) -> &mut MppPart {
+ &mut self.mpp_part
+ }
+}
+
impl From<&ClaimableHTLC> for events::ClaimedHTLC {
fn from(val: &ClaimableHTLC) -> Self {
events::ClaimedHTLC {
@@ -8271,28 +8294,28 @@ impl<
// Checks whether an incoming HTLC can be added to an in-progress MPP payment, verifying onion
// field compatibility and that the total value is sensible. On success, the HTLC is added to
- // the payment's claimable set and Ok(true) is returned if all MPP parts have arrived.
- fn check_incoming_mpp_part(
- &self, claimable_payment: &mut ClaimablePayment, claimable_htlc: ClaimableHTLC,
+ // the htlc claimable set and Ok(true) is returned if all MPP parts have arrived.
+ fn check_incoming_mpp_part<H: HasMppPart + Ord>(
+ &self, htlc_set: &mut Vec<H>, payment_onion_fields: &mut RecipientOnionFields, new_htlc: H,
mut onion_fields: RecipientOnionFields, payment_hash: PaymentHash,
) -> Result<bool, ()> {
- let onions_compatible = claimable_payment.onion_fields.check_merge(&mut onion_fields);
+ let onions_compatible = payment_onion_fields.check_merge(&mut onion_fields);
if onions_compatible.is_err() {
return Err(());
}
- let mut total_intended_recvd_value = claimable_htlc.mpp_part.sender_intended_value;
- for htlc in claimable_payment.htlcs.iter() {
- total_intended_recvd_value += htlc.mpp_part.sender_intended_value;
+ let mut total_intended_recvd_value = new_htlc.mpp_part().sender_intended_value;
+ for htlc in htlc_set.iter() {
+ total_intended_recvd_value += htlc.mpp_part().sender_intended_value;
if total_intended_recvd_value >= msgs::MAX_VALUE_MSAT {
break;
}
}
- let total_mpp_value = claimable_payment.onion_fields.total_mpp_amount_msat;
+ let total_mpp_value = payment_onion_fields.total_mpp_amount_msat;
// The condition determining whether an MPP is complete must match exactly the condition
// used in `timer_tick_occurred`
if total_intended_recvd_value >= msgs::MAX_VALUE_MSAT {
return Err(());
- } else if total_intended_recvd_value - claimable_htlc.mpp_part.sender_intended_value
+ } else if total_intended_recvd_value - new_htlc.mpp_part().sender_intended_value
>= total_mpp_value
{
log_trace!(
@@ -8302,23 +8325,17 @@ impl<
);
return Err(());
} else if total_intended_recvd_value >= total_mpp_value {
- claimable_payment.htlcs.push(claimable_htlc);
- let amount_msat = claimable_payment.htlcs.iter().map(|htlc| htlc.mpp_part.value).sum();
- claimable_payment
- .htlcs
+ htlc_set.push(new_htlc);
+ let amount_msat = htlc_set.iter().map(|htlc| htlc.mpp_part().value).sum();
+ htlc_set
.iter_mut()
- .for_each(|htlc| htlc.mpp_part.total_value_received = Some(amount_msat));
- let counterparty_skimmed_fee_msat = claimable_payment.total_counterparty_skimmed_msat();
- debug_assert!(
- total_intended_recvd_value.saturating_sub(amount_msat)
- <= counterparty_skimmed_fee_msat
- );
- claimable_payment.htlcs.sort();
+ .for_each(|htlc| htlc.mpp_part_mut().total_value_received = Some(amount_msat));
+ htlc_set.sort();
Ok(true)
} else {
- // Nothing to do - we haven't reached the total payment value yet, wait until we receive
- // more MPP parts.
- claimable_payment.htlcs.push(claimable_htlc);
+ // Nothing to do - we haven't reached the total payment value yet, wait until we
+ // receive more MPP parts.
+ htlc_set.push(new_htlc);
Ok(false)
}
}
@@ -8359,12 +8376,23 @@ impl<
let htlc_expiry = claimable_htlc.mpp_part.cltv_expiry;
match self.check_incoming_mpp_part(
- claimable_payment,
+ &mut claimable_payment.htlcs,
+ &mut claimable_payment.onion_fields,
claimable_htlc,
onion_fields,
payment_hash,
) {
Ok(true) => {
+ let counterparty_skimmed_fee_msat =
+ claimable_payment.total_counterparty_skimmed_msat();
+ let amount_msat: u64 =
+ claimable_payment.htlcs.iter().map(|h| h.mpp_part.value).sum();
+ let total_sender_intended: u64 =
+ claimable_payment.htlcs.iter().map(|h| h.mpp_part.sender_intended_value).sum();
+ debug_assert!(
+ total_sender_intended.saturating_sub(amount_msat)
+ <= counterparty_skimmed_fee_msat
+ );
let claim_deadline = Some(
match claimable_payment.htlcs.iter().map(|h| h.mpp_part.cltv_expiry).min() {
Some(claim_deadline) => claim_deadline,
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.