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).
This commit is contained in:
Matt Corallo 2023-04-07 20:43:54 +00:00
parent 3dd05ab261
commit 9c55adaa4a
2 changed files with 73 additions and 14 deletions

View file

@ -106,11 +106,13 @@ pub(super) enum PendingHTLCRouting {
}, },
Receive { Receive {
payment_data: msgs::FinalOnionHopData, payment_data: msgs::FinalOnionHopData,
payment_metadata: Option<Vec<u8>>,
incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed
phantom_shared_secret: Option<[u8; 32]>, phantom_shared_secret: Option<[u8; 32]>,
}, },
ReceiveKeysend { ReceiveKeysend {
payment_preimage: PaymentPreimage, payment_preimage: PaymentPreimage,
payment_metadata: Option<Vec<u8>>,
incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed
}, },
} }
@ -472,6 +474,7 @@ impl_writeable_tlv_based!(ClaimingPayment, {
struct ClaimablePayment { struct ClaimablePayment {
purpose: events::PaymentPurpose, purpose: events::PaymentPurpose,
onion_fields: Option<RecipientOnionFields>,
htlcs: Vec<ClaimableHTLC>, htlcs: Vec<ClaimableHTLC>,
} }
@ -2168,7 +2171,7 @@ where
msg: "Got non final data with an HMAC of 0", msg: "Got non final data with an HMAC of 0",
}); });
}, },
msgs::OnionHopDataFormat::FinalNode { payment_data, keysend_preimage, .. } => { // TODO: expose the payment_metadata to the user msgs::OnionHopDataFormat::FinalNode { payment_data, keysend_preimage, payment_metadata } => {
if payment_data.is_some() && keysend_preimage.is_some() { if payment_data.is_some() && keysend_preimage.is_some() {
return Err(ReceiveError { return Err(ReceiveError {
err_code: 0x4000|22, err_code: 0x4000|22,
@ -2178,6 +2181,7 @@ where
} else if let Some(data) = payment_data { } else if let Some(data) = payment_data {
PendingHTLCRouting::Receive { PendingHTLCRouting::Receive {
payment_data: data, payment_data: data,
payment_metadata,
incoming_cltv_expiry: hop_data.outgoing_cltv_value, incoming_cltv_expiry: hop_data.outgoing_cltv_value,
phantom_shared_secret, phantom_shared_secret,
} }
@ -2198,6 +2202,7 @@ where
PendingHTLCRouting::ReceiveKeysend { PendingHTLCRouting::ReceiveKeysend {
payment_preimage, payment_preimage,
payment_metadata,
incoming_cltv_expiry: hop_data.outgoing_cltv_value, incoming_cltv_expiry: hop_data.outgoing_cltv_value,
} }
} else { } else {
@ -3284,13 +3289,19 @@ where
routing, incoming_shared_secret, payment_hash, incoming_amt_msat, outgoing_amt_msat, .. routing, incoming_shared_secret, payment_hash, incoming_amt_msat, outgoing_amt_msat, ..
} }
}) => { }) => {
let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret) = match routing { let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret, mut onion_fields) = match routing {
PendingHTLCRouting::Receive { payment_data, incoming_cltv_expiry, phantom_shared_secret } => { PendingHTLCRouting::Receive { payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret } => {
let _legacy_hop_data = Some(payment_data.clone()); let _legacy_hop_data = Some(payment_data.clone());
(incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data }, Some(payment_data), phantom_shared_secret) let onion_fields =
RecipientOnionFields { payment_secret: Some(payment_data.payment_secret), payment_metadata };
(incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data },
Some(payment_data), phantom_shared_secret, onion_fields)
},
PendingHTLCRouting::ReceiveKeysend { payment_preimage, payment_metadata, incoming_cltv_expiry } => {
let onion_fields = RecipientOnionFields { payment_secret: None, payment_metadata };
(incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage),
None, None, onion_fields)
}, },
PendingHTLCRouting::ReceiveKeysend { payment_preimage, incoming_cltv_expiry } =>
(incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage), None, None),
_ => { _ => {
panic!("short_channel_id == 0 should imply any pending_forward entries are of type Receive"); panic!("short_channel_id == 0 should imply any pending_forward entries are of type Receive");
} }
@ -3363,9 +3374,16 @@ where
.or_insert_with(|| { .or_insert_with(|| {
committed_to_claimable = true; committed_to_claimable = true;
ClaimablePayment { ClaimablePayment {
purpose: purpose(), htlcs: Vec::new() purpose: purpose(), htlcs: Vec::new(), onion_fields: None,
} }
}); });
if let Some(earlier_fields) = &mut claimable_payment.onion_fields {
if earlier_fields.check_merge(&mut onion_fields).is_err() {
fail_htlc!(claimable_htlc, payment_hash);
}
} else {
claimable_payment.onion_fields = Some(onion_fields);
}
let ref mut htlcs = &mut claimable_payment.htlcs; let ref mut htlcs = &mut claimable_payment.htlcs;
if htlcs.len() == 1 { if htlcs.len() == 1 {
if let OnionPayload::Spontaneous(_) = htlcs[0].onion_payload { if let OnionPayload::Spontaneous(_) = htlcs[0].onion_payload {
@ -3471,6 +3489,7 @@ where
let purpose = events::PaymentPurpose::SpontaneousPayment(preimage); let purpose = events::PaymentPurpose::SpontaneousPayment(preimage);
e.insert(ClaimablePayment { e.insert(ClaimablePayment {
purpose: purpose.clone(), purpose: purpose.clone(),
onion_fields: Some(onion_fields.clone()),
htlcs: vec![claimable_htlc], htlcs: vec![claimable_htlc],
}); });
let prev_channel_id = prev_funding_outpoint.to_channel_id(); let prev_channel_id = prev_funding_outpoint.to_channel_id();
@ -6715,10 +6734,12 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
(0, payment_data, required), (0, payment_data, required),
(1, phantom_shared_secret, option), (1, phantom_shared_secret, option),
(2, incoming_cltv_expiry, required), (2, incoming_cltv_expiry, required),
(3, payment_metadata, option),
}, },
(2, ReceiveKeysend) => { (2, ReceiveKeysend) => {
(0, payment_preimage, required), (0, payment_preimage, required),
(2, incoming_cltv_expiry, required), (2, incoming_cltv_expiry, required),
(3, payment_metadata, option),
}, },
;); ;);
@ -7051,6 +7072,7 @@ where
let pending_outbound_payments = self.pending_outbound_payments.pending_outbound_payments.lock().unwrap(); let pending_outbound_payments = self.pending_outbound_payments.pending_outbound_payments.lock().unwrap();
let mut htlc_purposes: Vec<&events::PaymentPurpose> = Vec::new(); let mut htlc_purposes: Vec<&events::PaymentPurpose> = Vec::new();
let mut htlc_onion_fields: Vec<&_> = Vec::new();
(claimable_payments.claimable_payments.len() as u64).write(writer)?; (claimable_payments.claimable_payments.len() as u64).write(writer)?;
for (payment_hash, payment) in claimable_payments.claimable_payments.iter() { for (payment_hash, payment) in claimable_payments.claimable_payments.iter() {
payment_hash.write(writer)?; payment_hash.write(writer)?;
@ -7059,6 +7081,7 @@ where
htlc.write(writer)?; htlc.write(writer)?;
} }
htlc_purposes.push(&payment.purpose); htlc_purposes.push(&payment.purpose);
htlc_onion_fields.push(&payment.onion_fields);
} }
let mut monitor_update_blocked_actions_per_peer = None; let mut monitor_update_blocked_actions_per_peer = None;
@ -7173,6 +7196,7 @@ where
(7, self.fake_scid_rand_bytes, required), (7, self.fake_scid_rand_bytes, required),
(9, htlc_purposes, vec_type), (9, htlc_purposes, vec_type),
(11, self.probing_cookie_secret, required), (11, self.probing_cookie_secret, required),
(13, htlc_onion_fields, optional_vec),
}); });
Ok(()) Ok(())
@ -7551,6 +7575,7 @@ where
let mut fake_scid_rand_bytes: Option<[u8; 32]> = None; let mut fake_scid_rand_bytes: Option<[u8; 32]> = None;
let mut probing_cookie_secret: Option<[u8; 32]> = None; let mut probing_cookie_secret: Option<[u8; 32]> = None;
let mut claimable_htlc_purposes = None; let mut claimable_htlc_purposes = None;
let mut claimable_htlc_onion_fields = None;
let mut pending_claiming_payments = Some(HashMap::new()); let mut pending_claiming_payments = Some(HashMap::new());
let mut monitor_update_blocked_actions_per_peer = Some(Vec::new()); let mut monitor_update_blocked_actions_per_peer = Some(Vec::new());
read_tlv_fields!(reader, { read_tlv_fields!(reader, {
@ -7563,6 +7588,7 @@ where
(7, fake_scid_rand_bytes, option), (7, fake_scid_rand_bytes, option),
(9, claimable_htlc_purposes, vec_type), (9, claimable_htlc_purposes, vec_type),
(11, probing_cookie_secret, option), (11, probing_cookie_secret, option),
(13, claimable_htlc_onion_fields, optional_vec),
}); });
if fake_scid_rand_bytes.is_none() { if fake_scid_rand_bytes.is_none() {
fake_scid_rand_bytes = Some(args.entropy_source.get_secure_random_bytes()); fake_scid_rand_bytes = Some(args.entropy_source.get_secure_random_bytes());
@ -7712,15 +7738,29 @@ where
let expanded_inbound_key = inbound_payment::ExpandedKey::new(&inbound_pmt_key_material); let expanded_inbound_key = inbound_payment::ExpandedKey::new(&inbound_pmt_key_material);
let mut claimable_payments = HashMap::with_capacity(claimable_htlcs_list.len()); let mut claimable_payments = HashMap::with_capacity(claimable_htlcs_list.len());
if let Some(mut purposes) = claimable_htlc_purposes { if let Some(purposes) = claimable_htlc_purposes {
if purposes.len() != claimable_htlcs_list.len() { if purposes.len() != claimable_htlcs_list.len() {
return Err(DecodeError::InvalidValue); return Err(DecodeError::InvalidValue);
} }
for (purpose, (payment_hash, htlcs)) in purposes.drain(..).zip(claimable_htlcs_list.drain(..)) { if let Some(onion_fields) = claimable_htlc_onion_fields {
let existing_payment = claimable_payments.insert(payment_hash, ClaimablePayment { if onion_fields.len() != claimable_htlcs_list.len() {
purpose, htlcs, return Err(DecodeError::InvalidValue);
}); }
if existing_payment.is_some() { return Err(DecodeError::InvalidValue); } for (purpose, (onion, (payment_hash, htlcs))) in
purposes.into_iter().zip(onion_fields.into_iter().zip(claimable_htlcs_list.into_iter()))
{
let existing_payment = claimable_payments.insert(payment_hash, ClaimablePayment {
purpose, htlcs, onion_fields: onion,
});
if existing_payment.is_some() { return Err(DecodeError::InvalidValue); }
}
} else {
for (purpose, (payment_hash, htlcs)) in purposes.into_iter().zip(claimable_htlcs_list.into_iter()) {
let existing_payment = claimable_payments.insert(payment_hash, ClaimablePayment {
purpose, htlcs, onion_fields: None,
});
if existing_payment.is_some() { return Err(DecodeError::InvalidValue); }
}
} }
} else { } else {
// LDK versions prior to 0.0.107 did not write a `pending_htlc_purposes`, but do // LDK versions prior to 0.0.107 did not write a `pending_htlc_purposes`, but do
@ -7751,7 +7791,7 @@ where
events::PaymentPurpose::SpontaneousPayment(*payment_preimage), events::PaymentPurpose::SpontaneousPayment(*payment_preimage),
}; };
claimable_payments.insert(payment_hash, ClaimablePayment { claimable_payments.insert(payment_hash, ClaimablePayment {
purpose, htlcs, purpose, htlcs, onion_fields: None,
}); });
} }
} }

View file

@ -438,6 +438,11 @@ pub struct RecipientOnionFields {
pub payment_metadata: Option<Vec<u8>>, pub payment_metadata: Option<Vec<u8>>,
} }
impl_writeable_tlv_based!(RecipientOnionFields, {
(0, payment_secret, option),
(2, payment_metadata, option),
});
impl RecipientOnionFields { impl RecipientOnionFields {
/// Creates a [`RecipientOnionFields`] from only a [`PaymentSecret`]. This is the most common /// Creates a [`RecipientOnionFields`] from only a [`PaymentSecret`]. This is the most common
/// set of onion fields for today's BOLT11 invoices - most nodes require a [`PaymentSecret`] /// set of onion fields for today's BOLT11 invoices - most nodes require a [`PaymentSecret`]
@ -454,6 +459,20 @@ impl RecipientOnionFields {
pub fn spontaneous_empty() -> Self { pub fn spontaneous_empty() -> Self {
Self { payment_secret: None, payment_metadata: None } Self { payment_secret: None, payment_metadata: None }
} }
/// When we have received some HTLC(s) towards an MPP payment, as we receive further HTLC(s) we
/// have to make sure that some fields match exactly across the parts. For those that aren't
/// required to match, if they don't match we should remove them so as to not expose data
/// that's dependent on the HTLC receive order to users.
///
/// Here we implement this, first checking compatibility then mutating two objects and then
/// dropping any remaining non-matching fields from both.
pub(super) fn check_merge(&mut self, further_htlc_fields: &mut Self) -> Result<(), ()> {
if self.payment_secret != further_htlc_fields.payment_secret { return Err(()); }
if self.payment_metadata != further_htlc_fields.payment_metadata { return Err(()); }
// For custom TLVs we should just drop non-matching ones, but not reject the payment.
Ok(())
}
} }
pub(super) struct OutboundPayments { pub(super) struct OutboundPayments {