Skip to content

Commit 7796b99

Browse files
committed
Support receiving, validating, and claiming MPP keysend
1 parent 4689569 commit 7796b99

File tree

3 files changed

+88
-59
lines changed

3 files changed

+88
-59
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 31 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3498,7 +3498,7 @@ where
34983498
panic!("short_channel_id == 0 should imply any pending_forward entries are of type Receive");
34993499
}
35003500
};
3501-
let mut claimable_htlc = ClaimableHTLC {
3501+
let claimable_htlc = ClaimableHTLC {
35023502
prev_hop: HTLCPreviousHopData {
35033503
short_channel_id: prev_short_channel_id,
35043504
outpoint: prev_funding_outpoint,
@@ -3548,25 +3548,33 @@ where
35483548
}
35493549

35503550
macro_rules! check_total_value {
3551-
($payment_data: expr, $payment_preimage: expr) => {{
3551+
($payment_secret: expr, $payment_preimage: expr, $total_msat: expr, $is_keysend: expr) => {{
35523552
let mut payment_claimable_generated = false;
3553-
let purpose = || {
3553+
let purpose = if $is_keysend {
3554+
events::PaymentPurpose::SpontaneousPayment(
3555+
$payment_preimage.expect("Should never call check_total_value with $is_keysend as true but no preimage")
3556+
)
3557+
} else {
35543558
events::PaymentPurpose::InvoicePayment {
35553559
payment_preimage: $payment_preimage,
3556-
payment_secret: $payment_data.payment_secret,
3560+
payment_secret: $payment_secret.expect("Should never call check_total_value with $is_keysend as false but no payment secret"),
35573561
}
35583562
};
35593563
let mut claimable_payments = self.claimable_payments.lock().unwrap();
35603564
if claimable_payments.pending_claiming_payments.contains_key(&payment_hash) {
35613565
fail_htlc!(claimable_htlc, payment_hash);
35623566
}
3567+
if $is_keysend && $payment_secret.is_none() && claimable_payments.claimable_payments.get(&payment_hash).is_some() {
3568+
log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} for duplicative payment hash", log_bytes!(payment_hash.0));
3569+
fail_htlc!(claimable_htlc, payment_hash);
3570+
}
35633571
let ref mut claimable_payment = claimable_payments.claimable_payments
35643572
.entry(payment_hash)
35653573
// Note that if we insert here we MUST NOT fail_htlc!()
35663574
.or_insert_with(|| {
35673575
committed_to_claimable = true;
35683576
ClaimablePayment {
3569-
purpose: purpose(), htlcs: Vec::new(), onion_fields: None,
3577+
purpose: purpose.clone(), htlcs: Vec::new(), onion_fields: None,
35703578
}
35713579
});
35723580
if let Some(earlier_fields) = &mut claimable_payment.onion_fields {
@@ -3577,7 +3585,7 @@ where
35773585
claimable_payment.onion_fields = Some(onion_fields);
35783586
}
35793587
let ref mut htlcs = &mut claimable_payment.htlcs;
3580-
if htlcs.len() == 1 {
3588+
if !htlcs.is_empty() && !$is_keysend {
35813589
if let OnionPayload::Spontaneous(_) = htlcs[0].onion_payload {
35823590
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));
35833591
fail_htlc!(claimable_htlc, payment_hash);
@@ -3588,27 +3596,22 @@ where
35883596
for htlc in htlcs.iter() {
35893597
total_value += htlc.sender_intended_value;
35903598
earliest_expiry = cmp::min(earliest_expiry, htlc.cltv_expiry);
3591-
match &htlc.onion_payload {
3592-
OnionPayload::Invoice { .. } => {
3593-
if htlc.total_msat != $payment_data.total_msat {
3594-
log_trace!(self.logger, "Failing HTLCs with payment_hash {} as the HTLCs had inconsistent total values (eg {} and {})",
3595-
log_bytes!(payment_hash.0), $payment_data.total_msat, htlc.total_msat);
3596-
total_value = msgs::MAX_VALUE_MSAT;
3597-
}
3598-
if total_value >= msgs::MAX_VALUE_MSAT { break; }
3599-
},
3600-
_ => unreachable!(),
3599+
if htlc.total_msat != $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), $total_msat, htlc.total_msat);
3602+
total_value = msgs::MAX_VALUE_MSAT;
36013603
}
3604+
if total_value >= msgs::MAX_VALUE_MSAT { break; }
36023605
}
36033606
// The condition determining whether an MPP is complete must
36043607
// match exactly the condition used in `timer_tick_occurred`
36053608
if total_value >= msgs::MAX_VALUE_MSAT {
36063609
fail_htlc!(claimable_htlc, payment_hash);
3607-
} else if total_value - claimable_htlc.sender_intended_value >= $payment_data.total_msat {
3610+
} else if total_value - claimable_htlc.sender_intended_value >= $total_msat {
36083611
log_trace!(self.logger, "Failing HTLC with payment_hash {} as payment is already claimable",
36093612
log_bytes!(payment_hash.0));
36103613
fail_htlc!(claimable_htlc, payment_hash);
3611-
} else if total_value >= $payment_data.total_msat {
3614+
} else if total_value >= $total_msat {
36123615
#[allow(unused_assignments)] {
36133616
committed_to_claimable = true;
36143617
}
@@ -3619,7 +3622,7 @@ where
36193622
new_events.push_back((events::Event::PaymentClaimable {
36203623
receiver_node_id: Some(receiver_node_id),
36213624
payment_hash,
3622-
purpose: purpose(),
3625+
purpose,
36233626
amount_msat,
36243627
via_channel_id: Some(prev_channel_id),
36253628
via_user_channel_id: Some(prev_user_channel_id),
@@ -3667,41 +3670,10 @@ where
36673670
fail_htlc!(claimable_htlc, payment_hash);
36683671
}
36693672
}
3670-
check_total_value!(payment_data, payment_preimage);
3673+
check_total_value!(Some(payment_data.payment_secret), payment_preimage, payment_data.total_msat, false);
36713674
},
36723675
OnionPayload::Spontaneous(preimage) => {
3673-
let mut claimable_payments = self.claimable_payments.lock().unwrap();
3674-
if claimable_payments.pending_claiming_payments.contains_key(&payment_hash) {
3675-
fail_htlc!(claimable_htlc, payment_hash);
3676-
}
3677-
match claimable_payments.claimable_payments.entry(payment_hash) {
3678-
hash_map::Entry::Vacant(e) => {
3679-
let amount_msat = claimable_htlc.value;
3680-
claimable_htlc.total_value_received = Some(amount_msat);
3681-
let claim_deadline = Some(claimable_htlc.cltv_expiry - HTLC_FAIL_BACK_BUFFER);
3682-
let purpose = events::PaymentPurpose::SpontaneousPayment(preimage);
3683-
e.insert(ClaimablePayment {
3684-
purpose: purpose.clone(),
3685-
onion_fields: Some(onion_fields.clone()),
3686-
htlcs: vec![claimable_htlc],
3687-
});
3688-
let prev_channel_id = prev_funding_outpoint.to_channel_id();
3689-
new_events.push_back((events::Event::PaymentClaimable {
3690-
receiver_node_id: Some(receiver_node_id),
3691-
payment_hash,
3692-
amount_msat,
3693-
purpose,
3694-
via_channel_id: Some(prev_channel_id),
3695-
via_user_channel_id: Some(prev_user_channel_id),
3696-
claim_deadline,
3697-
onion_fields: Some(onion_fields),
3698-
}, None));
3699-
},
3700-
hash_map::Entry::Occupied(_) => {
3701-
log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} for a duplicative payment hash", log_bytes!(payment_hash.0));
3702-
fail_htlc!(claimable_htlc, payment_hash);
3703-
}
3704-
}
3676+
check_total_value!(payment_data.as_ref().map(|d| d.payment_secret), Some(preimage), payment_data.as_ref().map_or(claimable_htlc.value, |d| d.total_msat), true);
37053677
}
37063678
}
37073679
},
@@ -3719,7 +3691,7 @@ where
37193691
log_bytes!(payment_hash.0), payment_data.total_msat, inbound_payment.get().min_value_msat.unwrap());
37203692
fail_htlc!(claimable_htlc, payment_hash);
37213693
} else {
3722-
let payment_claimable_generated = check_total_value!(payment_data, inbound_payment.get().payment_preimage);
3694+
let payment_claimable_generated = check_total_value!(Some(payment_data.payment_secret), inbound_payment.get().payment_preimage, payment_data.total_msat, false);
37233695
if payment_claimable_generated {
37243696
inbound_payment.remove_entry();
37253697
}
@@ -4194,12 +4166,16 @@ where
41944166
/// event matches your expectation. If you fail to do so and call this method, you may provide
41954167
/// the sender "proof-of-payment" when they did not fulfill the full expected payment.
41964168
///
4169+
/// To accept multi-part keysend payments you must set [`UserConfig::accept_mpp_keysend`] to
4170+
/// true.
4171+
///
41974172
/// [`Event::PaymentClaimable`]: crate::events::Event::PaymentClaimable
41984173
/// [`Event::PaymentClaimable::claim_deadline`]: crate::events::Event::PaymentClaimable::claim_deadline
41994174
/// [`Event::PaymentClaimed`]: crate::events::Event::PaymentClaimed
42004175
/// [`process_pending_events`]: EventsProvider::process_pending_events
42014176
/// [`create_inbound_payment`]: Self::create_inbound_payment
42024177
/// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash
4178+
/// [`UserConfig::accept_mpp_keysend`]: crate::util::config::UserConfig::accept_mpp_keysend
42034179
pub fn claim_funds(&self, payment_preimage: PaymentPreimage) {
42044180
let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
42054181

@@ -4260,11 +4236,8 @@ where
42604236
expected_amt_msat = htlc.total_value_received;
42614237

42624238
if let OnionPayload::Spontaneous(_) = &htlc.onion_payload {
4263-
// We don't currently support MPP for spontaneous payments, so just check
4264-
// that there's one payment here and move on.
4265-
if sources.len() != 1 {
4266-
log_error!(self.logger, "Somehow ended up with an MPP spontaneous payment - this should not be reachable!");
4267-
debug_assert!(false);
4239+
if !self.default_configuration.accept_mpp_keysend && sources.len() != 1 {
4240+
log_info!(self.logger, "Rejecting a received MPP spontaneous payment since our config states we don't accept them.");
42684241
valid_mpp = false;
42694242
break;
42704243
}

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);

lightning/src/ln/functional_tests.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8193,6 +8193,62 @@ fn test_simple_mpp() {
81938193
claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]], false, payment_preimage);
81948194
}
81958195

8196+
fn do_test_mpp_keysend(accept_mpp_keysend: bool) {
8197+
let mut mpp_keysend_config = test_default_channel_config();
8198+
mpp_keysend_config.accept_mpp_keysend = accept_mpp_keysend;
8199+
let chanmon_cfgs = create_chanmon_cfgs(4);
8200+
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
8201+
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, Some(mpp_keysend_config)]);
8202+
let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
8203+
8204+
create_announced_chan_between_nodes(&nodes, 0, 1).0.contents.short_channel_id;
8205+
create_announced_chan_between_nodes(&nodes, 0, 2).0.contents.short_channel_id;
8206+
create_announced_chan_between_nodes(&nodes, 1, 3).0.contents.short_channel_id;
8207+
create_announced_chan_between_nodes(&nodes, 2, 3).0.contents.short_channel_id;
8208+
let network_graph = nodes[0].network_graph.clone();
8209+
8210+
let payer_pubkey = nodes[0].node.get_our_node_id();
8211+
let payee_pubkey = nodes[3].node.get_our_node_id();
8212+
let recv_value = 15_000_000;
8213+
let route_params = RouteParameters {
8214+
payment_params: PaymentParameters::for_keysend(payee_pubkey, 40, true),
8215+
final_value_msat: recv_value,
8216+
};
8217+
let scorer = test_utils::TestScorer::new();
8218+
let random_seed_bytes = chanmon_cfgs[0].keys_manager.get_secure_random_bytes();
8219+
let route = find_route(&payer_pubkey, &route_params, &network_graph, None, nodes[0].logger,
8220+
&scorer, &(), &random_seed_bytes).unwrap();
8221+
8222+
let payment_preimage = PaymentPreimage([42; 32]);
8223+
let payment_secret = PaymentSecret(payment_preimage.0);
8224+
let payment_hash = nodes[0].node.send_spontaneous_payment(&route, Some(payment_preimage),
8225+
RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_preimage.0)).unwrap();
8226+
check_added_monitors!(nodes[0], 2);
8227+
8228+
let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]];
8229+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
8230+
assert_eq!(events.len(), 2);
8231+
for (path_idx, expected_path) in expected_route.iter().enumerate() {
8232+
let ev = remove_first_msg_event_to_node(&expected_path[0].node.get_our_node_id(), &mut events);
8233+
let expect_payment = path_idx == expected_route.len() - 1;
8234+
pass_along_path(&nodes[0], *expected_path, recv_value, payment_hash.clone(),
8235+
Some(payment_secret), ev, expect_payment, Some(payment_preimage));
8236+
}
8237+
8238+
claim_payment_along_route(&nodes[0], expected_route, false, payment_preimage);
8239+
}
8240+
8241+
#[test]
8242+
fn test_mpp_keysend() {
8243+
do_test_mpp_keysend(true);
8244+
}
8245+
8246+
#[test]
8247+
#[should_panic]
8248+
fn test_mpp_keysend_fail() {
8249+
do_test_mpp_keysend(false);
8250+
}
8251+
81968252
#[test]
81978253
fn test_preimage_storage() {
81988254
// Simple test of payment preimage storage allowing no client-side storage to claim payments

0 commit comments

Comments
 (0)