Skip to content

Commit 9e4a35a

Browse files
authored
Merge pull request #2308 from alecchendev/2023-05-custom-htlc-tlvs
Add support for custom HTLC TLVs
2 parents 7a63ab7 + dec3fb3 commit 9e4a35a

File tree

11 files changed

+690
-38
lines changed

11 files changed

+690
-38
lines changed

lightning-invoice/src/payment.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,8 @@ fn pay_invoice_using_amount<P: Deref>(
146146
payer: P
147147
) -> Result<(), PaymentError> where P::Target: Payer {
148148
let payment_hash = PaymentHash((*invoice.payment_hash()).into_inner());
149-
let recipient_onion = RecipientOnionFields {
150-
payment_secret: Some(*invoice.payment_secret()),
151-
payment_metadata: invoice.payment_metadata().map(|v| v.clone()),
152-
};
149+
let mut recipient_onion = RecipientOnionFields::secret_only(*invoice.payment_secret());
150+
recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone());
153151
let mut payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key(),
154152
invoice.min_final_cltv_expiry_delta() as u32)
155153
.with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs())

lightning/src/events/mod.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -356,9 +356,19 @@ pub enum Event {
356356
/// Note that if the preimage is not known, you should call
357357
/// [`ChannelManager::fail_htlc_backwards`] or [`ChannelManager::fail_htlc_backwards_with_reason`]
358358
/// to free up resources for this HTLC and avoid network congestion.
359-
/// If you fail to call either [`ChannelManager::claim_funds`], [`ChannelManager::fail_htlc_backwards`],
360-
/// or [`ChannelManager::fail_htlc_backwards_with_reason`] within the HTLC's timeout, the HTLC will be
361-
/// automatically failed.
359+
///
360+
/// If [`Event::PaymentClaimable::onion_fields`] is `Some`, and includes custom TLVs with even type
361+
/// numbers, you should use [`ChannelManager::fail_htlc_backwards_with_reason`] with
362+
/// [`FailureCode::InvalidOnionPayload`] if you fail to understand and handle the contents, or
363+
/// [`ChannelManager::claim_funds_with_known_custom_tlvs`] upon successful handling.
364+
/// If you don't intend to check for custom TLVs, you can simply use
365+
/// [`ChannelManager::claim_funds`], which will automatically fail back even custom TLVs.
366+
///
367+
/// If you fail to call [`ChannelManager::claim_funds`],
368+
/// [`ChannelManager::claim_funds_with_known_custom_tlvs`],
369+
/// [`ChannelManager::fail_htlc_backwards`], or
370+
/// [`ChannelManager::fail_htlc_backwards_with_reason`] within the HTLC's timeout, the HTLC will
371+
/// be automatically failed.
362372
///
363373
/// # Note
364374
/// LDK will not stop an inbound payment from being paid multiple times, so multiple
@@ -370,6 +380,8 @@ pub enum Event {
370380
/// This event used to be called `PaymentReceived` in LDK versions 0.0.112 and earlier.
371381
///
372382
/// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds
383+
/// [`ChannelManager::claim_funds_with_known_custom_tlvs`]: crate::ln::channelmanager::ChannelManager::claim_funds_with_known_custom_tlvs
384+
/// [`FailureCode::InvalidOnionPayload`]: crate::ln::channelmanager::FailureCode::InvalidOnionPayload
373385
/// [`ChannelManager::fail_htlc_backwards`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards
374386
/// [`ChannelManager::fail_htlc_backwards_with_reason`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards_with_reason
375387
PaymentClaimable {

lightning/src/ln/channelmanager.rs

+89-14
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,17 @@ pub(super) enum PendingHTLCRouting {
110110
payment_metadata: Option<Vec<u8>>,
111111
incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed
112112
phantom_shared_secret: Option<[u8; 32]>,
113+
/// See [`RecipientOnionFields::custom_tlvs`] for more info.
114+
custom_tlvs: Vec<(u64, Vec<u8>)>,
113115
},
114116
ReceiveKeysend {
115117
/// This was added in 0.0.116 and will break deserialization on downgrades.
116118
payment_data: Option<msgs::FinalOnionHopData>,
117119
payment_preimage: PaymentPreimage,
118120
payment_metadata: Option<Vec<u8>>,
119121
incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed
122+
/// See [`RecipientOnionFields::custom_tlvs`] for more info.
123+
custom_tlvs: Vec<(u64, Vec<u8>)>,
120124
},
121125
}
122126

@@ -355,15 +359,32 @@ struct InboundOnionErr {
355359
pub enum FailureCode {
356360
/// We had a temporary error processing the payment. Useful if no other error codes fit
357361
/// and you want to indicate that the payer may want to retry.
358-
TemporaryNodeFailure = 0x2000 | 2,
362+
TemporaryNodeFailure,
359363
/// We have a required feature which was not in this onion. For example, you may require
360364
/// some additional metadata that was not provided with this payment.
361-
RequiredNodeFeatureMissing = 0x4000 | 0x2000 | 3,
365+
RequiredNodeFeatureMissing,
362366
/// You may wish to use this when a `payment_preimage` is unknown, or the CLTV expiry of
363367
/// the HTLC is too close to the current block height for safe handling.
364368
/// Using this failure code in [`ChannelManager::fail_htlc_backwards_with_reason`] is
365369
/// equivalent to calling [`ChannelManager::fail_htlc_backwards`].
366-
IncorrectOrUnknownPaymentDetails = 0x4000 | 15,
370+
IncorrectOrUnknownPaymentDetails,
371+
/// We failed to process the payload after the onion was decrypted. You may wish to
372+
/// use this when receiving custom HTLC TLVs with even type numbers that you don't recognize.
373+
///
374+
/// If available, the tuple data may include the type number and byte offset in the
375+
/// decrypted byte stream where the failure occurred.
376+
InvalidOnionPayload(Option<(u64, u16)>),
377+
}
378+
379+
impl Into<u16> for FailureCode {
380+
fn into(self) -> u16 {
381+
match self {
382+
FailureCode::TemporaryNodeFailure => 0x2000 | 2,
383+
FailureCode::RequiredNodeFeatureMissing => 0x4000 | 0x2000 | 3,
384+
FailureCode::IncorrectOrUnknownPaymentDetails => 0x4000 | 15,
385+
FailureCode::InvalidOnionPayload(_) => 0x4000 | 22,
386+
}
387+
}
367388
}
368389

369390
/// Error type returned across the peer_state mutex boundary. When an Err is generated for a
@@ -2674,11 +2695,11 @@ where
26742695
amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool,
26752696
counterparty_skimmed_fee_msat: Option<u64>,
26762697
) -> Result<PendingHTLCInfo, InboundOnionErr> {
2677-
let (payment_data, keysend_preimage, onion_amt_msat, outgoing_cltv_value, payment_metadata) = match hop_data {
2698+
let (payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, outgoing_cltv_value, payment_metadata) = match hop_data {
26782699
msgs::InboundOnionPayload::Receive {
2679-
payment_data, keysend_preimage, amt_msat, outgoing_cltv_value, payment_metadata, ..
2700+
payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata, ..
26802701
} =>
2681-
(payment_data, keysend_preimage, amt_msat, outgoing_cltv_value, payment_metadata),
2702+
(payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata),
26822703
_ =>
26832704
return Err(InboundOnionErr {
26842705
err_code: 0x4000|22,
@@ -2748,13 +2769,15 @@ where
27482769
payment_preimage,
27492770
payment_metadata,
27502771
incoming_cltv_expiry: outgoing_cltv_value,
2772+
custom_tlvs,
27512773
}
27522774
} else if let Some(data) = payment_data {
27532775
PendingHTLCRouting::Receive {
27542776
payment_data: data,
27552777
payment_metadata,
27562778
incoming_cltv_expiry: outgoing_cltv_value,
27572779
phantom_shared_secret,
2780+
custom_tlvs,
27582781
}
27592782
} else {
27602783
return Err(InboundOnionErr {
@@ -3941,17 +3964,18 @@ where
39413964
}
39423965
}) => {
39433966
let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret, mut onion_fields) = match routing {
3944-
PendingHTLCRouting::Receive { payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret } => {
3967+
PendingHTLCRouting::Receive { payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret, custom_tlvs } => {
39453968
let _legacy_hop_data = Some(payment_data.clone());
3946-
let onion_fields =
3947-
RecipientOnionFields { payment_secret: Some(payment_data.payment_secret), payment_metadata };
3969+
let onion_fields = RecipientOnionFields { payment_secret: Some(payment_data.payment_secret),
3970+
payment_metadata, custom_tlvs };
39483971
(incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data },
39493972
Some(payment_data), phantom_shared_secret, onion_fields)
39503973
},
3951-
PendingHTLCRouting::ReceiveKeysend { payment_data, payment_preimage, payment_metadata, incoming_cltv_expiry } => {
3974+
PendingHTLCRouting::ReceiveKeysend { payment_data, payment_preimage, payment_metadata, incoming_cltv_expiry, custom_tlvs } => {
39523975
let onion_fields = RecipientOnionFields {
39533976
payment_secret: payment_data.as_ref().map(|data| data.payment_secret),
3954-
payment_metadata
3977+
payment_metadata,
3978+
custom_tlvs,
39553979
};
39563980
(incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage),
39573981
payment_data, None, onion_fields)
@@ -4576,12 +4600,19 @@ where
45764600
/// Gets error data to form an [`HTLCFailReason`] given a [`FailureCode`] and [`ClaimableHTLC`].
45774601
fn get_htlc_fail_reason_from_failure_code(&self, failure_code: FailureCode, htlc: &ClaimableHTLC) -> HTLCFailReason {
45784602
match failure_code {
4579-
FailureCode::TemporaryNodeFailure => HTLCFailReason::from_failure_code(failure_code as u16),
4580-
FailureCode::RequiredNodeFeatureMissing => HTLCFailReason::from_failure_code(failure_code as u16),
4603+
FailureCode::TemporaryNodeFailure => HTLCFailReason::from_failure_code(failure_code.into()),
4604+
FailureCode::RequiredNodeFeatureMissing => HTLCFailReason::from_failure_code(failure_code.into()),
45814605
FailureCode::IncorrectOrUnknownPaymentDetails => {
45824606
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
45834607
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
4584-
HTLCFailReason::reason(failure_code as u16, htlc_msat_height_data)
4608+
HTLCFailReason::reason(failure_code.into(), htlc_msat_height_data)
4609+
},
4610+
FailureCode::InvalidOnionPayload(data) => {
4611+
let fail_data = match data {
4612+
Some((typ, offset)) => [BigSize(typ).encode(), offset.encode()].concat(),
4613+
None => Vec::new(),
4614+
};
4615+
HTLCFailReason::reason(failure_code.into(), fail_data)
45854616
}
45864617
}
45874618
}
@@ -4728,13 +4759,35 @@ where
47284759
/// event matches your expectation. If you fail to do so and call this method, you may provide
47294760
/// the sender "proof-of-payment" when they did not fulfill the full expected payment.
47304761
///
4762+
/// This function will fail the payment if it has custom TLVs with even type numbers, as we
4763+
/// will assume they are unknown. If you intend to accept even custom TLVs, you should use
4764+
/// [`claim_funds_with_known_custom_tlvs`].
4765+
///
47314766
/// [`Event::PaymentClaimable`]: crate::events::Event::PaymentClaimable
47324767
/// [`Event::PaymentClaimable::claim_deadline`]: crate::events::Event::PaymentClaimable::claim_deadline
47334768
/// [`Event::PaymentClaimed`]: crate::events::Event::PaymentClaimed
47344769
/// [`process_pending_events`]: EventsProvider::process_pending_events
47354770
/// [`create_inbound_payment`]: Self::create_inbound_payment
47364771
/// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash
4772+
/// [`claim_funds_with_known_custom_tlvs`]: Self::claim_funds_with_known_custom_tlvs
47374773
pub fn claim_funds(&self, payment_preimage: PaymentPreimage) {
4774+
self.claim_payment_internal(payment_preimage, false);
4775+
}
4776+
4777+
/// This is a variant of [`claim_funds`] that allows accepting a payment with custom TLVs with
4778+
/// even type numbers.
4779+
///
4780+
/// # Note
4781+
///
4782+
/// You MUST check you've understood all even TLVs before using this to
4783+
/// claim, otherwise you may unintentionally agree to some protocol you do not understand.
4784+
///
4785+
/// [`claim_funds`]: Self::claim_funds
4786+
pub fn claim_funds_with_known_custom_tlvs(&self, payment_preimage: PaymentPreimage) {
4787+
self.claim_payment_internal(payment_preimage, true);
4788+
}
4789+
4790+
fn claim_payment_internal(&self, payment_preimage: PaymentPreimage, custom_tlvs_known: bool) {
47384791
let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
47394792

47404793
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
@@ -4761,6 +4814,23 @@ where
47614814
log_error!(self.logger, "Got a duplicate pending claimable event on payment hash {}! Please report this bug",
47624815
log_bytes!(payment_hash.0));
47634816
}
4817+
4818+
if let Some(RecipientOnionFields { ref custom_tlvs, .. }) = payment.onion_fields {
4819+
if !custom_tlvs_known && custom_tlvs.iter().any(|(typ, _)| typ % 2 == 0) {
4820+
log_info!(self.logger, "Rejecting payment with payment hash {} as we cannot accept payment with unknown even TLVs: {}",
4821+
log_bytes!(payment_hash.0), log_iter!(custom_tlvs.iter().map(|(typ, _)| typ).filter(|typ| *typ % 2 == 0)));
4822+
claimable_payments.pending_claiming_payments.remove(&payment_hash);
4823+
mem::drop(claimable_payments);
4824+
for htlc in payment.htlcs {
4825+
let reason = self.get_htlc_fail_reason_from_failure_code(FailureCode::InvalidOnionPayload(None), &htlc);
4826+
let source = HTLCSource::PreviousHopData(htlc.prev_hop);
4827+
let receiver = HTLCDestination::FailedPayment { payment_hash };
4828+
self.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver);
4829+
}
4830+
return;
4831+
}
4832+
}
4833+
47644834
payment.htlcs
47654835
} else { return; }
47664836
};
@@ -7638,12 +7708,14 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
76387708
(1, phantom_shared_secret, option),
76397709
(2, incoming_cltv_expiry, required),
76407710
(3, payment_metadata, option),
7711+
(5, custom_tlvs, optional_vec),
76417712
},
76427713
(2, ReceiveKeysend) => {
76437714
(0, payment_preimage, required),
76447715
(2, incoming_cltv_expiry, required),
76457716
(3, payment_metadata, option),
76467717
(4, payment_data, option), // Added in 0.0.116
7718+
(5, custom_tlvs, optional_vec),
76477719
},
76487720
;);
76497721

@@ -8750,6 +8822,7 @@ where
87508822
payment_secret: None, // only used for retries, and we'll never retry on startup
87518823
payment_metadata: None, // only used for retries, and we'll never retry on startup
87528824
keysend_preimage: None, // only used for retries, and we'll never retry on startup
8825+
custom_tlvs: Vec::new(), // only used for retries, and we'll never retry on startup
87538826
pending_amt_msat: path_amt,
87548827
pending_fee_msat: Some(path_fee),
87558828
total_msat: path_amt,
@@ -10092,6 +10165,7 @@ mod tests {
1009210165
payment_data: Some(msgs::FinalOnionHopData {
1009310166
payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
1009410167
}),
10168+
custom_tlvs: Vec::new(),
1009510169
};
1009610170
// Check that if the amount we received + the penultimate hop extra fee is less than the sender
1009710171
// intended amount, we fail the payment.
@@ -10111,6 +10185,7 @@ mod tests {
1011110185
payment_data: Some(msgs::FinalOnionHopData {
1011210186
payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
1011310187
}),
10188+
custom_tlvs: Vec::new(),
1011410189
};
1011510190
assert!(node[0].node.construct_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]),
1011610191
sender_intended_amt_msat - extra_fee_msat, 42, None, true, Some(extra_fee_msat)).is_ok());

lightning/src/ln/functional_test_utils.rs

+3
Original file line numberDiff line numberDiff line change
@@ -2250,7 +2250,10 @@ pub fn do_claim_payment_along_route_with_extra_penultimate_hop_fees<'a, 'b, 'c>(
22502250
assert_eq!(path.last().unwrap().node.get_our_node_id(), expected_paths[0].last().unwrap().node.get_our_node_id());
22512251
}
22522252
expected_paths[0].last().unwrap().node.claim_funds(our_payment_preimage);
2253+
pass_claimed_payment_along_route(origin_node, expected_paths, expected_extra_fees, skip_last, our_payment_preimage)
2254+
}
22532255

2256+
pub fn pass_claimed_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_paths: &[&[&Node<'a, 'b, 'c>]], expected_extra_fees: &[u32], skip_last: bool, our_payment_preimage: PaymentPreimage) -> u64 {
22542257
let claim_event = expected_paths[0].last().unwrap().node.get_and_clear_pending_events();
22552258
assert_eq!(claim_event.len(), 1);
22562259
match claim_event[0] {

0 commit comments

Comments
 (0)