Skip to content

Commit 9c55ada

Browse files
committed
Pipe received payment_metadata through the HTLC receipt pipeline
When we receive an HTLC, we want to pass the `payment_metadata` through to the `PaymentClaimable` event. This does most of the internal refactoring required to do so - storing a `RecipientOnionFields` in the inbound HTLC tracking structs, including the `payment_metadata`. In the future this struct will allow us to do MPP keysend receipts (as it now stores an Optional `payment_secret` for all inbound payments) as well as custom TLV receipts (as the struct is extensible to store additional fields and the internal API supports filtering for fields which are consistent across HTLCs).
1 parent 3dd05ab commit 9c55ada

File tree

2 files changed

+73
-14
lines changed

2 files changed

+73
-14
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,13 @@ pub(super) enum PendingHTLCRouting {
106106
},
107107
Receive {
108108
payment_data: msgs::FinalOnionHopData,
109+
payment_metadata: Option<Vec<u8>>,
109110
incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed
110111
phantom_shared_secret: Option<[u8; 32]>,
111112
},
112113
ReceiveKeysend {
113114
payment_preimage: PaymentPreimage,
115+
payment_metadata: Option<Vec<u8>>,
114116
incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed
115117
},
116118
}
@@ -472,6 +474,7 @@ impl_writeable_tlv_based!(ClaimingPayment, {
472474

473475
struct ClaimablePayment {
474476
purpose: events::PaymentPurpose,
477+
onion_fields: Option<RecipientOnionFields>,
475478
htlcs: Vec<ClaimableHTLC>,
476479
}
477480

@@ -2168,7 +2171,7 @@ where
21682171
msg: "Got non final data with an HMAC of 0",
21692172
});
21702173
},
2171-
msgs::OnionHopDataFormat::FinalNode { payment_data, keysend_preimage, .. } => { // TODO: expose the payment_metadata to the user
2174+
msgs::OnionHopDataFormat::FinalNode { payment_data, keysend_preimage, payment_metadata } => {
21722175
if payment_data.is_some() && keysend_preimage.is_some() {
21732176
return Err(ReceiveError {
21742177
err_code: 0x4000|22,
@@ -2178,6 +2181,7 @@ where
21782181
} else if let Some(data) = payment_data {
21792182
PendingHTLCRouting::Receive {
21802183
payment_data: data,
2184+
payment_metadata,
21812185
incoming_cltv_expiry: hop_data.outgoing_cltv_value,
21822186
phantom_shared_secret,
21832187
}
@@ -2198,6 +2202,7 @@ where
21982202

21992203
PendingHTLCRouting::ReceiveKeysend {
22002204
payment_preimage,
2205+
payment_metadata,
22012206
incoming_cltv_expiry: hop_data.outgoing_cltv_value,
22022207
}
22032208
} else {
@@ -3284,13 +3289,19 @@ where
32843289
routing, incoming_shared_secret, payment_hash, incoming_amt_msat, outgoing_amt_msat, ..
32853290
}
32863291
}) => {
3287-
let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret) = match routing {
3288-
PendingHTLCRouting::Receive { payment_data, incoming_cltv_expiry, phantom_shared_secret } => {
3292+
let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret, mut onion_fields) = match routing {
3293+
PendingHTLCRouting::Receive { payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret } => {
32893294
let _legacy_hop_data = Some(payment_data.clone());
3290-
(incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data }, Some(payment_data), phantom_shared_secret)
3295+
let onion_fields =
3296+
RecipientOnionFields { payment_secret: Some(payment_data.payment_secret), payment_metadata };
3297+
(incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data },
3298+
Some(payment_data), phantom_shared_secret, onion_fields)
3299+
},
3300+
PendingHTLCRouting::ReceiveKeysend { payment_preimage, payment_metadata, incoming_cltv_expiry } => {
3301+
let onion_fields = RecipientOnionFields { payment_secret: None, payment_metadata };
3302+
(incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage),
3303+
None, None, onion_fields)
32913304
},
3292-
PendingHTLCRouting::ReceiveKeysend { payment_preimage, incoming_cltv_expiry } =>
3293-
(incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage), None, None),
32943305
_ => {
32953306
panic!("short_channel_id == 0 should imply any pending_forward entries are of type Receive");
32963307
}
@@ -3363,9 +3374,16 @@ where
33633374
.or_insert_with(|| {
33643375
committed_to_claimable = true;
33653376
ClaimablePayment {
3366-
purpose: purpose(), htlcs: Vec::new()
3377+
purpose: purpose(), htlcs: Vec::new(), onion_fields: None,
33673378
}
33683379
});
3380+
if let Some(earlier_fields) = &mut claimable_payment.onion_fields {
3381+
if earlier_fields.check_merge(&mut onion_fields).is_err() {
3382+
fail_htlc!(claimable_htlc, payment_hash);
3383+
}
3384+
} else {
3385+
claimable_payment.onion_fields = Some(onion_fields);
3386+
}
33693387
let ref mut htlcs = &mut claimable_payment.htlcs;
33703388
if htlcs.len() == 1 {
33713389
if let OnionPayload::Spontaneous(_) = htlcs[0].onion_payload {
@@ -3471,6 +3489,7 @@ where
34713489
let purpose = events::PaymentPurpose::SpontaneousPayment(preimage);
34723490
e.insert(ClaimablePayment {
34733491
purpose: purpose.clone(),
3492+
onion_fields: Some(onion_fields.clone()),
34743493
htlcs: vec![claimable_htlc],
34753494
});
34763495
let prev_channel_id = prev_funding_outpoint.to_channel_id();
@@ -6715,10 +6734,12 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
67156734
(0, payment_data, required),
67166735
(1, phantom_shared_secret, option),
67176736
(2, incoming_cltv_expiry, required),
6737+
(3, payment_metadata, option),
67186738
},
67196739
(2, ReceiveKeysend) => {
67206740
(0, payment_preimage, required),
67216741
(2, incoming_cltv_expiry, required),
6742+
(3, payment_metadata, option),
67226743
},
67236744
;);
67246745

@@ -7051,6 +7072,7 @@ where
70517072
let pending_outbound_payments = self.pending_outbound_payments.pending_outbound_payments.lock().unwrap();
70527073

70537074
let mut htlc_purposes: Vec<&events::PaymentPurpose> = Vec::new();
7075+
let mut htlc_onion_fields: Vec<&_> = Vec::new();
70547076
(claimable_payments.claimable_payments.len() as u64).write(writer)?;
70557077
for (payment_hash, payment) in claimable_payments.claimable_payments.iter() {
70567078
payment_hash.write(writer)?;
@@ -7059,6 +7081,7 @@ where
70597081
htlc.write(writer)?;
70607082
}
70617083
htlc_purposes.push(&payment.purpose);
7084+
htlc_onion_fields.push(&payment.onion_fields);
70627085
}
70637086

70647087
let mut monitor_update_blocked_actions_per_peer = None;
@@ -7173,6 +7196,7 @@ where
71737196
(7, self.fake_scid_rand_bytes, required),
71747197
(9, htlc_purposes, vec_type),
71757198
(11, self.probing_cookie_secret, required),
7199+
(13, htlc_onion_fields, optional_vec),
71767200
});
71777201

71787202
Ok(())
@@ -7551,6 +7575,7 @@ where
75517575
let mut fake_scid_rand_bytes: Option<[u8; 32]> = None;
75527576
let mut probing_cookie_secret: Option<[u8; 32]> = None;
75537577
let mut claimable_htlc_purposes = None;
7578+
let mut claimable_htlc_onion_fields = None;
75547579
let mut pending_claiming_payments = Some(HashMap::new());
75557580
let mut monitor_update_blocked_actions_per_peer = Some(Vec::new());
75567581
read_tlv_fields!(reader, {
@@ -7563,6 +7588,7 @@ where
75637588
(7, fake_scid_rand_bytes, option),
75647589
(9, claimable_htlc_purposes, vec_type),
75657590
(11, probing_cookie_secret, option),
7591+
(13, claimable_htlc_onion_fields, optional_vec),
75667592
});
75677593
if fake_scid_rand_bytes.is_none() {
75687594
fake_scid_rand_bytes = Some(args.entropy_source.get_secure_random_bytes());
@@ -7712,15 +7738,29 @@ where
77127738
let expanded_inbound_key = inbound_payment::ExpandedKey::new(&inbound_pmt_key_material);
77137739

77147740
let mut claimable_payments = HashMap::with_capacity(claimable_htlcs_list.len());
7715-
if let Some(mut purposes) = claimable_htlc_purposes {
7741+
if let Some(purposes) = claimable_htlc_purposes {
77167742
if purposes.len() != claimable_htlcs_list.len() {
77177743
return Err(DecodeError::InvalidValue);
77187744
}
7719-
for (purpose, (payment_hash, htlcs)) in purposes.drain(..).zip(claimable_htlcs_list.drain(..)) {
7720-
let existing_payment = claimable_payments.insert(payment_hash, ClaimablePayment {
7721-
purpose, htlcs,
7722-
});
7723-
if existing_payment.is_some() { return Err(DecodeError::InvalidValue); }
7745+
if let Some(onion_fields) = claimable_htlc_onion_fields {
7746+
if onion_fields.len() != claimable_htlcs_list.len() {
7747+
return Err(DecodeError::InvalidValue);
7748+
}
7749+
for (purpose, (onion, (payment_hash, htlcs))) in
7750+
purposes.into_iter().zip(onion_fields.into_iter().zip(claimable_htlcs_list.into_iter()))
7751+
{
7752+
let existing_payment = claimable_payments.insert(payment_hash, ClaimablePayment {
7753+
purpose, htlcs, onion_fields: onion,
7754+
});
7755+
if existing_payment.is_some() { return Err(DecodeError::InvalidValue); }
7756+
}
7757+
} else {
7758+
for (purpose, (payment_hash, htlcs)) in purposes.into_iter().zip(claimable_htlcs_list.into_iter()) {
7759+
let existing_payment = claimable_payments.insert(payment_hash, ClaimablePayment {
7760+
purpose, htlcs, onion_fields: None,
7761+
});
7762+
if existing_payment.is_some() { return Err(DecodeError::InvalidValue); }
7763+
}
77247764
}
77257765
} else {
77267766
// LDK versions prior to 0.0.107 did not write a `pending_htlc_purposes`, but do
@@ -7751,7 +7791,7 @@ where
77517791
events::PaymentPurpose::SpontaneousPayment(*payment_preimage),
77527792
};
77537793
claimable_payments.insert(payment_hash, ClaimablePayment {
7754-
purpose, htlcs,
7794+
purpose, htlcs, onion_fields: None,
77557795
});
77567796
}
77577797
}

lightning/src/ln/outbound_payment.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,11 @@ pub struct RecipientOnionFields {
438438
pub payment_metadata: Option<Vec<u8>>,
439439
}
440440

441+
impl_writeable_tlv_based!(RecipientOnionFields, {
442+
(0, payment_secret, option),
443+
(2, payment_metadata, option),
444+
});
445+
441446
impl RecipientOnionFields {
442447
/// Creates a [`RecipientOnionFields`] from only a [`PaymentSecret`]. This is the most common
443448
/// set of onion fields for today's BOLT11 invoices - most nodes require a [`PaymentSecret`]
@@ -454,6 +459,20 @@ impl RecipientOnionFields {
454459
pub fn spontaneous_empty() -> Self {
455460
Self { payment_secret: None, payment_metadata: None }
456461
}
462+
463+
/// When we have received some HTLC(s) towards an MPP payment, as we receive further HTLC(s) we
464+
/// have to make sure that some fields match exactly across the parts. For those that aren't
465+
/// required to match, if they don't match we should remove them so as to not expose data
466+
/// that's dependent on the HTLC receive order to users.
467+
///
468+
/// Here we implement this, first checking compatibility then mutating two objects and then
469+
/// dropping any remaining non-matching fields from both.
470+
pub(super) fn check_merge(&mut self, further_htlc_fields: &mut Self) -> Result<(), ()> {
471+
if self.payment_secret != further_htlc_fields.payment_secret { return Err(()); }
472+
if self.payment_metadata != further_htlc_fields.payment_metadata { return Err(()); }
473+
// For custom TLVs we should just drop non-matching ones, but not reject the payment.
474+
Ok(())
475+
}
457476
}
458477

459478
pub(super) struct OutboundPayments {

0 commit comments

Comments
 (0)