mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-02-25 15:20:24 +01:00
Merge pull request #2308 from alecchendev/2023-05-custom-htlc-tlvs
Add support for custom HTLC TLVs
This commit is contained in:
commit
9e4a35a60d
11 changed files with 690 additions and 38 deletions
|
@ -146,10 +146,8 @@ fn pay_invoice_using_amount<P: Deref>(
|
|||
payer: P
|
||||
) -> Result<(), PaymentError> where P::Target: Payer {
|
||||
let payment_hash = PaymentHash((*invoice.payment_hash()).into_inner());
|
||||
let recipient_onion = RecipientOnionFields {
|
||||
payment_secret: Some(*invoice.payment_secret()),
|
||||
payment_metadata: invoice.payment_metadata().map(|v| v.clone()),
|
||||
};
|
||||
let mut recipient_onion = RecipientOnionFields::secret_only(*invoice.payment_secret());
|
||||
recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone());
|
||||
let mut payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key(),
|
||||
invoice.min_final_cltv_expiry_delta() as u32)
|
||||
.with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs())
|
||||
|
|
|
@ -356,9 +356,19 @@ pub enum Event {
|
|||
/// Note that if the preimage is not known, you should call
|
||||
/// [`ChannelManager::fail_htlc_backwards`] or [`ChannelManager::fail_htlc_backwards_with_reason`]
|
||||
/// to free up resources for this HTLC and avoid network congestion.
|
||||
/// If you fail to call either [`ChannelManager::claim_funds`], [`ChannelManager::fail_htlc_backwards`],
|
||||
/// or [`ChannelManager::fail_htlc_backwards_with_reason`] within the HTLC's timeout, the HTLC will be
|
||||
/// automatically failed.
|
||||
///
|
||||
/// If [`Event::PaymentClaimable::onion_fields`] is `Some`, and includes custom TLVs with even type
|
||||
/// numbers, you should use [`ChannelManager::fail_htlc_backwards_with_reason`] with
|
||||
/// [`FailureCode::InvalidOnionPayload`] if you fail to understand and handle the contents, or
|
||||
/// [`ChannelManager::claim_funds_with_known_custom_tlvs`] upon successful handling.
|
||||
/// If you don't intend to check for custom TLVs, you can simply use
|
||||
/// [`ChannelManager::claim_funds`], which will automatically fail back even custom TLVs.
|
||||
///
|
||||
/// If you fail to call [`ChannelManager::claim_funds`],
|
||||
/// [`ChannelManager::claim_funds_with_known_custom_tlvs`],
|
||||
/// [`ChannelManager::fail_htlc_backwards`], or
|
||||
/// [`ChannelManager::fail_htlc_backwards_with_reason`] within the HTLC's timeout, the HTLC will
|
||||
/// be automatically failed.
|
||||
///
|
||||
/// # Note
|
||||
/// LDK will not stop an inbound payment from being paid multiple times, so multiple
|
||||
|
@ -370,6 +380,8 @@ pub enum Event {
|
|||
/// This event used to be called `PaymentReceived` in LDK versions 0.0.112 and earlier.
|
||||
///
|
||||
/// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds
|
||||
/// [`ChannelManager::claim_funds_with_known_custom_tlvs`]: crate::ln::channelmanager::ChannelManager::claim_funds_with_known_custom_tlvs
|
||||
/// [`FailureCode::InvalidOnionPayload`]: crate::ln::channelmanager::FailureCode::InvalidOnionPayload
|
||||
/// [`ChannelManager::fail_htlc_backwards`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards
|
||||
/// [`ChannelManager::fail_htlc_backwards_with_reason`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards_with_reason
|
||||
PaymentClaimable {
|
||||
|
|
|
@ -110,6 +110,8 @@ pub(super) enum PendingHTLCRouting {
|
|||
payment_metadata: Option<Vec<u8>>,
|
||||
incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed
|
||||
phantom_shared_secret: Option<[u8; 32]>,
|
||||
/// See [`RecipientOnionFields::custom_tlvs`] for more info.
|
||||
custom_tlvs: Vec<(u64, Vec<u8>)>,
|
||||
},
|
||||
ReceiveKeysend {
|
||||
/// This was added in 0.0.116 and will break deserialization on downgrades.
|
||||
|
@ -117,6 +119,8 @@ pub(super) enum PendingHTLCRouting {
|
|||
payment_preimage: PaymentPreimage,
|
||||
payment_metadata: Option<Vec<u8>>,
|
||||
incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed
|
||||
/// See [`RecipientOnionFields::custom_tlvs`] for more info.
|
||||
custom_tlvs: Vec<(u64, Vec<u8>)>,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -355,15 +359,32 @@ struct InboundOnionErr {
|
|||
pub enum FailureCode {
|
||||
/// We had a temporary error processing the payment. Useful if no other error codes fit
|
||||
/// and you want to indicate that the payer may want to retry.
|
||||
TemporaryNodeFailure = 0x2000 | 2,
|
||||
TemporaryNodeFailure,
|
||||
/// We have a required feature which was not in this onion. For example, you may require
|
||||
/// some additional metadata that was not provided with this payment.
|
||||
RequiredNodeFeatureMissing = 0x4000 | 0x2000 | 3,
|
||||
RequiredNodeFeatureMissing,
|
||||
/// You may wish to use this when a `payment_preimage` is unknown, or the CLTV expiry of
|
||||
/// the HTLC is too close to the current block height for safe handling.
|
||||
/// Using this failure code in [`ChannelManager::fail_htlc_backwards_with_reason`] is
|
||||
/// equivalent to calling [`ChannelManager::fail_htlc_backwards`].
|
||||
IncorrectOrUnknownPaymentDetails = 0x4000 | 15,
|
||||
IncorrectOrUnknownPaymentDetails,
|
||||
/// We failed to process the payload after the onion was decrypted. You may wish to
|
||||
/// use this when receiving custom HTLC TLVs with even type numbers that you don't recognize.
|
||||
///
|
||||
/// If available, the tuple data may include the type number and byte offset in the
|
||||
/// decrypted byte stream where the failure occurred.
|
||||
InvalidOnionPayload(Option<(u64, u16)>),
|
||||
}
|
||||
|
||||
impl Into<u16> for FailureCode {
|
||||
fn into(self) -> u16 {
|
||||
match self {
|
||||
FailureCode::TemporaryNodeFailure => 0x2000 | 2,
|
||||
FailureCode::RequiredNodeFeatureMissing => 0x4000 | 0x2000 | 3,
|
||||
FailureCode::IncorrectOrUnknownPaymentDetails => 0x4000 | 15,
|
||||
FailureCode::InvalidOnionPayload(_) => 0x4000 | 22,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error type returned across the peer_state mutex boundary. When an Err is generated for a
|
||||
|
@ -2674,11 +2695,11 @@ where
|
|||
amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool,
|
||||
counterparty_skimmed_fee_msat: Option<u64>,
|
||||
) -> Result<PendingHTLCInfo, InboundOnionErr> {
|
||||
let (payment_data, keysend_preimage, onion_amt_msat, outgoing_cltv_value, payment_metadata) = match hop_data {
|
||||
let (payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, outgoing_cltv_value, payment_metadata) = match hop_data {
|
||||
msgs::InboundOnionPayload::Receive {
|
||||
payment_data, keysend_preimage, amt_msat, outgoing_cltv_value, payment_metadata, ..
|
||||
payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata, ..
|
||||
} =>
|
||||
(payment_data, keysend_preimage, amt_msat, outgoing_cltv_value, payment_metadata),
|
||||
(payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata),
|
||||
_ =>
|
||||
return Err(InboundOnionErr {
|
||||
err_code: 0x4000|22,
|
||||
|
@ -2748,6 +2769,7 @@ where
|
|||
payment_preimage,
|
||||
payment_metadata,
|
||||
incoming_cltv_expiry: outgoing_cltv_value,
|
||||
custom_tlvs,
|
||||
}
|
||||
} else if let Some(data) = payment_data {
|
||||
PendingHTLCRouting::Receive {
|
||||
|
@ -2755,6 +2777,7 @@ where
|
|||
payment_metadata,
|
||||
incoming_cltv_expiry: outgoing_cltv_value,
|
||||
phantom_shared_secret,
|
||||
custom_tlvs,
|
||||
}
|
||||
} else {
|
||||
return Err(InboundOnionErr {
|
||||
|
@ -3941,17 +3964,18 @@ where
|
|||
}
|
||||
}) => {
|
||||
let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret, mut onion_fields) = match routing {
|
||||
PendingHTLCRouting::Receive { payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret } => {
|
||||
PendingHTLCRouting::Receive { payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret, custom_tlvs } => {
|
||||
let _legacy_hop_data = Some(payment_data.clone());
|
||||
let onion_fields =
|
||||
RecipientOnionFields { payment_secret: Some(payment_data.payment_secret), payment_metadata };
|
||||
let onion_fields = RecipientOnionFields { payment_secret: Some(payment_data.payment_secret),
|
||||
payment_metadata, custom_tlvs };
|
||||
(incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data },
|
||||
Some(payment_data), phantom_shared_secret, onion_fields)
|
||||
},
|
||||
PendingHTLCRouting::ReceiveKeysend { payment_data, payment_preimage, payment_metadata, incoming_cltv_expiry } => {
|
||||
PendingHTLCRouting::ReceiveKeysend { payment_data, payment_preimage, payment_metadata, incoming_cltv_expiry, custom_tlvs } => {
|
||||
let onion_fields = RecipientOnionFields {
|
||||
payment_secret: payment_data.as_ref().map(|data| data.payment_secret),
|
||||
payment_metadata
|
||||
payment_metadata,
|
||||
custom_tlvs,
|
||||
};
|
||||
(incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage),
|
||||
payment_data, None, onion_fields)
|
||||
|
@ -4576,12 +4600,19 @@ where
|
|||
/// Gets error data to form an [`HTLCFailReason`] given a [`FailureCode`] and [`ClaimableHTLC`].
|
||||
fn get_htlc_fail_reason_from_failure_code(&self, failure_code: FailureCode, htlc: &ClaimableHTLC) -> HTLCFailReason {
|
||||
match failure_code {
|
||||
FailureCode::TemporaryNodeFailure => HTLCFailReason::from_failure_code(failure_code as u16),
|
||||
FailureCode::RequiredNodeFeatureMissing => HTLCFailReason::from_failure_code(failure_code as u16),
|
||||
FailureCode::TemporaryNodeFailure => HTLCFailReason::from_failure_code(failure_code.into()),
|
||||
FailureCode::RequiredNodeFeatureMissing => HTLCFailReason::from_failure_code(failure_code.into()),
|
||||
FailureCode::IncorrectOrUnknownPaymentDetails => {
|
||||
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
|
||||
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
|
||||
HTLCFailReason::reason(failure_code as u16, htlc_msat_height_data)
|
||||
HTLCFailReason::reason(failure_code.into(), htlc_msat_height_data)
|
||||
},
|
||||
FailureCode::InvalidOnionPayload(data) => {
|
||||
let fail_data = match data {
|
||||
Some((typ, offset)) => [BigSize(typ).encode(), offset.encode()].concat(),
|
||||
None => Vec::new(),
|
||||
};
|
||||
HTLCFailReason::reason(failure_code.into(), fail_data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4728,13 +4759,35 @@ where
|
|||
/// event matches your expectation. If you fail to do so and call this method, you may provide
|
||||
/// the sender "proof-of-payment" when they did not fulfill the full expected payment.
|
||||
///
|
||||
/// This function will fail the payment if it has custom TLVs with even type numbers, as we
|
||||
/// will assume they are unknown. If you intend to accept even custom TLVs, you should use
|
||||
/// [`claim_funds_with_known_custom_tlvs`].
|
||||
///
|
||||
/// [`Event::PaymentClaimable`]: crate::events::Event::PaymentClaimable
|
||||
/// [`Event::PaymentClaimable::claim_deadline`]: crate::events::Event::PaymentClaimable::claim_deadline
|
||||
/// [`Event::PaymentClaimed`]: crate::events::Event::PaymentClaimed
|
||||
/// [`process_pending_events`]: EventsProvider::process_pending_events
|
||||
/// [`create_inbound_payment`]: Self::create_inbound_payment
|
||||
/// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash
|
||||
/// [`claim_funds_with_known_custom_tlvs`]: Self::claim_funds_with_known_custom_tlvs
|
||||
pub fn claim_funds(&self, payment_preimage: PaymentPreimage) {
|
||||
self.claim_payment_internal(payment_preimage, false);
|
||||
}
|
||||
|
||||
/// This is a variant of [`claim_funds`] that allows accepting a payment with custom TLVs with
|
||||
/// even type numbers.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// You MUST check you've understood all even TLVs before using this to
|
||||
/// claim, otherwise you may unintentionally agree to some protocol you do not understand.
|
||||
///
|
||||
/// [`claim_funds`]: Self::claim_funds
|
||||
pub fn claim_funds_with_known_custom_tlvs(&self, payment_preimage: PaymentPreimage) {
|
||||
self.claim_payment_internal(payment_preimage, true);
|
||||
}
|
||||
|
||||
fn claim_payment_internal(&self, payment_preimage: PaymentPreimage, custom_tlvs_known: bool) {
|
||||
let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
|
||||
|
||||
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
|
||||
|
@ -4761,6 +4814,23 @@ where
|
|||
log_error!(self.logger, "Got a duplicate pending claimable event on payment hash {}! Please report this bug",
|
||||
log_bytes!(payment_hash.0));
|
||||
}
|
||||
|
||||
if let Some(RecipientOnionFields { ref custom_tlvs, .. }) = payment.onion_fields {
|
||||
if !custom_tlvs_known && custom_tlvs.iter().any(|(typ, _)| typ % 2 == 0) {
|
||||
log_info!(self.logger, "Rejecting payment with payment hash {} as we cannot accept payment with unknown even TLVs: {}",
|
||||
log_bytes!(payment_hash.0), log_iter!(custom_tlvs.iter().map(|(typ, _)| typ).filter(|typ| *typ % 2 == 0)));
|
||||
claimable_payments.pending_claiming_payments.remove(&payment_hash);
|
||||
mem::drop(claimable_payments);
|
||||
for htlc in payment.htlcs {
|
||||
let reason = self.get_htlc_fail_reason_from_failure_code(FailureCode::InvalidOnionPayload(None), &htlc);
|
||||
let source = HTLCSource::PreviousHopData(htlc.prev_hop);
|
||||
let receiver = HTLCDestination::FailedPayment { payment_hash };
|
||||
self.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
payment.htlcs
|
||||
} else { return; }
|
||||
};
|
||||
|
@ -7638,12 +7708,14 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
|
|||
(1, phantom_shared_secret, option),
|
||||
(2, incoming_cltv_expiry, required),
|
||||
(3, payment_metadata, option),
|
||||
(5, custom_tlvs, optional_vec),
|
||||
},
|
||||
(2, ReceiveKeysend) => {
|
||||
(0, payment_preimage, required),
|
||||
(2, incoming_cltv_expiry, required),
|
||||
(3, payment_metadata, option),
|
||||
(4, payment_data, option), // Added in 0.0.116
|
||||
(5, custom_tlvs, optional_vec),
|
||||
},
|
||||
;);
|
||||
|
||||
|
@ -8750,6 +8822,7 @@ where
|
|||
payment_secret: None, // only used for retries, and we'll never retry on startup
|
||||
payment_metadata: None, // only used for retries, and we'll never retry on startup
|
||||
keysend_preimage: None, // only used for retries, and we'll never retry on startup
|
||||
custom_tlvs: Vec::new(), // only used for retries, and we'll never retry on startup
|
||||
pending_amt_msat: path_amt,
|
||||
pending_fee_msat: Some(path_fee),
|
||||
total_msat: path_amt,
|
||||
|
@ -10092,6 +10165,7 @@ mod tests {
|
|||
payment_data: Some(msgs::FinalOnionHopData {
|
||||
payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
|
||||
}),
|
||||
custom_tlvs: Vec::new(),
|
||||
};
|
||||
// Check that if the amount we received + the penultimate hop extra fee is less than the sender
|
||||
// intended amount, we fail the payment.
|
||||
|
@ -10111,6 +10185,7 @@ mod tests {
|
|||
payment_data: Some(msgs::FinalOnionHopData {
|
||||
payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
|
||||
}),
|
||||
custom_tlvs: Vec::new(),
|
||||
};
|
||||
assert!(node[0].node.construct_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]),
|
||||
sender_intended_amt_msat - extra_fee_msat, 42, None, true, Some(extra_fee_msat)).is_ok());
|
||||
|
|
|
@ -2250,7 +2250,10 @@ pub fn do_claim_payment_along_route_with_extra_penultimate_hop_fees<'a, 'b, 'c>(
|
|||
assert_eq!(path.last().unwrap().node.get_our_node_id(), expected_paths[0].last().unwrap().node.get_our_node_id());
|
||||
}
|
||||
expected_paths[0].last().unwrap().node.claim_funds(our_payment_preimage);
|
||||
pass_claimed_payment_along_route(origin_node, expected_paths, expected_extra_fees, skip_last, our_payment_preimage)
|
||||
}
|
||||
|
||||
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 {
|
||||
let claim_event = expected_paths[0].last().unwrap().node.get_and_clear_pending_events();
|
||||
assert_eq!(claim_event.len(), 1);
|
||||
match claim_event[0] {
|
||||
|
|
|
@ -43,7 +43,7 @@ use crate::io_extras::read_to_end;
|
|||
|
||||
use crate::events::{MessageSendEventsProvider, OnionMessageProvider};
|
||||
use crate::util::logger;
|
||||
use crate::util::ser::{LengthReadable, Readable, ReadableArgs, Writeable, Writer, WithoutLength, FixedLengthReader, HighZeroBytesDroppedBigSize, Hostname, TransactionU16LenLimited};
|
||||
use crate::util::ser::{LengthReadable, Readable, ReadableArgs, Writeable, Writer, WithoutLength, FixedLengthReader, HighZeroBytesDroppedBigSize, Hostname, TransactionU16LenLimited, BigSize};
|
||||
|
||||
use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
|
||||
|
||||
|
@ -1441,6 +1441,7 @@ mod fuzzy_internal_msgs {
|
|||
payment_data: Option<FinalOnionHopData>,
|
||||
payment_metadata: Option<Vec<u8>>,
|
||||
keysend_preimage: Option<PaymentPreimage>,
|
||||
custom_tlvs: Vec<(u64, Vec<u8>)>,
|
||||
amt_msat: u64,
|
||||
outgoing_cltv_value: u32,
|
||||
},
|
||||
|
@ -1457,6 +1458,7 @@ mod fuzzy_internal_msgs {
|
|||
payment_data: Option<FinalOnionHopData>,
|
||||
payment_metadata: Option<Vec<u8>>,
|
||||
keysend_preimage: Option<PaymentPreimage>,
|
||||
custom_tlvs: Vec<(u64, Vec<u8>)>,
|
||||
amt_msat: u64,
|
||||
outgoing_cltv_value: u32,
|
||||
},
|
||||
|
@ -1979,15 +1981,23 @@ impl Writeable for OutboundOnionPayload {
|
|||
});
|
||||
},
|
||||
Self::Receive {
|
||||
ref payment_data, ref payment_metadata, ref keysend_preimage, amt_msat, outgoing_cltv_value
|
||||
ref payment_data, ref payment_metadata, ref keysend_preimage, amt_msat,
|
||||
outgoing_cltv_value, ref custom_tlvs,
|
||||
} => {
|
||||
// We need to update [`ln::outbound_payment::RecipientOnionFields::with_custom_tlvs`]
|
||||
// to reject any reserved types in the experimental range if new ones are ever
|
||||
// standardized.
|
||||
let preimage = if let Some(ref preimage) = keysend_preimage {
|
||||
Some((5482373484, preimage.encode()))
|
||||
} else { None };
|
||||
let mut custom_tlvs: Vec<&(u64, Vec<u8>)> = custom_tlvs.iter().chain(preimage.iter()).collect();
|
||||
custom_tlvs.sort_unstable_by_key(|(typ, _)| *typ);
|
||||
_encode_varint_length_prefixed_tlv!(w, {
|
||||
(2, HighZeroBytesDroppedBigSize(*amt_msat), required),
|
||||
(4, HighZeroBytesDroppedBigSize(*outgoing_cltv_value), required),
|
||||
(8, payment_data, option),
|
||||
(16, payment_metadata.as_ref().map(|m| WithoutLength(m)), option),
|
||||
(5482373484, keysend_preimage, option)
|
||||
});
|
||||
(16, payment_metadata.as_ref().map(|m| WithoutLength(m)), option)
|
||||
}, custom_tlvs.iter());
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
|
@ -2002,7 +2012,11 @@ impl Readable for InboundOnionPayload {
|
|||
let mut payment_data: Option<FinalOnionHopData> = None;
|
||||
let mut payment_metadata: Option<WithoutLength<Vec<u8>>> = None;
|
||||
let mut keysend_preimage: Option<PaymentPreimage> = None;
|
||||
read_tlv_fields!(r, {
|
||||
let mut custom_tlvs = Vec::new();
|
||||
|
||||
let tlv_len = BigSize::read(r)?;
|
||||
let rd = FixedLengthReader::new(r, tlv_len.0);
|
||||
decode_tlv_stream_with_custom_tlv_decode!(rd, {
|
||||
(2, amt, required),
|
||||
(4, cltv_value, required),
|
||||
(6, short_id, option),
|
||||
|
@ -2010,6 +2024,12 @@ impl Readable for InboundOnionPayload {
|
|||
(16, payment_metadata, option),
|
||||
// See https://github.com/lightning/blips/blob/master/blip-0003.md
|
||||
(5482373484, keysend_preimage, option)
|
||||
}, |msg_type: u64, msg_reader: &mut FixedLengthReader<_>| -> Result<bool, DecodeError> {
|
||||
if msg_type < 1 << 16 { return Ok(false) }
|
||||
let mut value = Vec::new();
|
||||
msg_reader.read_to_end(&mut value)?;
|
||||
custom_tlvs.push((msg_type, value));
|
||||
Ok(true)
|
||||
});
|
||||
|
||||
if amt.0 > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) }
|
||||
|
@ -2033,6 +2053,7 @@ impl Readable for InboundOnionPayload {
|
|||
keysend_preimage,
|
||||
amt_msat: amt.0,
|
||||
outgoing_cltv_value: cltv_value.0,
|
||||
custom_tlvs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -3566,6 +3587,7 @@ mod tests {
|
|||
keysend_preimage: None,
|
||||
amt_msat: 0x0badf00d01020304,
|
||||
outgoing_cltv_value: 0xffffffff,
|
||||
custom_tlvs: vec![],
|
||||
};
|
||||
let encoded_value = outbound_msg.encode();
|
||||
let target_value = hex::decode("1002080badf00d010203040404ffffffff").unwrap();
|
||||
|
@ -3590,6 +3612,7 @@ mod tests {
|
|||
keysend_preimage: None,
|
||||
amt_msat: 0x0badf00d01020304,
|
||||
outgoing_cltv_value: 0xffffffff,
|
||||
custom_tlvs: vec![],
|
||||
};
|
||||
let encoded_value = outbound_msg.encode();
|
||||
let target_value = hex::decode("3602080badf00d010203040404ffffffff082442424242424242424242424242424242424242424242424242424242424242421badca1f").unwrap();
|
||||
|
@ -3604,10 +3627,78 @@ mod tests {
|
|||
amt_msat, outgoing_cltv_value,
|
||||
payment_metadata: None,
|
||||
keysend_preimage: None,
|
||||
custom_tlvs,
|
||||
} = inbound_msg {
|
||||
assert_eq!(payment_secret, expected_payment_secret);
|
||||
assert_eq!(amt_msat, 0x0badf00d01020304);
|
||||
assert_eq!(outgoing_cltv_value, 0xffffffff);
|
||||
assert_eq!(custom_tlvs, vec![]);
|
||||
} else { panic!(); }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encoding_final_onion_hop_data_with_bad_custom_tlvs() {
|
||||
// If custom TLVs have type number within the range reserved for protocol, treat them as if
|
||||
// they're unknown
|
||||
let bad_type_range_tlvs = vec![
|
||||
((1 << 16) - 4, vec![42]),
|
||||
((1 << 16) - 2, vec![42; 32]),
|
||||
];
|
||||
let mut msg = msgs::OutboundOnionPayload::Receive {
|
||||
payment_data: None,
|
||||
payment_metadata: None,
|
||||
keysend_preimage: None,
|
||||
custom_tlvs: bad_type_range_tlvs,
|
||||
amt_msat: 0x0badf00d01020304,
|
||||
outgoing_cltv_value: 0xffffffff,
|
||||
};
|
||||
let encoded_value = msg.encode();
|
||||
assert!(msgs::InboundOnionPayload::read(&mut Cursor::new(&encoded_value[..])).is_err());
|
||||
let good_type_range_tlvs = vec![
|
||||
((1 << 16) - 3, vec![42]),
|
||||
((1 << 16) - 1, vec![42; 32]),
|
||||
];
|
||||
if let msgs::OutboundOnionPayload::Receive { ref mut custom_tlvs, .. } = msg {
|
||||
*custom_tlvs = good_type_range_tlvs.clone();
|
||||
}
|
||||
let encoded_value = msg.encode();
|
||||
let inbound_msg = Readable::read(&mut Cursor::new(&encoded_value[..])).unwrap();
|
||||
match inbound_msg {
|
||||
msgs::InboundOnionPayload::Receive { custom_tlvs, .. } => assert!(custom_tlvs.is_empty()),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encoding_final_onion_hop_data_with_custom_tlvs() {
|
||||
let expected_custom_tlvs = vec![
|
||||
(5482373483, vec![0x12, 0x34]),
|
||||
(5482373487, vec![0x42u8; 8]),
|
||||
];
|
||||
let msg = msgs::OutboundOnionPayload::Receive {
|
||||
payment_data: None,
|
||||
payment_metadata: None,
|
||||
keysend_preimage: None,
|
||||
custom_tlvs: expected_custom_tlvs.clone(),
|
||||
amt_msat: 0x0badf00d01020304,
|
||||
outgoing_cltv_value: 0xffffffff,
|
||||
};
|
||||
let encoded_value = msg.encode();
|
||||
let target_value = hex::decode("2e02080badf00d010203040404ffffffffff0000000146c6616b021234ff0000000146c6616f084242424242424242").unwrap();
|
||||
assert_eq!(encoded_value, target_value);
|
||||
let inbound_msg: msgs::InboundOnionPayload = Readable::read(&mut Cursor::new(&target_value[..])).unwrap();
|
||||
if let msgs::InboundOnionPayload::Receive {
|
||||
payment_data: None,
|
||||
payment_metadata: None,
|
||||
keysend_preimage: None,
|
||||
custom_tlvs,
|
||||
amt_msat,
|
||||
outgoing_cltv_value,
|
||||
..
|
||||
} = inbound_msg {
|
||||
assert_eq!(custom_tlvs, expected_custom_tlvs);
|
||||
assert_eq!(amt_msat, 0x0badf00d01020304);
|
||||
assert_eq!(outgoing_cltv_value, 0xffffffff);
|
||||
} else { panic!(); }
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ use crate::ln::features::{InitFeatures, Bolt11InvoiceFeatures};
|
|||
use crate::ln::msgs;
|
||||
use crate::ln::msgs::{ChannelMessageHandler, ChannelUpdate};
|
||||
use crate::ln::wire::Encode;
|
||||
use crate::util::ser::{Writeable, Writer};
|
||||
use crate::util::ser::{Writeable, Writer, BigSize};
|
||||
use crate::util::test_utils;
|
||||
use crate::util::config::{UserConfig, ChannelConfig, MaxDustHTLCExposure};
|
||||
use crate::util::errors::APIError;
|
||||
|
@ -942,10 +942,16 @@ fn do_test_fail_htlc_backwards_with_reason(failure_code: FailureCode) {
|
|||
let mut htlc_msat_height_data = (payment_amount as u64).to_be_bytes().to_vec();
|
||||
htlc_msat_height_data.extend_from_slice(&CHAN_CONFIRM_DEPTH.to_be_bytes());
|
||||
htlc_msat_height_data
|
||||
},
|
||||
FailureCode::InvalidOnionPayload(data) => {
|
||||
match data {
|
||||
Some((typ, offset)) => [BigSize(typ).encode(), offset.encode()].concat(),
|
||||
None => Vec::new(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let failure_code = failure_code as u16;
|
||||
let failure_code = failure_code.into();
|
||||
let permanent_flag = 0x4000;
|
||||
let permanent_fail = (failure_code & permanent_flag) != 0;
|
||||
expect_payment_failed!(nodes[0], payment_hash, permanent_fail, failure_code, failure_data);
|
||||
|
@ -957,6 +963,8 @@ fn test_fail_htlc_backwards_with_reason() {
|
|||
do_test_fail_htlc_backwards_with_reason(FailureCode::TemporaryNodeFailure);
|
||||
do_test_fail_htlc_backwards_with_reason(FailureCode::RequiredNodeFeatureMissing);
|
||||
do_test_fail_htlc_backwards_with_reason(FailureCode::IncorrectOrUnknownPaymentDetails);
|
||||
do_test_fail_htlc_backwards_with_reason(FailureCode::InvalidOnionPayload(Some((1 << 16, 42))));
|
||||
do_test_fail_htlc_backwards_with_reason(FailureCode::InvalidOnionPayload(None));
|
||||
}
|
||||
|
||||
macro_rules! get_phantom_route {
|
||||
|
|
|
@ -171,6 +171,7 @@ pub(super) fn build_onion_payloads(path: &Path, total_msat: u64, mut recipient_o
|
|||
} else { None },
|
||||
payment_metadata: recipient_onion.payment_metadata.take(),
|
||||
keysend_preimage: *keysend_preimage,
|
||||
custom_tlvs: recipient_onion.custom_tlvs.clone(),
|
||||
amt_msat: value_msat,
|
||||
outgoing_cltv_value: cltv,
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ pub(crate) enum PendingOutboundPayment {
|
|||
payment_secret: Option<PaymentSecret>,
|
||||
payment_metadata: Option<Vec<u8>>,
|
||||
keysend_preimage: Option<PaymentPreimage>,
|
||||
custom_tlvs: Vec<(u64, Vec<u8>)>,
|
||||
pending_amt_msat: u64,
|
||||
/// Used to track the fee paid. Only present if the payment was serialized on 0.0.103+.
|
||||
pending_fee_msat: Option<u64>,
|
||||
|
@ -431,10 +432,13 @@ pub struct RecipientOnionFields {
|
|||
/// [`Self::payment_secret`] and while nearly all lightning senders support secrets, metadata
|
||||
/// may not be supported as universally.
|
||||
pub payment_metadata: Option<Vec<u8>>,
|
||||
/// See [`Self::custom_tlvs`] for more info.
|
||||
pub(super) custom_tlvs: Vec<(u64, Vec<u8>)>,
|
||||
}
|
||||
|
||||
impl_writeable_tlv_based!(RecipientOnionFields, {
|
||||
(0, payment_secret, option),
|
||||
(1, custom_tlvs, optional_vec),
|
||||
(2, payment_metadata, option),
|
||||
});
|
||||
|
||||
|
@ -443,7 +447,7 @@ impl RecipientOnionFields {
|
|||
/// set of onion fields for today's BOLT11 invoices - most nodes require a [`PaymentSecret`]
|
||||
/// but do not require or provide any further data.
|
||||
pub fn secret_only(payment_secret: PaymentSecret) -> Self {
|
||||
Self { payment_secret: Some(payment_secret), payment_metadata: None }
|
||||
Self { payment_secret: Some(payment_secret), payment_metadata: None, custom_tlvs: Vec::new() }
|
||||
}
|
||||
|
||||
/// Creates a new [`RecipientOnionFields`] with no fields. This generally does not create
|
||||
|
@ -455,7 +459,46 @@ impl RecipientOnionFields {
|
|||
/// [`ChannelManager::send_spontaneous_payment`]: super::channelmanager::ChannelManager::send_spontaneous_payment
|
||||
/// [`RecipientOnionFields::secret_only`]: RecipientOnionFields::secret_only
|
||||
pub fn spontaneous_empty() -> Self {
|
||||
Self { payment_secret: None, payment_metadata: None }
|
||||
Self { payment_secret: None, payment_metadata: None, custom_tlvs: Vec::new() }
|
||||
}
|
||||
|
||||
/// Creates a new [`RecipientOnionFields`] from an existing one, adding custom TLVs. Each
|
||||
/// TLV is provided as a `(u64, Vec<u8>)` for the type number and serialized value
|
||||
/// respectively. TLV type numbers must be unique and within the range
|
||||
/// reserved for custom types, i.e. >= 2^16, otherwise this method will return `Err(())`.
|
||||
///
|
||||
/// This method will also error for types in the experimental range which have been
|
||||
/// standardized within the protocol, which only includes 5482373484 (keysend) for now.
|
||||
///
|
||||
/// See [`Self::custom_tlvs`] for more info.
|
||||
pub fn with_custom_tlvs(mut self, mut custom_tlvs: Vec<(u64, Vec<u8>)>) -> Result<Self, ()> {
|
||||
custom_tlvs.sort_unstable_by_key(|(typ, _)| *typ);
|
||||
let mut prev_type = None;
|
||||
for (typ, _) in custom_tlvs.iter() {
|
||||
if *typ < 1 << 16 { return Err(()); }
|
||||
if *typ == 5482373484 { return Err(()); } // keysend
|
||||
match prev_type {
|
||||
Some(prev) if prev >= *typ => return Err(()),
|
||||
_ => {},
|
||||
}
|
||||
prev_type = Some(*typ);
|
||||
}
|
||||
self.custom_tlvs = custom_tlvs;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Gets the custom TLVs that will be sent or have been received.
|
||||
///
|
||||
/// Custom TLVs allow sending extra application-specific data with a payment. They provide
|
||||
/// additional flexibility on top of payment metadata, as while other implementations may
|
||||
/// require `payment_metadata` to reflect metadata provided in an invoice, custom TLVs
|
||||
/// do not have this restriction.
|
||||
///
|
||||
/// Note that if this field is non-empty, it will contain strictly increasing TLVs, each
|
||||
/// represented by a `(u64, Vec<u8>)` for its type number and serialized value respectively.
|
||||
/// This is validated when setting this field using [`Self::with_custom_tlvs`].
|
||||
pub fn custom_tlvs(&self) -> &Vec<(u64, Vec<u8>)> {
|
||||
&self.custom_tlvs
|
||||
}
|
||||
|
||||
/// When we have received some HTLC(s) towards an MPP payment, as we receive further HTLC(s) we
|
||||
|
@ -468,7 +511,17 @@ impl RecipientOnionFields {
|
|||
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.
|
||||
|
||||
let tlvs = &mut self.custom_tlvs;
|
||||
let further_tlvs = &mut further_htlc_fields.custom_tlvs;
|
||||
|
||||
let even_tlvs: Vec<&(u64, Vec<u8>)> = tlvs.iter().filter(|(typ, _)| *typ % 2 == 0).collect();
|
||||
let further_even_tlvs: Vec<&(u64, Vec<u8>)> = further_tlvs.iter().filter(|(typ, _)| *typ % 2 == 0).collect();
|
||||
if even_tlvs != further_even_tlvs { return Err(()) }
|
||||
|
||||
tlvs.retain(|tlv| further_tlvs.iter().any(|further_tlv| tlv == further_tlv));
|
||||
further_tlvs.retain(|further_tlv| tlvs.iter().any(|tlv| tlv == further_tlv));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -762,7 +815,8 @@ impl OutboundPayments {
|
|||
hash_map::Entry::Occupied(mut payment) => {
|
||||
let res = match payment.get() {
|
||||
PendingOutboundPayment::Retryable {
|
||||
total_msat, keysend_preimage, payment_secret, payment_metadata, pending_amt_msat, ..
|
||||
total_msat, keysend_preimage, payment_secret, payment_metadata,
|
||||
custom_tlvs, pending_amt_msat, ..
|
||||
} => {
|
||||
let retry_amt_msat = route.get_total_amount();
|
||||
if retry_amt_msat + *pending_amt_msat > *total_msat * (100 + RETRY_OVERFLOW_PERCENTAGE) / 100 {
|
||||
|
@ -773,6 +827,7 @@ impl OutboundPayments {
|
|||
(*total_msat, RecipientOnionFields {
|
||||
payment_secret: *payment_secret,
|
||||
payment_metadata: payment_metadata.clone(),
|
||||
custom_tlvs: custom_tlvs.clone(),
|
||||
}, *keysend_preimage)
|
||||
},
|
||||
PendingOutboundPayment::Legacy { .. } => {
|
||||
|
@ -971,6 +1026,7 @@ impl OutboundPayments {
|
|||
payment_secret: recipient_onion.payment_secret,
|
||||
payment_metadata: recipient_onion.payment_metadata,
|
||||
keysend_preimage,
|
||||
custom_tlvs: recipient_onion.custom_tlvs,
|
||||
starting_block_height: best_block_height,
|
||||
total_msat: route.get_total_amount(),
|
||||
});
|
||||
|
@ -1420,6 +1476,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
|
|||
(6, total_msat, required),
|
||||
(7, payment_metadata, option),
|
||||
(8, pending_amt_msat, required),
|
||||
(9, custom_tlvs, optional_vec),
|
||||
(10, starting_block_height, required),
|
||||
(not_written, retry_strategy, (static_value, None)),
|
||||
(not_written, attempts, (static_value, PaymentAttempts::new())),
|
||||
|
@ -1450,6 +1507,28 @@ mod tests {
|
|||
|
||||
use alloc::collections::VecDeque;
|
||||
|
||||
#[test]
|
||||
fn test_recipient_onion_fields_with_custom_tlvs() {
|
||||
let onion_fields = RecipientOnionFields::spontaneous_empty();
|
||||
|
||||
let bad_type_range_tlvs = vec![
|
||||
(0, vec![42]),
|
||||
(1, vec![42; 32]),
|
||||
];
|
||||
assert!(onion_fields.clone().with_custom_tlvs(bad_type_range_tlvs).is_err());
|
||||
|
||||
let keysend_tlv = vec![
|
||||
(5482373484, vec![42; 32]),
|
||||
];
|
||||
assert!(onion_fields.clone().with_custom_tlvs(keysend_tlv).is_err());
|
||||
|
||||
let good_tlvs = vec![
|
||||
((1 << 16) + 1, vec![42]),
|
||||
((1 << 16) + 3, vec![42; 32]),
|
||||
];
|
||||
assert!(onion_fields.with_custom_tlvs(good_tlvs).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "std")]
|
||||
fn fails_paying_after_expiration() {
|
||||
|
|
|
@ -3394,6 +3394,354 @@ fn claim_from_closed_chan() {
|
|||
do_claim_from_closed_chan(false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_tlvs_basic() {
|
||||
do_test_custom_tlvs(false, false, false);
|
||||
do_test_custom_tlvs(true, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_tlvs_explicit_claim() {
|
||||
// Test that when receiving even custom TLVs the user must explicitly accept in case they
|
||||
// are unknown.
|
||||
do_test_custom_tlvs(false, true, false);
|
||||
do_test_custom_tlvs(false, true, true);
|
||||
}
|
||||
|
||||
fn do_test_custom_tlvs(spontaneous: bool, even_tlvs: bool, known_tlvs: bool) {
|
||||
let chanmon_cfgs = create_chanmon_cfgs(2);
|
||||
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
|
||||
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None; 2]);
|
||||
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
|
||||
|
||||
create_announced_chan_between_nodes(&nodes, 0, 1);
|
||||
|
||||
let amt_msat = 100_000;
|
||||
let (mut route, our_payment_hash, our_payment_preimage, our_payment_secret) = get_route_and_payment_hash!(&nodes[0], &nodes[1], amt_msat);
|
||||
let payment_id = PaymentId(our_payment_hash.0);
|
||||
let custom_tlvs = vec![
|
||||
(if even_tlvs { 5482373482 } else { 5482373483 }, vec![1, 2, 3, 4]),
|
||||
(5482373487, vec![0x42u8; 16]),
|
||||
];
|
||||
let onion_fields = RecipientOnionFields {
|
||||
payment_secret: if spontaneous { None } else { Some(our_payment_secret) },
|
||||
payment_metadata: None,
|
||||
custom_tlvs: custom_tlvs.clone()
|
||||
};
|
||||
if spontaneous {
|
||||
nodes[0].node.send_spontaneous_payment(&route, Some(our_payment_preimage), onion_fields, payment_id).unwrap();
|
||||
} else {
|
||||
nodes[0].node.send_payment_with_route(&route, our_payment_hash, onion_fields, payment_id).unwrap();
|
||||
}
|
||||
check_added_monitors(&nodes[0], 1);
|
||||
|
||||
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
|
||||
let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
|
||||
let mut payment_event = SendEvent::from_event(ev);
|
||||
|
||||
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
|
||||
check_added_monitors!(&nodes[1], 0);
|
||||
commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);
|
||||
expect_pending_htlcs_forwardable!(nodes[1]);
|
||||
|
||||
let events = nodes[1].node.get_and_clear_pending_events();
|
||||
assert_eq!(events.len(), 1);
|
||||
match events[0] {
|
||||
Event::PaymentClaimable { ref purpose, amount_msat, ref onion_fields, .. } => {
|
||||
match &purpose {
|
||||
PaymentPurpose::InvoicePayment { payment_secret, .. } => {
|
||||
assert_eq!(our_payment_secret, *payment_secret);
|
||||
assert_eq!(Some(*payment_secret), onion_fields.as_ref().unwrap().payment_secret);
|
||||
},
|
||||
PaymentPurpose::SpontaneousPayment(payment_preimage) => {
|
||||
assert_eq!(our_payment_preimage, *payment_preimage);
|
||||
},
|
||||
}
|
||||
assert_eq!(amount_msat, amt_msat);
|
||||
assert_eq!(onion_fields.clone().unwrap().custom_tlvs().clone(), custom_tlvs);
|
||||
},
|
||||
_ => panic!("Unexpected event"),
|
||||
}
|
||||
|
||||
match (known_tlvs, even_tlvs) {
|
||||
(true, _) => {
|
||||
nodes[1].node.claim_funds_with_known_custom_tlvs(our_payment_preimage);
|
||||
let expected_total_fee_msat = pass_claimed_payment_along_route(&nodes[0], &[&[&nodes[1]]], &[0; 1], false, our_payment_preimage);
|
||||
expect_payment_sent!(&nodes[0], our_payment_preimage, Some(expected_total_fee_msat));
|
||||
},
|
||||
(false, false) => {
|
||||
claim_payment(&nodes[0], &[&nodes[1]], our_payment_preimage);
|
||||
},
|
||||
(false, true) => {
|
||||
nodes[1].node.claim_funds(our_payment_preimage);
|
||||
let expected_destinations = vec![HTLCDestination::FailedPayment { payment_hash: our_payment_hash }];
|
||||
expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], expected_destinations);
|
||||
pass_failed_payment_back(&nodes[0], &[&[&nodes[1]]], false, our_payment_hash, PaymentFailureReason::RecipientRejected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_retry_custom_tlvs() {
|
||||
// Test that custom TLVs are successfully sent on retries
|
||||
let chanmon_cfgs = create_chanmon_cfgs(3);
|
||||
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
|
||||
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
|
||||
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
|
||||
|
||||
create_announced_chan_between_nodes(&nodes, 0, 1);
|
||||
let (chan_2_update, _, chan_2_id, _) = create_announced_chan_between_nodes(&nodes, 2, 1);
|
||||
|
||||
// Rebalance
|
||||
send_payment(&nodes[2], &vec!(&nodes[1])[..], 1_500_000);
|
||||
|
||||
let amt_msat = 1_000_000;
|
||||
let (route, payment_hash, payment_preimage, payment_secret) =
|
||||
get_route_and_payment_hash!(nodes[0], nodes[2], amt_msat);
|
||||
|
||||
// Initiate the payment
|
||||
let payment_id = PaymentId(payment_hash.0);
|
||||
let mut route_params = RouteParameters {
|
||||
payment_params: route.payment_params.clone().unwrap(),
|
||||
final_value_msat: amt_msat,
|
||||
};
|
||||
|
||||
let custom_tlvs = vec![((1 << 16) + 1, vec![0x42u8; 16])];
|
||||
let onion_fields = RecipientOnionFields::secret_only(payment_secret);
|
||||
let onion_fields = onion_fields.with_custom_tlvs(custom_tlvs.clone()).unwrap();
|
||||
|
||||
nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
|
||||
nodes[0].node.send_payment(payment_hash, onion_fields,
|
||||
payment_id, route_params.clone(), Retry::Attempts(1)).unwrap();
|
||||
check_added_monitors!(nodes[0], 1); // one monitor per path
|
||||
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
|
||||
assert_eq!(events.len(), 1);
|
||||
|
||||
// Add the HTLC along the first hop.
|
||||
let fail_path_msgs_1 = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
|
||||
let (update_add, commitment_signed) = match fail_path_msgs_1 {
|
||||
MessageSendEvent::UpdateHTLCs { node_id: _, updates: msgs::CommitmentUpdate {
|
||||
ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs,
|
||||
ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed }
|
||||
} => {
|
||||
assert_eq!(update_add_htlcs.len(), 1);
|
||||
assert!(update_fail_htlcs.is_empty());
|
||||
assert!(update_fulfill_htlcs.is_empty());
|
||||
assert!(update_fail_malformed_htlcs.is_empty());
|
||||
assert!(update_fee.is_none());
|
||||
(update_add_htlcs[0].clone(), commitment_signed.clone())
|
||||
},
|
||||
_ => panic!("Unexpected event"),
|
||||
};
|
||||
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &update_add);
|
||||
commitment_signed_dance!(nodes[1], nodes[0], commitment_signed, false);
|
||||
|
||||
// Attempt to forward the payment and complete the path's failure.
|
||||
expect_pending_htlcs_forwardable!(&nodes[1]);
|
||||
expect_pending_htlcs_forwardable_and_htlc_handling_failed!(&nodes[1],
|
||||
vec![HTLCDestination::NextHopChannel {
|
||||
node_id: Some(nodes[2].node.get_our_node_id()),
|
||||
channel_id: chan_2_id
|
||||
}]);
|
||||
let htlc_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
|
||||
assert!(htlc_updates.update_add_htlcs.is_empty());
|
||||
assert_eq!(htlc_updates.update_fail_htlcs.len(), 1);
|
||||
assert!(htlc_updates.update_fulfill_htlcs.is_empty());
|
||||
assert!(htlc_updates.update_fail_malformed_htlcs.is_empty());
|
||||
check_added_monitors!(nodes[1], 1);
|
||||
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(),
|
||||
&htlc_updates.update_fail_htlcs[0]);
|
||||
commitment_signed_dance!(nodes[0], nodes[1], htlc_updates.commitment_signed, false);
|
||||
let mut events = nodes[0].node.get_and_clear_pending_events();
|
||||
match events[1] {
|
||||
Event::PendingHTLCsForwardable { .. } => {},
|
||||
_ => panic!("Unexpected event")
|
||||
}
|
||||
events.remove(1);
|
||||
expect_payment_failed_conditions_event(events, payment_hash, false,
|
||||
PaymentFailedConditions::new().mpp_parts_remain());
|
||||
|
||||
// Rebalance the channel so the retry of the payment can succeed.
|
||||
send_payment(&nodes[2], &vec!(&nodes[1])[..], 1_500_000);
|
||||
|
||||
// Retry the payment and make sure it succeeds
|
||||
route_params.payment_params.previously_failed_channels.push(chan_2_update.contents.short_channel_id);
|
||||
nodes[0].router.expect_find_route(route_params, Ok(route));
|
||||
nodes[0].node.process_pending_htlc_forwards();
|
||||
check_added_monitors!(nodes[0], 1);
|
||||
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
|
||||
assert_eq!(events.len(), 1);
|
||||
let payment_claimable = pass_along_path(&nodes[0], &[&nodes[1], &nodes[2]], 1_000_000,
|
||||
payment_hash, Some(payment_secret), events.pop().unwrap(), true, None).unwrap();
|
||||
let onion_fields = match payment_claimable {
|
||||
Event::PaymentClaimable { onion_fields, .. } => onion_fields,
|
||||
_ => panic!("Unexpected event"),
|
||||
};
|
||||
assert_eq!(onion_fields.unwrap().custom_tlvs(), &custom_tlvs);
|
||||
claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], false, payment_preimage);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_tlvs_consistency() {
|
||||
let even_type_1 = 1 << 16;
|
||||
let odd_type_1 = (1 << 16)+ 1;
|
||||
let even_type_2 = (1 << 16) + 2;
|
||||
let odd_type_2 = (1 << 16) + 3;
|
||||
let value_1 = || vec![1, 2, 3, 4];
|
||||
let differing_value_1 = || vec![1, 2, 3, 5];
|
||||
let value_2 = || vec![42u8; 16];
|
||||
|
||||
// Drop missing odd tlvs
|
||||
do_test_custom_tlvs_consistency(
|
||||
vec![(odd_type_1, value_1()), (odd_type_2, value_2())],
|
||||
vec![(odd_type_1, value_1())],
|
||||
Some(vec![(odd_type_1, value_1())]),
|
||||
);
|
||||
// Drop non-matching odd tlvs
|
||||
do_test_custom_tlvs_consistency(
|
||||
vec![(odd_type_1, value_1()), (odd_type_2, value_2())],
|
||||
vec![(odd_type_1, differing_value_1()), (odd_type_2, value_2())],
|
||||
Some(vec![(odd_type_2, value_2())]),
|
||||
);
|
||||
// Fail missing even tlvs
|
||||
do_test_custom_tlvs_consistency(
|
||||
vec![(odd_type_1, value_1()), (even_type_2, value_2())],
|
||||
vec![(odd_type_1, value_1())],
|
||||
None,
|
||||
);
|
||||
// Fail non-matching even tlvs
|
||||
do_test_custom_tlvs_consistency(
|
||||
vec![(even_type_1, value_1()), (odd_type_2, value_2())],
|
||||
vec![(even_type_1, differing_value_1()), (odd_type_2, value_2())],
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
fn do_test_custom_tlvs_consistency(first_tlvs: Vec<(u64, Vec<u8>)>, second_tlvs: Vec<(u64, Vec<u8>)>,
|
||||
expected_receive_tlvs: Option<Vec<(u64, Vec<u8>)>>) {
|
||||
|
||||
let chanmon_cfgs = create_chanmon_cfgs(4);
|
||||
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
|
||||
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
|
||||
let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
|
||||
|
||||
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0);
|
||||
create_announced_chan_between_nodes_with_value(&nodes, 0, 2, 100_000, 0);
|
||||
create_announced_chan_between_nodes_with_value(&nodes, 1, 3, 100_000, 0);
|
||||
let chan_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 100_000, 0);
|
||||
|
||||
let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV)
|
||||
.with_bolt11_features(nodes[3].node.invoice_features()).unwrap();
|
||||
let mut route = get_route!(nodes[0], payment_params, 15_000_000).unwrap();
|
||||
assert_eq!(route.paths.len(), 2);
|
||||
route.paths.sort_by(|path_a, _| {
|
||||
// Sort the path so that the path through nodes[1] comes first
|
||||
if path_a.hops[0].pubkey == nodes[1].node.get_our_node_id() {
|
||||
core::cmp::Ordering::Less } else { core::cmp::Ordering::Greater }
|
||||
});
|
||||
|
||||
let (our_payment_preimage, our_payment_hash, our_payment_secret) = get_payment_preimage_hash!(&nodes[3]);
|
||||
let payment_id = PaymentId([42; 32]);
|
||||
let amt_msat = 15_000_000;
|
||||
|
||||
// Send first part
|
||||
let onion_fields = RecipientOnionFields {
|
||||
payment_secret: Some(our_payment_secret),
|
||||
payment_metadata: None,
|
||||
custom_tlvs: first_tlvs
|
||||
};
|
||||
let session_privs = nodes[0].node.test_add_new_pending_payment(our_payment_hash,
|
||||
onion_fields.clone(), payment_id, &route).unwrap();
|
||||
let cur_height = nodes[0].best_block_info().1;
|
||||
nodes[0].node.test_send_payment_along_path(&route.paths[0], &our_payment_hash,
|
||||
onion_fields.clone(), amt_msat, cur_height, payment_id,
|
||||
&None, session_privs[0]).unwrap();
|
||||
check_added_monitors!(nodes[0], 1);
|
||||
|
||||
{
|
||||
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
|
||||
assert_eq!(events.len(), 1);
|
||||
pass_along_path(&nodes[0], &[&nodes[1], &nodes[3]], amt_msat, our_payment_hash, Some(our_payment_secret), events.pop().unwrap(), false, None);
|
||||
}
|
||||
assert!(nodes[3].node.get_and_clear_pending_events().is_empty());
|
||||
|
||||
// Send second part
|
||||
let onion_fields = RecipientOnionFields {
|
||||
payment_secret: Some(our_payment_secret),
|
||||
payment_metadata: None,
|
||||
custom_tlvs: second_tlvs
|
||||
};
|
||||
nodes[0].node.test_send_payment_along_path(&route.paths[1], &our_payment_hash,
|
||||
onion_fields.clone(), amt_msat, cur_height, payment_id, &None, session_privs[1]).unwrap();
|
||||
check_added_monitors!(nodes[0], 1);
|
||||
|
||||
{
|
||||
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
|
||||
assert_eq!(events.len(), 1);
|
||||
let payment_event = SendEvent::from_event(events.pop().unwrap());
|
||||
|
||||
nodes[2].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
|
||||
commitment_signed_dance!(nodes[2], nodes[0], payment_event.commitment_msg, false);
|
||||
|
||||
expect_pending_htlcs_forwardable!(nodes[2]);
|
||||
check_added_monitors!(nodes[2], 1);
|
||||
|
||||
let mut events = nodes[2].node.get_and_clear_pending_msg_events();
|
||||
assert_eq!(events.len(), 1);
|
||||
let payment_event = SendEvent::from_event(events.pop().unwrap());
|
||||
|
||||
nodes[3].node.handle_update_add_htlc(&nodes[2].node.get_our_node_id(), &payment_event.msgs[0]);
|
||||
check_added_monitors!(nodes[3], 0);
|
||||
commitment_signed_dance!(nodes[3], nodes[2], payment_event.commitment_msg, true, true);
|
||||
}
|
||||
expect_pending_htlcs_forwardable_ignore!(nodes[3]);
|
||||
nodes[3].node.process_pending_htlc_forwards();
|
||||
|
||||
if let Some(expected_tlvs) = expected_receive_tlvs {
|
||||
// Claim and match expected
|
||||
let events = nodes[3].node.get_and_clear_pending_events();
|
||||
println!("events: {:?}", events);
|
||||
assert_eq!(events.len(), 1);
|
||||
match events[0] {
|
||||
Event::PaymentClaimable { ref purpose, amount_msat, ref onion_fields, .. } => {
|
||||
match &purpose {
|
||||
PaymentPurpose::InvoicePayment { payment_secret, .. } => {
|
||||
assert_eq!(our_payment_secret, *payment_secret);
|
||||
assert_eq!(Some(*payment_secret), onion_fields.as_ref().unwrap().payment_secret);
|
||||
},
|
||||
PaymentPurpose::SpontaneousPayment(payment_preimage) => {
|
||||
assert_eq!(our_payment_preimage, *payment_preimage);
|
||||
},
|
||||
}
|
||||
assert_eq!(amount_msat, amt_msat);
|
||||
assert_eq!(onion_fields.clone().unwrap().custom_tlvs, expected_tlvs);
|
||||
},
|
||||
_ => panic!("Unexpected event"),
|
||||
}
|
||||
|
||||
do_claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]], false, our_payment_preimage);
|
||||
expect_payment_sent(&nodes[0], our_payment_preimage, Some(Some(2000)), true);
|
||||
} else {
|
||||
// Expect fail back
|
||||
let expected_destinations = vec![HTLCDestination::FailedPayment { payment_hash: our_payment_hash }];
|
||||
expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[3], expected_destinations);
|
||||
check_added_monitors!(nodes[3], 1);
|
||||
|
||||
let fail_updates_1 = get_htlc_update_msgs!(nodes[3], nodes[2].node.get_our_node_id());
|
||||
nodes[2].node.handle_update_fail_htlc(&nodes[3].node.get_our_node_id(), &fail_updates_1.update_fail_htlcs[0]);
|
||||
commitment_signed_dance!(nodes[2], nodes[3], fail_updates_1.commitment_signed, false);
|
||||
|
||||
expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[2], vec![HTLCDestination::NextHopChannel { node_id: Some(nodes[3].node.get_our_node_id()), channel_id: chan_2_3.2 }]);
|
||||
check_added_monitors!(nodes[2], 1);
|
||||
|
||||
let fail_updates_2 = get_htlc_update_msgs!(nodes[2], nodes[0].node.get_our_node_id());
|
||||
nodes[0].node.handle_update_fail_htlc(&nodes[2].node.get_our_node_id(), &fail_updates_2.update_fail_htlcs[0]);
|
||||
commitment_signed_dance!(nodes[0], nodes[2], fail_updates_2.commitment_signed, false);
|
||||
|
||||
expect_payment_failed_conditions(&nodes[0], our_payment_hash, true, PaymentFailedConditions::new().mpp_parts_remain());
|
||||
}
|
||||
}
|
||||
|
||||
fn do_test_payment_metadata_consistency(do_reload: bool, do_modify: bool) {
|
||||
// Check that a payment metadata received on one HTLC that doesn't match the one received on
|
||||
// another results in the HTLC being rejected.
|
||||
|
@ -3433,7 +3781,7 @@ fn do_test_payment_metadata_consistency(do_reload: bool, do_modify: bool) {
|
|||
|
||||
// Send the MPP payment, delivering the updated commitment state to nodes[1].
|
||||
nodes[0].node.send_payment(payment_hash, RecipientOnionFields {
|
||||
payment_secret: Some(payment_secret), payment_metadata: Some(payment_metadata),
|
||||
payment_secret: Some(payment_secret), payment_metadata: Some(payment_metadata), custom_tlvs: vec![],
|
||||
}, payment_id, route_params.clone(), Retry::Attempts(1)).unwrap();
|
||||
check_added_monitors!(nodes[0], 2);
|
||||
|
||||
|
|
|
@ -132,6 +132,16 @@ macro_rules! _check_encoded_tlv_order {
|
|||
/// [`Writer`]: crate::util::ser::Writer
|
||||
#[macro_export]
|
||||
macro_rules! encode_tlv_stream {
|
||||
($stream: expr, {$(($type: expr, $field: expr, $fieldty: tt)),* $(,)*}) => {
|
||||
$crate::_encode_tlv_stream!($stream, {$(($type, $field, $fieldty)),*})
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of [`encode_tlv_stream`].
|
||||
/// This is exported for use by other exported macros, do not use directly.
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! _encode_tlv_stream {
|
||||
($stream: expr, {$(($type: expr, $field: expr, $fieldty: tt)),* $(,)*}) => { {
|
||||
#[allow(unused_imports)]
|
||||
use $crate::{
|
||||
|
@ -153,7 +163,21 @@ macro_rules! encode_tlv_stream {
|
|||
$crate::_check_encoded_tlv_order!(last_seen, $type, $fieldty);
|
||||
)*
|
||||
}
|
||||
} }
|
||||
} };
|
||||
($stream: expr, $tlvs: expr) => { {
|
||||
for tlv in $tlvs {
|
||||
let (typ, value): &&(u64, Vec<u8>) = tlv;
|
||||
$crate::_encode_tlv!($stream, *typ, *value, required_vec);
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)] {
|
||||
let mut last_seen: Option<u64> = None;
|
||||
for tlv in $tlvs {
|
||||
let (typ, _): &&(u64, Vec<u8>) = tlv;
|
||||
$crate::_check_encoded_tlv_order!(last_seen, *typ, required_vec);
|
||||
}
|
||||
}
|
||||
} };
|
||||
}
|
||||
|
||||
/// Adds the length of the serialized field to a [`LengthCalculatingWriter`].
|
||||
|
@ -210,18 +234,27 @@ macro_rules! _get_varint_length_prefixed_tlv_length {
|
|||
#[macro_export]
|
||||
macro_rules! _encode_varint_length_prefixed_tlv {
|
||||
($stream: expr, {$(($type: expr, $field: expr, $fieldty: tt)),*}) => { {
|
||||
_encode_varint_length_prefixed_tlv!($stream, {$(($type, $field, $fieldty)),*}, &[])
|
||||
} };
|
||||
($stream: expr, {$(($type: expr, $field: expr, $fieldty: tt)),*}, $extra_tlvs: expr) => { {
|
||||
use $crate::util::ser::BigSize;
|
||||
use alloc::vec::Vec;
|
||||
let len = {
|
||||
#[allow(unused_mut)]
|
||||
let mut len = $crate::util::ser::LengthCalculatingWriter(0);
|
||||
$(
|
||||
$crate::_get_varint_length_prefixed_tlv_length!(len, $type, $field, $fieldty);
|
||||
)*
|
||||
for tlv in $extra_tlvs {
|
||||
let (typ, value): &&(u64, Vec<u8>) = tlv;
|
||||
$crate::_get_varint_length_prefixed_tlv_length!(len, *typ, *value, required_vec);
|
||||
}
|
||||
len.0
|
||||
};
|
||||
BigSize(len as u64).write($stream)?;
|
||||
$crate::encode_tlv_stream!($stream, { $(($type, $field, $fieldty)),* });
|
||||
} }
|
||||
$crate::_encode_tlv_stream!($stream, { $(($type, $field, $fieldty)),* });
|
||||
$crate::_encode_tlv_stream!($stream, $extra_tlvs);
|
||||
} };
|
||||
}
|
||||
|
||||
/// Errors if there are missing required TLV types between the last seen type and the type currently being processed.
|
||||
|
@ -785,7 +818,8 @@ macro_rules! _init_and_read_tlv_fields {
|
|||
///
|
||||
/// For example,
|
||||
/// ```
|
||||
/// # use lightning::impl_writeable_tlv_based;
|
||||
/// # use lightning::{impl_writeable_tlv_based, _encode_varint_length_prefixed_tlv};
|
||||
/// # extern crate alloc;
|
||||
/// struct LightningMessage {
|
||||
/// tlv_integer: u32,
|
||||
/// tlv_default_integer: u32,
|
||||
|
|
3
pending_changelog/custom_tlv_downgrade.txt
Normal file
3
pending_changelog/custom_tlv_downgrade.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
## Backwards Compatibility
|
||||
|
||||
* Since the addition of custom HTLC TLV support in 0.0.117, if you downgrade you may unintentionally accept payments with features you don't understand.
|
Loading…
Add table
Reference in a new issue