Skip to content

Commit 8dde177

Browse files
committed
Support receiving MPP keysend
This commit refactors a significant portion of the receive validation in `ChannelManager::process_pending_htlc_forwards` now that we repurpose previous MPP validation logic to accomodate keysends. This also removes a previous restriction on claiming, as well as tests sending and receiving MPP keysends.
1 parent 4be3adb commit 8dde177

File tree

3 files changed

+212
-79
lines changed

3 files changed

+212
-79
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 37 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -3505,7 +3505,7 @@ where
35053505
panic!("short_channel_id == 0 should imply any pending_forward entries are of type Receive");
35063506
}
35073507
};
3508-
let mut claimable_htlc = ClaimableHTLC {
3508+
let claimable_htlc = ClaimableHTLC {
35093509
prev_hop: HTLCPreviousHopData {
35103510
short_channel_id: prev_short_channel_id,
35113511
outpoint: prev_funding_outpoint,
@@ -3555,13 +3555,11 @@ where
35553555
}
35563556

35573557
macro_rules! check_total_value {
3558-
($payment_data: expr, $payment_preimage: expr) => {{
3558+
($purpose: expr) => {{
35593559
let mut payment_claimable_generated = false;
3560-
let purpose = || {
3561-
events::PaymentPurpose::InvoicePayment {
3562-
payment_preimage: $payment_preimage,
3563-
payment_secret: $payment_data.payment_secret,
3564-
}
3560+
let is_keysend = match $purpose {
3561+
events::PaymentPurpose::SpontaneousPayment(_) => true,
3562+
events::PaymentPurpose::InvoicePayment { .. } => false,
35653563
};
35663564
let mut claimable_payments = self.claimable_payments.lock().unwrap();
35673565
if claimable_payments.pending_claiming_payments.contains_key(&payment_hash) {
@@ -3573,9 +3571,18 @@ where
35733571
.or_insert_with(|| {
35743572
committed_to_claimable = true;
35753573
ClaimablePayment {
3576-
purpose: purpose(), htlcs: Vec::new(), onion_fields: None,
3574+
purpose: $purpose.clone(), htlcs: Vec::new(), onion_fields: None,
35773575
}
35783576
});
3577+
if $purpose != claimable_payment.purpose {
3578+
let log_keysend = |keysend| if keysend { "keysend" } else { "non-keysend" };
3579+
log_trace!(self.logger, "Failing new {} HTLC with payment_hash {} as we already had an existing {} HTLC with the same payment hash", log_keysend(is_keysend), log_bytes!(payment_hash.0), log_keysend(!is_keysend));
3580+
fail_htlc!(claimable_htlc, payment_hash);
3581+
}
3582+
if !self.default_configuration.accept_mpp_keysend && is_keysend && !claimable_payment.htlcs.is_empty() {
3583+
log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} as we already had an existing keysend HTLC with the same payment hash and our config states we don't accept MPP keysend", log_bytes!(payment_hash.0));
3584+
fail_htlc!(claimable_htlc, payment_hash);
3585+
}
35793586
if let Some(earlier_fields) = &mut claimable_payment.onion_fields {
35803587
if earlier_fields.check_merge(&mut onion_fields).is_err() {
35813588
fail_htlc!(claimable_htlc, payment_hash);
@@ -3584,38 +3591,27 @@ where
35843591
claimable_payment.onion_fields = Some(onion_fields);
35853592
}
35863593
let ref mut htlcs = &mut claimable_payment.htlcs;
3587-
if htlcs.len() == 1 {
3588-
if let OnionPayload::Spontaneous(_) = htlcs[0].onion_payload {
3589-
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as we already had an existing keysend HTLC with the same payment hash", log_bytes!(payment_hash.0));
3590-
fail_htlc!(claimable_htlc, payment_hash);
3591-
}
3592-
}
35933594
let mut total_value = claimable_htlc.sender_intended_value;
35943595
let mut earliest_expiry = claimable_htlc.cltv_expiry;
35953596
for htlc in htlcs.iter() {
35963597
total_value += htlc.sender_intended_value;
35973598
earliest_expiry = cmp::min(earliest_expiry, htlc.cltv_expiry);
3598-
match &htlc.onion_payload {
3599-
OnionPayload::Invoice { .. } => {
3600-
if htlc.total_msat != $payment_data.total_msat {
3601-
log_trace!(self.logger, "Failing HTLCs with payment_hash {} as the HTLCs had inconsistent total values (eg {} and {})",
3602-
log_bytes!(payment_hash.0), $payment_data.total_msat, htlc.total_msat);
3603-
total_value = msgs::MAX_VALUE_MSAT;
3604-
}
3605-
if total_value >= msgs::MAX_VALUE_MSAT { break; }
3606-
},
3607-
_ => unreachable!(),
3599+
if htlc.total_msat != claimable_htlc.total_msat {
3600+
log_trace!(self.logger, "Failing HTLCs with payment_hash {} as the HTLCs had inconsistent total values (eg {} and {})",
3601+
log_bytes!(payment_hash.0), claimable_htlc.total_msat, htlc.total_msat);
3602+
total_value = msgs::MAX_VALUE_MSAT;
36083603
}
3604+
if total_value >= msgs::MAX_VALUE_MSAT { break; }
36093605
}
36103606
// The condition determining whether an MPP is complete must
36113607
// match exactly the condition used in `timer_tick_occurred`
36123608
if total_value >= msgs::MAX_VALUE_MSAT {
36133609
fail_htlc!(claimable_htlc, payment_hash);
3614-
} else if total_value - claimable_htlc.sender_intended_value >= $payment_data.total_msat {
3610+
} else if total_value - claimable_htlc.sender_intended_value >= claimable_htlc.total_msat {
36153611
log_trace!(self.logger, "Failing HTLC with payment_hash {} as payment is already claimable",
36163612
log_bytes!(payment_hash.0));
36173613
fail_htlc!(claimable_htlc, payment_hash);
3618-
} else if total_value >= $payment_data.total_msat {
3614+
} else if total_value >= claimable_htlc.total_msat {
36193615
#[allow(unused_assignments)] {
36203616
committed_to_claimable = true;
36213617
}
@@ -3626,7 +3622,7 @@ where
36263622
new_events.push_back((events::Event::PaymentClaimable {
36273623
receiver_node_id: Some(receiver_node_id),
36283624
payment_hash,
3629-
purpose: purpose(),
3625+
purpose: $purpose,
36303626
amount_msat,
36313627
via_channel_id: Some(prev_channel_id),
36323628
via_user_channel_id: Some(prev_user_channel_id),
@@ -3674,49 +3670,23 @@ where
36743670
fail_htlc!(claimable_htlc, payment_hash);
36753671
}
36763672
}
3677-
check_total_value!(payment_data, payment_preimage);
3673+
let purpose = events::PaymentPurpose::InvoicePayment {
3674+
payment_preimage: payment_preimage.clone(),
3675+
payment_secret: payment_data.payment_secret,
3676+
};
3677+
check_total_value!(purpose);
36783678
},
36793679
OnionPayload::Spontaneous(preimage) => {
3680-
let mut claimable_payments = self.claimable_payments.lock().unwrap();
3681-
if claimable_payments.pending_claiming_payments.contains_key(&payment_hash) {
3682-
fail_htlc!(claimable_htlc, payment_hash);
3683-
}
3684-
match claimable_payments.claimable_payments.entry(payment_hash) {
3685-
hash_map::Entry::Vacant(e) => {
3686-
let amount_msat = claimable_htlc.value;
3687-
claimable_htlc.total_value_received = Some(amount_msat);
3688-
let claim_deadline = Some(claimable_htlc.cltv_expiry - HTLC_FAIL_BACK_BUFFER);
3689-
let purpose = events::PaymentPurpose::SpontaneousPayment(preimage);
3690-
e.insert(ClaimablePayment {
3691-
purpose: purpose.clone(),
3692-
onion_fields: Some(onion_fields.clone()),
3693-
htlcs: vec![claimable_htlc],
3694-
});
3695-
let prev_channel_id = prev_funding_outpoint.to_channel_id();
3696-
new_events.push_back((events::Event::PaymentClaimable {
3697-
receiver_node_id: Some(receiver_node_id),
3698-
payment_hash,
3699-
amount_msat,
3700-
purpose,
3701-
via_channel_id: Some(prev_channel_id),
3702-
via_user_channel_id: Some(prev_user_channel_id),
3703-
claim_deadline,
3704-
onion_fields: Some(onion_fields),
3705-
}, None));
3706-
},
3707-
hash_map::Entry::Occupied(_) => {
3708-
log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} for a duplicative payment hash", log_bytes!(payment_hash.0));
3709-
fail_htlc!(claimable_htlc, payment_hash);
3710-
}
3711-
}
3680+
let purpose = events::PaymentPurpose::SpontaneousPayment(preimage);
3681+
check_total_value!(purpose);
37123682
}
37133683
}
37143684
},
37153685
hash_map::Entry::Occupied(inbound_payment) => {
3716-
if payment_data.is_none() {
3686+
if let OnionPayload::Spontaneous(_) = claimable_htlc.onion_payload {
37173687
log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} because we already have an inbound payment with the same payment hash", log_bytes!(payment_hash.0));
37183688
fail_htlc!(claimable_htlc, payment_hash);
3719-
};
3689+
}
37203690
let payment_data = payment_data.unwrap();
37213691
if inbound_payment.get().payment_secret != payment_data.payment_secret {
37223692
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as it didn't match our expected payment secret.", log_bytes!(payment_hash.0));
@@ -3726,7 +3696,11 @@ where
37263696
log_bytes!(payment_hash.0), payment_data.total_msat, inbound_payment.get().min_value_msat.unwrap());
37273697
fail_htlc!(claimable_htlc, payment_hash);
37283698
} else {
3729-
let payment_claimable_generated = check_total_value!(payment_data, inbound_payment.get().payment_preimage);
3699+
let purpose = events::PaymentPurpose::InvoicePayment {
3700+
payment_preimage: inbound_payment.get().payment_preimage,
3701+
payment_secret: payment_data.payment_secret,
3702+
};
3703+
let payment_claimable_generated = check_total_value!(purpose);
37303704
if payment_claimable_generated {
37313705
inbound_payment.remove_entry();
37323706
}
@@ -4265,18 +4239,6 @@ where
42654239
break;
42664240
}
42674241
expected_amt_msat = htlc.total_value_received;
4268-
4269-
if let OnionPayload::Spontaneous(_) = &htlc.onion_payload {
4270-
// We don't currently support MPP for spontaneous payments, so just check
4271-
// that there's one payment here and move on.
4272-
if sources.len() != 1 {
4273-
log_error!(self.logger, "Somehow ended up with an MPP spontaneous payment - this should not be reachable!");
4274-
debug_assert!(false);
4275-
valid_mpp = false;
4276-
break;
4277-
}
4278-
}
4279-
42804242
claimable_amt_msat += htlc.value;
42814243
}
42824244
mem::drop(per_peer_state);

lightning/src/ln/functional_test_utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2115,7 +2115,7 @@ pub fn do_pass_along_path<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_p
21152115
},
21162116
PaymentPurpose::SpontaneousPayment(payment_preimage) => {
21172117
assert_eq!(expected_preimage.unwrap(), *payment_preimage);
2118-
assert!(our_payment_secret.is_none());
2118+
assert_eq!(our_payment_secret, onion_fields.as_ref().unwrap().payment_secret);
21192119
},
21202120
}
21212121
assert_eq!(*amount_msat, recv_value);

0 commit comments

Comments
 (0)