Add PaymentContext to payment::ReceiveTlvs

Providing LDK-specific data in a BlindedPath allows for the data to be
received with the corresponding payment. This is useful as it can then
be surfaced in PaymentClaimable events where the user may correlated
with an Offer, for instance. Define a PaymentContext enum for including
various context (e.g., offer, refund, static invoice).
This commit is contained in:
Jeffrey Czyz 2024-03-26 09:48:03 -05:00
parent 12c3a24beb
commit c881538478
No known key found for this signature in database
GPG key ID: 912EF12EA67705F5
4 changed files with 61 additions and 6 deletions

View file

@ -53,6 +53,8 @@ pub struct ReceiveTlvs {
pub payment_secret: PaymentSecret, pub payment_secret: PaymentSecret,
/// Constraints for the receiver of this payment. /// Constraints for the receiver of this payment.
pub payment_constraints: PaymentConstraints, pub payment_constraints: PaymentConstraints,
/// Context for the receiver of this payment.
pub payment_context: PaymentContext,
} }
/// Data to construct a [`BlindedHop`] for sending a payment over. /// Data to construct a [`BlindedHop`] for sending a payment over.
@ -97,6 +99,27 @@ pub struct PaymentConstraints {
pub htlc_minimum_msat: u64, pub htlc_minimum_msat: u64,
} }
/// The context of an inbound payment, which is included in a [`BlindedPath`] via [`ReceiveTlvs`]
/// and surfaced in [`PaymentPurpose`].
///
/// [`BlindedPath`]: crate::blinded_path::BlindedPath
/// [`PaymentPurpose`]: crate::events::PaymentPurpose
#[derive(Clone, Debug)]
pub enum PaymentContext {
/// The payment context was unknown.
Unknown(UnknownPaymentContext),
}
/// An unknown payment context.
#[derive(Clone, Debug)]
pub struct UnknownPaymentContext(());
impl PaymentContext {
pub(crate) fn unknown() -> Self {
PaymentContext::Unknown(UnknownPaymentContext(()))
}
}
impl TryFrom<CounterpartyForwardingInfo> for PaymentRelay { impl TryFrom<CounterpartyForwardingInfo> for PaymentRelay {
type Error = (); type Error = ();
@ -137,7 +160,8 @@ impl Writeable for ReceiveTlvs {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> { fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
encode_tlv_stream!(w, { encode_tlv_stream!(w, {
(12, self.payment_constraints, required), (12, self.payment_constraints, required),
(65536, self.payment_secret, required) (65536, self.payment_secret, required),
(65537, self.payment_context, required)
}); });
Ok(()) Ok(())
} }
@ -163,11 +187,14 @@ impl Readable for BlindedPaymentTlvs {
(12, payment_constraints, required), (12, payment_constraints, required),
(14, features, option), (14, features, option),
(65536, payment_secret, option), (65536, payment_secret, option),
(65537, payment_context, (default_value, PaymentContext::unknown())),
}); });
let _padding: Option<utils::Padding> = _padding; let _padding: Option<utils::Padding> = _padding;
if let Some(short_channel_id) = scid { if let Some(short_channel_id) = scid {
if payment_secret.is_some() { return Err(DecodeError::InvalidValue) } if payment_secret.is_some() {
return Err(DecodeError::InvalidValue)
}
Ok(BlindedPaymentTlvs::Forward(ForwardTlvs { Ok(BlindedPaymentTlvs::Forward(ForwardTlvs {
short_channel_id, short_channel_id,
payment_relay: payment_relay.ok_or(DecodeError::InvalidValue)?, payment_relay: payment_relay.ok_or(DecodeError::InvalidValue)?,
@ -179,6 +206,7 @@ impl Readable for BlindedPaymentTlvs {
Ok(BlindedPaymentTlvs::Receive(ReceiveTlvs { Ok(BlindedPaymentTlvs::Receive(ReceiveTlvs {
payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?, payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?,
payment_constraints: payment_constraints.0.unwrap(), payment_constraints: payment_constraints.0.unwrap(),
payment_context: payment_context.0.unwrap(),
})) }))
} }
} }
@ -309,10 +337,27 @@ impl Readable for PaymentConstraints {
} }
} }
impl_writeable_tlv_based_enum!(PaymentContext,
;
(0, Unknown),
);
impl Writeable for UnknownPaymentContext {
fn write<W: Writer>(&self, _w: &mut W) -> Result<(), io::Error> {
Ok(())
}
}
impl Readable for UnknownPaymentContext {
fn read<R: io::Read>(_r: &mut R) -> Result<Self, DecodeError> {
Ok(UnknownPaymentContext(()))
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use bitcoin::secp256k1::PublicKey; use bitcoin::secp256k1::PublicKey;
use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, ReceiveTlvs, PaymentConstraints, PaymentRelay}; use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, ReceiveTlvs, PaymentConstraints, PaymentContext, PaymentRelay};
use crate::ln::PaymentSecret; use crate::ln::PaymentSecret;
use crate::ln::features::BlindedHopFeatures; use crate::ln::features::BlindedHopFeatures;
use crate::ln::functional_test_utils::TEST_FINAL_CLTV; use crate::ln::functional_test_utils::TEST_FINAL_CLTV;
@ -361,6 +406,7 @@ mod tests {
max_cltv_expiry: 0, max_cltv_expiry: 0,
htlc_minimum_msat: 1, htlc_minimum_msat: 1,
}, },
payment_context: PaymentContext::unknown(),
}; };
let htlc_maximum_msat = 100_000; let htlc_maximum_msat = 100_000;
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, 12).unwrap(); let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, 12).unwrap();
@ -379,6 +425,7 @@ mod tests {
max_cltv_expiry: 0, max_cltv_expiry: 0,
htlc_minimum_msat: 1, htlc_minimum_msat: 1,
}, },
payment_context: PaymentContext::unknown(),
}; };
let blinded_payinfo = super::compute_payinfo(&[], &recv_tlvs, 4242, TEST_FINAL_CLTV as u16).unwrap(); let blinded_payinfo = super::compute_payinfo(&[], &recv_tlvs, 4242, TEST_FINAL_CLTV as u16).unwrap();
assert_eq!(blinded_payinfo.fee_base_msat, 0); assert_eq!(blinded_payinfo.fee_base_msat, 0);
@ -432,6 +479,7 @@ mod tests {
max_cltv_expiry: 0, max_cltv_expiry: 0,
htlc_minimum_msat: 3, htlc_minimum_msat: 3,
}, },
payment_context: PaymentContext::unknown(),
}; };
let htlc_maximum_msat = 100_000; let htlc_maximum_msat = 100_000;
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, TEST_FINAL_CLTV as u16).unwrap(); let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, TEST_FINAL_CLTV as u16).unwrap();
@ -482,6 +530,7 @@ mod tests {
max_cltv_expiry: 0, max_cltv_expiry: 0,
htlc_minimum_msat: 1, htlc_minimum_msat: 1,
}, },
payment_context: PaymentContext::unknown(),
}; };
let htlc_minimum_msat = 3798; let htlc_minimum_msat = 3798;
assert!(super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_minimum_msat - 1, TEST_FINAL_CLTV as u16).is_err()); assert!(super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_minimum_msat - 1, TEST_FINAL_CLTV as u16).is_err());
@ -536,6 +585,7 @@ mod tests {
max_cltv_expiry: 0, max_cltv_expiry: 0,
htlc_minimum_msat: 1, htlc_minimum_msat: 1,
}, },
payment_context: PaymentContext::unknown(),
}; };
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, 10_000, TEST_FINAL_CLTV as u16).unwrap(); let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, 10_000, TEST_FINAL_CLTV as u16).unwrap();

View file

@ -9,7 +9,7 @@
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use crate::blinded_path::BlindedPath; use crate::blinded_path::BlindedPath;
use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs}; use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentContext, PaymentRelay, ReceiveTlvs};
use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PaymentFailureReason}; use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PaymentFailureReason};
use crate::ln::PaymentSecret; use crate::ln::PaymentSecret;
use crate::ln::channelmanager; use crate::ln::channelmanager;
@ -63,6 +63,7 @@ fn blinded_payment_path(
htlc_minimum_msat: htlc_minimum_msat:
intro_node_min_htlc_opt.unwrap_or_else(|| channel_upds.last().unwrap().htlc_minimum_msat), intro_node_min_htlc_opt.unwrap_or_else(|| channel_upds.last().unwrap().htlc_minimum_msat),
}, },
payment_context: PaymentContext::unknown(),
}; };
let mut secp_ctx = Secp256k1::new(); let mut secp_ctx = Secp256k1::new();
BlindedPath::new_for_payment( BlindedPath::new_for_payment(
@ -108,6 +109,7 @@ fn do_one_hop_blinded_path(success: bool) {
max_cltv_expiry: u32::max_value(), max_cltv_expiry: u32::max_value(),
htlc_minimum_msat: chan_upd.htlc_minimum_msat, htlc_minimum_msat: chan_upd.htlc_minimum_msat,
}, },
payment_context: PaymentContext::unknown(),
}; };
let mut secp_ctx = Secp256k1::new(); let mut secp_ctx = Secp256k1::new();
let blinded_path = BlindedPath::one_hop_for_payment( let blinded_path = BlindedPath::one_hop_for_payment(
@ -151,6 +153,7 @@ fn mpp_to_one_hop_blinded_path() {
max_cltv_expiry: u32::max_value(), max_cltv_expiry: u32::max_value(),
htlc_minimum_msat: chan_upd_1_3.htlc_minimum_msat, htlc_minimum_msat: chan_upd_1_3.htlc_minimum_msat,
}, },
payment_context: PaymentContext::unknown(),
}; };
let blinded_path = BlindedPath::one_hop_for_payment( let blinded_path = BlindedPath::one_hop_for_payment(
nodes[3].node.get_our_node_id(), payee_tlvs, TEST_FINAL_CLTV as u16, nodes[3].node.get_our_node_id(), payee_tlvs, TEST_FINAL_CLTV as u16,
@ -1281,6 +1284,7 @@ fn custom_tlvs_to_blinded_path() {
max_cltv_expiry: u32::max_value(), max_cltv_expiry: u32::max_value(),
htlc_minimum_msat: chan_upd.htlc_minimum_msat, htlc_minimum_msat: chan_upd.htlc_minimum_msat,
}, },
payment_context: PaymentContext::unknown(),
}; };
let mut secp_ctx = Secp256k1::new(); let mut secp_ctx = Secp256k1::new();
let blinded_path = BlindedPath::one_hop_for_payment( let blinded_path = BlindedPath::one_hop_for_payment(

View file

@ -32,7 +32,7 @@ use bitcoin::secp256k1::Secp256k1;
use bitcoin::{secp256k1, Sequence}; use bitcoin::{secp256k1, Sequence};
use crate::blinded_path::{BlindedPath, NodeIdLookUp}; use crate::blinded_path::{BlindedPath, NodeIdLookUp};
use crate::blinded_path::payment::{PaymentConstraints, ReceiveTlvs}; use crate::blinded_path::payment::{PaymentConstraints, PaymentContext, ReceiveTlvs};
use crate::chain; use crate::chain;
use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock}; use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock};
use crate::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator}; use crate::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator};
@ -8995,6 +8995,7 @@ where
max_cltv_expiry, max_cltv_expiry,
htlc_minimum_msat: 1, htlc_minimum_msat: 1,
}, },
payment_context: PaymentContext::unknown(),
}; };
self.router.create_blinded_payment_paths( self.router.create_blinded_payment_paths(
payee_node_id, first_hops, payee_tlvs, amount_msats, secp_ctx payee_node_id, first_hops, payee_tlvs, amount_msats, secp_ctx

View file

@ -2713,7 +2713,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, &NS)> for InboundOnionPayload w
}) })
}, },
ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(ReceiveTlvs { ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(ReceiveTlvs {
payment_secret, payment_constraints payment_secret, payment_constraints, payment_context: _
})} => { })} => {
if total_msat.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) } if total_msat.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) }
Ok(Self::BlindedReceive { Ok(Self::BlindedReceive {